ICTA controller added

This commit is contained in:
Joachim Stolberg 2020-02-29 19:20:54 +01:00
parent a6c7fc3731
commit 2a8dfd755f
31 changed files with 2465 additions and 34 deletions

View File

@ -24,7 +24,8 @@ local CYCLE_TIME = 6
local recipes = techage.recipes
local RecipeType = { [2] = "ta2_electronic_fab",
local RecipeType = {
[2] = "ta2_electronic_fab",
[3] = "ta3_electronic_fab",
[4] = "ta4_electronic_fab",
}
@ -239,22 +240,6 @@ minetest.register_craft({
},
})
minetest.register_craftitem("techage:vacuum_tube", {
description = S("TA3 Vacuum Tube"),
inventory_image = "techage_vacuum_tube.png",
})
minetest.register_craftitem("techage:ta4_wlanchip", {
description = S("TA4 WLAN Chip"),
inventory_image = "techage_wlanchip.png",
})
minetest.register_craftitem("techage:wlanchip", {
description = S("WLAN Chip"),
inventory_image = "techage_wlanchip.png",
})
if minetest.global_exists("unified_inventory") then
unified_inventory.register_craft_type("ta2_electronic_fab", {
description = S("TA2 Ele Fab"),
@ -270,18 +255,3 @@ if minetest.global_exists("unified_inventory") then
})
end
recipes.add("ta2_electronic_fab", {
output = "techage:vacuum_tube 2",
input = {"default:glass 1", "basic_materials:copper_wire 1", "basic_materials:plastic_sheet 1", "techage:usmium_nuggets 1"}
})
recipes.add("ta3_electronic_fab", {
output = "techage:vacuum_tube 2",
input = {"default:glass 1", "basic_materials:copper_wire 1", "basic_materials:plastic_sheet 1", "techage:usmium_nuggets 1"}
})
recipes.add("ta3_electronic_fab", {
output = "techage:ta4_wlanchip 8",
input = {"default:mese_crystal 1", "default:copper_ingot 1", "default:gold_ingot 1", "techage:ta4_silicon_wafer 1"}
})

View File

@ -339,6 +339,7 @@ function techage.check_numbers(numbers, placer_name)
end
function techage.send_multi(src, numbers, topic, payload)
--print("send_multi", src, numbers, topic)
for _,num in ipairs(string_split(numbers, " ")) do
if Number2Pos[num] and Number2Pos[num].name then
local data = Number2Pos[num]
@ -350,6 +351,7 @@ function techage.send_multi(src, numbers, topic, payload)
end
function techage.send_single(src, number, topic, payload)
--print("send_single", src, number, topic)
if Number2Pos[number] and Number2Pos[number].name then
local data = Number2Pos[number]
if data.pos and NodeDef[data.name] and NodeDef[data.name].on_recv_message then

107
icta_controller/action.lua Normal file
View File

@ -0,0 +1,107 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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(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)
local s1 = 'local text = string.gsub("'..(techage.icta_escape(data.text))..'", "*", env.result[#])'
local s2 = 'output(env.pos, text)'
return s1.."\n\t"..s2
end,
})

167
icta_controller/battery.lua Normal file
View File

