<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://pandorabox.io/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=FeXoR</id>
	<title>Pandorabox - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://pandorabox.io/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=FeXoR"/>
	<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php/Special:Contributions/FeXoR"/>
	<updated>2026-05-24T13:12:11Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.35.0</generator>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3436</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3436"/>
		<updated>2026-05-03T15:46:17Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Digibuilder/Quarry Farming - proof of concept (one layer, R4, stable) */ Reduced placed crops per cycle from 13 to 7 (caused penalty before)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
&lt;br /&gt;
-- Create a list with all items to be send into the centrifuges&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium9_dust&amp;quot;, &amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;, &amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;, &amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;, &amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;, &amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;, &amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;, &amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
-- Add a counter which item in the list to be picked next&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
&lt;br /&gt;
-- Instead of requestin one stack to be injected request 3, each of a specific type&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	-- Add the item type to the injection request because than the stack size is obayed&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	-- Whenever on item type is requested increase the counter to pick the next item type next time and cycle to 0 when the end of the list is reached&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
-- Remove processed items from centrifuges&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
-- Sort items in the mithril chest to compactify and create larger stacks&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
-- Repeat every 8 seconds or slower when the server reports high CPU usage in this map block&lt;br /&gt;
interrupt(4+4*heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Digibuilder/Quarry Farming - proof of concept (one layer, R4, stable) ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local builder = {&lt;br /&gt;
	channel = &amp;quot;digibuilder&amp;quot;,&lt;br /&gt;
	-- Code assumes [1].x &amp;lt; [2].x, [1].y &amp;lt; [2].y, [1].z &amp;lt; [2].z&lt;br /&gt;
	area = {{x = -5, y = -5, z = -5}, {x = 5, y = -5, z = 5}},&lt;br /&gt;
	actions_per_cycle = 7&lt;br /&gt;
}&lt;br /&gt;
local quarry_channel = &amp;quot;quarry&amp;quot;&lt;br /&gt;
local injector_channel = &amp;quot;injector&amp;quot;&lt;br /&gt;
local ripe_enough = 4&lt;br /&gt;
local delay = 4&lt;br /&gt;
&lt;br /&gt;
-- Initialization&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.i = mem.i or 0&lt;br /&gt;
	mem.i_ripe = 0&lt;br /&gt;
	mem.messages = 0&lt;br /&gt;
	mem.extend = {&lt;br /&gt;
		x = builder.area[2].x - builder.area[1].x + 1,&lt;br /&gt;
		y = builder.area[2].y - builder.area[1].y + 1,&lt;br /&gt;
		z = builder.area[2].z - builder.area[1].z + 1&lt;br /&gt;
	}&lt;br /&gt;
	mem.n_voxels = mem.extend.x * mem.extend.y * mem.extend.z&lt;br /&gt;
	mem.harvesting = mem.harvesting or false&lt;br /&gt;
	interrupt(delay + heat)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function position_in_area_from_index (index, extend)&lt;br /&gt;
	extend = extend or mem.extend&lt;br /&gt;
	-- Shift from area[1]&lt;br /&gt;
	local remainder = index&lt;br /&gt;
	local shift = {}&lt;br /&gt;
	shift.x = remainder % extend.x&lt;br /&gt;
	remainder = (remainder - shift.x) / extend.x&lt;br /&gt;
	shift.y = remainder % extend.y&lt;br /&gt;
	remainder = (remainder - shift.y) / extend.y&lt;br /&gt;
	shift.z = remainder % extend.z&lt;br /&gt;
	return shift&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Actually do stuff&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	local s = position_in_area_from_index(mem.i)&lt;br /&gt;
	local v = {&lt;br /&gt;
		x = builder.area[1].x + s.x,&lt;br /&gt;
		y = builder.area[1].y + s.y,&lt;br /&gt;
		z = builder.area[1].z + s.z&lt;br /&gt;
	}&lt;br /&gt;
	if mem.harvesting then&lt;br /&gt;
		digiline_send(quarry_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	else&lt;br /&gt;
		digiline_send(builder.channel, {command = &amp;quot;getnode&amp;quot;, pos = v})&lt;br /&gt;
	end&lt;br /&gt;
	for a = 1, builder.actions_per_cycle do&lt;br /&gt;
		local s = position_in_area_from_index(mem.i)&lt;br /&gt;
		-- Target node location relative to digibuilder&lt;br /&gt;
		local v = {&lt;br /&gt;
			x = builder.area[1].x + s.x,&lt;br /&gt;
			y = builder.area[1].y + s.y,&lt;br /&gt;
			z = builder.area[1].z + s.z&lt;br /&gt;
		}&lt;br /&gt;
		digiline_send(builder.channel, {command = &amp;quot;setnode&amp;quot;, name = &amp;quot;farming:soy_beans&amp;quot;, pos = v, down = true})&lt;br /&gt;
		-- Prepare for next action&lt;br /&gt;
		mem.i = (mem.i + 1) % mem.n_voxels&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(injector_channel, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
	print(&amp;quot;Planting &amp;quot; .. tostring(mem.i) .. &amp;quot;/&amp;quot; .. tostring(mem.n_voxels) .. &amp;quot;, &amp;quot; .. (mem.harvesting and &amp;quot;harvesting&amp;quot; or tostring(&amp;quot;ripe &amp;quot; .. tostring(mem.i_ripe) .. &amp;quot;/&amp;quot; .. tostring(ripe_enough))))&lt;br /&gt;
	mem.messages = (mem.messages + 1) % 28&lt;br /&gt;
	if mem.messages == 0 then clearterm() end&lt;br /&gt;
	interrupt(delay + heat)&lt;br /&gt;
elseif event.type == &amp;quot;digiline&amp;quot; and event.channel == builder.channel and event.msg then&lt;br /&gt;
	if  event.msg.name ~= nil and event.msg.success == nil then -- If getnode feedback&lt;br /&gt;
		if event.msg.name == &amp;quot;farming:soy_7&amp;quot; then&lt;br /&gt;
			mem.i_ripe = mem.i_ripe + 1&lt;br /&gt;
		else&lt;br /&gt;
			mem.i_ripe = 0&lt;br /&gt;
		end&lt;br /&gt;
		if mem.i_ripe &amp;gt;= ripe_enough and not mem.harvesting then&lt;br /&gt;
			digiline_send(quarry_channel, {command = &amp;quot;set&amp;quot;, restart = true})&lt;br /&gt;
			mem.i_ripe = 0&lt;br /&gt;
			mem.harvesting = true&lt;br /&gt;
			print(&amp;quot;Harvesting&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
elseif event.type == &amp;quot;digiline&amp;quot; and event.channel == quarry_channel and event.msg.finished then&lt;br /&gt;
	print(&amp;quot;Harvest completed&amp;quot;)&lt;br /&gt;
	mem.harvesting = false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3435</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3435"/>
		<updated>2026-05-03T15:41:12Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Digibuilder/Quarry Farming - proof of concept (one layer, R4, stable) */ Added syntax highlighting&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
&lt;br /&gt;
-- Create a list with all items to be send into the centrifuges&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium9_dust&amp;quot;, &amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;, &amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;, &amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;, &amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;, &amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;, &amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;, &amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
-- Add a counter which item in the list to be picked next&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
&lt;br /&gt;
-- Instead of requestin one stack to be injected request 3, each of a specific type&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	-- Add the item type to the injection request because than the stack size is obayed&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	-- Whenever on item type is requested increase the counter to pick the next item type next time and cycle to 0 when the end of the list is reached&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
-- Remove processed items from centrifuges&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
-- Sort items in the mithril chest to compactify and create larger stacks&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
-- Repeat every 8 seconds or slower when the server reports high CPU usage in this map block&lt;br /&gt;
interrupt(4+4*heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Digibuilder/Quarry Farming - proof of concept (one layer, R4, stable) ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local builder = {&lt;br /&gt;
	channel = &amp;quot;digibuilder&amp;quot;,&lt;br /&gt;
	-- Code assumes [1].x &amp;lt; [2].x, [1].y &amp;lt; [2].y, [1].z &amp;lt; [2].z&lt;br /&gt;
	area = {{x = -5, y = -5, z = -5}, {x = 5, y = -5, z = 5}},&lt;br /&gt;
	actions_per_cycle = 13&lt;br /&gt;
}&lt;br /&gt;
local quarry_channel = &amp;quot;quarry&amp;quot;&lt;br /&gt;
local injector_channel = &amp;quot;injector&amp;quot;&lt;br /&gt;
local ripe_enough = 4&lt;br /&gt;
local delay = 4&lt;br /&gt;
&lt;br /&gt;
-- Initialization&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.i = mem.i or 0&lt;br /&gt;
	mem.i_ripe = 0&lt;br /&gt;
	mem.messages = 0&lt;br /&gt;
	mem.extend = {&lt;br /&gt;
		x = builder.area[2].x - builder.area[1].x + 1,&lt;br /&gt;
		y = builder.area[2].y - builder.area[1].y + 1,&lt;br /&gt;
		z = builder.area[2].z - builder.area[1].z + 1&lt;br /&gt;
	}&lt;br /&gt;
	mem.n_voxels = mem.extend.x * mem.extend.y * mem.extend.z&lt;br /&gt;
	mem.harvesting = mem.harvesting or false&lt;br /&gt;
	interrupt(delay + heat)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function position_in_area_from_index (index, extend)&lt;br /&gt;
	extend = extend or mem.extend&lt;br /&gt;
	-- Shift from area[1]&lt;br /&gt;
	local remainder = index&lt;br /&gt;
	local shift = {}&lt;br /&gt;
	shift.x = remainder % extend.x&lt;br /&gt;
	remainder = (remainder - shift.x) / extend.x&lt;br /&gt;
	shift.y = remainder % extend.y&lt;br /&gt;
	remainder = (remainder - shift.y) / extend.y&lt;br /&gt;
	shift.z = remainder % extend.z&lt;br /&gt;
	return shift&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Actually do stuff&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	local s = position_in_area_from_index(mem.i)&lt;br /&gt;
	local v = {&lt;br /&gt;
		x = builder.area[1].x + s.x,&lt;br /&gt;
		y = builder.area[1].y + s.y,&lt;br /&gt;
		z = builder.area[1].z + s.z&lt;br /&gt;
	}&lt;br /&gt;
	if mem.harvesting then&lt;br /&gt;
		digiline_send(quarry_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	else&lt;br /&gt;
		digiline_send(builder.channel, {command = &amp;quot;getnode&amp;quot;, pos = v})&lt;br /&gt;
	end&lt;br /&gt;
	for a = 1, builder.actions_per_cycle do&lt;br /&gt;
		local s = position_in_area_from_index(mem.i)&lt;br /&gt;
		-- Target node location relative to digibuilder&lt;br /&gt;
		local v = {&lt;br /&gt;
			x = builder.area[1].x + s.x,&lt;br /&gt;
			y = builder.area[1].y + s.y,&lt;br /&gt;
			z = builder.area[1].z + s.z&lt;br /&gt;
		}&lt;br /&gt;
		digiline_send(builder.channel, {command = &amp;quot;setnode&amp;quot;, name = &amp;quot;farming:soy_beans&amp;quot;, pos = v, down = true})&lt;br /&gt;
		-- Prepare for next action&lt;br /&gt;
		mem.i = (mem.i + 1) % mem.n_voxels&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(injector_channel, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
	print(&amp;quot;Planting &amp;quot; .. tostring(mem.i) .. &amp;quot;/&amp;quot; .. tostring(mem.n_voxels) .. &amp;quot;, &amp;quot; .. (mem.harvesting and &amp;quot;harvesting&amp;quot; or tostring(&amp;quot;ripe &amp;quot; .. tostring(mem.i_ripe) .. &amp;quot;/&amp;quot; .. tostring(ripe_enough))))&lt;br /&gt;
	mem.messages = (mem.messages + 1) % 28&lt;br /&gt;
	if mem.messages == 0 then clearterm() end&lt;br /&gt;
	interrupt(delay + heat)&lt;br /&gt;
elseif event.type == &amp;quot;digiline&amp;quot; and event.channel == builder.channel and event.msg then&lt;br /&gt;
	if  event.msg.name ~= nil and event.msg.success == nil then -- If getnode feedback&lt;br /&gt;
		if event.msg.name == &amp;quot;farming:soy_7&amp;quot; then&lt;br /&gt;
			mem.i_ripe = mem.i_ripe + 1&lt;br /&gt;
		else&lt;br /&gt;
			mem.i_ripe = 0&lt;br /&gt;
		end&lt;br /&gt;
		if mem.i_ripe &amp;gt;= ripe_enough and not mem.harvesting then&lt;br /&gt;
			digiline_send(quarry_channel, {command = &amp;quot;set&amp;quot;, restart = true})&lt;br /&gt;
			mem.i_ripe = 0&lt;br /&gt;
			mem.harvesting = true&lt;br /&gt;
			print(&amp;quot;Harvesting&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
elseif event.type == &amp;quot;digiline&amp;quot; and event.channel == quarry_channel and event.msg.finished then&lt;br /&gt;
	print(&amp;quot;Harvest completed&amp;quot;)&lt;br /&gt;
	mem.harvesting = false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3434</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3434"/>
		<updated>2026-05-03T15:28:38Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Added Digibuilder Farm&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
&lt;br /&gt;
-- Create a list with all items to be send into the centrifuges&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium9_dust&amp;quot;, &amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;, &amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;, &amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;, &amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;, &amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;, &amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;, &amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
-- Add a counter which item in the list to be picked next&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
&lt;br /&gt;
-- Instead of requestin one stack to be injected request 3, each of a specific type&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	-- Add the item type to the injection request because than the stack size is obayed&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	-- Whenever on item type is requested increase the counter to pick the next item type next time and cycle to 0 when the end of the list is reached&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
-- Remove processed items from centrifuges&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
-- Sort items in the mithril chest to compactify and create larger stacks&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
-- Repeat every 8 seconds or slower when the server reports high CPU usage in this map block&lt;br /&gt;
interrupt(4+4*heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Digibuilder/Quarry Farming - proof of concept (one layer, R4, stable) ==&lt;br /&gt;
-- Settings&lt;br /&gt;
local builder = {&lt;br /&gt;
	channel = &amp;quot;digibuilder&amp;quot;,&lt;br /&gt;
	-- Code assumes [1].x &amp;lt; [2].x, [1].y &amp;lt; [2].y, [1].z &amp;lt; [2].z&lt;br /&gt;
	area = {{x = -5, y = -5, z = -5}, {x = 5, y = -5, z = 5}},&lt;br /&gt;
	actions_per_cycle = 13&lt;br /&gt;
}&lt;br /&gt;
local quarry_channel = &amp;quot;quarry&amp;quot;&lt;br /&gt;
local injector_channel = &amp;quot;injector&amp;quot;&lt;br /&gt;
local ripe_enough = 4&lt;br /&gt;
local delay = 4&lt;br /&gt;
&lt;br /&gt;
-- Initialization&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.i = mem.i or 0&lt;br /&gt;
	mem.i_ripe = 0&lt;br /&gt;
	mem.messages = 0&lt;br /&gt;
	mem.extend = {&lt;br /&gt;
		x = builder.area[2].x - builder.area[1].x + 1,&lt;br /&gt;
		y = builder.area[2].y - builder.area[1].y + 1,&lt;br /&gt;
		z = builder.area[2].z - builder.area[1].z + 1&lt;br /&gt;
	}&lt;br /&gt;
	mem.n_voxels = mem.extend.x * mem.extend.y * mem.extend.z&lt;br /&gt;
	mem.harvesting = mem.harvesting or false&lt;br /&gt;
	interrupt(delay + heat)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function position_in_area_from_index (index, extend)&lt;br /&gt;
	extend = extend or mem.extend&lt;br /&gt;
	-- Shift from area[1]&lt;br /&gt;
	local remainder = index&lt;br /&gt;
	local shift = {}&lt;br /&gt;
	shift.x = remainder % extend.x&lt;br /&gt;
	remainder = (remainder - shift.x) / extend.x&lt;br /&gt;
	shift.y = remainder % extend.y&lt;br /&gt;
	remainder = (remainder - shift.y) / extend.y&lt;br /&gt;
	shift.z = remainder % extend.z&lt;br /&gt;
	return shift&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Actually do stuff&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	local s = position_in_area_from_index(mem.i)&lt;br /&gt;
	local v = {&lt;br /&gt;
		x = builder.area[1].x + s.x,&lt;br /&gt;
		y = builder.area[1].y + s.y,&lt;br /&gt;
		z = builder.area[1].z + s.z&lt;br /&gt;
	}&lt;br /&gt;
	if mem.harvesting then&lt;br /&gt;
		digiline_send(quarry_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	else&lt;br /&gt;
		digiline_send(builder.channel, {command = &amp;quot;getnode&amp;quot;, pos = v})&lt;br /&gt;
	end&lt;br /&gt;
	for a = 1, builder.actions_per_cycle do&lt;br /&gt;
		local s = position_in_area_from_index(mem.i)&lt;br /&gt;
		-- Target node location relative to digibuilder&lt;br /&gt;
		local v = {&lt;br /&gt;
			x = builder.area[1].x + s.x,&lt;br /&gt;
			y = builder.area[1].y + s.y,&lt;br /&gt;
			z = builder.area[1].z + s.z&lt;br /&gt;
		}&lt;br /&gt;
		digiline_send(builder.channel, {command = &amp;quot;setnode&amp;quot;, name = &amp;quot;farming:soy_beans&amp;quot;, pos = v, down = true})&lt;br /&gt;
		-- Prepare for next action&lt;br /&gt;
		mem.i = (mem.i + 1) % mem.n_voxels&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(injector_channel, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
	print(&amp;quot;Planting &amp;quot; .. tostring(mem.i) .. &amp;quot;/&amp;quot; .. tostring(mem.n_voxels) .. &amp;quot;, &amp;quot; .. (mem.harvesting and &amp;quot;harvesting&amp;quot; or tostring(&amp;quot;ripe &amp;quot; .. tostring(mem.i_ripe) .. &amp;quot;/&amp;quot; .. tostring(ripe_enough))))&lt;br /&gt;
	mem.messages = (mem.messages + 1) % 28&lt;br /&gt;
	if mem.messages == 0 then clearterm() end&lt;br /&gt;
	interrupt(delay + heat)&lt;br /&gt;
elseif event.type == &amp;quot;digiline&amp;quot; and event.channel == builder.channel and event.msg then&lt;br /&gt;
	if  event.msg.name ~= nil and event.msg.success == nil then -- If getnode feedback&lt;br /&gt;
		if event.msg.name == &amp;quot;farming:soy_7&amp;quot; then&lt;br /&gt;
			mem.i_ripe = mem.i_ripe + 1&lt;br /&gt;
		else&lt;br /&gt;
			mem.i_ripe = 0&lt;br /&gt;
		end&lt;br /&gt;
		if mem.i_ripe &amp;gt;= ripe_enough and not mem.harvesting then&lt;br /&gt;
			digiline_send(quarry_channel, {command = &amp;quot;set&amp;quot;, restart = true})&lt;br /&gt;
			mem.i_ripe = 0&lt;br /&gt;
			mem.harvesting = true&lt;br /&gt;
			print(&amp;quot;Harvesting&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
elseif event.type == &amp;quot;digiline&amp;quot; and event.channel == quarry_channel and event.msg.finished then&lt;br /&gt;
	print(&amp;quot;Harvest completed&amp;quot;)&lt;br /&gt;
	mem.harvesting = false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3408</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3408"/>
		<updated>2026-02-17T20:22:26Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Uranium enrichment - during bugy times */ Added comments&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
&lt;br /&gt;
-- Create a list with all items to be send into the centrifuges&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium9_dust&amp;quot;, &amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;, &amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;, &amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;, &amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;, &amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;, &amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;, &amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
-- Add a counter which item in the list to be picked next&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
&lt;br /&gt;
-- Instead of requestin one stack to be injected request 3, each of a specific type&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	-- Add the item type to the injection request because than the stack size is obayed&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	-- Whenever on item type is requested increase the counter to pick the next item type next time and cycle to 0 when the end of the list is reached&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
-- Remove processed items from centrifuges&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
-- Sort items in the mithril chest to compactify and create larger stacks&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
-- Repeat every 8 seconds or slower when the server reports high CPU usage in this map block&lt;br /&gt;
interrupt(4+4*heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3407</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3407"/>
		<updated>2026-02-15T12:43:07Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Uranium enrichment - during bugy times */ Increased impact of heat&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium9_dust&amp;quot;, &amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;, &amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;, &amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;, &amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;, &amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;, &amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;, &amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
interrupt(4+4*heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3406</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3406"/>
		<updated>2026-02-15T11:28:20Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Uranium enrichment - during bugy times */ Added missing uranium grade 9&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium9_dust&amp;quot;, &amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;, &amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;, &amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;, &amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;, &amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;, &amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;, &amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
interrupt(8+heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3405</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3405"/>
		<updated>2026-02-15T09:53:32Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Uranium enrichment - during bugy times */ Compactified&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;, &amp;quot;technic:uranium2_dust&amp;quot;, &amp;quot;technic:uranium3_dust&amp;quot;, &amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;, &amp;quot;technic:uranium6_dust&amp;quot;, &amp;quot;technic:uranium_dust&amp;quot;, &amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium10_dust&amp;quot;, &amp;quot;technic:uranium11_dust&amp;quot;, &amp;quot;technic:uranium12_dust&amp;quot;, &amp;quot;technic:uranium13_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium14_dust&amp;quot;, &amp;quot;technic:uranium15_dust&amp;quot;, &amp;quot;technic:uranium16_dust&amp;quot;, &amp;quot;technic:uranium17_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium18_dust&amp;quot;, &amp;quot;technic:uranium19_dust&amp;quot;, &amp;quot;technic:uranium20_dust&amp;quot;, &amp;quot;technic:uranium21_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium22_dust&amp;quot;, &amp;quot;technic:uranium23_dust&amp;quot;, &amp;quot;technic:uranium24_dust&amp;quot;, &amp;quot;technic:uranium25_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium26_dust&amp;quot;, &amp;quot;technic:uranium27_dust&amp;quot;, &amp;quot;technic:uranium28_dust&amp;quot;, &amp;quot;technic:uranium29_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium30_dust&amp;quot;, &amp;quot;technic:uranium31_dust&amp;quot;, &amp;quot;technic:uranium32_dust&amp;quot;, &amp;quot;technic:uranium33_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium34_dust&amp;quot;, &amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3404</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3404"/>
		<updated>2026-02-15T09:45:05Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Replaced incomplete panel example with uranium enrichment&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Uranium enrichment - during bugy times ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64})&lt;br /&gt;
--digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
--digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
--interrupt(8+heat)&lt;br /&gt;
&lt;br /&gt;
-- In case of bug https://github.com/mt-mods/pipeworks/issues/180&lt;br /&gt;
mem.i = mem.i or 0&lt;br /&gt;
name_list = {&lt;br /&gt;
	&amp;quot;technic:uranium1_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium2_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium3_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium4_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium5_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium6_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium8_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium10_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium11_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium12_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium13_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium14_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium15_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium16_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium17_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium18_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium19_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium20_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium21_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium22_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium23_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium24_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium25_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium26_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium27_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium28_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium29_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium30_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium31_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium32_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium33_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:uranium34_dust&amp;quot;,&lt;br /&gt;
	&amp;quot;technic:chernobylite_dust&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
for i = 0, 2 do&lt;br /&gt;
	digiline_send(&amp;quot;injector_multiple&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = true, count = 64, name = name_list[mem.i + 1]})&lt;br /&gt;
	mem.i = (mem.i + 1) % #name_list&lt;br /&gt;
end&lt;br /&gt;
digiline_send(&amp;quot;injector&amp;quot;, {slotseq = &amp;quot;random&amp;quot;, exmatch = false})&lt;br /&gt;
digiline_send(&amp;quot;mc&amp;quot;, {command = &amp;quot;sort&amp;quot;})&lt;br /&gt;
interrupt(8+heat)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3403</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3403"/>
		<updated>2026-02-11T14:19:47Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Added Control Panel example&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Control Panel example ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
print(event)&lt;br /&gt;
&lt;br /&gt;
local panel_channel = &amp;quot;panel&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local separator = &amp;quot;&amp;quot;&lt;br /&gt;
for i = 0, 74 do separator = separator .. &amp;quot;_&amp;quot; end&lt;br /&gt;
local pages = {&amp;quot;main&amp;quot;}&lt;br /&gt;
local structure = {&lt;br /&gt;
	main = {&lt;br /&gt;
		caption = &amp;quot;Main Menu&amp;quot;,&lt;br /&gt;
		separator = separator,&lt;br /&gt;
		content = {&lt;br /&gt;
			&amp;quot;Content 1&amp;quot;,&lt;br /&gt;
			&amp;quot;Content 2&amp;quot;,&lt;br /&gt;
			&amp;quot;Content 3&amp;quot;,&lt;br /&gt;
			&amp;quot;Content 4&amp;quot;,&lt;br /&gt;
			&amp;quot;Content 5&amp;quot;,&lt;br /&gt;
			&amp;quot;Content 6&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		status1 = &amp;quot;Status 1&amp;quot;,&lt;br /&gt;
		label_up = &amp;quot;Up&amp;quot;,&lt;br /&gt;
		label_left = &amp;quot;Left&amp;quot;,&lt;br /&gt;
		label_right = &amp;quot;Right&amp;quot;,&lt;br /&gt;
		label_down = &amp;quot;Down&amp;quot;,&lt;br /&gt;
		label_back = &amp;quot;Back&amp;quot;,&lt;br /&gt;
		label_enter = &amp;quot;Enter&amp;quot;,&lt;br /&gt;
		status2 = &amp;quot;Status 2&amp;quot;&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
mem.page = mem.page or pages[1]&lt;br /&gt;
mem.selection = mem.selection or 1&lt;br /&gt;
&lt;br /&gt;
if event.channel == panel_channel then&lt;br /&gt;
	if event.msg == &amp;quot;up&amp;quot; then&lt;br /&gt;
		mem.selection = ((mem.selection + #structure[mem.page].content - 2) % #structure[mem.page].content) + 1&lt;br /&gt;
	elseif event.msg == &amp;quot;down&amp;quot; then&lt;br /&gt;
		mem.selection = (mem.selection % #structure[mem.page].content) + 1&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function template ()&lt;br /&gt;
	return structure[mem.page].caption ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. structure[mem.page].separator ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. (mem.selection ==  1 and &amp;quot;# &amp;quot; or &amp;quot;   &amp;quot;) .. (structure[mem.page].content[1] or &amp;quot;&amp;quot;) ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. (mem.selection ==  2 and &amp;quot;# &amp;quot; or &amp;quot;   &amp;quot;) .. (structure[mem.page].content[2] or &amp;quot;&amp;quot;) ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. (mem.selection ==  3 and &amp;quot;# &amp;quot; or &amp;quot;   &amp;quot;) .. (structure[mem.page].content[3] or &amp;quot;&amp;quot;) ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. (mem.selection ==  4 and &amp;quot;# &amp;quot; or &amp;quot;   &amp;quot;) .. (structure[mem.page].content[4] or &amp;quot;&amp;quot;) ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. (mem.selection ==  5 and &amp;quot;# &amp;quot; or &amp;quot;   &amp;quot;) .. (structure[mem.page].content[5] or &amp;quot;&amp;quot;) ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. (mem.selection ==  6 and &amp;quot;# &amp;quot; or &amp;quot;   &amp;quot;) .. (structure[mem.page].content[6] or &amp;quot;&amp;quot;) ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. structure[mem.page].separator ..&lt;br /&gt;
	&amp;quot;\n&amp;quot; .. structure[mem.page].status1&lt;br /&gt;
-- 	&amp;quot;\n                                      &amp;quot; .. structure[mem.page].label_up ..&lt;br /&gt;
-- 	&amp;quot;\n\n\n&amp;quot; .. structure[mem.page].label_left ..&lt;br /&gt;
-- 	&amp;quot;                                                                     &amp;quot; .. structure[mem.page].label_right ..&lt;br /&gt;
-- 	&amp;quot;\n\n\n                                      &amp;quot; .. structure[mem.page].label_down ..&lt;br /&gt;
-- 	&amp;quot;\n&amp;quot; .. structure[mem.page].label_back ..&lt;br /&gt;
-- 	&amp;quot;                                                                     &amp;quot; .. structure[mem.page].label_enter ..&lt;br /&gt;
-- 	&amp;quot;\n\n&amp;quot; .. structure[mem.page].status2 ..&lt;br /&gt;
-- 	&amp;quot;\n\nInvisible - So you can curse here to your heart's desire :p&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
digiline_send(panel_channel, template())&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3402</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3402"/>
		<updated>2026-02-04T21:01:07Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 9 */ Added Firefly in a Bottle&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
* Lava in a Bottle&lt;br /&gt;
* Firefly in a Bottle&lt;br /&gt;
* Plasma Ball/light (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
* Warp Device (jumpdrive)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3401</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3401"/>
		<updated>2026-02-04T21:00:25Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 9 */ Added Lava in a Bottle and Plasma Ball/light&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
* Lava in a Bottle&lt;br /&gt;
* Plasma Ball/light (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
* Warp Device (jumpdrive)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3400</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3400"/>
		<updated>2026-02-04T20:56:21Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 6 */ Removed Power Plant (mesecons) for it has a light level of 6 where I added it&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
* Warp Device (jumpdrive)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3399</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3399"/>
		<updated>2026-02-04T20:54:58Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 5 */ Added Power Plant (mesecons)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
* Warp Device (jumpdrive)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3398</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3398"/>
		<updated>2026-02-04T20:52:38Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 4 */ Added jumpdrive to Warp Device&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
* Warp Device (jumpdrive)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3397</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3397"/>
		<updated>2026-02-04T20:51:52Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 4 */ Added Warp Device&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
* Warp Device&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Lights&amp;diff=3396</id>
		<title>Lights</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Lights&amp;diff=3396"/>
		<updated>2026-02-04T20:50:45Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Light Level 3 */ Added Mese Block&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some nodes fight against the impending darkness. This is a table listing them.&lt;br /&gt;
&lt;br /&gt;
TODO: Some are off by one (I think due to LIGHT_MAX being 14 and not 15, or something)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Light Level 15 ===&lt;br /&gt;
* Fireplace (homedecor)&lt;br /&gt;
* Industrial Light&lt;br /&gt;
* Mesecons Lamp (on)&lt;br /&gt;
* Super Glow Glass&lt;br /&gt;
* Air (with light)&lt;br /&gt;
* Sun Matter&lt;br /&gt;
* Ultra Plastic&lt;br /&gt;
* Glowshroom&lt;br /&gt;
* Wall / Ceiling Light (scifi_nodes)&lt;br /&gt;
* Orange Light Bars&lt;br /&gt;
* Liquid Pipe (2) (scifi_nodes)&lt;br /&gt;
* Glass Screen&lt;br /&gt;
* Glow Flower&lt;br /&gt;
* Trap Plant&lt;br /&gt;
* Forcefield&lt;br /&gt;
* Glowlight (thick)&lt;br /&gt;
* Travelnet (white)&lt;br /&gt;
* Futuristic Light Block (morelights)&lt;br /&gt;
* Modern Light Block (morelights)&lt;br /&gt;
* Vintage Light Block (morelights)&lt;br /&gt;
* Post Light (morelights)&lt;br /&gt;
* Bar Light (morelights)&lt;br /&gt;
* Ceiling Light (morelights)&lt;br /&gt;
* Beacon Beam&lt;br /&gt;
* Vacuum&lt;br /&gt;
* Digiline Glass Screen&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass &lt;br /&gt;
&lt;br /&gt;
=== Light Level 14 ===&lt;br /&gt;
* Headlamp&lt;br /&gt;
* Ice Fire&lt;br /&gt;
* Lava Lamp&lt;br /&gt;
* Lava Tools&lt;br /&gt;
* Ultra Steel Framed Obsidian Glass (tinted)&lt;br /&gt;
* Reactor Core (active)&lt;br /&gt;
* Technic Dummy Light source&lt;br /&gt;
* LV Lamp (technic)&lt;br /&gt;
* Meselamp&lt;br /&gt;
* Ceiling Fan&lt;br /&gt;
* Small CRT Television&lt;br /&gt;
* Glowlight (thin)&lt;br /&gt;
* Glowlight Small Cube&lt;br /&gt;
&lt;br /&gt;
=== Light Level 13 ===&lt;br /&gt;
* Beacon &lt;br /&gt;
* Jack O Lantern / Pumpkin (or 14?)&lt;br /&gt;
* Fancy Fire&lt;br /&gt;
* Jumpdrive Backbone/Engine/Controller&lt;br /&gt;
* LED Marquee&lt;br /&gt;
* Lightstone (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 12 ===&lt;br /&gt;
* Grass Light (morelights)&lt;br /&gt;
* Stone Block (morelights)&lt;br /&gt;
* Sandstone Block (morelights)&lt;br /&gt;
* Stairlight (morelights)&lt;br /&gt;
* Modern Light Block (small) (morelights)&lt;br /&gt;
* Vintage Light Block (small) (morelights)&lt;br /&gt;
* Can Light (morelights)&lt;br /&gt;
* Wall Lamp (morelights)&lt;br /&gt;
* Vintage Lantern (morelights)&lt;br /&gt;
* Futuristic Light Block&lt;br /&gt;
* Doom Light (scifi_nodes)&lt;br /&gt;
* Stained Glass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 11 ===&lt;br /&gt;
* Glow Glass&lt;br /&gt;
* Super Steel Frame Obsidian Glass (tinted) (new_glass)&lt;br /&gt;
* Super Plastic&lt;br /&gt;
* Fish Tank&lt;br /&gt;
&lt;br /&gt;
=== Light Level 10 ===&lt;br /&gt;
* Travelnet (all except white (MAX) and black(0))&lt;br /&gt;
* Digtron Light&lt;br /&gt;
* PlasmaTV (homedecor)&lt;br /&gt;
* Mesecons Command Block&lt;br /&gt;
* Modern Tablelamp (morelights)&lt;br /&gt;
* Vintage Hanging Light Bulb (morelights)&lt;br /&gt;
* Vintage Chandelier (morelights)&lt;br /&gt;
* Greenlights (2) (scifi_nodes)&lt;br /&gt;
* Green Light Bar (scifi_nodes)&lt;br /&gt;
* Gray Power Pipe (scifi_nodes)&lt;br /&gt;
* Red / Blue / Green / Black - Stripe / Metal Light (Box) (scifi_nodes)&lt;br /&gt;
* Twin Lights&lt;br /&gt;
* rusty floor (scifi_nodes)&lt;br /&gt;
* blue floor (scifi_nodes)&lt;br /&gt;
* Octagon Glass&lt;br /&gt;
* Pink Flower&lt;br /&gt;
* Jelly Plant (Blue/Green)&lt;br /&gt;
* Curly Plant&lt;br /&gt;
* Slug Weed&lt;br /&gt;
* Umbrella Weed&lt;br /&gt;
* Smartshop&lt;br /&gt;
&lt;br /&gt;
=== Light Level 9 ===&lt;br /&gt;
* Monitor and Keyboard (on) (computers)&lt;br /&gt;
* Glowing Embers&lt;br /&gt;
* Barbecue&lt;br /&gt;
* LV Led (active)&lt;br /&gt;
* Mesecon Torch (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 8 ===&lt;br /&gt;
* Path Light (morelights)&lt;br /&gt;
* Oil Lamp (morelights)&lt;br /&gt;
* Vending Machine (fancy_vend)&lt;br /&gt;
* Snapple Piepad (computers)&lt;br /&gt;
* Furnace (while burning)&lt;br /&gt;
* Super Steel Framed Obsidian Glass (new_glass)&lt;br /&gt;
* Coal Alloy Furnace (active)&lt;br /&gt;
* Corium Source&lt;br /&gt;
* Mesecon Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 7 ===&lt;br /&gt;
* Digilines Button (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 6 ===&lt;br /&gt;
* Digiline LCD&lt;br /&gt;
* Lava Block (Lavastuff)&lt;br /&gt;
* Power Plant (mesecons)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 5 ===&lt;br /&gt;
* Widescreen/Tallscreen (scifi_nodes)&lt;br /&gt;
* powered Mese Block&lt;br /&gt;
* Digicode (scifi_nodes)&lt;br /&gt;
* Blue Grid (scifi_nodes)&lt;br /&gt;
* Screen (scifi_nodes)&lt;br /&gt;
* Hanging Trap Plant&lt;br /&gt;
* Alien Egg&lt;br /&gt;
* Teleport Pad&lt;br /&gt;
* Blinking Light (scifi_nodes)&lt;br /&gt;
* electronic screen (2) (scifi_nodes)&lt;br /&gt;
* Palm Scanner&lt;br /&gt;
* (protected) Wall Switch&lt;br /&gt;
* Corium Flowing&lt;br /&gt;
&lt;br /&gt;
=== Light Level 4 ===&lt;br /&gt;
* Advtrains Ks Signals&lt;br /&gt;
* Pony Vanio (computers)&lt;br /&gt;
* Elevator&lt;br /&gt;
* Protection Block/Logo&lt;br /&gt;
* Mesecon (on)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 3 ===&lt;br /&gt;
* Mese Block&lt;br /&gt;
* Digimese&lt;br /&gt;
* Stained Glass (homedecor)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 2 ===&lt;br /&gt;
* Dirt with alien Grass&lt;br /&gt;
&lt;br /&gt;
=== Light Level 1 ===&lt;br /&gt;
* Advtrains Signals&lt;br /&gt;
* Salt Crystal&lt;br /&gt;
* Black Wall Screen (scifi_nodes)&lt;br /&gt;
&lt;br /&gt;
=== Light Level 0 ===&lt;br /&gt;
* Travelnet (black)&lt;br /&gt;
* literally any other node&lt;br /&gt;
&lt;br /&gt;
=== Lavastuff ===&lt;br /&gt;
* Helmet: 4 &lt;br /&gt;
* Chestplate: 6&lt;br /&gt;
* Leggins: 5&lt;br /&gt;
* Boots: 4&lt;br /&gt;
* Shield: 5&lt;br /&gt;
* Multitool: 14&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mod]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3395</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3395"/>
		<updated>2026-01-14T18:39:18Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Reordered sections&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3394</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3394"/>
		<updated>2026-01-14T18:35:00Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Fast and simple Game Controller JD Hopper */ Updated and retitled&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Easy Ride - Fast and simple Game Controller jumpdrive controls ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	Enhanced luacontroller (mooncontroller:mooncontroller*) with this code&lt;br /&gt;
	Jumpdrive (jumpdrive:engine) placed relative to the Luacontroller according to variable 'offset'&lt;br /&gt;
	Game Controller (digistuff:controller)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local channel = {}&lt;br /&gt;
channel.jumpdrive = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
channel.game_controller = &amp;quot;gc&amp;quot;&lt;br /&gt;
channel.wall_knob = &amp;quot;knob&amp;quot;&lt;br /&gt;
local offset = {x = 0, y = 0, z = 1} -- Position of the Jumpdrive relative to the Luacontroller, by default adjacent North&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	print(&amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
	digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == channel.jumpdrive then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			if event.msg.success ~= true then print(&amp;quot;jumpdrive&amp;quot; .. tostring(event.msg.msg)) end&lt;br /&gt;
			digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.radius ~= nil then&lt;br /&gt;
			mem.knob = mem.knob or 0&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == channel.wall_knob then&lt;br /&gt;
		print(&amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(channel.jumpdrive, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == channel.game_controller and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x + mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(pos.x + offset.x - mem.dist * v.x),&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist * v.y),&lt;br /&gt;
					z = round(pos.z + offset.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y + mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
				channel.jumpdrive,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = pos.x + offset.x,&lt;br /&gt;
					y = round(pos.y + offset.y - mem.dist),&lt;br /&gt;
					z = pos.z + offset.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(channel.jumpdrive,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3393</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3393"/>
		<updated>2025-08-21T16:55:04Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Updated privs for thomas (Doesn't have staff for some reason, didn't visit for years so probably not important ;)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil || privs ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3392</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3392"/>
		<updated>2025-08-21T16:43:13Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Updated privs for nil&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil || privs ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3391</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3391"/>
		<updated>2025-08-21T16:15:51Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Updated privs of jihuu&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3390</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3390"/>
		<updated>2025-08-21T16:12:06Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Fixed double basic_privs entry for Huhhila&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3389</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3389"/>
		<updated>2025-08-21T15:54:50Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Updated privs of Huhhila&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila || basic_privs || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3388</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3388"/>
		<updated>2025-08-21T15:19:32Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Updated actual privs of FeXoR&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3387</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3387"/>
		<updated>2025-08-21T15:14:53Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* List of accounts and privileges */ Added basic_privs to entrance of FeXoR (for I have those)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3384</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3384"/>
		<updated>2025-08-03T13:42:23Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Fast and simple Game Controller JD Hopper */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Fast and simple Game Controller JD Hopper ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
	mesecons_luacontroller:luacontroller* with this code (or mooncontroller:mooncontroller*)&lt;br /&gt;
	digistuff:controller (Game Controller)&lt;br /&gt;
	Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
	All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
	Mount to the game controller and move like you would otherwise&lt;br /&gt;
Notes:&lt;br /&gt;
	Which key is useed to move down e.g. in liquids with the avatar depends on the client settings.&lt;br /&gt;
	This can be set here with &amp;quot;decend_keybinding&amp;quot;&lt;br /&gt;
	Pitchmode not active&lt;br /&gt;
ToDo:&lt;br /&gt;
	Clean up unused vector functions&lt;br /&gt;
	Maybe add pitchmode toggle (though not synced with actual pitchmode)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local wall_knob_channel = &amp;quot;knob&amp;quot;&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == jumpdrive_channel&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif mem.knob ~= nil then&lt;br /&gt;
			mem.position = event.msg.position&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1 BUG Probably not large enough!&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == wall_knob_channel then&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == game_controller_channel and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.position ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = mem.position.x,&lt;br /&gt;
					y = round(mem.position.y + mem.dist),&lt;br /&gt;
					z = mem.position.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = mem.position.x,&lt;br /&gt;
					y = round(mem.position.y - mem.dist),&lt;br /&gt;
					z = mem.position.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3383</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3383"/>
		<updated>2025-08-03T12:45:02Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Fast and simple Game Controller JD Hopper */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Fast and simple Game Controller JD Hopper ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local wall_knob_channel = &amp;quot;knob&amp;quot;&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.knob = mem.knob or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == jumpdrive_channel&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif mem.knob ~= nil then&lt;br /&gt;
			mem.position = event.msg.position&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1 BUG Probably not large enough!&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == wall_knob_channel then&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == game_controller_channel and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.position ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = mem.position.x,&lt;br /&gt;
					y = round(mem.position.y + mem.dist),&lt;br /&gt;
					z = mem.position.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = mem.position.x,&lt;br /&gt;
					y = round(mem.position.y - mem.dist),&lt;br /&gt;
					z = mem.position.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3382</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3382"/>
		<updated>2025-08-03T12:37:10Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Fast and simple Game Controller JD Hopper ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local wall_knob_channel = &amp;quot;knob&amp;quot;&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.knob = 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == jumpdrive_channel&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif mem.knob ~= nil then&lt;br /&gt;
			mem.position = event.msg.position&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1 BUG Probably not large enough!&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == wall_knob_channel then&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == game_controller_channel and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.position ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = mem.position.x,&lt;br /&gt;
					y = round(mem.position.y + mem.dist),&lt;br /&gt;
					z = mem.position.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = mem.position.x,&lt;br /&gt;
					y = round(mem.position.y - mem.dist),&lt;br /&gt;
					z = mem.position.z&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3381</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3381"/>
		<updated>2025-08-03T12:33:19Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Fast and simple Game Controller JD Hopper */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Fast and simple Game Controller JD Hopper ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local wall_knob_channel = &amp;quot;knob&amp;quot;&lt;br /&gt;
local decend_keybinding = &amp;quot;aux1&amp;quot; -- Player preferrence, depending on &amp;quot;aux1_decends&amp;quot; this is either &amp;quot;aux1&amp;quot; or &amp;quot;sneak&amp;quot; e.g. in liquids&lt;br /&gt;
-- No pitchmode&lt;br /&gt;
&lt;br /&gt;
-- Functions and helpers&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.normalize = function (v) return vector.scale(v, 1/vector.length(v)) end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
vector.cross_product = function (v1, v2) return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.z} end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.knob = 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if event.channel == jumpdrive_channel&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.success ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif mem.knob ~= nil then&lt;br /&gt;
			mem.position = event.msg.position&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1 BUG Probably not large enough!&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == wall_knob_channel then&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif&lt;br /&gt;
		event.channel == game_controller_channel and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.position ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		if event.msg.up == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.down == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * event.msg.look_vector.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * event.msg.look_vector.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * event.msg.look_vector.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.left == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x + mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z + mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.right == true then&lt;br /&gt;
			local v = vector.normalize(vector.cross_product(event.msg.look_vector, {x = 0, y = 1, z = 0}))&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x - mem.dist * v.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist * v.y),&lt;br /&gt;
					z = round(mem.position.z - mem.dist * v.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x),&lt;br /&gt;
					y = round(mem.position.y + mem.dist),&lt;br /&gt;
					z = round(mem.position.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		elseif event.msg[decend_keybinding] == true then&lt;br /&gt;
			digiline_send(&lt;br /&gt;
			jumpdrive_channel,&lt;br /&gt;
				{&lt;br /&gt;
					command = &amp;quot;set&amp;quot;,&lt;br /&gt;
					x = round(mem.position.x),&lt;br /&gt;
					y = round(mem.position.y - mem.dist),&lt;br /&gt;
					z = round(mem.position.z)&lt;br /&gt;
				}&lt;br /&gt;
			)&lt;br /&gt;
			digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3380</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3380"/>
		<updated>2025-08-03T10:54:40Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Fast and simple Game Controller JD Hopper */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Fast and simple Game Controller JD Hopper ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local wall_knob_channel = &amp;quot;knob&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.knob = 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if 	event.channel == jumpdrive_channel then&lt;br /&gt;
		if event.msg.success ~= nil and&lt;br /&gt;
			mem.knob ~= nil&lt;br /&gt;
		then&lt;br /&gt;
			digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif mem.knob ~= nil then&lt;br /&gt;
			mem.position = event.msg.position&lt;br /&gt;
			-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1 BUG Probably not large enough!&lt;br /&gt;
			mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.channel == wall_knob_channel then&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	elseif event.channel == game_controller_channel and&lt;br /&gt;
		event.msg.jump == true and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.position ~= nil and&lt;br /&gt;
		mem.dist ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		digiline_send(&lt;br /&gt;
		jumpdrive_channel,&lt;br /&gt;
			{&lt;br /&gt;
				command = &amp;quot;set&amp;quot;,&lt;br /&gt;
				x = math.floor(0.5 + mem.position.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
				y = math.floor(0.5 + mem.position.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
				z = math.floor(0.5 + mem.position.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
			}&lt;br /&gt;
		)&lt;br /&gt;
		digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3379</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3379"/>
		<updated>2025-08-03T10:32:50Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Fast and simple Game Controller JD Hopper&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Fast and simple Game Controller JD Hopper ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local wall_knob_channel = &amp;quot;knob&amp;quot;&lt;br /&gt;
local delay = heat or 1&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Permission denied, &amp;quot; .. tostring(user))&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.knob = 1&lt;br /&gt;
	mem.ready = false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; then&lt;br /&gt;
	if 	event.channel == jumpdrive_channel and&lt;br /&gt;
		mem.knob ~= nil&lt;br /&gt;
	then&lt;br /&gt;
		mem.position = event.msg.position&lt;br /&gt;
		-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
		mem.dist = (1 + mem.knob) * 4 * event.msg.radius + 2&lt;br /&gt;
		mem.ready = true&lt;br /&gt;
	elseif event.channel == wall_knob_channel then&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Knob: &amp;quot; .. tostring(event.msg))&lt;br /&gt;
		mem.knob = event.msg&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		mem.ready = false&lt;br /&gt;
	elseif event.channel == game_controller_channel and&lt;br /&gt;
		event.msg.jump == true and&lt;br /&gt;
		permission_check(event.msg.name) == true and&lt;br /&gt;
		event.msg.look_vector ~= nil and&lt;br /&gt;
		mem.position ~= nil and&lt;br /&gt;
		mem.dist ~= nil and&lt;br /&gt;
		mem.ready == true&lt;br /&gt;
	then&lt;br /&gt;
		digiline_send(&lt;br /&gt;
		jumpdrive_channel,&lt;br /&gt;
			{&lt;br /&gt;
				command = &amp;quot;set&amp;quot;,&lt;br /&gt;
				x = math.floor(0.5 + mem.position.x + mem.dist * event.msg.look_vector.x),&lt;br /&gt;
				y = math.floor(0.5 + mem.position.y + mem.dist * event.msg.look_vector.y),&lt;br /&gt;
				z = math.floor(0.5 + mem.position.z + mem.dist * event.msg.look_vector.z)&lt;br /&gt;
			}&lt;br /&gt;
		)&lt;br /&gt;
		digiline_send(jumpdrive_channel,{command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		mem.ready = false&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	else&lt;br /&gt;
		-- Something may be wrong - request jumpdrive parameters to reset&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
elseif event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	mem.ready = false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3356</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3356"/>
		<updated>2025-05-04T08:51:32Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */ Added mooncontroller output for in-game command to mark target node&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	local rounded = {x = round(target.x), y = round(target.y), z = round(target.z)}&lt;br /&gt;
	print(&amp;quot;Mark with: /area_pos1 &amp;quot;..tostring(rounded.x)..&amp;quot;,&amp;quot;..tostring(rounded.y)..&amp;quot;,&amp;quot;..tostring(rounded.z))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(rounded.x)..&amp;quot;\ny: &amp;quot;..tostring(rounded.y)..&amp;quot;\nz: &amp;quot;..tostring(rounded.z))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3355</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3355"/>
		<updated>2025-05-04T07:58:29Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */ Add mooncontroller output and cleanup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller (Game Controller)&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
ToDo:&lt;br /&gt;
- Save the last feedback of digistuff:controller somewhere else to compare against after reset and only trigger next look data input when different&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot; -- Digiline channel of digistuff:controller&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot; -- Digiline channel of the Monitor&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = 1, z = 0},-- By default Game Controller above Moon Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5 + a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	print(&amp;quot;Mooncontroller pos:&amp;quot;)&lt;br /&gt;
	print(pos)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Estimate taget node location&lt;br /&gt;
if mem.look.lower.look_vector and mem.look.upper.look_vector then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node corners and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is in any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Angles between look vectors and node diagonal:&amp;quot;)&lt;br /&gt;
	print(angles)&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	print(&amp;quot;Distance to the node's corners:&amp;quot;)&lt;br /&gt;
	print(distances)&lt;br /&gt;
	local target = vector.add(&lt;br /&gt;
		vector.add(&lt;br /&gt;
			vector.add(&lt;br /&gt;
				vector.add(pos, offset.mooncontroller), &lt;br /&gt;
			offset.camera),&lt;br /&gt;
		vector.scale(mem.look.lower.look_vector, distances.lower)),&lt;br /&gt;
	vector.scale(diagonal, 0.5))&lt;br /&gt;
	&lt;br /&gt;
	print(&amp;quot;Target absolute coordinates:&amp;quot;)&lt;br /&gt;
	print(target)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(round(target.x))..&amp;quot;\ny: &amp;quot;..tostring(round(target.y))..&amp;quot;\nz: &amp;quot;..tostring(round(target.z)))&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
	print(&amp;quot;Ready for new input&amp;quot;)&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3354</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3354"/>
		<updated>2025-05-01T15:25:25Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */ Return target map coordinates&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller&lt;br /&gt;
- mooncontroller:mooncontroller* below digistuff:controller (for the planned location calculations) with this code&lt;br /&gt;
Optional: digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected of cause ;)&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount to the game controller&lt;br /&gt;
- Press &amp;quot;Backward&amp;quot; while targeting a node's corner with the lowest x, y and z coordinates&lt;br /&gt;
- Press &amp;quot;Forward&amp;quot; while targeting that same node's corner with the highest x, y and z coordinates&lt;br /&gt;
- Read the target node's distance and coordinates from the screen&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
local offset = {&lt;br /&gt;
	mooncontroller = {x = 0, y = -1, z = 0},-- By default LUA Controller expected below Game Controller&lt;br /&gt;
	camera = {x = 0, y = 1.5, z = 0}, -- By default the shift of a usual avatar mounted to a game controller relative to it's voxel's center&lt;br /&gt;
	vcenter = {x = 0.5, y = 0.5, z = 0.5} -- Shift from voxel map and center coordinates&lt;br /&gt;
}&lt;br /&gt;
local luac_offset = {x = 0, y = -1, z = 0} &lt;br /&gt;
local camera_offset = {x = 0.5, y = 1.5, z = 0.5} &lt;br /&gt;
&lt;br /&gt;
-- initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local round = function (a) return math.floor(0.5+a) end&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local pi = math.pi&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local sub = string.sub&lt;br /&gt;
&lt;br /&gt;
local vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.invert = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.invert(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(monitor_channel, &amp;quot;Moonctr at:\nx: &amp;quot;..tostring(pos.x)..&amp;quot;\ny: &amp;quot;..tostring(pos.y)..&amp;quot;\nz: &amp;quot;..tostring(pos.z)) end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if mem.look.lower.look_vector ~= nil and mem.look.upper.look_vector ~= nil then&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node edges and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- vector.inner_product returns values between -1 and 1 for a distance between camera and target &amp;gt; sqrt(3)&lt;br /&gt;
	-- So this is a condition for the code to work propperly - but that is an any reasonable case fullfilled&lt;br /&gt;
	-- That's perfectly suited for arcus cosine that will return values between 0 and pi&lt;br /&gt;
	-- For that sinus will always be non-negative - thus we don't get negative distances - but infinity is possible if you target the same point twice&lt;br /&gt;
	local angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
-- 	digiline_send(monitor_channel, &amp;quot;Angles:\n&amp;quot;..sub(tostring(angles.lower), 1, 5)..&lt;br /&gt;
-- 		&amp;quot;, &amp;quot;..sub(tostring(angles.upper), 1, 5))&lt;br /&gt;
	local distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	local target = vector.add(vector.add(vector.add(vector.subtract(pos, offset.mooncontroller), offset.camera), vector.scale(mem.look.lower.look_vector, distances.lower)), vector.scale(diagonal, 0.5))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Target:\nx: &amp;quot;..tostring(round(target.x*1000)/1000)..&amp;quot;\ny: &amp;quot;..tostring(round(target.y*1000)/1000)..&amp;quot;\nz: &amp;quot;..tostring(round(target.z*1000)/1000))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3353</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3353"/>
		<updated>2025-05-01T07:50:16Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller&lt;br /&gt;
- mesecons_luacontroller:luacontroller* below digistuff:controller (for the planned location calculations)&lt;br /&gt;
- digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local pi = math.pi&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local sub = string.sub&lt;br /&gt;
&lt;br /&gt;
vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + x2.x, y = v1.y + x2.y, z = v1.z + x2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.reverse = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.reverse(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(monitor_channel, &amp;quot;Diagonal:\n&amp;quot;..sub(tostring(vector.length(diagonal)), 1, 8)) end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if mem.look.lower.look_vector ~= nil and mem.look.upper.look_vector ~= nil then&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Got 2 looks&amp;quot;)&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node edges and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- It elludes me why I never get negative lengths - so these explanations may be flawed x)&lt;br /&gt;
	angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Angles:\n&amp;quot;..sub(tostring(angles.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(angles.upper), 1, 5))&lt;br /&gt;
	distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Distances:\n&amp;quot;..sub(tostring(distances.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(distances.upper), 1, 5))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3352</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3352"/>
		<updated>2025-05-01T07:48:00Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller&lt;br /&gt;
- mesecons_luacontroller:luacontroller* below digistuff:controller (for the planned location calculations)&lt;br /&gt;
- digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local pi = math.pi&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local sub = string.sub&lt;br /&gt;
&lt;br /&gt;
vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + x2.x, y = v1.y + x2.y, z = v1.z + x2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.reverse = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.reverse(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(monitor_channel, &amp;quot;Diagonal:\n&amp;quot;..sub(tostring(vector.length(diagonal)), 1, 8)) end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if mem.look.lower.look_vector ~= nil and mem.look.upper.look_vector ~= nil then&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Got 2 looks&amp;quot;)&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node edges and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the supplementary angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- It elludes me why I never get negative lengths - so these explanation may be flawed x)&lt;br /&gt;
	angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Angles:\n&amp;quot;..sub(tostring(angles.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(angles.upper), 1, 5))&lt;br /&gt;
	distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Distances:\n&amp;quot;..sub(tostring(distances.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(distances.upper), 1, 5))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3351</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3351"/>
		<updated>2025-05-01T07:37:45Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller&lt;br /&gt;
- mesecons_luacontroller:luacontroller* below digistuff:controller (for the planned location calculations)&lt;br /&gt;
- digiterms:lcd_monitor (may work with digiterms:cathodic_*monitor, too)&lt;br /&gt;
All digiline connected&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local pi = math.pi&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local sub = string.sub&lt;br /&gt;
&lt;br /&gt;
vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + x2.x, y = v1.y + x2.y, z = v1.z + x2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.reverse = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.reverse(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(monitor_channel, &amp;quot;Diagonal:\n&amp;quot;..sub(tostring(vector.length(diagonal)), 1, 8)) end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if mem.look.lower.look_vector ~= nil and mem.look.upper.look_vector ~= nil then&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Got 2 looks&amp;quot;)&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node edges and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the opposing angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- It elludes me why I never get negative lengths - so these explanation may be flawed x)&lt;br /&gt;
	angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Angles:\n&amp;quot;..sub(tostring(angles.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(angles.upper), 1, 5))&lt;br /&gt;
	distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Distances:\n&amp;quot;..sub(tostring(distances.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(distances.upper), 1, 5))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3350</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3350"/>
		<updated>2025-05-01T07:34:59Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game controller targeting system for Huhhila ;) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller&lt;br /&gt;
- mesecons_luacontroller:luacontroller* digiline connected to the digistuff:controller and directly below it for the planned location calculations&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local pi = math.pi&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local sub = string.sub&lt;br /&gt;
&lt;br /&gt;
vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + x2.x, y = v1.y + x2.y, z = v1.z + x2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.reverse = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.reverse(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(monitor_channel, &amp;quot;Diagonal:\n&amp;quot;..sub(tostring(vector.length(diagonal)), 1, 8)) end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if mem.look.lower.look_vector ~= nil and mem.look.upper.look_vector ~= nil then&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Got 2 looks&amp;quot;)&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node edges and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the opposing angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- It elludes me why I never get negative lengths - so these explanation may be flawed x)&lt;br /&gt;
	angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Angles:\n&amp;quot;..sub(tostring(angles.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(angles.upper), 1, 5))&lt;br /&gt;
	distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Distances:\n&amp;quot;..sub(tostring(distances.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(distances.upper), 1, 5))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3349</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3349"/>
		<updated>2025-05-01T07:30:01Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Replacing diamond calculation for Hedgehog with Game controller targeting system for Huhhila&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game controller targeting system for Huhhila ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Nodes:&lt;br /&gt;
- digistuff:controller&lt;br /&gt;
- mesecons_luacontroller:luacontroller* digiline connected to the injector (directly below the digistuff:controller for the planned location calculations)&lt;br /&gt;
Digiline connected ofc. ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Configuration&lt;br /&gt;
local controller_channel = &amp;quot;controller&amp;quot;&lt;br /&gt;
local monitor_channel = &amp;quot;mon&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- initialisation&lt;br /&gt;
local default_look = {lower = {}, upper = {}}&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; or mem.look == nil then mem.look = default_look end&lt;br /&gt;
local diagonal = {x = 1, y = 1, z = 1}&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local pi = math.pi&lt;br /&gt;
local acos = math.acos&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local sub = string.sub&lt;br /&gt;
&lt;br /&gt;
vector = {}&lt;br /&gt;
vector.add = function (v1, v2) return {x = v1.x + x2.x, y = v1.y + x2.y, z = v1.z + x2.z} end&lt;br /&gt;
vector.scale = function (v, a) return {x = a * v.x, y = a * v.y, z = a * v.z} end&lt;br /&gt;
vector.reverse = function (v) return vector.scale(v, -1) end&lt;br /&gt;
vector.subtract = function (v1, v2) return vector.add(v1, vector.reverse(v2)) end&lt;br /&gt;
vector.inner_product = function (v1, v2) return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z end&lt;br /&gt;
vector.length = function (v) return sqrt(vector.inner_product(v, v)) end&lt;br /&gt;
vector.angle = function (v1, v2) return acos(vector.inner_product(v1, v2) / vector.length(v1) / vector.length(v2)) end&lt;br /&gt;
diagonal.length = vector.length(diagonal)&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(monitor_channel, &amp;quot;Diagonal:\n&amp;quot;..sub(tostring(vector.length(diagonal)), 1, 8)) end&lt;br /&gt;
&lt;br /&gt;
-- Store look data on y movement&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == controller_channel then&lt;br /&gt;
	if event.msg.movement_y == -1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.lower[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Lower corner&amp;quot;)&lt;br /&gt;
	elseif event.msg.movement_y == 1 then&lt;br /&gt;
		for k, v in pairs(event.msg) do&lt;br /&gt;
			mem.look.upper[k] = v&lt;br /&gt;
		end&lt;br /&gt;
		digiline_send(monitor_channel, &amp;quot;Upper corner&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if mem.look.lower.look_vector ~= nil and mem.look.upper.look_vector ~= nil then&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Got 2 looks&amp;quot;)&lt;br /&gt;
	-- These angles are not necessarily the inner angles of the triangle of the two targeted node edges and the camera position!&lt;br /&gt;
	-- Depending of the orientation it can also be the opposing angle (pi - inner_angle)&lt;br /&gt;
	-- This doesn't matter because the sin is symetric to pi/2&lt;br /&gt;
	-- Similarly the angle between the two view vectors may be shifted by 2*pi - which doesn't matter because sin is 2*pi cyclic&lt;br /&gt;
	-- It elludes me why I never get negative lengths - so these explanation may be flawed x)&lt;br /&gt;
	angles = {&lt;br /&gt;
		lower = vector.angle(mem.look.lower.look_vector, diagonal),&lt;br /&gt;
		upper = vector.angle(mem.look.upper.look_vector, diagonal)&lt;br /&gt;
	}&lt;br /&gt;
	angles.spread = angles.lower - angles.upper&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Angles:\n&amp;quot;..sub(tostring(angles.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(angles.upper), 1, 5))&lt;br /&gt;
	distances = {&lt;br /&gt;
		lower = diagonal.length / sin(angles.spread) * sin(angles.upper),&lt;br /&gt;
		upper = diagonal.length / sin(angles.spread) * sin(angles.lower)&lt;br /&gt;
	}&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Distances:\n&amp;quot;..sub(tostring(distances.lower), 1, 5)..&lt;br /&gt;
		&amp;quot;, &amp;quot;..sub(tostring(distances.upper), 1, 5))&lt;br /&gt;
	digiline_send(monitor_channel, &amp;quot;Resetting&amp;quot;)&lt;br /&gt;
	mem.look = default_look&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3207</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3207"/>
		<updated>2024-10-27T15:41:12Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Number of voxel calculations for diamonds in different orientations&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some calculation about a diamond in 2 orientations for Hedgehog ;) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
debug = 0&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;3D Diamond with square layers (less symmetric)&amp;quot;)&lt;br /&gt;
# Scatch of layer with width 3:&lt;br /&gt;
# ooo&lt;br /&gt;
# ooo&lt;br /&gt;
# ooo&lt;br /&gt;
&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(100):&lt;br /&gt;
	w = 2*i+1&lt;br /&gt;
	if debug: print(&amp;quot;  Width of layer: &amp;quot; + str(w))&lt;br /&gt;
	a = w**2&lt;br /&gt;
	if debug: print(&amp;quot;  Nodes for this layer: &amp;quot; + str(a))&lt;br /&gt;
	n += a&lt;br /&gt;
&lt;br /&gt;
n *= 2&lt;br /&gt;
w = 2*100+1&lt;br /&gt;
if debug: print(&amp;quot;  Central width: &amp;quot;+str(w))&lt;br /&gt;
&lt;br /&gt;
a = w**2&lt;br /&gt;
if debug: print(&amp;quot;  Nodes for center layer: &amp;quot; + str(a))&lt;br /&gt;
&lt;br /&gt;
n += a&lt;br /&gt;
print(&amp;quot; Total number of nodes: &amp;quot; + str(n))&lt;br /&gt;
# 2707001&lt;br /&gt;
&lt;br /&gt;
# (2*100+1)**2 + 2 * ((2*0+1)**2 + (2*1+1)**2 + ... + (2*99+1)**2)&lt;br /&gt;
# = (2*100+1)**2 + 2 * sum(i=0 to 99)[(2*i+1)**2]&lt;br /&gt;
# = (2*100+1)**2 + 2/3*(99+1)*(2*99+1)*(2*99+3)&lt;br /&gt;
print(&amp;quot; (Test: &amp;quot; + str((2*100+1)**2 + 2/3*(99+1)*(2*99+1)*(2*99+3)) + &amp;quot; )\n&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;3D Diamond with non-square layers (more symmetric)&amp;quot;)&lt;br /&gt;
# Scatch of layer with width 3:&lt;br /&gt;
#  o&lt;br /&gt;
# ooo&lt;br /&gt;
#  o&lt;br /&gt;
&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(100):&lt;br /&gt;
	if debug: print(&amp;quot;  Width of layer: &amp;quot; + str(2*i+1))&lt;br /&gt;
	a = (i+1)*(i+2)-1&lt;br /&gt;
	if debug: print(&amp;quot;  Nodes for this layer: &amp;quot; + str(a))&lt;br /&gt;
	n += a&lt;br /&gt;
&lt;br /&gt;
n *= 2&lt;br /&gt;
if debug: print(&amp;quot;  Central width: &amp;quot;+str(2*100+1))&lt;br /&gt;
&lt;br /&gt;
a = (100+1)*(100+2)-1&lt;br /&gt;
if debug: print(&amp;quot;  Nodes for center layer: &amp;quot; + str(a))&lt;br /&gt;
&lt;br /&gt;
n += a&lt;br /&gt;
print(&amp;quot; Total number of nodes: &amp;quot; + str(n))&lt;br /&gt;
# 696901&lt;br /&gt;
&lt;br /&gt;
# 2 * ((0+1)*(0+2)-1 + (1+1)*(1+2)-1 + ... + (n+1)*(n+2)-1) + (100+1)*(100+2)-1&lt;br /&gt;
# = (100+1)*(100+2)-1 + 2* sum(i=0 to 99)[(i+1)*(i+2)-1]&lt;br /&gt;
# = (100+1)*(100+2)-1 + 2/3*(99+1)*(99**2+5*99+3)&lt;br /&gt;
print(&amp;quot; (Test: &amp;quot; + str((100+1)*(100+2)-1 + 2/3*(99+1)*(99**2+5*99+3)) + &amp;quot; )\n&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Cables&amp;diff=3196</id>
		<title>Cables</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Cables&amp;diff=3196"/>
		<updated>2024-05-01T21:32:55Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Adjusted link to moved page and added common abbreviation&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Cables are the main transporters of [[Technic Energy Units|Energy Units (EU)]] in Technic. As usual, there are 3 types: LV, MV and HV.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can change energy tiers with a [[Supply Converter]] albeit with a 10% energy loss.&lt;br /&gt;
&lt;br /&gt;
[[Category:Technic]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Energy&amp;diff=3195</id>
		<title>Energy</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Energy&amp;diff=3195"/>
		<updated>2024-05-01T21:27:44Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: FeXoR moved page Energy to Technic Energy Units: Energy is a bit vague&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Technic Energy Units]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Technic_Energy_Units&amp;diff=3194</id>
		<title>Technic Energy Units</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Technic_Energy_Units&amp;diff=3194"/>
		<updated>2024-05-01T21:27:44Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: FeXoR moved page Energy to Technic Energy Units: Energy is a bit vague&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Overview ==&lt;br /&gt;
Energy is the basic resource in Technic. It is needed by every machine (with different amounts) to power them. You cannot change energy types without a [[Supply Converter]], and there is a 10% decrease in energy when you switch.&lt;br /&gt;
&lt;br /&gt;
== Energy Uses ==&lt;br /&gt;
Energy can be used and made with machines. Here is a list of all energy users and generators.&lt;br /&gt;
=== Energy Sources ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin:auto&amp;quot;&lt;br /&gt;
|+ Technic Energy Sources&lt;br /&gt;
|- &lt;br /&gt;
! Type !! LV !! MV !! HV !! Notes&lt;br /&gt;
|- &lt;br /&gt;
| Fuel-Fired || 200EU || 600EU || 1.2kEU || Needs fuel to run. Various fuel-types are available with various burn-times. Sticks: short, biofuel: medium, lava-orbs: long.&lt;br /&gt;
|-&lt;br /&gt;
| Solar Panel || (light + y position) * 3EU &amp;lt;br/&amp;gt;Has a cap of 200EU|| colspan=&amp;quot;2&amp;quot; style=&amp;quot;background:#EAECF0| || The panel only works above -10y, with light above 12, during daytime. Full production of solar generators is achieved at about 50y&lt;br /&gt;
|-&lt;br /&gt;
| Solar Array || (light + y position) * 10EU&amp;lt;br/&amp;gt;Has a cap of 500EU || (light + y position) * 30EU&amp;lt;br/&amp;gt;Has a cap of 1.5kEU ||  (light + y position) * 100EU&amp;lt;br/&amp;gt;Has a cap of 5kEU || The panel array only works above 0y, with light above 12, during daytime.&lt;br /&gt;
|-&lt;br /&gt;
| Hydro || amount of flowing water around * 4EU || amount of flowing water around * 40EU || style=&amp;quot;background:#EAECF0| || Maximum power generated is 180EU and 1.8kEU, respectively&lt;br /&gt;
|-&lt;br /&gt;
| Geothermal || 1 water + 1 lava around: 50EU&amp;lt;br/&amp;gt; 2 water + 1 lava around: 100EU&amp;lt;br/&amp;gt; 1 lava + 2 water around: 200EU&amp;lt;br/&amp;gt; 2 water + 2 lava around: 300EU || colspan=&amp;quot;2&amp;quot; style=&amp;quot;background:#EAECF0| || Put the water and lava opposite one another in order to not destroy each other.&lt;br /&gt;
|-&lt;br /&gt;
| Nuclear || colspan=&amp;quot;2&amp;quot; style=&amp;quot;background:#EAECF0| || 100kEU || Needs to be refueled with fuel-rods. Minimum 6 rods at a time, one in each slot.&lt;br /&gt;
|-&lt;br /&gt;
| Battery Box || 500EU || 2kEU || 10kEU || Needs charge before giving energy. Each RE-battery upgrade adds 10% to the storage.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Energy Demands ===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot; style=&amp;quot;margin:auto&amp;quot;&lt;br /&gt;
|+ Technic Energy Demands&lt;br /&gt;
|- &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot;|Type !! rowspan=&amp;quot;2&amp;quot;|LV !! colspan=&amp;quot;3&amp;quot;|MV !! colspan=&amp;quot;3&amp;quot;|HV&lt;br /&gt;
|-&lt;br /&gt;
! 0 batteries !! 1 battery !! 2 batteries !! 0 batteries !! 1 battery !! 2 batteries&lt;br /&gt;
|- &lt;br /&gt;
| [[Alloy Furnace]] || 300EU || 3kEU || 2kEU || 1kEU || colspan=&amp;quot;3&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| [[Compressor]] || 300EU || 800EU || 600EU || 400EU || 1.5kEU || 1kEU || 750EU&lt;br /&gt;
|-&lt;br /&gt;
| [[Centrifuge]] || style=&amp;quot;background:#EAECF0| || 8kEU || 7kEU || 6kEU || colspan=&amp;quot;3&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| [[Electric Furnace]] || 300EU || 2kEU || 1kEU || 500EU || 4kEU || 2.5kEU || 1.5kEU&lt;br /&gt;
|-&lt;br /&gt;
| [[Extractor]] || 300EU ||  800EU || 600EU || 400EU || colspan=&amp;quot;3&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| [[Grinder]] || 200EU || 600EU || 450EU || 300EU || 1.2kEU || 900EU || 600EU&lt;br /&gt;
|-&lt;br /&gt;
| [[Freezer]] || style=&amp;quot;background:#EAECF0| ||  800EU || 600EU || 400EU || colspan=&amp;quot;3&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| Lamp || 50EU || colspan=&amp;quot;6&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| LED || 5EU || colspan=&amp;quot;6&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| Music Player || colspan=&amp;quot;6&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| [[Quarry]] || colspan=&amp;quot;4&amp;quot; style=&amp;quot;background:#EAECF0| || 10kEU || colspan=&amp;quot;2&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|-&lt;br /&gt;
| Tool Workshop || style=&amp;quot;background:#EAECF0| || 5kEU || 3.5kEU || 2kEU || colspan=&amp;quot;3&amp;quot; style=&amp;quot;background:#EAECF0|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
Not all machines have LV, MV, and HV tiers. This is to prevent the other tiers from being irrevelant.&lt;br /&gt;
&lt;br /&gt;
[https://github.com/pandorabox-io/in-game/issues/172 There is an issue] on {{#fab:github}} [https://github.com/pandorabox-io Pandorabox github] about ideas concerning LV.&lt;br /&gt;
[[Category:Automation]]&lt;br /&gt;
[[Category:Technic]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Low_Earth_Orbit&amp;diff=3193</id>
		<title>Low Earth Orbit</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Low_Earth_Orbit&amp;diff=3193"/>
		<updated>2024-05-01T19:22:50Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Fixed typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Low Earth Orbit is the realm in between [[Earth]] and [[Moon]], ranging from 1km to 5km. It doesn't really have anything except for some space stations. There are no entrance requirements. Since there is vacuum there, wear a spacesuit. If you decide to go there, be sure to visit [[User:SwissalpS|SwissalpS]]'s [[Submoonine]], and check out the other builds.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Realm]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3192</id>
		<title>Admins, Moderators and Staff</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Admins,_Moderators_and_Staff&amp;diff=3192"/>
		<updated>2024-05-01T19:08:57Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Linked FeXoR user page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= About roles and privileges =&lt;br /&gt;
&lt;br /&gt;
==== Admins ====&lt;br /&gt;
Accounts with the &amp;lt;code&amp;gt;privs&amp;lt;/code&amp;gt; privilege are admin accounts. Admin accounts should only be used for emergencies or cleanups, not for building stuff.&lt;br /&gt;
&lt;br /&gt;
==== Moderators ====&lt;br /&gt;
Moderators have &amp;lt;code&amp;gt;ban&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;kick&amp;lt;/code&amp;gt; privileges, and sometimes also &amp;lt;code&amp;gt;basic_privs&amp;lt;/code&amp;gt;. Moderators help to keep the server nice. See also the [[Moderator manual]].&lt;br /&gt;
&lt;br /&gt;
==== Staff ====&lt;br /&gt;
Players with the &amp;lt;code&amp;gt;staff&amp;lt;/code&amp;gt; privilege can access server control.&lt;br /&gt;
 &lt;br /&gt;
==== Spawn builders ====&lt;br /&gt;
Spawn builders have the &amp;lt;code&amp;gt;spawn_builder&amp;lt;/code&amp;gt; privilege, and can build in the protected area at spawn.&lt;br /&gt;
&lt;br /&gt;
= List of accounts and privileges =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=2 | Name&lt;br /&gt;
! colspan=6| Privilege&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | basic_privs&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | staff&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | ban&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | kick&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot; | spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|1hit ||  ||  || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|6r1d ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|a ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|AceRichman ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|admin || privs || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Adventurer ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|almafa ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|BuckarooBanzai ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|calculon ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|ceylan ||  ||  || staff || ban ||  ||  &lt;br /&gt;
|-&lt;br /&gt;
|chi ||  ||  || staff ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|chuche952 ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|coil ||  ||  ||  || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|coil0 || privs ||  ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|DarkRoy ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Denis2 ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:FeXoR|FeXoR]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Hedgehog ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Huhhila ||  || basic_privs || staff || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|int ||  ||  || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|iska ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jackb ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|jacrackorn ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|jihuu ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Justice ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|kiedtl ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|korlen ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Lukc ||  || basic_privs ||  ||  ||  || &lt;br /&gt;
|-&lt;br /&gt;
|Lypsoto ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|[[User:MCLV|MCLV]] ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|nil ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|OgelGames || privs ||  || staff || ban || kick || spawn_builder &lt;br /&gt;
|-&lt;br /&gt;
|petarpro ||  ||  ||  || ban ||  || &lt;br /&gt;
|-&lt;br /&gt;
|pipo ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Pixalou ||  ||  ||  || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Rabbit1 ||  ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|Ruggila ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|sean ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|SoloSniper ||  ||  || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|SwissalpS ||  ||  ||  ||  ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|SX ||  || basic_privs || staff || ban || kick || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|T4im ||  || basic_privs ||  ||  || kick || &lt;br /&gt;
|-&lt;br /&gt;
|thomas || privs || basic_privs || staff || ban || kick || &lt;br /&gt;
|-&lt;br /&gt;
|Warden ||  ||  ||  || ban || kick ||  &lt;br /&gt;
|-&lt;br /&gt;
|Yoyodyne || privs ||  || staff || ban ||  || spawn_builder&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Moderation]]&lt;br /&gt;
[[Category:Stub]]&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=Sherwood_Railway&amp;diff=3191</id>
		<title>Sherwood Railway</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=Sherwood_Railway&amp;diff=3191"/>
		<updated>2024-05-01T18:38:16Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Attempt to fix image&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Sherwood_rwy_full_fix.png|thumb|right|400px|Map of the route. Shows tunnel portals with and elevated sections.]]&lt;br /&gt;
&lt;br /&gt;
The '''Sherwood Railway''' is a proposal for a new branch line railway and passenger service to Sherwood Forest. It will add two new stations and connect two existing ones in Technic City.&lt;br /&gt;
&lt;br /&gt;
The map illustrates the proposal.&lt;br /&gt;
* The red section is to be build by [[User:Blockhead|Blockhead]] and [[User:Monniasza|Monniasza]] as a new railway.&lt;br /&gt;
* The blue section is an existing track owned by [[User:Yaofyr|Yaofyr]]. It will be used by at least one automatic passenger train. The YCL line will be interlocked by Blockhead on this section so there can be no collisions.&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=File:Sherwood_rwy_full_fix.png&amp;diff=3190</id>
		<title>File:Sherwood rwy full fix.png</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=File:Sherwood_rwy_full_fix.png&amp;diff=3190"/>
		<updated>2024-05-01T18:37:25Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: File uploaded with MsUpload&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;File uploaded with MsUpload&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=File:Sherwood_rwy_full_fix.jpg&amp;diff=3189</id>
		<title>File:Sherwood rwy full fix.jpg</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=File:Sherwood_rwy_full_fix.jpg&amp;diff=3189"/>
		<updated>2024-05-01T18:35:44Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: File uploaded with MsUpload&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;File uploaded with MsUpload&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3054</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3054"/>
		<updated>2024-04-07T15:15:46Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: /* Game Controller steered Jumpdrive with Noteblock feedback */ Updated usage string&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
	<entry>
		<id>https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3053</id>
		<title>User:FeXoR</title>
		<link rel="alternate" type="text/html" href="https://pandorabox.io/index.php?title=User:FeXoR&amp;diff=3053"/>
		<updated>2024-04-07T14:56:10Z</updated>

		<summary type="html">&lt;p&gt;FeXoR: Replaced MIA with Game Controller JD&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Hiho Pandorabox o/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.&lt;br /&gt;
&lt;br /&gt;
I also value the transparency of this server due to this wiki and its contents like the mod list.&lt;br /&gt;
&lt;br /&gt;
The maintenance you pull off is awesome!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Thank you for this great place ;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Builds =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
pandorabox_shipbuildingcontest2021_jolly_hedgy.png|Jolly Hedgy&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Glitchy things =&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
translucent_pod2_bottom_01.png&lt;br /&gt;
translucent_pod2_bottom_02.png&lt;br /&gt;
translucent_pod2_bottom_03.png&lt;br /&gt;
unified_inventory_in_minetest_5_1_1.png&lt;br /&gt;
monitor_visible_at_light_level_0.png&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= LUA Controller Code =&lt;br /&gt;
&lt;br /&gt;
== Event Catcher - Mooncontroller ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Event Catcher code for the mooncontroller&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;#&amp;quot; .. tostring(mem.events.count) .. &amp;quot;, &amp;quot; .. get_time_string() .. &amp;quot;:&amp;quot;)&lt;br /&gt;
print(get_string(event))&lt;br /&gt;
&lt;br /&gt;
mem.events.count = (mem.events.count + 1)%1000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Variable Printer&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
help = &amp;quot;\tType 'variables' to get a list of all global variables&amp;quot;&lt;br /&gt;
help = help .. &amp;quot;\n\tType the name of a variable to print it e.g. _G or help&amp;quot;&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then print(help) end&lt;br /&gt;
&lt;br /&gt;
local variables = {}&lt;br /&gt;
for k, v in pairs(_G) do&lt;br /&gt;
	variables[k] = v&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;terminal&amp;quot; then&lt;br /&gt;
	if event.text == &amp;quot;variables&amp;quot; then&lt;br /&gt;
		n_vars = 0&lt;br /&gt;
		for k, v in pairs(variables) do&lt;br /&gt;
			n_vars = n_vars + 1&lt;br /&gt;
			print(k .. &amp;quot; (&amp;quot; .. type(_G[k]) .. &amp;quot;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		print(&amp;quot;\t&amp;quot; .. tostring(n_vars) .. &amp;quot; variables&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		print(variables[event.text])&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- MIT License&lt;br /&gt;
-- &lt;br /&gt;
-- Copyright (c) 2021 Florian Finke&lt;br /&gt;
-- &lt;br /&gt;
-- Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
-- of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
-- in the Software without restriction, including without limitation the rights&lt;br /&gt;
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
-- copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
-- furnished to do so, subject to the following conditions:&lt;br /&gt;
-- &lt;br /&gt;
-- The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
-- copies or substantial portions of the Software.&lt;br /&gt;
-- &lt;br /&gt;
-- THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
-- SOFTWARE.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Basic Pandorabox tools&lt;br /&gt;
&lt;br /&gt;
BUG:&lt;br /&gt;
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type &amp;quot;table&amp;quot; before probing keys!&lt;br /&gt;
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)&lt;br /&gt;
TODO:&lt;br /&gt;
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!&lt;br /&gt;
- Add page for channels and other settings&lt;br /&gt;
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)&lt;br /&gt;
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that&lt;br /&gt;
- Unify usage of linebuffer&lt;br /&gt;
- Hardcoded textarea name &amp;quot;display&amp;quot; (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Optimization&lt;br /&gt;
-- ########&lt;br /&gt;
local math, os, string, table = math, os, string, table&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Settings&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local permission = {authorised_users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}}&lt;br /&gt;
if mem.permission == nil then mem.permission = {ignore = false} end&lt;br /&gt;
permission.check = function(user)&lt;br /&gt;
	if mem.permission.ignore == true then return true end&lt;br /&gt;
	local is_allowed = false&lt;br /&gt;
	for i, u in ipairs(permission.authorised_users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			is_allowed = true&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return is_allowed&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = &amp;quot;mon_ec&amp;quot;}}&lt;br /&gt;
if mem.events == nil then mem.events = {count = 0} end&lt;br /&gt;
&lt;br /&gt;
local jumpdrive = {channel = &amp;quot;jumpdrive&amp;quot;}&lt;br /&gt;
if mem.linebuffer == nil then&lt;br /&gt;
	mem.linebuffer = {}&lt;br /&gt;
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {&amp;quot;Here the Jumpdrive's responses will be shown.&amp;quot;} end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local quarries = {channel = &amp;quot;quarry&amp;quot;}&lt;br /&gt;
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end&lt;br /&gt;
&lt;br /&gt;
local touchscreen = {&lt;br /&gt;
	channel = &amp;quot;ts&amp;quot;,&lt;br /&gt;
	pages = {&amp;quot;Events&amp;quot;, &amp;quot;Jumpdrive&amp;quot;, &amp;quot;LUA Libs&amp;quot;},&lt;br /&gt;
	update_pages = {&amp;quot;Events&amp;quot;},&lt;br /&gt;
	permissions = {&amp;quot;Open&amp;quot;, &amp;quot;Users&amp;quot;, &amp;quot;Locked&amp;quot;},&lt;br /&gt;
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}&lt;br /&gt;
}&lt;br /&gt;
if mem.page == nil then&lt;br /&gt;
	mem.page = 1&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
end&lt;br /&gt;
if mem.ts_lock == nil then mem.ts_lock = 2 end&lt;br /&gt;
&lt;br /&gt;
local breakers = {port = &amp;quot;b&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Helper functions&lt;br /&gt;
-- ########&lt;br /&gt;
&lt;br /&gt;
local character = {}&lt;br /&gt;
character.is_numeric = function (sChar)&lt;br /&gt;
	if sChar:byte() &amp;gt;= 48 and sChar:byte() &amp;lt;= 57 then return true&lt;br /&gt;
	else return false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local coordinates = {names = {&amp;quot;x&amp;quot;, &amp;quot;y&amp;quot;, &amp;quot;z&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus&lt;br /&gt;
function coordinates:to_table(sInput)&lt;br /&gt;
	local tNumberList = {}&lt;br /&gt;
	if type(sInput) == &amp;quot;string&amp;quot; then&lt;br /&gt;
		local bContinuous = false&lt;br /&gt;
		for nChar = 1, #sInput do&lt;br /&gt;
			local nByte = sInput:byte(nChar)&lt;br /&gt;
			-- A new numerical string starts - potentially - ignoring repetitions of &amp;quot;-&amp;quot;&lt;br /&gt;
			if (bContinuous == false and (nByte == 45 or (nByte &amp;gt;= 48 and nByte &amp;lt;= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then&lt;br /&gt;
				-- Override previous non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
				if #tNumberList &amp;gt; 0 and tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = string.char(nByte)&lt;br /&gt;
				else table.insert(tNumberList, string.char(nByte))&lt;br /&gt;
				end&lt;br /&gt;
				bContinuous = true&lt;br /&gt;
			elseif bContinuous and (nByte &amp;gt;= 48 and nByte &amp;lt;= 57) then&lt;br /&gt;
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)&lt;br /&gt;
			elseif nByte ~= 45 then&lt;br /&gt;
				bContinuous = false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		-- Remove tailing non valid numerical string &amp;quot;-&amp;quot;&lt;br /&gt;
		if tNumberList[#tNumberList] == &amp;quot;-&amp;quot; then tNumberList[#tNumberList] = nil end&lt;br /&gt;
	end&lt;br /&gt;
	local tOutput = {}&lt;br /&gt;
	for i, name in ipairs(self.names) do&lt;br /&gt;
		tOutput[name] = tonumber(tNumberList[i] or &amp;quot;0&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return tOutput&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function coordinates:to_string(coordinate_table) return coordinate_table.x .. &amp;quot;,&amp;quot; .. coordinate_table.y .. &amp;quot;,&amp;quot; .. coordinate_table.z end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function get_string(data, maxdepth)&lt;br /&gt;
	local maxdepth = maxdepth or 3&lt;br /&gt;
	if type(data) == &amp;quot;string&amp;quot; then return data&lt;br /&gt;
	elseif type(data) == &amp;quot;table&amp;quot; and maxdepth &amp;gt; 0 then&lt;br /&gt;
		local oString = &amp;quot;{&amp;quot;&lt;br /&gt;
		for k, v in pairs(data) do&lt;br /&gt;
			local val = v&lt;br /&gt;
			if type(v) == &amp;quot;table&amp;quot; then val = get_string(val, maxdepth - 1) end&lt;br /&gt;
			oString = oString .. tostring(k) .. &amp;quot;=&amp;quot; .. tostring(val) .. &amp;quot;, &amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return string.sub(oString, 1, -3) .. &amp;quot;}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		return tostring(data)&lt;br /&gt;
	end&lt;br /&gt;
	return &amp;quot;Something went wrong!&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function get_time_string(datetable)&lt;br /&gt;
	local date_table = datetable or os.datetable()&lt;br /&gt;
	local date_string = date_table.year .. &amp;quot;-&amp;quot; .. date_table.month .. &amp;quot;-&amp;quot; .. date_table.day&lt;br /&gt;
	local time_string = date_table.hour .. &amp;quot;:&amp;quot; .. date_table.min .. &amp;quot;:&amp;quot; .. date_table.sec&lt;br /&gt;
	return date_string .. &amp;quot;T&amp;quot; .. time_string&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function merge_shallow_tables(t1, t2)&lt;br /&gt;
	local t3 = {}&lt;br /&gt;
	for k, v in pairs(t1) do t3[k] = v end&lt;br /&gt;
	for k, v in pairs(t2) do t3[k] = v end&lt;br /&gt;
	return t3&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function add_line_to_buffer(linebuffer, message)&lt;br /&gt;
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)&lt;br /&gt;
	if type(message) ~= &amp;quot;string&amp;quot; then message = get_string(message) end&lt;br /&gt;
	table.insert(linebuffer.memory, 1, message)&lt;br /&gt;
	while table.maxn(linebuffer.memory) &amp;gt; linebuffer.max_lines do table.remove(linebuffer.memory) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function touchscreen_add_line(msg)&lt;br /&gt;
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. &amp;quot;: &amp;quot; .. tostring(msg))&lt;br /&gt;
	while table.maxn(mem.event_catcher.touchscreen_line_table) &amp;gt; event_catcher.touchscreen.max_lines do&lt;br /&gt;
		table.remove(mem.event_catcher.touchscreen_line_table)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function send_to_monitors(message)&lt;br /&gt;
	-- Omitt appending it's own and other &amp;quot;display&amp;quot; type content to avoid doubling the output&lt;br /&gt;
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = &amp;quot;&amp;lt;cut&amp;gt;&amp;quot; end&lt;br /&gt;
	&lt;br /&gt;
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)&lt;br /&gt;
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)&lt;br /&gt;
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end&lt;br /&gt;
	if message ~= nil and message.type == &amp;quot;interrupt&amp;quot; then message.time = get_time_string() end&lt;br /&gt;
	touchscreen_add_line(get_string(message))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Touchscreen&lt;br /&gt;
-- ########&lt;br /&gt;
local function update_page(page)&lt;br /&gt;
	if touchscreen.pages[mem.page] == page then&lt;br /&gt;
		local message = {&lt;br /&gt;
			{command = &amp;quot;clear&amp;quot;},&lt;br /&gt;
			-- BUG background9 needs to be before bgcolor&lt;br /&gt;
 			-- {command = &amp;quot;add&amp;quot;, element = &amp;quot;background9&amp;quot;, X = 0, Y = 0, W = 0, H = 0, image = &amp;quot;ui_formbg_9_sliced.png&amp;quot;, auto_clip = true, middle = 16},&lt;br /&gt;
			-- BUG focus doesn't seem to work at all: focus = &amp;quot;target&amp;quot;&lt;br /&gt;
			{command = &amp;quot;set&amp;quot;, width = 13, height = 10, no_prepend = true, real_coordinates = true},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;bgcolor&amp;quot;, bgcolor = &amp;quot;#202040FF&amp;quot;, fullscreen = &amp;quot;false&amp;quot;, fbgcolor = &amp;quot;#10101040&amp;quot;},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Page:&amp;quot;, X=0.2,Y=0.3},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;page&amp;quot;, listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},&lt;br /&gt;
			&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Lock:&amp;quot;, X=0.2,Y=8.5},&lt;br /&gt;
			{command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;lock&amp;quot;, listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},&lt;br /&gt;
		}&lt;br /&gt;
		if page == &amp;quot;Events&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;Events:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.event_catcher.touchscreen_line_table, &amp;quot;\n&amp;quot;), X=1.5, Y=0.55, W=11.25, H=9.3})&lt;br /&gt;
		&lt;br /&gt;
		elseif page == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;request_data&amp;quot;, label = &amp;quot;Refresh&amp;quot;, X=1.7,Y=0.25,W=1.2,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, X=3.1, Y=0.5,&lt;br /&gt;
				label = &amp;quot;Distance: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.distance)) .. &amp;quot;  |  &amp;quot; ..&lt;br /&gt;
				&amp;quot;EU needed: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.power_req)) .. &amp;quot; stored: &amp;quot; .. tostring(math.ceil(mem.jumpdrive.powerstorage))&lt;br /&gt;
			})&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			-- BUG The &amp;quot;set&amp;quot; focus propperty doesn't seem to work. The first input field added get's the focus&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;target&amp;quot;,&lt;br /&gt;
				label = &amp;quot;Target (Current: &amp;quot; .. coordinates:to_string(mem.jumpdrive.position) .. &amp;quot;)&amp;quot;,&lt;br /&gt;
				default = coordinates:to_string(mem.jumpdrive.target),&lt;br /&gt;
				X=3.8, Y=1.8, W=4.5})&lt;br /&gt;
			&lt;br /&gt;
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textarea&amp;quot;, name = &amp;quot;display&amp;quot;, label = &amp;quot;The Jumpdrive says:&amp;quot;,&lt;br /&gt;
				default = table.concat(mem.linebuffer.jumpdrive, &amp;quot;\n&amp;quot;), X=1.6, Y=3.8, W=10.7, H=6})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;Radius&amp;quot;, X=12,Y=3.7})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;textlist&amp;quot;, name = &amp;quot;radius&amp;quot;,&lt;br /&gt;
				listelements = {&amp;quot;1&amp;quot;,&amp;quot;2&amp;quot;,&amp;quot;3&amp;quot;,&amp;quot;4&amp;quot;,&amp;quot;5&amp;quot;,&amp;quot;6&amp;quot;,&amp;quot;7&amp;quot;,&amp;quot;8&amp;quot;,&amp;quot;9&amp;quot;,&amp;quot;10&amp;quot;,&amp;quot;11&amp;quot;,&amp;quot;12&amp;quot;,&amp;quot;13&amp;quot;,&amp;quot;14&amp;quot;,&amp;quot;15&amp;quot;},&lt;br /&gt;
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})&lt;br /&gt;
			&lt;br /&gt;
			-- Message very long, split here&lt;br /&gt;
