Difference between revisions of "User:FeXoR"

From Pandorabox
Jump to navigation Jump to search
(Added Events page to always be updated)
 
(2 intermediate revisions by the same user not shown)
Line 128: Line 128:
  
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
--[[
 
 
--[[
 
--[[
 
Basic Pandorabox tools
 
Basic Pandorabox tools
Line 155: Line 154:
 
-- ########
 
-- ########
  
local permission = {authorised_users = {"FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}}
+
local permission = {authorised_users = {"singleplayer", "FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}}
 
if mem.permission == nil then mem.permission = {ignore = false} end
 
if mem.permission == nil then mem.permission = {ignore = false} end
 
permission.check = function(user)
 
permission.check = function(user)
Line 379: Line 378:
 
end
 
end
 
end
 
end
 +
 +
-- Always mark current page for update when "Execute" is clicked to provide a method to update any page on a newly placed touchscreen
 +
if event.type == "program" then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end
  
 
-- Handle touchscreen messages
 
-- Handle touchscreen messages
Line 610: Line 612:
  
  
-- MIT License
+
--[[
--
+
MIT License
-- Copyright (c) 2021 Florian Finke
+
Copyright (c) 2021 Florian Finke
--
+
 
-- Permission is hereby granted, free of charge, to any person obtaining a copy
+
Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
+
of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
+
in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
+
copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
+
furnished to do so, subject to the following conditions:
--
+
 
-- The above copyright notice and this permission notice shall be included in all
+
The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
+
copies or substantial portions of the Software.
--
+
 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
+
SOFTWARE.
 +
]]
  
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Multi ingredient autocrafter LUA code with buffer chest ==
+
== Game Controller steered Jumpdrive with Noteblock feedback ==
  
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
--[[ Multi Ingredient Autocrafter (MIA)
+
--[[
 +
Usage:
 +
- Mount the game controller (Rightclick it)
 +
- Set direction of travel (Look that way)
 +
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])
 +
- Jump (Release the jump button)
 +
Setup:
 +
LUAc with this code attached to:
 +
- Jumpdrive (jumpdrive:engine), digiline channel "jumpdrive" (default)
 +
- Digilines Game Controller (digistuff:controller), digiline channel "gc"
 +
Optional (for feedback):
 +
- Digilines Noteblock (digistuff:noteblock), digiline channel "speaker"
  
Setup:
+
(See the settings to e.g. change the channels)
Input tube for stacks of ingredients
 
Mithril Chest (Channel stored in mithril_chest)
 
Digiline Injector (Channel stored in injector_excess)
 
Output tube for excess/wrong stacks put in
 
Digiline Injector (Channel stored in injector_craft)
 
Autocrafter (Channel stored in autocrafter)
 
Digiline Injector (Channel stored in injector_output)
 
Output tube for crafted items
 
  
BUG Hardcoded Stacksize, doesn't propperly work with buckets and unnecessarily resource intense for minegeld
+
Jump on ;)
 
]]
 
]]
  
 
-- Settings
 
-- Settings
local recipe = {
+
local users = {"singleplayer", "FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}
{"default:stick", "default:wood", "default:stick"},
+
local jumpdrive_channel = "jumpdrive"
{"default:wood", "", "default:wood"},
+
local game_controller_channel = "gc"
{"default:stick", "default:wood", "default:stick"}
+
local note_block_channel = "speaker"
}
+
local delay = heat
 
+
local sounds = {"c", "d", "e", "f", "g", "a", "b", "c2"}
local channels = {
+
--local sounds = {"c", "csharp", "d", "dsharp", "e", "f", "fsharp", "g", "gsharp", "a", "asharp", "b", "c2", "csharp2", "d2", "dsharp2", "e2", "f2", "fsharp2", "g2", "gsharp2", "a2", "asharp2", "b2"}
mithril_chest = "mc",
+
local max_dist = 48
autocrafter = "ac",
 
injector_craft = "i_craft",
 
injector_excess = "i_excess",
 
injector_output = "i_out"
 
}
 
 
 
local cycle_length = 33
 
local max_cafts_per_cycle = cycle_length
 
local max_craft_ingredients_kept = max_cafts_per_cycle * 9
 
  
 
-- Functions
 
-- Functions
local function get_ingredient_list(reci)
+
local function permission_check(user)
reci = reci or recipe
+
for i, u in ipairs(users) do
local ingredients = {}
+
if user == u then
for i, v in ipairs(reci) do
+
return true
for j, w in ipairs(v) do
 
if w ~= "" then
 
if ingredients[w] == nil then ingredients[w] = 1
 
else ingredients[w] = ingredients[w] + 1
 
end
 
end
 
 
end
 
end
 
end
 
end
local ingredient_list = {type = {}, amount = {}}
+
return false
for t, a in pairs(ingredients) do
+
end
table.insert(ingredient_list.type, t)
+
 
table.insert(ingredient_list.amount, a)
+
-- Do stuff
end
+
if event.type == "program" then
return ingredient_list
+
digiline_send(jumpdrive_channel, {command = "get"})
 +
digiline_send(note_block_channel, "get_sounds") -- DEBUG
 +
digiline_send(note_block_channel, "digistuff_piezo_short")
 +
--digiline_send(note_block_channel, "default_place_node_metal")
 +
--digiline_send(note_block_channel, "anvil_clang")
 +
--digiline_send(note_block_channel, "sine")
 +
--digiline_send(note_block_channel, "unified_inventory_refill")
 +
--digiline_send(note_block_channel, "homedecor_toilet")
 +
end
 +
 
 +
if event.channel == jumpdrive_channel and
 +
type(event.msg) == "table"
 +
then
 +
if type(event.msg.position) == "table" then mem.position = event.msg.position end
 +
-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1
 +
if type(event.msg.radius) == "number" then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end
 +
if event.msg.success == true then digiline_send(note_block_channel, "technic_laser_mk1") end
 +
if event.msg.success == false then digiline_send(note_block_channel, "technic_laser_mk2") end
 
end
 
end
  
local function get_max_crafts (ingredient_list, content)
+
if event.channel == game_controller_channel then
ingredient_list = ingredient_list or get_ingredient_list(recipe)
+
if event.msg == "player_left" and mem.target == nil then -- Not released pre-jump
content = content or event.msg
+
mem.power = 0
local available = {}
+
digiline_send(note_block_channel, "unified_inventory_refill")
for i, t in ipairs(ingredient_list.type) do table.insert(available, 0) end
+
elseif
for slot, stack in ipairs(content) do
+
type(event.msg) == "table"
local i_seperator = stack:find(" ", 1, true)
+
then
if i_seperator ~= nil then
+
if type(event.msg.name) == "string" and permission_check(event.msg.name) == true then
for i, t in ipairs(ingredient_list.type) do
+
if event.msg.jump == true then
if stack:sub(1, i_seperator - 1) == t then
+
if type(mem.power) == "number" then
available[i] = available[i] + tonumber(stack:sub(i_seperator + 1))
+
mem.power = math.min(mem.power + 1, #sounds)
break
+
else
 +
mem.power = 1
 +
end
 +
digiline_send(note_block_channel, sounds[mem.power])
 +
else
 +
if type(mem.power) == "number" and mem.power > 0
 +
then
 +
if type(event.msg.look_vector) == "table" and
 +
type(mem.position) == "table" and type(mem.min_dist) == "number"
 +
then
 +
local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power
 +
mem.target = {
 +
x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),
 +
y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),
 +
z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)
 +
}
 +
digiline_send(game_controller_channel, "release")
 +
interrupt(delay)
 +
else
 +
digiline_send("DEBUG", "Insufficient information to set target!")
 +
end
 
end
 
end
 
end
 
end
 +
else
 +
digiline_send(note_block_channel, "sine") -- Access denied
 +
digiline_send(game_controller_channel, "release")
 
end
 
end
 
end
 
end
local max_crafts = 10000
 
for i, a in ipairs(ingredient_list.amount) do
 
available[i] = math.floor(available[i] / a)
 
if max_crafts > available[i] then max_crafts = available[i] end
 
end
 
return max_crafts
 
 
end
 
end
  
-- Do stuff
+
if event.type == "interrupt" then
if event.type == "program" then
+
if mem.position == nil then
digiline_send(channels.autocrafter, recipe)
+
digiline_send(jumpdrive_channel, {command = "get"})
digiline_send(channels.autocrafter, "on")
+
interrupt(delay)
interrupt(cycle_length + heat)
+
end
elseif event.type == "interrupt" then
+
if mem.target ~= nil then
digiline_send(channels.mithril_chest, {command = "sort"})
+
digiline_send(jumpdrive_channel, {command = "set", x = mem.target.x, y = mem.target.y, z = mem.target.z})
digiline_send(channels.mithril_chest, {command = "get_list"})
+
mem.target = nil
for i = 1, 9 do digiline_send(channels.injector_output, {slotseq = 0, exmatch = false}) end
+
mem.power = 0
interrupt(cycle_length + heat)
+
mem.position = nil
elseif event.type == "digiline" then
+
digiline_send(jumpdrive_channel, {command = "jump"})
if event.channel == channels.mithril_chest and type(event.msg) == "table" then
+
interrupt(delay)
local ingredient_list = get_ingredient_list()
 
local crafts = math.min(get_max_crafts(ingredient_list), max_cafts_per_cycle)
 
for i, a in ipairs(ingredient_list.amount) do
 
local to_send = crafts * a
 
-- BUG: Hardcoded Stacksize
 
local full_stacks = math.floor(to_send / 99)
 
for n = 1, full_stacks do
 
digiline_send(channels.injector_craft, {slotseq = 0, exmatch = true, name = ingredient_list.type[i], count = 99})
 
end
 
to_send = to_send - 99 * full_stacks
 
if to_send > 0 then
 
digiline_send(channels.injector_craft, {slotseq = 0, exmatch = true, name = ingredient_list.type[i], count = to_send})
 
end
 
-- TODO Extract non fitting and overdue stacks
 
end
 
 
end
 
end
 
end
 
end
 +
 +
--[[
 +
MIT License
 +
Copyright (c) 2021 Florian Finke
 +
 +
Permission is hereby granted, free of charge, to any person obtaining a copy
 +
of this software and associated documentation files (the "Software"), to deal
 +
in the Software without restriction, including without limitation the rights
 +
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 +
copies of the Software, and to permit persons to whom the Software is
 +
furnished to do so, subject to the following conditions:
 +
 +
The above copyright notice and this permission notice shall be included in all
 +
copies or substantial portions of the Software.
 +
 +
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 +
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 +
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 +
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 +
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 +
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 +
SOFTWARE.
 +
]]
  
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 15:15, 7 April 2024

Hiho Pandorabox o/


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

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

The maintenance you pull off is awesome!


Thank you for this great place ;)


Builds

Glitchy things

LUA Controller Code

Event Catcher - Mooncontroller

-- Event Catcher code for the mooncontroller


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

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

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


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

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

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

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


-- ########
-- Variable Printer
-- ########

help = "\tType 'variables' to get a list of all global variables"
help = help .. "\n\tType the name of a variable to print it e.g. _G or help"
if event.type == "program" then print(help) end

local variables = {}
for k, v in pairs(_G) do
	variables[k] = v
end

if event.type == "terminal" then
	if event.text == "variables" then
		n_vars = 0
		for k, v in pairs(variables) do
			n_vars = n_vars + 1
			print(k .. " (" .. type(_G[k]) .. ")")
		end
		print("\t" .. tostring(n_vars) .. " variables")
	else
		print(variables[event.text])
	end
end


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

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

--[[
Basic Pandorabox tools

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


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


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

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

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

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

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

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

local breakers = {port = "b"}

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


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

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


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

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

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


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

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

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

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

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

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


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

-- Always mark current page for update when "Execute" is clicked to provide a method to update any page on a newly placed touchscreen
if event.type == "program" then table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page]) end

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


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

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


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

send_to_monitors(event)

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

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


--[[
MIT License
Copyright (c) 2021 Florian Finke

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

Game Controller steered Jumpdrive with Noteblock feedback

--[[
Usage:
- Mount the game controller (Rightclick it)
- Set direction of travel (Look that way)
- Increase jump distance (Hold the jump key and slightly move your mouse [or pushing the forward key])
- Jump (Release the jump button)
Setup:
LUAc with this code attached to:
- Jumpdrive (jumpdrive:engine), digiline channel "jumpdrive" (default)
- Digilines Game Controller (digistuff:controller), digiline channel "gc"
Optional (for feedback):
- Digilines Noteblock (digistuff:noteblock), digiline channel "speaker"

(See the settings to e.g. change the channels)

Jump on ;)
]]

-- Settings
local users = {"singleplayer", "FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}
local jumpdrive_channel = "jumpdrive"
local game_controller_channel = "gc"
local note_block_channel = "speaker"
local delay = heat
local sounds = {"c", "d", "e", "f", "g", "a", "b", "c2"}
--local sounds = {"c", "csharp", "d", "dsharp", "e", "f", "fsharp", "g", "gsharp", "a", "asharp", "b", "c2", "csharp2", "d2", "dsharp2", "e2", "f2", "fsharp2", "g2", "gsharp2", "a2", "asharp2", "b2"}
local max_dist = 48

-- Functions
local function permission_check(user)
	for i, u in ipairs(users) do
		if user == u then
			return true
		end
	end
	return false
end

-- Do stuff
if event.type == "program" then
	digiline_send(jumpdrive_channel, {command = "get"})
	digiline_send(note_block_channel, "get_sounds") -- DEBUG
	digiline_send(note_block_channel, "digistuff_piezo_short")
	--digiline_send(note_block_channel, "default_place_node_metal")
	--digiline_send(note_block_channel, "anvil_clang")
	--digiline_send(note_block_channel, "sine")
	--digiline_send(note_block_channel, "unified_inventory_refill")
	--digiline_send(note_block_channel, "homedecor_toilet")
end

if event.channel == jumpdrive_channel and
	type(event.msg) == "table"
then
	if type(event.msg.position) == "table" then mem.position = event.msg.position end
	-- Set jump distance slightly larger than diagonal extend of jumped area even for radius 1
	if type(event.msg.radius) == "number" then mem.min_dist = math.min(4 * event.msg.radius + 2, max_dist) end
	if event.msg.success == true then digiline_send(note_block_channel, "technic_laser_mk1") end
	if event.msg.success == false then digiline_send(note_block_channel, "technic_laser_mk2") end
end

if event.channel == game_controller_channel then
	if event.msg == "player_left" and mem.target == nil then -- Not released pre-jump
		mem.power = 0
		digiline_send(note_block_channel, "unified_inventory_refill")
	elseif
		type(event.msg) == "table"
	then
		if type(event.msg.name) == "string" and permission_check(event.msg.name) == true then
			if event.msg.jump == true then
				if type(mem.power) == "number" then
					mem.power = math.min(mem.power + 1, #sounds)
				else
					mem.power = 1
				end
				digiline_send(note_block_channel, sounds[mem.power])
			else
				if type(mem.power) == "number" and mem.power > 0
				then
					if type(event.msg.look_vector) == "table" and
						type(mem.position) == "table" and type(mem.min_dist) == "number"
					then
						local distance = mem.min_dist + (max_dist - mem.min_dist) / #sounds * mem.power
						mem.target = {
								x = math.floor(0.5 + mem.position.x + distance * event.msg.look_vector.x),
								y = math.floor(0.5 + mem.position.y + distance * event.msg.look_vector.y),
								z = math.floor(0.5 + mem.position.z + distance * event.msg.look_vector.z)
						}
						digiline_send(game_controller_channel, "release")
						interrupt(delay)
					else
						digiline_send("DEBUG", "Insufficient information to set target!")
					end
				end
			end
		else
			digiline_send(note_block_channel, "sine") -- Access denied
			digiline_send(game_controller_channel, "release")
		end
	end
end

if event.type == "interrupt" then
	if mem.position == nil then
		digiline_send(jumpdrive_channel, {command = "get"})
		interrupt(delay)
	end
	if mem.target ~= nil then
		digiline_send(jumpdrive_channel, {command = "set", x = mem.target.x, y = mem.target.y, z = mem.target.z})
		mem.target = nil
		mem.power = 0
		mem.position = nil
		digiline_send(jumpdrive_channel, {command = "jump"})
		interrupt(delay)
	end
end

--[[
MIT License
Copyright (c) 2021 Florian Finke

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]