@ -0,0 +1,167 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
ICTA Controller - Battery
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local logic = techage.logic
local BATTERY_CAPACITY = 5000000
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 percent = calc_percent(meta:get_int("content"))
meta:set_string("infotext", S("Battery").." ("..percent.."%)")
if percent == 0 then
local node = minetest.get_node(pos)
node.name = "techage:battery_empty"
minetest.swap_node(pos, node)
return false
end
return true
end
local function register_battery(ext, percent, nici)
minetest.register_node("techage:battery"..ext, {
description = S("Battery").." "..ext,
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)
local meta = minetest.get_meta(pos)
meta:set_int("content", BATTERY_CAPACITY * percent)
local node = minetest.get_node(pos)
node.name = "techage:battery"
minetest.swap_node(pos, node)
on_timer(pos, 1)
minetest.get_node_timer(pos):start(30)
end,
on_timer = on_timer,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
local percent = calc_percent(tonumber(oldmetadata.fields.content))
local stack
if percent > 95 then
stack = ItemStack("techage:battery")
elseif percent > 75 then
stack = ItemStack("techage:battery75")
elseif percent > 50 then
stack = ItemStack("techage:battery50")
elseif percent > 25 then
stack = ItemStack("techage:battery25")
else
return
end
local inv = minetest.get_inventory({type="player", name=digger:get_player_name()})
inv:add_item("main", stack)
end,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {choppy=1, cracky=1, crumbly=1, not_in_creative_inventory=nici},
drop = "",
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
end
register_battery("", 1.0, 0)
register_battery("75", 0.75, 1)
register_battery("50", 0.5, 1)
register_battery("25", 0.25, 1)
minetest.register_node("techage: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",
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:battery 2",
recipe = {
{"", "moreores:silver_ingot", ""},
{"", "default:copper_ingot", ""},
{"", "moreores:silver_ingot", ""},
}
})
else
minetest.register_craft({
output = "techage:battery 2",
recipe = {
{"", "default:tin_ingot", ""},
{"", "default:copper_ingot", ""},
{"", "default:tin_ingot", ""},
}
})
end
techage.register_node({"techage:battery", "techage:battery25", "techage:battery50", "techage:battery75"},
{
on_node_load = function(pos)
minetest.get_node_timer(pos):start(30)
end,
})

View File