-- 			digiline_send(touchscreen.channel, message)&lt;br /&gt;
-- 			message = {}&lt;br /&gt;
			&lt;br /&gt;
			-- BUG The &amp;quot;H&amp;quot; parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;field&amp;quot;, name = &amp;quot;jump_step_value&amp;quot;, label = &amp;quot;Distance&amp;quot;,&lt;br /&gt;
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_increase&amp;quot;, label = &amp;quot;+&amp;quot;, X=1.7,Y=1,W=1.1,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;jump_step_decrease&amp;quot;, label = &amp;quot;-&amp;quot;, X=1.7,Y=2.6,W=1.1,H=0.5})&lt;br /&gt;
-- 			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xi&amp;quot;, label = &amp;quot;E&amp;quot;, X=3.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;xd&amp;quot;, label = &amp;quot;W&amp;quot;, X=3.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yi&amp;quot;, label = &amp;quot;^&amp;quot;, X=5.3,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;yd&amp;quot;, label = &amp;quot;v&amp;quot;, X=5.3,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zi&amp;quot;, label = &amp;quot;N&amp;quot;, X=6.8,Y=1,W=1.5,H=0.5})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;zd&amp;quot;, label = &amp;quot;S&amp;quot;, X=6.8,Y=2.6,W=1.5,H=0.5})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;reset_target&amp;quot;, label = &amp;quot;Reset&amp;quot;, X=2.8,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;set_target&amp;quot;, label = &amp;quot;Set&amp;quot;, X=8.3,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button&amp;quot;, name = &amp;quot;simulate&amp;quot;, label = &amp;quot;Test&amp;quot;, X=9.4,Y=1.8,W=1,H=0.8})&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;button_exit&amp;quot;, name = &amp;quot;jump&amp;quot;, label = &amp;quot;Jump&amp;quot;, X=10.5,Y=1.8,W=1.5,H=0.8})&lt;br /&gt;
			&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;checkbox&amp;quot;, name = &amp;quot;reset_quarries_on_jump&amp;quot;, label = &amp;quot;Reset quarries on jump&amp;quot;, selected = mem.reset_quarries_on_jump, X=8.5,Y=3})&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(message, {command = &amp;quot;add&amp;quot;, element = &amp;quot;label&amp;quot;, label = &amp;quot;This page is not yet handled: &amp;quot; .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		digiline_send(touchscreen.channel, message)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Always mark current page for update when &amp;quot;Execute&amp;quot; is clicked to provide a method to update any page on a newly placed touchscreen&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end&lt;br /&gt;
&lt;br /&gt;
-- Handle touchscreen messages&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == touchscreen.channel and event.msg then&lt;br /&gt;
	if event.msg.page ~= nil then&lt;br /&gt;
		local i_page = tonumber(string.sub(event.msg.page, 5))&lt;br /&gt;
		if i_page ~= mem.page then&lt;br /&gt;
			mem.page = i_page&lt;br /&gt;
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
		end&lt;br /&gt;
	elseif event.msg.lock ~= nil then&lt;br /&gt;
		local i_lock = tonumber(string.sub(event.msg.lock, 5))&lt;br /&gt;
		if permission.check(event.msg.clicker) then&lt;br /&gt;
			if i_lock ~= mem.ts_lock then&lt;br /&gt;
				mem.ts_lock = i_lock&lt;br /&gt;
				if mem.ts_lock == 1 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = true&lt;br /&gt;
				elseif mem.ts_lock == 2 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;unlock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				elseif mem.ts_lock == 3 then&lt;br /&gt;
					digiline_send(touchscreen.channel, {command = &amp;quot;lock&amp;quot;})&lt;br /&gt;
					mem.permission.ignore = false&lt;br /&gt;
				else send_to_monitors(&amp;quot;Unhandled ts_lock value: &amp;quot; .. tostring(mem.ts_lock))&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			send_to_monitors(&amp;quot;You can't unlock, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	elseif touchscreen.pages[mem.page] == &amp;quot;Jumpdrive&amp;quot; then&lt;br /&gt;
		local authorised = permission.check(event.msg.clicker)&lt;br /&gt;
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.jump_step_value ~= nil then&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) &amp;lt; min_jump_step_value then mem.instant_jump.distance = min_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end&lt;br /&gt;
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if event.msg.radius ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))&lt;br /&gt;
				if event.msg.target ~= nil then&lt;br /&gt;
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))&lt;br /&gt;
				else&lt;br /&gt;
					digiline_send(jumpdrive.channel, {command = &amp;quot;set&amp;quot;, r = mem.jumpdrive.radius, formupdate = true})&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change the radius, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.key_enter_field ~= nil then&lt;br /&gt;
			if event.msg.key_enter_field == &amp;quot;target&amp;quot; then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			elseif event.msg.key_enter_field == &amp;quot;jump_step_value&amp;quot; then&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else send_to_monitors(&amp;quot;Unknown key_enter_field: &amp;quot; .. tostring(event.msg.key_enter_field)) end&lt;br /&gt;
		&lt;br /&gt;
		elseif event.msg.reset_target ~= nil then&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;reset&amp;quot;})&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.set_target ~= nil then&lt;br /&gt;
			if event.msg.target ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.request_data ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		elseif event.msg.simulate ~= nil then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
			digiline_send(jumpdrive.channel, {command = &amp;quot;simulate&amp;quot;})&lt;br /&gt;
		elseif event.msg.jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
		elseif event.msg.jump_step_increase ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.jump_step_decrease ~= nil then&lt;br /&gt;
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1&lt;br /&gt;
			if target_jump_step_value &amp;gt; min_jump_step_value then mem.instant_jump.distance = target_jump_step_value&lt;br /&gt;
			else mem.instant_jump.distance = min_jump_step_value end&lt;br /&gt;
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;) end&lt;br /&gt;
		elseif event.msg.xi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.xd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.yd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zi ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.zd ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.jumpdrive.target = mem.jumpdrive.position&lt;br /&gt;
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance&lt;br /&gt;
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = &amp;quot;set&amp;quot;, formupdate = false}, mem.jumpdrive.target))&lt;br /&gt;
				digiline_send(jumpdrive.channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't jump, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		elseif event.msg.reset_quarries_on_jump ~= nil then&lt;br /&gt;
			if authorised then&lt;br /&gt;
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump&lt;br /&gt;
				if event.msg.reset_quarries_on_jump == &amp;quot;true&amp;quot; then mem.reset_quarries_on_jump = true&lt;br /&gt;
				else mem.reset_quarries_on_jump = false&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
			else&lt;br /&gt;
				send_to_monitors(&amp;quot;You can't change this, &amp;quot; .. tostring(event.msg.clicker) .. &amp;quot; ;)&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Jumpdrive&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.jumpdrive == nil then&lt;br /&gt;
	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 = &amp;quot;&amp;quot;, time = 0}&lt;br /&gt;
end&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;}) end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;digiline&amp;quot; and event.channel == jumpdrive.channel and event.msg then&lt;br /&gt;
	local output = &amp;quot;&amp;quot;&lt;br /&gt;
	local updated = {}&lt;br /&gt;
	for k, v in pairs(event.msg) do&lt;br /&gt;
		if mem.jumpdrive[k] ~= nil then&lt;br /&gt;
