diff --git a/basic_machines/electronic_fab.lua b/basic_machines/electronic_fab.lua index e080057..d59c64a 100644 --- a/basic_machines/electronic_fab.lua +++ b/basic_machines/electronic_fab.lua @@ -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"} -}) diff --git a/basis/command.lua b/basis/command.lua index 3748a2e..4a9bb64 100644 --- a/basis/command.lua +++ b/basis/command.lua @@ -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 diff --git a/icta_controller/action.lua b/icta_controller/action.lua new file mode 100644 index 0000000..b578d31 --- /dev/null +++ b/icta_controller/action.lua @@ -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, +}) + diff --git a/icta_controller/battery.lua b/icta_controller/battery.lua new file mode 100644 index 0000000..335d6ac --- /dev/null +++ b/icta_controller/battery.lua @@ -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, +}) diff --git a/icta_controller/commands.lua b/icta_controller/commands.lua new file mode 100644 index 0000000..cbec723 --- /dev/null +++ b/icta_controller/commands.lua @@ -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 , + 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, +}) + diff --git a/icta_controller/condition.lua b/icta_controller/condition.lua new file mode 100644 index 0000000..01bf510 --- /dev/null +++ b/icta_controller/condition.lua @@ -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, +}) + diff --git a/icta_controller/controller.lua b/icta_controller/controller.lua new file mode 100644 index 0000000..9f2dd51 --- /dev/null +++ b/icta_controller/controller.lua @@ -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 % == 0 then +-- env.result[1] = +-- env.blocked[1] = env.result[1] +-- if env.blocked[1] then +-- env.timer[1] = env.ticks + +-- end +-- env.conditions[1] = env.blocked[1] +-- else +-- env.conditions[1] = false +-- end +-- if env.blocked[1] and env.timer[1] == env.ticks then +-- +-- env.blocked[1] = false +-- end + +-- -- Callback variant +-- if env.blocked[1] == false and env.ticks % == 0 then +-- env.result[1], env.blocked[1] = +-- if env.blocked[1] then +-- env.timer[1] = env.ticks + +-- end +-- env.conditions[1] = env.blocked[1] +-- else +-- env.conditions[1] = false +-- end +-- if env.blocked[1] and env.timer[1] == env.ticks then +-- +-- 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", "") + 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", "") + 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, +}) + diff --git a/icta_controller/display.lua b/icta_controller/display.lua new file mode 100644 index 0000000..f3980b7 --- /dev/null +++ b/icta_controller/display.lua @@ -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, +}) + diff --git a/icta_controller/edit.lua b/icta_controller/edit.lua new file mode 100644 index 0000000..9fbac17 --- /dev/null +++ b/icta_controller/edit.lua @@ -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 diff --git a/icta_controller/formspec.lua b/icta_controller/formspec.lua new file mode 100644 index 0000000..9a2055f --- /dev/null +++ b/icta_controller/formspec.lua @@ -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 THEN + +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;;]".. + "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 = "" 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 diff --git a/icta_controller/signaltower.lua b/icta_controller/signaltower.lua new file mode 100644 index 0000000..58efa4a --- /dev/null +++ b/icta_controller/signaltower.lua @@ -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, +}) diff --git a/icta_controller/stopwatch.lua b/icta_controller/stopwatch.lua new file mode 100644 index 0000000..e83364f --- /dev/null +++ b/icta_controller/stopwatch.lua @@ -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 "" + end + return "" +end + +-- env = { +-- event = , +-- last_event = -- last event time +-- ticks = , +-- 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 +-- number = , +-- owner = , +-- }, +-- +-- 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, +}) diff --git a/icta_controller/submenu.lua b/icta_controller/submenu.lua new file mode 100644 index 0000000..b637013 --- /dev/null +++ b/icta_controller/submenu.lua @@ -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 + diff --git a/init.lua b/init.lua index b3f6336..3abc958 100644 --- a/init.lua +++ b/init.lua @@ -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") diff --git a/items/electronic.lua b/items/electronic.lua new file mode 100644 index 0000000..a9d3343 --- /dev/null +++ b/items/electronic.lua @@ -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"} +}) diff --git a/logic/player_detector.lua b/logic/player_detector.lua index c9b1128..305cda8 100644 --- a/logic/player_detector.lua +++ b/logic/player_detector.lua @@ -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 "" diff --git a/mod.conf b/mod.conf index 4ffed44..3ea051f 100644 --- a/mod.conf +++ b/mod.conf @@ -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! \ No newline at end of file diff --git a/textures/techage_battery_green.png b/textures/techage_battery_green.png new file mode 100644 index 0000000..b66821f Binary files /dev/null and b/textures/techage_battery_green.png differ diff --git a/textures/techage_battery_inventory.png b/textures/techage_battery_inventory.png new file mode 100644 index 0000000..c713955 Binary files /dev/null and b/textures/techage_battery_inventory.png differ diff --git a/textures/techage_battery_red.png b/textures/techage_battery_red.png new file mode 100644 index 0000000..fb194d9 Binary files /dev/null and b/textures/techage_battery_red.png differ diff --git a/textures/techage_display.png b/textures/techage_display.png new file mode 100644 index 0000000..9e9e3e7 Binary files /dev/null and b/textures/techage_display.png differ diff --git a/textures/techage_display_inventory.png b/textures/techage_display_inventory.png new file mode 100644 index 0000000..58b11be Binary files /dev/null and b/textures/techage_display_inventory.png differ diff --git a/textures/techage_ramchip.png b/textures/techage_ramchip.png new file mode 100644 index 0000000..36d0c89 Binary files /dev/null and b/textures/techage_ramchip.png differ diff --git a/textures/techage_signaltower.png b/textures/techage_signaltower.png new file mode 100644 index 0000000..6fe09bb Binary files /dev/null and b/textures/techage_signaltower.png differ diff --git a/textures/techage_signaltower_amber.png b/textures/techage_signaltower_amber.png new file mode 100644 index 0000000..74c6473 Binary files /dev/null and b/textures/techage_signaltower_amber.png differ diff --git a/textures/techage_signaltower_green.png b/textures/techage_signaltower_green.png new file mode 100644 index 0000000..c75409e Binary files /dev/null and b/textures/techage_signaltower_green.png differ diff --git a/textures/techage_signaltower_red.png b/textures/techage_signaltower_red.png new file mode 100644 index 0000000..2835266 Binary files /dev/null and b/textures/techage_signaltower_red.png differ diff --git a/textures/techage_signaltower_top.png b/textures/techage_signaltower_top.png new file mode 100644 index 0000000..7c9ed8d Binary files /dev/null and b/textures/techage_signaltower_top.png differ diff --git a/textures/techage_smartline.png b/textures/techage_smartline.png new file mode 100644 index 0000000..471aee3 Binary files /dev/null and b/textures/techage_smartline.png differ diff --git a/textures/techage_ta4_controller.png b/textures/techage_ta4_controller.png new file mode 100644 index 0000000..a1e5932 Binary files /dev/null and b/textures/techage_ta4_controller.png differ diff --git a/textures/techage_ta4_controller_inventory.png b/textures/techage_ta4_controller_inventory.png new file mode 100644 index 0000000..b36bdae Binary files /dev/null and b/textures/techage_ta4_controller_inventory.png differ