mods that hopefully work for a TechAge-based pack

This commit is contained in:
2026-09-09 06:27:51 -06:00
parent 4e6b08d194
commit c27db2a537
6234 changed files with 375578 additions and 0 deletions
@@ -0,0 +1,107 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Action Registration
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local logic = techage.logic
-- tables with all data from action registrations
local kvRegisteredActn = {}
-- list of keys for actions
local aActnTypes = {}
-- list of titles for actions
local aActnTitles = {}
--
-- API function for action registrations
--
function techage.icta_register_action(key, tData)
table.insert(aActnTypes, key)
table.insert(aActnTitles, tData.title)
tData.__idx__ = #aActnTypes
if kvRegisteredActn[key] ~= nil then
print("[Techage] Action registration error "..key)
return
end
kvRegisteredActn[key] = tData
for _,item in ipairs(tData.formspec) do
if item.type == "textlist" then
item.tChoices = string.split(item.choices, ",")
item.num_choices = #item.tChoices
end
end
end
-- return formspec string
function techage.actn_formspec(row, kvSelect)
return techage.submenu_generate_formspec(
row, "actn", "Action type", aActnTypes, aActnTitles, kvRegisteredActn, kvSelect)
end
-- evaluate the row action input
-- and return new data
function techage.actn_eval_input(kvSelect, fields)
kvSelect = techage.submenu_eval_input(kvRegisteredActn, aActnTypes, aActnTitles, kvSelect, fields)
return kvSelect
end
-- return the Lua code
function techage.code_action(kvSelect, environ)
if kvSelect and kvRegisteredActn[kvSelect.choice] then
if techage.submenu_verify(environ.owner, kvRegisteredActn, kvSelect) then
return kvRegisteredActn[kvSelect.choice].code(kvSelect, environ)
end
end
return nil
end
techage.icta_register_action("default", {
title = "",
formspec = {},
code = function(data, environ) return false, false end,
button = function(data, environ) return "..." end,
})
techage.icta_register_action("print", {
title = "print to output window",
formspec = {
{
type = "ascii",
name = "text",
label = "Output the following text",
default = "",
},
{
type = "label",
name = "lbl",
label = "Use a '*' character as reference to any\ncondition state",
},
},
button = function(data, environ)
return 'print("'..data.text:sub(1,12)..'")'
end,
code = function(data, environ)
return function(env, output, idx)
local text = string.gsub(data.text, "*", tostring(env.result[idx]))
output(env.pos, text)
end
end,
})
@@ -0,0 +1,187 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Battery
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local BATTERY_CAPACITY = 10000000
local function calc_percent(content)
local val = (BATTERY_CAPACITY - math.min(content or 0, BATTERY_CAPACITY))
return 100 - math.floor((val * 100.0 / BATTERY_CAPACITY))
end
local function on_timer(pos, elapsed)
local meta = minetest.get_meta(pos)
local number = meta:get_string("node_number")
local percent = calc_percent(meta:get_int("content"))
meta:set_string("infotext", S("Battery").." "..number..": "..percent.." %")
if percent == 0 then
local node = minetest.get_node(pos)
node.name = "techage:ta4_battery_empty"
minetest.swap_node(pos, node)
return false
end
return true
end
minetest.register_alias("techage:ta4_battery75", "techage:ta4_battery")
minetest.register_alias("techage:ta4_battery50", "techage:ta4_battery")
minetest.register_alias("techage:ta4_battery25", "techage:ta4_battery")
minetest.register_node("techage:ta4_battery", {
description = S("Battery"),
inventory_image = 'techage_battery_inventory.png',
wield_image = 'techage_battery_inventory.png',
tiles = {
-- up, down, right, left, back, front
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png^techage_battery_green.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
},
},
after_place_node = function(pos, placer, itemstack)
local content = BATTERY_CAPACITY
if itemstack then
local stack_meta = itemstack:get_meta()
if stack_meta then
-- This ensures that dug batteries of the old system are considered full.
local string_content = stack_meta:get_string("content")
if string_content ~= "" then
-- Batteries dug in the new system are handled correctly.
content = techage.in_range(stack_meta:get_int("content"), 0, BATTERY_CAPACITY)
end
end
end
M(pos):set_int("content", content)
local number = techage.add_node(pos, "techage:ta4_battery")
M(pos):set_string("node_number", number)
on_timer(pos, 1)
minetest.get_node_timer(pos):start(30)
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
end,
on_timer = on_timer,
preserve_metadata = function(pos, oldnode, oldmetadata, drops)
local content = M(pos):get_int("content")
local meta = drops[1]:get_meta()
meta:set_int("content", content)
local percent = calc_percent(content)
local text = S("Battery").." ("..percent.." %)"
meta:set_string("description", text)
end,
paramtype = "light",
use_texture_alpha = techage.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {choppy=1, cracky=1, crumbly=1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("techage:ta4_battery_empty", {
description = S("Battery"),
tiles = {
-- up, down, right, left, back, front
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png^techage_battery_red.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
},
},
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
meta:set_int("content", 0)
end,
paramtype = "light",
use_texture_alpha = techage.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {choppy=1, cracky=1, crumbly=1, not_in_creative_inventory=1},
drop = "",
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
if minetest.global_exists("moreores") then
minetest.register_craft({
output = "techage:ta4_battery 2",
recipe = {
{"", "moreores:silver_ingot", ""},
{"", "default:copper_ingot", ""},
{"", "moreores:silver_ingot", ""},
}
})
else
minetest.register_craft({
output = "techage:ta4_battery 2",
recipe = {
{"", "default:tin_ingot", ""},
{"", "default:copper_ingot", ""},
{"", "default:tin_ingot", ""},
}
})
end
techage.register_node({"techage:ta4_battery"}, {
on_node_load = function(pos)
minetest.get_node_timer(pos):start(30)
end,
on_recv_message = function(pos, src, topic, payload)
if topic == "load" then
local meta = minetest.get_meta(pos)
return calc_percent(meta:get_int("content"))
else
return "unsupported"
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 134 then
local meta = minetest.get_meta(pos)
return 0, {calc_percent(meta:get_int("content"))}
else
return 2, ""
end
end,
})
@@ -0,0 +1,958 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Register all controller commands
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local logic = techage.logic
function techage.compare(op1, op2, method)
if method == "is" then
return op1 == op2
elseif method == "is not" then
return op1 ~= op2
elseif method == "greater" then
return op1 > op2
elseif method == "less" then
return op1 < op2
end
end
function techage.fmt_number(num)
local mtch = num:match('^(%d+).*')
if mtch and num ~= mtch then
return mtch.."..."
end
return num
end
techage.icta_register_condition("initial", {
title = "initial",
formspec = {
{
type = "label",
name = "lbl",
label = "Condition is true only after\ncontroller start.",
},
},
-- Return two chunks of executable Lua code for the controller, according:
-- return <read condition>, <expected result>
code = function(data, environ)
local condition = function(env, idx)
return env.ticks
end
local result = function(val)
return val == 1
end
return condition, result
end,
button = function(data, environ) return "Initial after start" end,
})
techage.icta_register_condition("true", {
title = "true",
formspec = {
{
type = "label",
name = "lbl",
label = "Condition is always true.",
},
},
code = function(data, environ)
local condition = function(env, idx)
return true
end
local result = function(val)
return val == true
end
return condition, result
end,
button = function(data, environ) return "true" end,
})
techage.icta_register_condition("condition", {
title = "condition",
formspec = {
{
type = "textlist",
name = "condition",
label = "condition row number",
choices = "1,2,3,4,5,6,7,8",
default = "",
},
{
type = "textlist",
name = "operand",
label = "condition",
choices = "was true, was not true",
default = "was true",
},
{
type = "label",
name = "lbl",
label = "Used to execute two or more\nactions based on one condition.",
},
},
code = function(data, environ)
local condition = function(env, idx)
local index = data.condition:byte(-1) - 0x30
return env.condition[index]
end
local result = function(val)
return val == (data.operand == "was true")
end
return condition, result
end,
button = function(data, environ) return "cond("..data.condition:sub(-1,-1)..","..data.operand..")" end,
})
techage.icta_register_condition("input", {
title = "inputs",
formspec = {
{
type = "digits",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "operand",
choices = "is,is not",
default = "is",
},
{
type = "textlist",
name = "value",
choices = "on,off,invalid",
default = "on",
},
{
type = "label",
name = "lbl",
label = "An input is only available,\nif a block sends on/off\ncommands to the controller.",
},
},
button = function(data, environ) -- default button label
return 'inp('..techage.fmt_number(data.number)..','..data.operand.." "..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return env.input[data.number]
end
local result = function(val)
return techage.compare(val, data.value or "invalid", data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("state", {
title = "read block state",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "operand",
label = "",
choices = "is,is not",
default = "is",
},
{
type = "textlist",
name = "value",
label = "",
choices = "stopped,running,standby,blocked,nopower,fault,unloaded,invalid,on,off,empty,loaded,loading,full",
default = "stopped",
},
{
type = "label",
name = "lbl",
label = "Read the state of a TA3/TA4 machine.\n",
},
},
button = function(data, environ) -- default button label
return 'sts('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "state")
end
local result = function(val)
return techage.compare(val, data.value or "invalid", data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("fuel", {
title = "read amount of fuel",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "operand",
label = "",
choices = "greater,less",
default = "greater",
},
{
type = "digits",
name = "value",
label = "than",
default = ""
},
{
type = "label",
name = "lbl",
label = "Read and evaluate the fuel value\nof a fuel consuming block.",
},
},
button = function(data, environ)
return 'fuel('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "fuel")
end
local result = function(val)
return techage.compare(val, tonumber(data.value) or 0, data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("load", {
title = "read power/liquid load",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "operand",
label = "",
choices = "greater,less",
default = "greater",
},
{
type = "digits",
name = "value",
label = "than",
default = ""
},
{
type = "label",
name = "lbl",
label = "Read and evaluate the load (0..100)\nof a tank/storage block.",
},
},
button = function(data, environ)
return 'load('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "load")
end
local result = function(val)
return techage.compare(val, tonumber(data.value) or 0, data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("depth", {
title = "read quarry depth",
formspec = {
{
type = "number",
name = "number",
label = "quarry number",
default = "",
},
{
type = "textlist",
name = "operand",
label = "",
choices = "greater,less",
default = "greater",
},
{
type = "digits",
name = "value",
label = "than",
default = ""
},
{
type = "label",
name = "lbl",
label = "Read and evaluate the current\ndepth of a quarry block.",
},
},
button = function(data, environ)
return 'depth('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "depth")
end
local result = function(val)
return techage.compare(val, tonumber(data.value) or 0, data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("delivered", {
title = "read delivered power",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "operand",
label = "",
choices = "greater,less",
default = "greater",
},
{
type = "digits",
name = "value",
label = "than",
default = ""
},
{
type = "label",
name = "lbl",
label = "Read and evaluate the delivered\npower of a generator block.\nPower consuming blocks like accus\ncould also provide a negative value.",
},
},
button = function(data, environ)
return 'deliv('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "delivered")
end
local result = function(val)
return techage.compare(val, tonumber(data.value) or 0, data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("chest", {
title = "read chest state",
formspec = {
{
type = "number",
name = "number",
label = "chest number",
default = "",
},
{
type = "textlist",
name = "operand",
label = "",
choices = "is,is not",
default = "is",
},
{
type = "textlist",
name = "value",
label = "",
choices = "empty,loaded,full,invalid",
default = "empty",
},
{
type = "label",
name = "lbl",
label = "Read the state from a Techage chest\n"..
"and other similar blocks.",
},
},
button = function(data, environ) -- default button label
return 'chest('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "state")
end
local result = function(val)
return techage.compare(val, data.value or "invalid", data.operand)
end
return condition, result
end,
})
techage.icta_register_condition("signaltower", {
title = "read Signal Tower state",
formspec = {
{
type = "number",
name = "number",
label = "Signal Tower number",
default = "",
},
{
type = "textlist",
name = "operand",
choices = "is,is not",
default = "is",
},
{
type = "textlist",
name = "value",
choices = "off,green,amber,red,invalid",
default = "off",
},
{
type = "label",
name = "lbl",
label = "Read the color state\nof a Signal Tower.",
},
},
button = function(data, environ) -- default button label
return 'tower('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "state")
end
local result = function(val)
return techage.compare(val, data.value or "invalid", data.operand)
end
return condition, result
end,
})
techage.icta_register_action("signaltower", {
title = "TA4 Signal Tower",
formspec = {
{
type = "numbers",
name = "number",
label = "Signal Tower number",
default = "",
},
{
type = "textlist",
name = "value",
label = "lamp color",
choices = "off,green,amber,red",
default = "red",
},
{
type = "label",
name = "lbl",
label = "Turn on/off a Signal Tower lamp.",
},
},
button = function(data, environ)
return 'tower('..techage.fmt_number(data.number)..","..data.value..')'
end,
code = function(data, environ)
return function(env, output, idx)
techage.send_multi(environ.number, data.number, data.value)
end
end,
})
techage.icta_register_action("signallamp", {
title = "TA4 Signal Lamp",
formspec = {
{
type = "numbers",
name = "number",
label = "Signal Tower number",
default = "",
},
{
type = "textlist",
name = "payload",
label = "lamp number",
choices = "1,2,3,4",
default = "1",
},
{
type = "textlist",
name = "value",
label = "lamp color",
choices = "off,green,amber,red",
default = "red",
},
{
type = "label",
name = "lbl",
label = "Turn on/off a Signal Tower lamp.",
},
},
button = function(data, environ)
return 'tower('..techage.fmt_number(data.number)..","..data.payload..","..data.value..')'
end,
code = function(data, environ)
return function(env, output, idx)
techage.send_multi(environ.number, data.number, data.value, tonumber(data.payload))
end
end,
})
techage.icta_register_action("switch", {
title = "turn block on/off",
formspec = {
{
type = "numbers",
name = "number",
label = "block number(s)",
default = "",
},
{
type = "textlist",
name = "value",
label = "state",
choices = "on,off",
default = "on",
},
{
type = "label",
name = "lbl",
label = "Used for lamps, machines, gates,...",
},
},
button = function(data, environ)
return 'turn('..techage.fmt_number(data.number)..","..data.value..')'
end,
code = function(data, environ)
return function(env, output, idx)
techage.send_multi(environ.number, data.number, data.value)
end
end,
})
techage.icta_register_action("display", {
title = "Display: overwrite one line",
formspec = {
{
type = "numbers",
name = "number",
label = "Display number",
default = "",
},
{
type = "textlist",
name = "row",
label = "Display line",
choices = "1,2,3,4,5",
default = "1",
},
{
type = "ascii",
name = "text",
label = "text",
default = "",
},
{
type = "label",
name = "lbl",
label = "Use a '*' character as reference\nto any condition result",
},
},
code = function(data, environ)
return function(env, output, idx)
local text = string.gsub(data.text, "*", tostring(env.result[idx]))
local payload = safer_lua.Store()
payload.set("row", data.row)
payload.set("str", text)
techage.send_multi(environ.number, data.number, "set", payload)
end
end,
button = function(data, environ)
return "lcd("..techage.fmt_number(data.number)..","..data.row..',"'..data.text..'")'
end,
})
techage.icta_register_action("cleardisplay", {
title = "Display: Clear screen",
formspec = {
{
type = "number",
name = "number",
label = "Display number",
default = "",
},
},
code = function(data, environ)
return function(env, output, idx)
techage.send_multi(environ.number, data.number, "clear")
end
end,
button = function(data, environ)
return "clear lcd("..techage.fmt_number(data.number)..")"
end,
})
techage.icta_register_action("chat", {
title = "send chat message",
formspec = {
{
type = "ascii",
name = "text",
label = "message",
default = "",
},
{
type = "label",
name = "lbl",
label = "The chat message is send to the\nController owner, only.",
},
},
code = function(data, environ)
return function(env, output, idx)
minetest.chat_send_player(environ.owner, "[TA4 ICTA Controller] "..data.text)
end
end,
button = function(data, environ)
return 'chat("'..data.text:sub(1,12)..'")'
end,
})
function techage.icta_door_toggle(pos, owner, state)
pos = minetest.string_to_pos("("..pos..")")
if pos then
local door = doors.get(pos)
if door then
local player = {
get_player_name = function() return owner end,
is_player = function() return true end,
}
if state == "open" then
door:open(player)
elseif state == "close" then
door:close(player)
end
end
end
end
techage.icta_register_action("door", {
title = "open/close door",
formspec = {
{
type = "digits",
name = "pos",
label = "door position like: 123,7,-1200",
default = "",
},
{
type = "textlist",
name = "door_state",
label = "door state",
choices = "open,close",
default = "open",
},
{
type = "label",
name = "lbl",
label = "For standard doors like the Steel Doors.\n"..
"Use the Techage Info Tool to\neasily determine a door position.",
},
},
code = function(data, environ)
return function(env, output, idx)
techage.icta_door_toggle(data.pos, environ.owner, data.door_state)
end
end,
button = function(data, environ)
return 'door("'..data.pos..'",'..data.door_state..")"
end,
})
techage.icta_register_action("move", {
title = "TA4 Move Controller",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "cmnd",
label = "command",
choices = "a2b,b2a,move",
default = "a2b",
},
},
button = function(data, environ) -- default button label
return 'move('..techage.fmt_number(data.number)..","..data.cmnd..')'
end,
code = function(data, environ)
return function(env, output, idx)
return techage.send_single(environ.number, data.number, data.cmnd)
end
end,
})
techage.icta_register_action("turn", {
title = "TA4 Turn Controller",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "cmnd",
label = "command",
choices = "left,right,uturn",
default = "left",
},
},
button = function(data, environ) -- default button label
return 'move('..techage.fmt_number(data.number)..","..data.cmnd..')'
end,
code = function(data, environ)
return function(env, output, idx)
return techage.send_single(environ.number, data.number, data.cmnd)
end
end,
})
techage.icta_register_action("goto", {
title = "TA4 Sequencer",
formspec = {
{
type = "number",
name = "number",
label = "block number",
default = "",
},
{
type = "textlist",
name = "cmnd",
label = "command",
choices = "goto,stop",
default = "left",
},
{
type = "number",
name = "slot",
label = "time slot",
default = "1",
},
{
type = "label",
name = "lbl",
label = "The 'stop' command needs no time slot.",
},
},
button = function(data, environ) -- default button label
return data.cmnd..'('..techage.fmt_number(data.number)..","..data.slot..')'
end,
code = function(data, environ)
return function(env, output, idx)
return techage.send_single(environ.number, data.number, data.cmnd, tonumber(data.slot or "1") or 1)
end
end,
})
function techage.icta_player_detect(own_num, number, name)
local state = techage.send_single(own_num, number, "name", nil)
if state ~= "" then
if name == "*" or string.find(name, state) then
return state
end
elseif name == "-" then
return state
end
return nil
end
techage.icta_register_condition("playerdetector", {
title = "read Player Detector",
formspec = {
{
type = "number",
name = "number",
label = "Player Detector number",
default = "",
},
{
type = "ascii",
name = "name",
label = "player name(s) or * for all",
default = "",
},
{
type = "label",
name = "lbl",
label = "Read and check the name\nof a Player Detector.\nUse a '*' character for all player names.\n Use a '-' character for no player.",
},
},
code = function(data, environ)
local condition = function(env, idx)
return techage.icta_player_detect(environ.number, data.number, data.name)
end
local result = function(val)
return val ~= nil
end
return condition, result
end,
button = function(data, environ)
return "detector("..techage.fmt_number(data.number)..","..data.name:sub(1,8)..")"
end,
})
techage.icta_register_action("set_filter", {
title = "turn Distributor filter on/off",
formspec = {
{
type = "number",
name = "number",
label = "distri number",
default = "",
},
{
type = "textlist",
name = "color",
label = "filter port",
choices = "red,green,blue,yellow",
default = "red",
},
{
type = "textlist",
name = "value",
label = "state",
choices = "on,off",
default = "on",
},
{
type = "label",
name = "lbl",
label = "turn Distributor filter port on/off\n",
},
},
button = function(data, environ)
return 'turn('..techage.fmt_number(data.number)..","..data.color..","..data.value..')'
end,
code = function(data, environ)
return function(env, output, idx)
local payload = data.color.."="..data.value
techage.send_single(environ.number, data.number, "port", payload)
end
end,
})
techage.icta_register_condition("get_filter", {
title = "read state of a Distributor filter slot",
formspec = {
{
type = "number",
name = "number",
label = "distri number",
default = "",
},
{
type = "textlist",
name = "color",
label = "filter port",
choices = "red,green,blue,yellow",
default = "red",
},
{
type = "textlist",
name = "operand",
choices = "is,is not",
default = "is",
},
{
type = "textlist",
name = "value",
label = "state",
choices = "on,off",
default = "off",
},
{
type = "label",
name = "lbl",
label = "Read state of a Distributor filter slot.\n",
},
},
button = function(data, environ) -- default button label
return 'fltr('..techage.fmt_number(data.number)..","..data.color..' '..data.operand..' '..data.value..')'
end,
code = function(data, environ)
local condition = function(env, idx)
return techage.send_single(environ.number, data.number, "port", data.color)
end
local result = function(val)
return techage.compare(val, data.value or "invalid", data.operand)
end
return condition, result
end,
})
techage.icta_register_action("exchange", {
title = "place/remove a block via the Door Controller II",
formspec = {
{
type = "number",
name = "number",
label = "number",
default = "",
},
{
type = "number",
name = "slot",
label = "slot no",
default = "1",
},
{
type = "label",
name = "lbl",
label = "place/remove a block via\nthe Door Controller II\n",
},
},
button = function(data, environ)
return 'exch('..techage.fmt_number(data.number)..","..data.slot..')'
end,
code = function(data, environ)
return function(env, output, idx)
local payload = data.slot
techage.send_single(environ.number, data.number, "exchange", payload)
end
end,
})
@@ -0,0 +1,84 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Condition Registration
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local logic = techage.logic
-- tables with all data from condition registrations
local kvRegisteredCond = {}
-- list of keys for conditions
local aCondTypes = {}
-- list of titles for conditions
local aCondTitles = {}
--
-- API functions for condition registrations
--
function techage.icta_register_condition(key, tData)
table.insert(aCondTypes, key)
table.insert(aCondTitles, tData.title)
if kvRegisteredCond[key] ~= nil then
print("[Techage] Condition registration error "..key)
return
end
kvRegisteredCond[key] = tData
for _,item in ipairs(tData.formspec) do
if item.type == "textlist" then
item.tChoices = string.split(item.choices, ",")
item.num_choices = #item.tChoices
end
end
end
-- return formspec string
function techage.cond_formspec(row, kvSelect)
return techage.submenu_generate_formspec(
row, "cond", "Condition type", aCondTypes, aCondTitles, kvRegisteredCond, kvSelect)
end
-- evaluate the row condition input
-- and return new data
function techage.cond_eval_input(kvSelect, fields)
kvSelect = techage.submenu_eval_input(kvRegisteredCond, aCondTypes, aCondTitles, kvSelect, fields)
return kvSelect
end
-- return the Lua code
function techage.code_condition(kvSelect, environ)
if kvSelect and kvRegisteredCond[kvSelect.choice] then
if techage.submenu_verify(environ.owner, kvRegisteredCond, kvSelect) then
return kvRegisteredCond[kvSelect.choice].code(kvSelect, environ)
end
end
return nil, nil
end
techage.icta_register_condition("default", {
title = "",
formspec = {},
code = function(data, environ)
local condition = function(env, idx)
return false
end
local result = function(val)
return false
end
return condition, result
end,
button = function(data, environ) return "..." end,
})
@@ -0,0 +1,472 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local logic = techage.logic
--
-- Helper functions
--
local function gen_table(size, val)
local tbl = {}
for idx = 1,size do
if type(val) == "table" then
tbl[idx] = table.copy(val)
else
tbl[idx] = val
end
end
return tbl
end
local function integer(s, min, max)
if s and s ~= "" and s:find("^%d+$") then
local num = tonumber(s)
if num < min then num = min end
if num > max then num = max end
return num
end
return min
end
local sOUTPUT = "Edit commands (see help)"
local Cache = {}
local FS_DATA = gen_table(techage.NUM_RULES, {})
local function output(pos, text, flush_buffer)
local meta = minetest.get_meta(pos)
if not flush_buffer then
text = meta:get_string("output") .. "\n" .. (text or "")
text = text:sub(-500,-1)
end
meta:set_string("output", text)
meta:set_string("formspec", techage.formspecOutput(meta))
end
-- cyclic execution (cycle, cond, result, after, actn)
local function TemplCyc(cycle, cond, result, after, actn, idx)
return function(env, output)
if env.blocked[idx] == false and env.ticks % cycle == 0 then
env.result[idx] = cond(env, idx)
env.blocked[idx] = result(env.result[idx])
if env.blocked[idx] then
env.timer[idx] = env.ticks + after
end
env.condition[idx] = env.blocked[idx]
else
env.condition[idx] = false
end
if env.blocked[idx] and env.timer[idx] == env.ticks then
actn(env, output, idx)
env.blocked[idx] = false
end
end
end
-- event based execution
local function TemplEvt(cond, result, after, actn, idx)
return function(env, output)
if env.blocked[idx] == false and env.event then
env.result[idx] = cond(env, idx)
env.blocked[idx] = result(env.result[idx])
if env.blocked[idx] then
env.timer[idx] = env.ticks + after
end
env.condition[idx] = env.blocked[idx]
else
env.condition[idx] = false
end
if env.blocked[idx] and env.timer[idx] == env.ticks then
actn(env, output, idx)
env.blocked[idx] = false
end
end
end
-- generate the Lua code from the NUM_RULES rules
local function generate(pos, meta, environ)
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
local tbl = {}
for idx = 1,techage.NUM_RULES do
local cycle = integer(fs_data[idx].cycle, 0, 1000)
local cond, result = techage.code_condition(fs_data[idx].cond, environ)
local after = integer(fs_data[idx].after, 0, 1000)
local actn = techage.code_action(fs_data[idx].actn, environ)
-- valid rule?
if cycle and cond and after and actn then
-- add rule number
local f
if cycle == 0 then -- event
f = TemplEvt(cond, result, after, actn, idx)
else -- cyclic
f = TemplCyc(cycle, cond, result, after, actn, idx)
end
-- add to list of rules
tbl[#tbl+1] = f
elseif cond ~= nil and actn == nil then
output(pos, "Error in action in rule "..idx)
elseif cond == nil and actn ~= nil then
output(pos, "Error in condition in rule "..idx)
end
end
return tbl
end
local function runtime_environ(pos)
return {
ticks = 0,
pos = pos,
timer = gen_table(8, 0),
blocked = gen_table(8, false),
result = gen_table(8, false),
condition = gen_table(8, false),
input = {}, -- node number is key
}
end
local function compile(pos, meta, number)
local gen_environ = {
meta = meta,
pos = pos,
number = number,
owner = meta:get_string("owner"),
}
local functions = generate(pos, meta, gen_environ)
Cache[number] = {
code = functions,
env = runtime_environ(pos),
}
return true
end
local function execute(pos, number, event)
local code = Cache[number].code
local env = Cache[number].env
if event then
env.event = true
else
env.event = false
env.ticks = env.ticks + 1
end
for _,func in ipairs(code) do
local res, err = pcall(func, env, output)
if not res then
output(pos, err)
return false
end
end
return true
end
local function battery(pos)
local battery_pos = minetest.find_node_near(pos, 1, {"techage:ta4_battery"})
if battery_pos then
local meta = minetest.get_meta(pos)
meta:set_string("battery", minetest.pos_to_string(battery_pos))
return true
end
return false
end
local function start_controller(pos, meta)
local number = meta:get_string("number")
if not battery(pos) then
meta:set_string("formspec", techage.formspecError(meta))
return false
end
meta:set_string("output", "<press update>")
meta:set_int("cpu", 0)
if compile(pos, meta, number) then
meta:set_int("state", techage.RUNNING)
minetest.get_node_timer(pos):start(1)
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
meta:set_string("formspec", techage.formspecRules(meta, fs_data, sOUTPUT))
meta:set_string("infotext", "Controller "..number..": running")
return true
end
return false
end
local function stop_controller(pos, meta)
local number = meta:get_string("number")
meta:set_int("state", techage.STOPPED)
minetest.get_node_timer(pos):stop()
meta:set_string("infotext", "Controller "..number..": stopped")
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
meta:set_string("formspec", techage.formspecRules(meta, fs_data, sOUTPUT))
end
local function no_battery(pos)
local meta = minetest.get_meta(pos)
local number = meta:get_string("number")
meta:set_int("state", techage.STOPPED)
minetest.get_node_timer(pos):stop()
meta:set_string("infotext", "Controller "..number..": No battery")
meta:set_string("formspec", techage.formspecError(meta))
end
local function update_battery(meta, cpu)
local pos = minetest.string_to_pos(meta:get_string("battery"))
if pos then
meta = minetest.get_meta(pos)
local content = meta:get_int("content") - cpu
if content <= 0 then
meta:set_int("content", 0)
return false
end
meta:set_int("content", content)
return true
end
end
local function on_timer(pos, elapsed)
local meta = minetest.get_meta(pos)
local t = minetest.get_us_time()
local number = meta:get_string("number")
if Cache[number] or compile(pos, meta, number) then
local res = execute(pos, number, elapsed == -1)
if res then
t = minetest.get_us_time() - t
if not update_battery(meta, t) then
no_battery(pos)
return false
end
end
--print("on_timer", t)
return res
end
return false
end
local function on_receive_fields(pos, formname, fields, player)
local meta = minetest.get_meta(pos)
local owner = meta:get_string("owner")
if not player or not player:is_player() then
return
end
if player:get_player_name() ~= owner then
return
end
--print("fields", dump(fields))
if fields.quit then -- cancel button
return
end
if fields.notes then -- notes tab?
meta:set_string("notes", fields.notes)
end
if fields.go then
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
local output = techage.edit_command(fs_data, fields.cmnd)
stop_controller(pos, meta)
meta:set_string("formspec", techage.formspecRules(meta, fs_data, output))
meta:set_string("fs_data", minetest.serialize(fs_data))
end
if fields._type_ == "main" then
techage.store_main_form_data(meta, fields)
local key = techage.main_form_button_pressed(fields)
if key then
-- store data before going into sub-menu
meta:set_string("fs_old", meta:get_string("fs_data"))
meta:set_string("formspec", techage.formspecSubMenu(meta, key))
end
elseif fields._col_ == "cond" then
techage.cond_formspec_update(meta, fields)
elseif fields._col_ == "actn" then
techage.actn_formspec_update(meta, fields)
end
if fields._exit_ == "ok" then -- exit from sub-menu?
if fields._button_ then
techage.formspec_button_update(meta, fields)
end
-- simulate tab selection
fields.tab = "1"
elseif fields._cancel_ == "cancel" then -- abort from sub-menu?
-- restore old data
meta:set_string("fs_data", meta:get_string("fs_old"))
-- simulate tab selection
fields.tab = "1"
elseif fields.save == "Save" then -- abort from sub-menu?
-- store as old data
meta:set_string("fs_old", meta:get_string("fs_data"))
-- simulate tab selection
fields.tab = "1"
elseif fields.sb_help then
local evt = minetest.explode_scrollbar_event(fields.sb_help)
meta:set_string("formspec", techage.formspecHelp(evt.value))
end
if fields.update then
meta:set_string("formspec", techage.formspecOutput(meta))
elseif fields.clear then
meta:set_string("output", "<press update>")
meta:set_string("formspec", techage.formspecOutput(meta))
elseif fields.list then
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
local s = techage.listing(fs_data)
output(pos, s, true)
elseif fields.tab == "1" then
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
meta:set_string("formspec", techage.formspecRules(meta, fs_data, sOUTPUT))
elseif fields.tab == "2" then
meta:set_string("formspec", techage.formspecOutput(meta))
elseif fields.tab == "3" then
meta:set_string("formspec", techage.formspecNotes(meta))
elseif fields.tab == "4" then
meta:set_string("formspec", techage.formspecHelp(1))
elseif fields.start == "Start" then
local environ = {
meta = meta,
pos = pos,
number = meta:get_string("number"),
owner = meta:get_string("owner"),
}
--print("CODE:", generate(pos, meta, environ))
start_controller(pos, meta)
minetest.log("action", player:get_player_name() ..
" starts the ta4_controller at ".. minetest.pos_to_string(pos))
elseif fields.stop == "Stop" then
stop_controller(pos, meta)
end
end
minetest.register_node("techage:ta4_icta_controller", {
description = "TA4 ICTA Controller",
inventory_image = "techage_ta4_controller_inventory.png",
wield_image = "techage_ta4_controller_inventory.png",
stack_max = 1,
tiles = {
-- up, down, right, left, back, front
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png",
"techage_smartline.png^techage_ta4_controller.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -6/32, -6/32, 14/32, 6/32, 6/32, 16/32},
},
},
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local number = techage.add_node(pos, "techage:ta4_icta_controller")
local fs_data = FS_DATA
meta:set_string("fs_data", minetest.serialize(fs_data))
meta:set_string("owner", placer:get_player_name())
meta:set_string("number", number)
meta:set_int("state", techage.STOPPED)
meta:set_string("formspec", techage.formspecRules(meta, fs_data, sOUTPUT))
meta:set_string("infotext", "TA4 ICTA Controller "..number..": stopped")
end,
on_receive_fields = on_receive_fields,
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
techage.del_mem(pos)
end,
on_timer = on_timer,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = techage.CLIP,
groups = {choppy=1, cracky=1, crumbly=1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
minetest.register_craft({
output = "techage:ta4_icta_controller",
recipe = {
{"basic_materials:plastic_sheet", "dye:blue", "basic_materials:plastic_sheet"},
{"", "default:copper_ingot", ""},
{"techage:ta4_wlanchip", "techage:ta4_ramchip", "techage:ta4_ramchip"},
},
})
-- write inputs from remote nodes
local function set_input(pos, own_number, rmt_number, val)
if rmt_number then
if Cache[own_number] and Cache[own_number].env.input then
local t = minetest.get_us_time()
Cache[own_number].env.input[rmt_number] = val
Cache[own_number].env.last_event = t
-- only two events per second
if not Cache[own_number].last_event or Cache[own_number].last_event < t then
minetest.after(0.01, on_timer, pos, -1)
Cache[own_number].last_event = t + 500000 -- add 500 ms
end
end
end
end
techage.register_node({"techage:ta4_icta_controller"}, {
on_recv_message = function(pos, src, topic, payload)
local meta = minetest.get_meta(pos)
local number = meta:get_string("number")
local state = meta:get_int("state")
if state == techage.RUNNING and topic == "on" then
set_input(pos, number, src, topic)
elseif state == techage.RUNNING and topic == "off" then
set_input(pos, number, src, topic)
elseif topic == "state" then
local state = meta:get_int("state") or 0
return techage.StateStrings[state] or "stopped"
else
return "unsupported"
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
local meta = minetest.get_meta(pos)
local number = meta:get_string("number")
local state = meta:get_int("state")
if state == techage.RUNNING and topic == 1 and payload[1] == 1 then
set_input(pos, number, src, topic)
elseif state == techage.RUNNING and topic == 1 and payload[1] == 0 then
set_input(pos, number, src, topic)
else
return 2
end
return 0
end,
on_beduino_request_data = function(pos, src, topic, payload)
local meta = minetest.get_meta(pos)
local number = meta:get_string("number")
local state = meta:get_int("state")
if topic == 129 then
local state = meta:get_int("state") or 0
return 0, {state or techage.STOPPED}
else
return 2, ""
end
end,
})
@@ -0,0 +1,40 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Formspec edit command
]]--
function techage.edit_command(fs_data, text)
local cmnd, pos1, pos2 = text:match('^(%S)%s(%d+)%s(%d+)$')
if pos2 == nil then
cmnd, pos1 = text:match('^(%S)%s(%d+)$')
end
if cmnd and pos1 and pos2 then
pos1 = math.max(1, math.min(pos1, techage.NUM_RULES))
pos2 = math.max(1, math.min(pos2, techage.NUM_RULES))
if cmnd == "x" then
local temp = fs_data[pos1]
fs_data[pos1] = fs_data[pos2]
fs_data[pos2] = temp
return "rows "..pos1.." and "..pos2.." exchanged"
end
if cmnd == "c" then
fs_data[pos2] = table.copy(fs_data[pos1])
return "row "..pos1.." copied to "..pos2
end
elseif cmnd == "d" and pos1 then
pos1 = math.max(1, math.min(pos1, techage.NUM_RULES))
fs_data[pos1] = {}
return "row "..pos1.." deleted"
end
return "Invalid command '"..text.."'"
end
@@ -0,0 +1,243 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Formspec
]]--
techage.NUM_RULES = 8
local SIZE = "size[13,8]"
local sHELP = [[ICTA Controller Help
Control other nodes by means of rules like:
IF <condition> THEN <action>
These rules allow to execute actions based on conditions.
Examples for conditions are:
- the Player Detector detects a player
- a button is pressed
- a machine is fault, blocked, standby,...
Actions are:
- switch on/off lamps and machines
- send chat messages to the owner
- output a text message to the display
The controller executes all rules cyclically.
The cycle time for each rule is configurable
(1..1000 sec).
0 means, the rule will only be called, if
the controller received a command from
another blocks, such as buttons.
Actions can be delayed. Therefore, the
'after' value can be set (0..1000 sec).
Edit command examples:
- 'x 1 8' exchange rows 1 with row 8
- 'c 1 2' copy row 1 to 2
- 'd 3' delete row 3
The 'outp' tab is for debugging outputs via 'print'
The 'notes' tab for your notes.
The controller needs battery power to work.
The battery pack has to be placed near the
controller (1 node distance).
The needed battery power is directly dependent
on the CPU time the controller consumes.
The Manual in German:
https://github.com/joe7575/techage/blob/master/manuals/ta4_icta_controller_DE.md
Or the same as PDF:
https://github.com/joe7575/techage/blob/master/manuals/ta4_icta_controller_DE.pdf
]]
-- to simplify the search for a pressed main form button (condition/action)
local lButtonKeys = {}
for idx = 1,techage.NUM_RULES do
lButtonKeys[#lButtonKeys+1] = "cond"..idx
lButtonKeys[#lButtonKeys+1] = "actn"..idx
end
local function buttons(s)
return "button_exit[7.4,7.5;1.8,1;cancel;Cancel]"..
"button[9.3,7.5;1.8,1;save;Save]"..
"button[11.2,7.5;1.8,1;"..s.."]"
end
function techage.formspecError(meta)
local running = meta:get_int("state") == techage.RUNNING
local cmnd = running and "stop;Stop" or "start;Start"
local init = meta:get_string("init")
init = minetest.formspec_escape(init)
return "size[4,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0,0;No Battery?]"..
"button[1,2;1.8,1;start;Start]"
end
local function button(data)
if data then
return data.button
else
return "..."
end
end
function techage.listing(fs_data)
local tbl = {}
for idx = 1,techage.NUM_RULES do
tbl[#tbl+1] = idx.." ("..fs_data[idx].cycle.."s): IF "..button(fs_data[idx].cond)
tbl[#tbl+1] = " THEN "..button(fs_data[idx].actn).." after "..fs_data[idx].after.."s\n"
end
return table.concat(tbl)
end
local function formspec_rules(fs_data)
local tbl = {"field[0,0;0,0;_type_;;main]"..
"label[0.4,0;Cycle/s:]label[2.5,0;IF cond:]label[7,0;THEN action:]label[11.5,0;after/s:]"}
for idx = 1,techage.NUM_RULES do
local ypos = idx * 0.75 - 0.4
tbl[#tbl+1] = "label[0,"..(0.2+ypos)..";"..idx.."]"
tbl[#tbl+1] = "field[0.7,"..(0.3+ypos)..";1.4,1;cycle"..idx..";;"..(fs_data[idx].cycle or "").."]"
tbl[#tbl+1] = "button[1.9,"..ypos..";4.9,1;cond"..idx..";"..minetest.formspec_escape(button(fs_data[idx].cond)).."]"
tbl[#tbl+1] = "button[6.8,"..ypos..";4.9,1;actn"..idx..";"..minetest.formspec_escape(button(fs_data[idx].actn)).."]"
tbl[#tbl+1] = "field[12,"..(0.3+ypos)..";1.4,1;after"..idx..";;"..(fs_data[idx].after or "").."]"
end
return table.concat(tbl)
end
function techage.store_main_form_data(meta, fields)
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
for idx = 1,techage.NUM_RULES do
fs_data[idx].cycle = fields["cycle"..idx] or ""
fs_data[idx].after = fields["after"..idx] or "0"
end
meta:set_string("fs_data", minetest.serialize(fs_data))
end
function techage.main_form_button_pressed(fields)
for _,key in ipairs(lButtonKeys) do
if fields[key] then
return key
end
end
return nil
end
function techage.formspecSubMenu(meta, key)
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
if key:sub(1,4) == "cond" then
local row = tonumber(key:sub(5,5))
return techage.cond_formspec(row, fs_data[row].cond)
else
local row = tonumber(key:sub(5,5))
return techage.actn_formspec(row, fs_data[row].actn)
end
end
function techage.formspec_button_update(meta, fields)
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
local row = tonumber(fields._row_ or 1)
if fields._col_ == "cond" then
fs_data[row].cond = techage.cond_eval_input(fs_data[row].cond, fields)
elseif fields._col_ == "actn" then
fs_data[row].actn = techage.actn_eval_input(fs_data[row].actn, fields)
end
meta:set_string("fs_data", minetest.serialize(fs_data))
end
function techage.cond_formspec_update(meta, fields)
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
local row = tonumber(fields._row_ or 1)
fs_data[row].cond = techage.cond_eval_input(fs_data[row].cond, fields)
meta:set_string("formspec", techage.cond_formspec(row, fs_data[row].cond))
meta:set_string("fs_data", minetest.serialize(fs_data))
end
function techage.actn_formspec_update(meta, fields)
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
local row = tonumber(fields._row_ or 1)
fs_data[row].actn = techage.actn_eval_input(fs_data[row].actn, fields)
meta:set_string("formspec", techage.actn_formspec(row, fs_data[row].actn))
meta:set_string("fs_data", minetest.serialize(fs_data))
end
function techage.formspecRules(meta, fs_data, output)
local running = meta:get_int("state") == techage.RUNNING
local cmnd = running and "stop;Stop" or "start;Start"
local init = meta:get_string("init")
init = minetest.formspec_escape(init)
return SIZE..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"tabheader[0,0;tab;rules,outp,notes,help;1;;true]"..
formspec_rules(fs_data)..
"label[0.2,7.0;"..output.."]"..
"field[0.3,7.8;4,1;cmnd;;<cmnd>]"..
"button[4.0,7.5;1.5,1;go;GO]"..
buttons(cmnd)
end
function techage.formspecOutput(meta)
local running = meta:get_int("state") == techage.RUNNING
local cmnd = running and "stop;Stop" or "start;Start"
local output = meta:get_string("output")
output = minetest.formspec_escape(output)
return SIZE..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"tabheader[0,0;tab;rules,outp,notes,help;2;;true]"..
"textarea[0.3,0.2;13,8.3;output;Output:;"..output.."]"..
"button[5.5,7.5;1.8,1;list;List]"..
"button[7.4,7.5;1.8,1;clear;Clear]"..
"button[9.3,7.5;1.8,1;update;Update]"..
"button[11.2,7.5;1.8,1;"..cmnd.."]"
end
function techage.formspecNotes(meta)
local running = meta:get_int("state") == techage.RUNNING
local cmnd = running and "stop;Stop" or "start;Start"
local notes = meta:get_string("notes") or ""
if notes == "" then notes = "<space for your notes>" end
notes = minetest.formspec_escape(notes)
return SIZE..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"tabheader[0,0;tab;rules,outp,notes,help;3;;true]"..
"textarea[0.3,0.2;13,8.3;notes;Notepad:;"..notes.."]"..
buttons(cmnd)
end
function techage.formspecHelp(offs)
return SIZE..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"tabheader[0,0;tab;rules,outp,notes,help;4;;true]"..
"field[0,0;0,0;_type_;;help]"..
"label[0,"..(-offs/50)..";"..sHELP.."]"..
--"label[0.2,0;test]"..
"scrollbar[12,1;0.5,7;vertical;sb_help;"..offs.."]"
end
@@ -0,0 +1,158 @@
--[[
TechAge
=======
Copyright (C) 2019-2022 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Signal Tower
]]--
local function switch_on(pos, node, color)
local meta = minetest.get_meta(pos)
meta:set_string("state", color)
node.name = "techage:ta4_signaltower_"..color
minetest.swap_node(pos, node)
end
local function switch_off(pos, node)
local meta = minetest.get_meta(pos)
meta:set_string("state", "off")
node.name = "techage:ta4_signaltower"
minetest.swap_node(pos, node)
end
minetest.register_node("techage:ta4_signaltower", {
description = "TA4 Signal Tower",
tiles = {
'techage_signaltower_top.png',
'techage_signaltower_top.png',
'techage_signaltower.png',
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -5/32, -16/32, -5/32, 5/32, 16/32, 5/32},
},
},
after_place_node = function(pos, placer)
local number = techage.add_node(pos, "techage:ta4_signaltower")
local meta = minetest.get_meta(pos)
meta:set_string("state", "off")
meta:set_string("infotext", "TA4 Signal Tower "..number)
end,
on_rightclick = function(pos, node, clicker)
if not minetest.is_protected(pos, clicker:get_player_name()) then
switch_on(pos, node, "green")
end
end,
after_dig_node = function(pos, oldnode, oldmetadata)
techage.remove_node(pos, oldnode, oldmetadata)
end,
paramtype = "light",
use_texture_alpha = techage.CLIP,
light_source = 0,
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_glass_defaults(),
})
for _,color in ipairs({"green", "amber", "red"}) do
minetest.register_node("techage:ta4_signaltower_"..color, {
description = "TA4 Signal Tower",
tiles = {
'techage_signaltower_top.png',
'techage_signaltower_top.png',
'techage_signaltower_'..color..'.png',
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -5/32, -16/32, -5/32, 5/32, 16/32, 5/32},
},
},
on_rightclick = function(pos, node, clicker)
if not minetest.is_protected(pos, clicker:get_player_name()) then
switch_off(pos, node)
end
end,
paramtype = "light",
use_texture_alpha = techage.CLIP,
light_source = 10,
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {crumbly=0, not_in_creative_inventory=1},
is_ground_content = false,
sounds = default.node_sound_glass_defaults(),
drop = "techage:ta4_signaltower",
})
end
minetest.register_craft({
output = "techage:ta4_signaltower",
recipe = {
{"dye:red", "default:copper_ingot", ""},
{"dye:orange", "default:glass", ""},
{"dye:green", "techage:ta4_wlanchip", ""},
},
})
techage.register_node({"techage:ta4_signaltower",
"techage:ta4_signaltower_green",
"techage:ta4_signaltower_amber",
"techage:ta4_signaltower_red"}, {
on_recv_message = function(pos, src, topic, payload)
local node = minetest.get_node(pos)
if topic == "green" then
switch_on(pos, node, "green")
elseif topic == "amber" then
switch_on(pos, node, "amber")
elseif topic == "red" then
switch_on(pos, node, "red")
elseif topic == "off" then
switch_off(pos, node)
elseif topic == "state" then
local meta = minetest.get_meta(pos)
return meta:get_string("state")
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
if topic == 2 then
local color = ({"green", "amber", "red"})[payload[1]]
local node = minetest.get_node(pos)
if color then
switch_on(pos, node, color)
else
switch_off(pos, node)
end
return 0
else
return 2 -- unknown or invalid topic
end
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 130 then
local meta = minetest.get_meta(pos)
local color = ({off = 0, green = 1, amber = 2, red = 3})[meta:get_string("state")] or 1
return 0, {color}
else
return 2, "" -- unknown or invalid topic
end
end,
})
@@ -0,0 +1,213 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
AGPL v3
See LICENSE.txt for more information
ICTA Controller - Formspec
A sub-menu control to generate a formspec sting for conditions and actions
]]--
local function index(list, x)
for idx, v in ipairs(list) do
if v == x then return idx end
end
return nil
end
-- generate the choice dependent part of the form
local function add_controls_to_table(tbl, kvDefinition, kvSelect)
local offs = 1.4
if kvDefinition[kvSelect.choice] then
local lControls = kvDefinition[kvSelect.choice].formspec
for _,elem in ipairs(lControls) do
if elem.type == "label" then
tbl[#tbl+1] = "label[0,"..offs..";Description:\n"..elem.label.."]"
offs = offs + 0.4
elseif elem.label and elem.label ~= "" then
tbl[#tbl+1] = "label[0,"..offs..";"..elem.label..":]"
offs = offs + 0.4
end
if elem.type == "numbers" or elem.type == "number" or elem.type == "digits" or elem.type == "letters"
or elem.type == "ascii" then
local val = kvSelect[elem.name] or elem.default
tbl[#tbl+1] = "field[0.3,"..(offs+0.2)..";8,1;"..elem.name..";;"..val.."]"
offs = offs + 0.9
elseif elem.type == "textlist" then
local l = elem.choices:split(",")
local val = index(l, kvSelect[elem.name]) or elem.default
tbl[#tbl+1] = "dropdown[0.0,"..(offs)..";8.5,1.4;"..elem.name..";"..elem.choices..";"..val.."]"
offs = offs + 0.9
end
end
end
return tbl
end
local function default_data(kvDefinition, kvSelect)
local lControls = kvDefinition[kvSelect.choice].formspec
for _,elem in ipairs(lControls) do
kvSelect[elem.name] = elem.default
end
kvSelect.button = kvDefinition[kvSelect.choice].button(kvSelect)
return kvSelect
end
-- Copy field/formspec data to the table kvSelect
-- kvDefinition: submenu formspec definition
-- kvSelect: form data
-- fields: formspec input
local function field_to_kvSelect(kvDefinition, kvSelect, fields)
local error = false
local lControls = kvDefinition[kvSelect.choice].formspec
for _,elem in ipairs(lControls) do
if elem.type == "numbers" then
if fields[elem.name] then
if fields[elem.name]:find("^[%d ]+$") then
kvSelect[elem.name] = fields[elem.name]
else
kvSelect[elem.name] = elem.default
error = true
end
end
elseif elem.type == "number" then
if fields[elem.name] then
if fields[elem.name]:find("^[%d ]+$") then
kvSelect[elem.name] = fields[elem.name]
else
kvSelect[elem.name] = elem.default
error = true
end
end
elseif elem.type == "digits" then -- including positions
if fields[elem.name] then
if fields[elem.name]:find("^[+%%-,%d]+$") then
kvSelect[elem.name] = fields[elem.name]
else
kvSelect[elem.name] = elem.default
error = true
end
end
elseif elem.type == "letters" then
if fields[elem.name] then
if fields[elem.name]:find("^[+-]?%a+$") then
kvSelect[elem.name] = fields[elem.name]
else
kvSelect[elem.name] = elem.default
error = true
end
end
elseif elem.type == "ascii" then
if fields[elem.name] then
kvSelect[elem.name] = fields[elem.name]
end
elseif elem.type == "textlist" then
if fields[elem.name] ~= nil then
kvSelect[elem.name] = fields[elem.name]
end
end
end
-- store user input of button text
if fields._button_ then
kvSelect._button_ = fields._button_
end
-- select button text
if error then
kvSelect.button = "invalid"
elseif kvSelect._button_ and kvSelect._button_ ~= "" then
kvSelect.button = kvSelect._button_
else
kvSelect.button = kvDefinition[kvSelect.choice].button(kvSelect)
end
return kvSelect
end
function techage.submenu_verify(owner, kvDefinition, kvSelect)
local error = false
local lControls = kvDefinition[kvSelect.choice].formspec
for _,elem in ipairs(lControls) do
if elem.type == "numbers" then
if not kvSelect[elem.name]:find("^[%d ]+$") then
error = true
end
if not techage.check_numbers(kvSelect[elem.name], owner) then
error = true
end
elseif elem.type == "number" then
if not kvSelect[elem.name]:find("^[%d]+$") then
error = true
end
if not techage.check_numbers(kvSelect[elem.name], owner) then
error = true
end
elseif elem.type == "digits" then -- including positions
if not kvSelect[elem.name]:find("^[+%%-,%d]+$") then
error = true
end
elseif elem.type == "letters" then
if not kvSelect[elem.name]:find("^[+-]?%a+$") then
error = true
end
elseif elem.type == "ascii" then
if kvSelect[elem.name] == "" or kvSelect[elem.name] == nil then
error = true
end
elseif elem.type == "textlist" then
if kvSelect[elem.name] == "" or kvSelect[elem.name] == nil then
error = true
end
end
end
return (error == false)
end
-- generate a formspec string from the given control definition
-- row, col: numbers to identify the control
-- title: Title text for the control
-- lKeys: list of keywords of selected choices according to fields
-- lChoice: list of possible choices for the control
-- kvDefinition: definitions of the choice dependent controls
-- kvSelect: data of the last selected item {choice, number, value, ...}
function techage.submenu_generate_formspec(row, col, title, lKeys, lChoice, kvDefinition, kvSelect)
if kvSelect == nil or next(kvSelect) == nil then
kvSelect = {choice = "default"}
end
local tbl = {"size[8.2,9]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_row_;;"..row.."]"..
"field[0,0;0,0;_col_;;"..col.."]"}
local sChoice = table.concat(lChoice, ",")
local idx = index(lKeys, kvSelect.choice) or 1
tbl[#tbl+1] = "label[0,0;"..title..":]"
tbl[#tbl+1] = "dropdown[0,0.5;8.5,1;choice;"..sChoice..";"..idx.."]"
tbl = add_controls_to_table(tbl, kvDefinition, kvSelect)
tbl[#tbl+1] = "field[0.2,8.7;4,1;_button_;Alternative button text;"..(kvSelect._button_ or "").."]"
tbl[#tbl+1] = "button[4,8.4;2,1;_cancel_;cancel]"
tbl[#tbl+1] = "button[6,8.4;2,1;_exit_;ok]"
return table.concat(tbl)
end
-- return the selected and configured menu item based on user inputs (fields)
function techage.submenu_eval_input(kvDefinition, lKeys, lChoice, kvSelect, fields)
-- determine selected choice
if fields.choice then
-- load with default values
local idx = index(lChoice, fields.choice) or 1
kvSelect = {choice = lKeys[idx]}
kvSelect = default_data(kvDefinition, kvSelect)
kvSelect = field_to_kvSelect(kvDefinition, kvSelect, fields)
else
-- add real data
kvSelect = field_to_kvSelect(kvDefinition, kvSelect, fields)
end
return kvSelect
end