@ -0,0 +1,618 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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
local function send_single_string(environ, number, topic, payload)
payload = payload or "nil"
local s = 'techage.send_single("%s", "%s", "%s", %s)'
return string.format(s, environ.number, number, topic, payload)
end
local function send_multi_string(environ, numbers, topic, payload)
payload = payload or "nil"
local s = 'techage.send_multi("%s", "%s", "%s", %s)'
return string.format(s, environ.number, numbers, topic, payload)
end
function techage.operand(s)
if s == "is" then
return "== "
elseif s == "is not" then
return "~= "
elseif s == "greater" then
return "> "
elseif s == "less" then
return "< "
end
end
function techage.fmt_number(num)
local mtch = num:match('^(%d+).*')
if mtch and num ~= mtch then
return mtch.."..."
end
return num
end
-- '#' is used as placeholder for rule numbers and has to be escaped
function techage.icta_escape(s)
s = tostring(s)
return s:gsub("#", '"..string.char(35).."')
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)
return 'env.ticks', '== 1'
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)
return '"true"', '== "true"'
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 idx = data.condition:byte(-1) - 0x30
local expected_result = "== false"
if data.operand == "was true" then
expected_result = "== true"
end
return "env.condition["..idx.."]", expected_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)
return 'env.input["'..data.number..'"]',
techage.operand(data.operand)..'"'..data.value..'"'
end,
})
techage.icta_register_condition("state", {
title = "block state request",
formspec = {
{
type = "digits",
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",
default = "stopped",
},
{
type = "label",
name = "lbl",
label = "Read the state from 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)
return send_single_string(environ, data.number, "state"),
techage.operand(data.operand)..'"'..data.value..'"'
end,
})
techage.icta_register_condition("fuel", {
title = "fuel request",
formspec = {
{
type = "digits",
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\nfrom a fuel consuming block.",
},
},
button = function(data, environ)
return 'fuel('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
return send_single_string(environ, data.number, "fuel"),
techage.operand(data.operand)..tonumber(data.value)
end,
})
techage.icta_register_condition("load", {
title = "load request",
formspec = {
{
type = "digits",
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)\nfrom a tank/storage block.",
},
},
button = function(data, environ)
return 'load('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end,
code = function(data, environ)
return send_single_string(environ, data.number, "load"),
techage.operand(data.operand)..tonumber(data.value)
end,
})
techage.icta_register_condition("chest", {
title = "chest state request",
formspec = {
{
type = "digits",
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,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)
return send_single_string(environ, data.number, "state"),
techage.operand(data.operand)..'"'..data.value..'"'
end,
})
techage.icta_register_condition("signaltower", {
title = "Signal Tower state request",
formspec = {
{
type = "digits",
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\nfrom 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)
return send_single_string(environ, data.number, "state"),
techage.operand(data.operand)..'"'..data.value..'"'
end,
})
techage.icta_register_action("signaltower", {
title = "Signal Tower command",
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 send_multi_string(environ, data.number, data.value)
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 send_multi_string(environ, data.number, data.value)
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,6,7,8,9",
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)
local s1 = string.format('local text = string.gsub("%s", "*", tostring(env.result[#]))', techage.icta_escape(data.text))
local s2 = string.format('local payload = {row = %s, str = text}', data.row)
local s3 = send_multi_string(environ, data.number, "set", "payload")
return s1.."\n\t"..s2.."\n\t"..s3
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 = "numbers",
name = "number",
label = "Display number",
default = "",
},
},
code = function(data, environ)
return send_multi_string(environ, data.number, "clear")
end,
button = function(data, environ)
return "clear lcd("..techage.fmt_number(data.number)..")"
end,
})
techage.icta_register_action("chat", {
title = "chat send",
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 'minetest.chat_send_player("'..environ.owner..'", "[TA4 ICTA Controller] '..data.text..' ")'
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 = "doors open/close",
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 Programmer to\neasily determine a door position.",
},
},
code = function(data, environ)
return 'techage.icta_door_toggle("'..data.pos..'", "'..environ.owner..'", "'..data.door_state..'")'
end,
button = function(data, environ)
return 'door("'..data.pos..'",'..data.door_state..")"
end,
})
function techage.icta_player_detect(own_num, number, name)
local state = techage.send_single(own_num, number, "name", nil)
print("state="..state.."< name="..name.."<")
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 = "Player Detector name request",
formspec = {
{
type = "digits",
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\nfrom a Player Detector.\nUse a '*' character for all player names.\n Use a '-' character for no player.",
},
},
code = function(data, environ)
return 'techage.icta_player_detect("'..environ.number..'", "'..data.number..'", "'..data.name..'")', "~= nil"
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 = "numbers",
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)
local payload = '{slot = "'..data.color..'", val = "'..data.value..'"}'
return send_multi_string(environ, data.number, "filter", payload)
end,
})

View File

@ -0,0 +1,77 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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(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) return false, false end,
button = function(data, environ) return "..." end,
})

View File