-- 			if mem.jumpdrive[k] ~= v then output = output .. &amp;quot; &amp;quot; .. tostring(k) .. &amp;quot;: &amp;quot; .. get_string(mem.jumpdrive[k]) .. &amp;quot; -&amp;gt; &amp;quot; .. get_string(v) end&lt;br /&gt;
			mem.jumpdrive[k] = v&lt;br /&gt;
		else&lt;br /&gt;
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, &amp;quot;Unknown jumpdrive propperty: &amp;quot; .. get_string(k) .. &amp;quot;:&amp;quot; .. get_string(v))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if event.msg.success ~= nil then&lt;br /&gt;
		if event.msg.success == true then&lt;br /&gt;
			output = output .. &amp;quot; Success!&amp;quot;&lt;br /&gt;
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = &amp;quot;set&amp;quot;, restart = true}) end&lt;br /&gt;
		else output = output .. &amp;quot; Failure! (Best refresh and reset)&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if event.msg.msg then output = output .. &amp;quot; &amp;quot; .. event.msg.msg end&lt;br /&gt;
	if event.msg.time then&lt;br /&gt;
		output = output .. &amp;quot; Jumped (&amp;quot; .. tostring(event.msg.time) .. &amp;quot;)&amp;quot;&lt;br /&gt;
		mem.jumpdrive.position = mem.jumpdrive.target&lt;br /&gt;
 		digiline_send(jumpdrive.channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	end&lt;br /&gt;
	if output ~= nil then&lt;br /&gt;
		if output ~= &amp;quot;&amp;quot; then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. output) end&lt;br /&gt;
	else&lt;br /&gt;
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. &amp;quot;:&amp;quot; .. &amp;quot;Output was nil!&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	table.insert(touchscreen.update_pages, 1, &amp;quot;Jumpdrive&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ########&lt;br /&gt;
-- Event Catcher and update screens&lt;br /&gt;
-- ########&lt;br /&gt;
if mem.event_catcher == nil then&lt;br /&gt;
 	mem.event_catcher = {touchscreen_line_table = {&amp;quot;Initialized at &amp;quot; .. get_time_string() .. &amp;quot;, &amp;quot; .. _VERSION}}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
send_to_monitors(event)&lt;br /&gt;
&lt;br /&gt;
for i, page in ipairs(touchscreen.update_pages) do&lt;br /&gt;
	if page == touchscreen.pages[mem.page] then&lt;br /&gt;
		update_page(touchscreen.pages[mem.page])&lt;br /&gt;
		break&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
mem.events.count = mem.events.count + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Game Controller steered Jumpdrive with Noteblock feedback ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--[[&lt;br /&gt;
Usage:&lt;br /&gt;
- Mount the game controller (Rightclick it)&lt;br /&gt;
- Set direction of travel (Look that way)&lt;br /&gt;
- Increase jump distance (Hold the jump key slightly moving your mouse or pushing the forward key)&lt;br /&gt;
- Jump (Release the jump button)&lt;br /&gt;
Setup:&lt;br /&gt;
LUAc with this code attached to:&lt;br /&gt;
- Jumpdrive (jumpdrive:engine), digiline channel &amp;quot;jumpdrive&amp;quot; (default)&lt;br /&gt;
- Digilines Game Controller (digistuff:controller), digiline channel &amp;quot;gc&amp;quot;&lt;br /&gt;
Optional (for feedback):&lt;br /&gt;
- Digilines Noteblock (digistuff:noteblock), digiline channel &amp;quot;speaker&amp;quot;&lt;br /&gt;
&lt;br /&gt;
(See the settings to e.g. change the channels)&lt;br /&gt;
&lt;br /&gt;
Jump on ;)&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Settings&lt;br /&gt;
local users = {&amp;quot;singleplayer&amp;quot;, &amp;quot;FeXoR&amp;quot;, &amp;quot;6r1d&amp;quot;, &amp;quot;SX&amp;quot;, &amp;quot;SwissalpS&amp;quot;, &amp;quot;Huhhila&amp;quot;, &amp;quot;BuckarooBanzai&amp;quot;, &amp;quot;admin&amp;quot;}&lt;br /&gt;
local jumpdrive_channel = &amp;quot;jumpdrive&amp;quot;&lt;br /&gt;
local game_controller_channel = &amp;quot;gc&amp;quot;&lt;br /&gt;
local note_block_channel = &amp;quot;speaker&amp;quot;&lt;br /&gt;
local delay = heat&lt;br /&gt;
local sounds = {&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;}&lt;br /&gt;
--local sounds = {&amp;quot;c&amp;quot;, &amp;quot;csharp&amp;quot;, &amp;quot;d&amp;quot;, &amp;quot;dsharp&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;f&amp;quot;, &amp;quot;fsharp&amp;quot;, &amp;quot;g&amp;quot;, &amp;quot;gsharp&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;asharp&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c2&amp;quot;, &amp;quot;csharp2&amp;quot;, &amp;quot;d2&amp;quot;, &amp;quot;dsharp2&amp;quot;, &amp;quot;e2&amp;quot;, &amp;quot;f2&amp;quot;, &amp;quot;fsharp2&amp;quot;, &amp;quot;g2&amp;quot;, &amp;quot;gsharp2&amp;quot;, &amp;quot;a2&amp;quot;, &amp;quot;asharp2&amp;quot;, &amp;quot;b2&amp;quot;}&lt;br /&gt;
local max_dist = 48&lt;br /&gt;
&lt;br /&gt;
-- Functions&lt;br /&gt;
local function permission_check(user)&lt;br /&gt;
	for i, u in ipairs(users) do&lt;br /&gt;
		if user == u then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Do stuff&lt;br /&gt;
if event.type == &amp;quot;program&amp;quot; then&lt;br /&gt;
	digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;get_sounds&amp;quot;) -- DEBUG&lt;br /&gt;
	digiline_send(note_block_channel, &amp;quot;digistuff_piezo_short&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;default_place_node_metal&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;anvil_clang&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;sine&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	--digiline_send(note_block_channel, &amp;quot;homedecor_toilet&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == jumpdrive_channel and&lt;br /&gt;
	type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
then&lt;br /&gt;
	if type(event.msg.position) == &amp;quot;table&amp;quot; then mem.position = event.msg.position end&lt;br /&gt;
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1&lt;br /&gt;
	if type(event.msg.radius) == &amp;quot;number&amp;quot; then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end&lt;br /&gt;
	if event.msg.success == true then digiline_send(note_block_channel, &amp;quot;technic_laser_mk1&amp;quot;) end&lt;br /&gt;
	if event.msg.success == false then digiline_send(note_block_channel, &amp;quot;technic_laser_mk2&amp;quot;) end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.channel == game_controller_channel then&lt;br /&gt;
	if event.msg == &amp;quot;player_left&amp;quot; and mem.target == nil then -- Not released pre-jump&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		digiline_send(note_block_channel, &amp;quot;unified_inventory_refill&amp;quot;)&lt;br /&gt;
	elseif&lt;br /&gt;
		type(event.msg) == &amp;quot;table&amp;quot;&lt;br /&gt;
	then&lt;br /&gt;
		if type(event.msg.name) == &amp;quot;string&amp;quot; and permission_check(event.msg.name) == true then&lt;br /&gt;
			if event.msg.jump == true then&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; then&lt;br /&gt;
					mem.power = math.min(mem.power + 1, #sounds)&lt;br /&gt;
				else&lt;br /&gt;
					mem.power = 1&lt;br /&gt;
				end&lt;br /&gt;
				digiline_send(note_block_channel, sounds[mem.power])&lt;br /&gt;
			else&lt;br /&gt;
				if type(mem.power) == &amp;quot;number&amp;quot; and mem.power &amp;gt; 0&lt;br /&gt;
				then&lt;br /&gt;
					if type(event.msg.look_vector) == &amp;quot;table&amp;quot; and&lt;br /&gt;
						type(mem.position) == &amp;quot;table&amp;quot; and type(mem.min_dist) == &amp;quot;number&amp;quot;&lt;br /&gt;
					then&lt;br /&gt;
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power&lt;br /&gt;
						mem.target = {&lt;br /&gt;
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),&lt;br /&gt;
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),&lt;br /&gt;
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)&lt;br /&gt;
						}&lt;br /&gt;
						digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
						interrupt(delay)&lt;br /&gt;
					else&lt;br /&gt;
						digiline_send(&amp;quot;DEBUG&amp;quot;, &amp;quot;Insufficient information to set target!&amp;quot;)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		else&lt;br /&gt;
			digiline_send(note_block_channel, &amp;quot;sine&amp;quot;) -- Access denied&lt;br /&gt;
			digiline_send(game_controller_channel, &amp;quot;release&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
if event.type == &amp;quot;interrupt&amp;quot; then&lt;br /&gt;
	if mem.position == nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;get&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
	if mem.target ~= nil then&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;set&amp;quot;, x = mem.target.x, y = mem.target.y, z = mem.target.z})&lt;br /&gt;
		mem.target = nil&lt;br /&gt;
		mem.power = 0&lt;br /&gt;
		mem.position = nil&lt;br /&gt;
		digiline_send(jumpdrive_channel, {command = &amp;quot;jump&amp;quot;})&lt;br /&gt;
		interrupt(delay)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2021 Florian Finke&lt;br /&gt;
&lt;br /&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy&lt;br /&gt;
of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal&lt;br /&gt;
in the Software without restriction, including without limitation the rights&lt;br /&gt;
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&lt;br /&gt;
copies of the Software, and to permit persons to whom the Software is&lt;br /&gt;
furnished to do so, subject to the following conditions:&lt;br /&gt;
&lt;br /&gt;
The above copyright notice and this permission notice shall be included in all&lt;br /&gt;
copies or substantial portions of the Software.&lt;br /&gt;
&lt;br /&gt;
THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&lt;br /&gt;
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&lt;br /&gt;
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&lt;br /&gt;
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&lt;br /&gt;
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&lt;br /&gt;
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE&lt;br /&gt;
SOFTWARE.&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>FeXoR</name></author>
	</entry>
</feed>