@ -0,0 +1,512 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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
----------------- template -------------------------------
-- -- Rule 1
-- if env.blocked[1] == false and env.ticks % <cycle> == 0 then
-- env.result[1] = <check condition>
-- env.blocked[1] = env.result[1] <expected result>
-- if env.blocked[1] then
-- env.timer[1] = env.ticks + <after>
-- end
-- env.conditions[1] = env.blocked[1]
-- else
-- env.conditions[1] = false
-- end
-- if env.blocked[1] and env.timer[1] == env.ticks then
-- <action>
-- env.blocked[1] = false
-- end
-- -- Callback variant
-- if env.blocked[1] == false and env.ticks % <cycle> == 0 then
-- env.result[1], env.blocked[1] = <callback>
-- if env.blocked[1] then
-- env.timer[1] = env.ticks + <after>
-- end
-- env.conditions[1] = env.blocked[1]
-- else
-- env.conditions[1] = false
-- end
-- if env.blocked[1] and env.timer[1] == env.ticks then
-- <action>
-- env.blocked[1] = false
-- end
-- cyclic execution (cycle, cond, result, after, actn)
local TemplCyc = [[
-- Rule #
if env.blocked[#] == false and env.ticks %% %s == 0 then
env.result[#] = %s
env.blocked[#] = env.result[#] %s
if env.blocked[#] then
env.timer[#] = env.ticks + %s
end
env.condition[#] = env.blocked[#]
else
env.condition[#] = false
end
if env.blocked[#] and env.timer[#] == env.ticks then
%s
env.blocked[#] = false
end
]]
-- event based execution
local TemplEvt = [[
-- Rule #
if env.blocked[#] == false and env.event then
env.result[#] = %s
env.blocked[#] = env.result[#] %s
if env.blocked[#] then
env.timer[#] = env.ticks + %s
end
env.condition[#] = env.blocked[#]
else
env.condition[#] = false
end
if env.blocked[#] and env.timer[#] == env.ticks then
%s
env.blocked[#] = false
end
]]
-- event based execution of callback function
local TemplEvtClbk = [[
-- Rule #
if env.blocked[#] == false and env.event then
env.result[#], env.blocked[#] = %s(env, %s)
if env.blocked[#] then
env.timer[#] = env.ticks + %s
end
env.condition[#] = env.blocked[#]
else
env.condition[#] = false
end
if env.blocked[#] and env.timer[#] == env.ticks then
%s
env.blocked[#] = false
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
-- chunks are compiled as vararg functions. Parameters are available via: local a, b, c = ...
local tbl = {"local env, output = ...\n"}
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 s
if cycle == 0 then -- event?
if result then
s = string.format(TemplEvt, cond, result, after, actn)
else -- callback function
local data = dump(fs_data[idx].cond)
s = string.format(TemplEvtClbk, cond, data, after, actn)
end
else -- cyclic
s = string.format(TemplCyc, cycle, cond, result, after, actn)
end
-- add to list of rules
tbl[#tbl+1] = s:gsub("#", idx)
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 table.concat(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 text = generate(pos, meta, gen_environ)
if text then
local code, err = loadstring(text)
if code then
Cache[number] = {
code = code,
env = runtime_environ(pos),
}
return true
else
output(pos, err)
return false
end
end
return false
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
local res, err = pcall(code, env, output)
if not res then
output(pos, err)
return false
end
return true
end
local function battery(pos)
local battery_pos = minetest.find_node_near(pos, 1, {"techage: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_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_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("formspec", techage.cond_formspec(1, 1, nil))
meta:set_string("infotext", "TA4 ICTA Controller "..number..": stopped")
end,
on_receive_fields = on_receive_fields,
on_dig = function(pos, node, puncher, pointed_thing)
if minetest.is_protected(pos, puncher:get_player_name()) then
return
end
minetest.node_dig(pos, node, puncher, pointed_thing)
techage.remove_node(pos)
end,
on_timer = on_timer,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {choppy=1, cracky=1, crumbly=1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
minetest.register_craft({
output = "techage:ta4_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_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")
return techage.statestring(state)
else
return "unsupported"
end
end,
})

171
icta_controller/display.lua Normal file
View File

@ -0,0 +1,171 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
ICTA Controller - Display
]]--
lcdlib.register_display_entity("techage:display_entity")
local function display_update(pos, objref)
local meta = minetest.get_meta(pos)
local text = meta:get_string("text") or ""
text = string.gsub(text, "|", " \n")
local texture = lcdlib.make_multiline_texture(
"default", text,
--120, 120, 9, "top", "#000")
70, 70, 5, "top", "#000")
objref:set_properties({ textures = {texture},
visual_size = {x=0.94, y=0.94} })
end
local function on_timer(pos)
local node = minetest.get_node(pos)
-- check if display is loaded and a player in front of the display
if node.name == "techage:display" then
local dir = minetest.facedir_to_dir((node.param2 + 2) % 4)
local pos2 = vector.add(pos, vector.multiply(dir, 6))
for _, obj in pairs(minetest.get_objects_inside_radius(pos2, 6)) do
if obj:is_player() then
lcdlib.update_entities(pos)
break
end
end
end
return false
end
local lcd_box = {
type = "wallmounted",
wall_top = {-8/16, 15/32, -8/16, 8/16, 8/16, 8/16}
}
minetest.register_node("techage:display", {
description = "TA4 Display",
inventory_image = 'techage_display_inventory.png',
tiles = {"techage_display.png"},
drawtype = "nodebox",
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "wallmounted",
node_box = lcd_box,
selection_box = lcd_box,
light_source = 6,
display_entities = {
["techage:display_entity"] = { depth = 0.42,
on_display_update = display_update},
},
after_place_node = function(pos, placer)
local number = techage.add_node(pos, "techage:display")
local meta = minetest.get_meta(pos)
meta:set_string("number", number)
meta:set_string("text", "My\nTechage\nTA4\nDisplay\nNo: "..number)
meta:set_int("startscreen", 1)
lcdlib.update_entities(pos)
end,
after_dig_node = function(pos)
techage.remove_node(pos)
end,
on_timer = on_timer,
on_place = lcdlib.on_place,
on_construct = lcdlib.on_construct,
on_destruct = lcdlib.on_destruct,
on_rotate = lcdlib.on_rotate,
groups = {cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_glass_defaults(),
})
minetest.register_craft({
output = "techage:display",
recipe = {
{"", "", ""},
{"default:glass", "dye:green", "techage:ta4_wlanchip"},
{"", "default:copper_ingot", ""},
},
})
local function add_line(meta, payload)
local text = meta:get_string("text")
local rows
if meta:get_int("startscreen") == 1 then
rows = {}
meta:set_int("startscreen", 0)
else
rows = string.split(text, "|")
end
if #rows > 8 then
table.remove(rows, 1)
end
table.insert(rows, payload)
text = table.concat(rows, "|")
meta:set_string("text", text)
end
local function write_row(meta, payload)
local text = meta:get_string("text")
if type(payload) == "table" then
local row = tonumber(payload.row) or 0
if row > 9 then row = 9 end
local str = payload.str or "oops"
if row == 0 then
meta:set_string("infotext", str)
return
end
local rows
if meta:get_int("startscreen") == 1 then
rows = {}
meta:set_int("startscreen", 0)
else
rows = string.split(text, "|")
end
if #rows < 9 then
for i = #rows, 9 do
table.insert(rows, " ")
end
end
rows[row] = str
text = table.concat(rows, "|")
meta:set_string("text", text)
end
end
techage.register_node({"techage:display"}, {
on_recv_message = function(pos, src, topic, payload)
local node = minetest.get_node(pos)
local timer = minetest.get_node_timer(pos)
if topic == "add" then -- add one line and scroll if necessary
local meta = minetest.get_meta(pos)
add_line(meta, payload)
if not timer:is_started() then
timer:start(1)
end
elseif topic == "set" then -- overwrite the given row
local meta = minetest.get_meta(pos)
write_row(meta, payload)
if not timer:is_started() then
timer:start(1)
end
elseif topic == "clear" then -- clear the screen
local meta = minetest.get_meta(pos)
meta:set_string("text", "")
if not timer:is_started() then
timer:start(1)
end
end
end,
})

40
icta_controller/edit.lua Normal file
View File

@ -0,0 +1,40 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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

View File

@ -0,0 +1,236 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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.
]]
-- 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

View File

@ -0,0 +1,133 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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: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:signaltower"
minetest.swap_node(pos, node)
end
minetest.register_node("techage: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: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)
techage.remove_node(pos)
end,
paramtype = "light",
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: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",
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:signaltower",
})
end
minetest.register_craft({
output = "techage:signaltower",
recipe = {
{"dye:red", "default:copper_ingot", ""},
{"dye:orange", "default:glass", ""},
{"dye:green", "techage:ta4_wlanchip", ""},
},
})
techage.register_node({"techage:signaltower",
"techage:signaltower_green",
"techage:signaltower_amber",
"techage: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,
})

View File

@ -0,0 +1,132 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
ICTA Controller - Stopwatch
Start/stop the watch with an on/off commands.
The player name clicking the stop is stored in addition.
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = techage.S
local logic = techage.logic
local function retrieve_clicker_name(number)
local pos = techage.get_node_info(number).pos
if pos then
local meta = minetest.get_meta(pos)
return meta:get_string("clicker_name") or "<unknown>"
end
return "<error>"
end
-- env = {
-- event = <bool>,
-- last_event = <number> -- last event time
-- ticks = <number,
-- pos = <pos>,
-- timer = gen_table(8, 0),
-- blocked = gen_table(8, false),
-- result = gen_table(8, false),
-- condition = gen_table(8, false),
-- input = <table>, -- node number is key
-- number = <number>,
-- owner = <string>,
-- },
--
-- return cond_result, trigger_action
function techage.stopwatch(env, data)
if env.input[data.number] == "on" then
env.time = env.last_event
if not env.highscore then
env.highscore = 99999
end
return nil, false
else
local time = (env.last_event - env.time) / 1000000
local name = retrieve_clicker_name(data.number)
env.highscore = math.min(time, env.highscore)
local s1 = string.format("%2.1f s", time)
local s2 = string.format("%2.1f s", env.highscore)
env.stopwatch_result = {s1, s2, name}
return nil, true
end
end
techage.icta_register_condition("stopwatch", {
title = "stopwatch",
formspec = {
{
type = "numbers",
name = "number",
label = "Switch number",
default = "",
},
{
type = "label",
name = "lbl",
label = "Hint: Stop the time between switching on\nand switching off of the connected switch.",
},
},
code = function(data, environ)
return "techage.stopwatch"
end,
button = function(data, environ)
return 'stopwatch('..sl.fmt_number(data.number)..')'
end,
})
techage.icta_register_action("stopwatch", {
title = "stopwatch",
formspec = {
{
type = "numbers",
name = "number",
label = "Display number",
default = "",
},
{
type = "textlist",
name = "row",
label = "Display line",
choices = "1,2,3,4,5,6,7,8,9",
default = "1",
},
{
type = "ascii",
name = "text",
label = "label",
default = "",
},
{
type = "textlist",
name = "type",
label = "type",
choices = "time,highscore,name",
default = "time",
},
{
type = "label",
name = "lbl",
label = "Hint: Display number for the output\nof time, highscore and player name.",
},
},
button = function(data, environ)
return "lcd("..sl.fmt_number(data.number)..","..data.row..","..data.type..')'
end,
code = function(data, environ)
local idx = ({time=1, highscore= 2, name=3})[data.type]
local s1 = string.format('local payload = {row = %s, str = "%s "..env.stopwatch_result['..idx..']}', data.row, techage.escape(data.text))
local s2 = string.format('techage.send_multi("%s", "%s", "row", payload)', environ.number, data.number)
return s1.."\n\t"..s2
end,
})

196
icta_controller/submenu.lua Normal file
View File

@ -0,0 +1,196 @@
--[[
TechAge
=======
Copyright (C) 2019-2020 Joachim Stolberg
GPL 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 val = ""
local offs = 1.4
if kvDefinition[kvSelect.choice] then
local lControls = kvDefinition[kvSelect.choice].formspec
for idx,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 == "digits" or elem.type == "letters"
or elem.type == "ascii" then
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(",")
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 idx,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 idx,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 == "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(kvDefinition, kvSelect)
local error = false
local lControls = kvDefinition[kvSelect.choice].formspec
for idx,elem in ipairs(lControls) do
if elem.type == "numbers" then
if not kvSelect[elem.name]:find("^[%d ]+$") 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

View File

@ -209,6 +209,19 @@ else
dofile(MP.."/hydrogen/electrolyzer.lua")
dofile(MP.."/hydrogen/fuelcell.lua")
-- ICTA Controller
dofile(MP.."/icta_controller/submenu.lua")
dofile(MP.."/icta_controller/condition.lua")
dofile(MP.."/icta_controller/action.lua")
dofile(MP.."/icta_controller/formspec.lua")
dofile(MP.."/icta_controller/controller.lua")
dofile(MP.."/icta_controller/commands.lua")
dofile(MP.."/icta_controller/edit.lua")
dofile(MP.."/icta_controller/battery.lua")
--dofile(MP.."/icta_controller/stopwatch.lua")
dofile(MP.."/icta_controller/display.lua")
dofile(MP.."/icta_controller/signaltower.lua")
-- Items
dofile(MP.."/items/barrel.lua")
dofile(MP.."/items/baborium.lua")
@ -224,6 +237,7 @@ else
dofile(MP.."/items/aluminium.lua")
dofile(MP.."/items/plastic.lua")
dofile(MP.."/items/hydrogen.lua")
dofile(MP.."/items/electronic.lua")
if techage.basalt_stone_enabled then
dofile(MP.."/items/basalt.lua")

56
items/electronic.lua Normal file
View File

@ -0,0 +1,56 @@
--[[
TechAge
=======
Copyright (C) 2019 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
Bauxite
]]--
local S = techage.S
minetest.register_craftitem("techage:vacuum_tube", {
description = S("TA3 Vacuum Tube"),
inventory_image = "techage_vacuum_tube.png",
})
minetest.register_craftitem("techage:ta4_wlanchip", {
description = S("TA4 WLAN Chip"),
inventory_image = "techage_wlanchip.png",
})
minetest.register_craftitem("techage:wlanchip", {
description = S("WLAN Chip"),
inventory_image = "techage_wlanchip.png",
})
minetest.register_craftitem("techage:ta4_ramchip", {
description = S("TA4 RAM Chip"),
inventory_image = "techage_ramchip.png",
})
techage.recipes.add("ta2_electronic_fab", {
output = "techage:vacuum_tube 2",
input = {"default:glass 1", "basic_materials:copper_wire 1", "basic_materials:plastic_sheet 1", "techage:usmium_nuggets 1"}
})
techage.recipes.add("ta3_electronic_fab", {
output = "techage:vacuum_tube 2",
input = {"default:glass 1", "basic_materials:copper_wire 1", "basic_materials:plastic_sheet 1", "techage:usmium_nuggets 1"}
})
techage.recipes.add("ta3_electronic_fab", {
output = "techage:ta4_wlanchip 8",
input = {"default:mese_crystal 1", "default:copper_ingot 1", "default:gold_ingot 1", "techage:ta4_silicon_wafer 1"}
})
techage.recipes.add("ta3_electronic_fab", {
output = "techage:ta4_ramchip 8",
input = {"default:mese_crystal 1", "default:gold_ingot 1", "default:copper_ingot 1", "techage:ta4_silicon_wafer 1"}
})

View File

@ -188,7 +188,7 @@ minetest.register_craft({
})
techage.register_node({"techage:ta3_playerdetector_off", "techage:ta3_playerdetector_on"}, {
on_recv_message = function(pos, topic, payload)
on_recv_message = function(pos, src, topic, payload)
if topic == "name" then
local nvm = techage.get_nvm(pos)
return nvm.player_name or ""

View File

@ -1,4 +1,4 @@
name = techage
depends = default,tubelib2,basic_materials,bucket,stairs,screwdriver,minecart
depends = default,doors,tubelib2,basic_materials,bucket,stairs,screwdriver,minecart,lcdlib
optional_depends = unified_inventory,wielded_light,unifieddyes
description = Techage, go through 4 tech ages in search of wealth and power!

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B