From 7a4826e77d3cfb89e4132e4178bf4fc7dca1bd33 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sun, 20 Sep 2020 20:39:02 +0300 Subject: [PATCH 01/20] Move basic global network funcs to network.lua --- technic/machines/init.lua | 2 + technic/machines/network.lua | 70 ++++++++++++++++++++++++++ technic/machines/switching_station.lua | 63 ----------------------- 3 files changed, 72 insertions(+), 63 deletions(-) create mode 100644 technic/machines/network.lua diff --git a/technic/machines/init.lua b/technic/machines/init.lua index 84b5c33..ba42a24 100644 --- a/technic/machines/init.lua +++ b/technic/machines/init.lua @@ -14,6 +14,8 @@ technic.digilines = { } } +dofile(path.."/network.lua") + dofile(path.."/register/init.lua") -- Tiers diff --git a/technic/machines/network.lua b/technic/machines/network.lua new file mode 100644 index 0000000..acaf377 --- /dev/null +++ b/technic/machines/network.lua @@ -0,0 +1,70 @@ +-- +-- Power network specific functions and data should live here +-- + +function technic.remove_network(network_id) + local cables = technic.cables + for pos_hash,cable_net_id in pairs(cables) do + if cable_net_id == network_id then + cables[pos_hash] = nil + end + end + technic.networks[network_id] = nil +end + +function technic.sw_pos2network(pos) + return pos and technic.cables[minetest.hash_node_position({x=pos.x,y=pos.y-1,z=pos.z})] +end + +function technic.pos2network(pos) + return pos and technic.cables[minetest.hash_node_position(pos)] +end + +function technic.network2pos(network_id) + return network_id and minetest.get_position_from_hash(network_id) +end + +function technic.network2sw_pos(network_id) + -- Return switching station position for network. + -- It is not guaranteed that position actually contains switching station. + local sw_pos = minetest.get_position_from_hash(network_id) + sw_pos.y = sw_pos.y + 1 + return sw_pos +end + +local node_timeout = {} + +function technic.get_timeout(tier, pos) + if node_timeout[tier] == nil then + -- it is normal that some multi tier nodes always drop here when checking all LV, MV and HV tiers + return 0 + end + return node_timeout[tier][minetest.hash_node_position(pos)] or 0 +end + +function technic.touch_node(tier, pos, timeout) + if node_timeout[tier] == nil then + -- this should get built up during registration + node_timeout[tier] = {} + end + node_timeout[tier][minetest.hash_node_position(pos)] = timeout or 2 +end + +-- +-- Technic power network administrative functions +-- + +technic.powerctrl_state = true + +minetest.register_chatcommand("powerctrl", { + params = "state", + description = "Enables or disables technic's switching station ABM", + privs = { basic_privs = true }, + func = function(name, state) + if state == "on" then + technic.powerctrl_state = true + else + technic.powerctrl_state = false + end + end +}) diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 83a4690..057b08a 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -227,54 +227,6 @@ local function traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_node end end -function technic.remove_network(network_id) - local cables = technic.cables - for pos_hash,cable_net_id in pairs(cables) do - if cable_net_id == network_id then - cables[pos_hash] = nil - end - end - technic.networks[network_id] = nil -end - -function technic.sw_pos2network(pos) - return pos and technic.cables[minetest.hash_node_position({x=pos.x,y=pos.y-1,z=pos.z})] -end - -function technic.pos2network(pos) - return pos and technic.cables[minetest.hash_node_position(pos)] -end - -function technic.network2pos(network_id) - return network_id and minetest.get_position_from_hash(network_id) -end - -function technic.network2sw_pos(network_id) - -- Return switching station position for network. - -- It is not guaranteed that position actually contains switching station. - local sw_pos = minetest.get_position_from_hash(network_id) - sw_pos.y = sw_pos.y + 1 - return sw_pos -end - -local node_timeout = {} - -function technic.get_timeout(tier, pos) - if node_timeout[tier] == nil then - -- it is normal that some multi tier nodes always drop here when checking all LV, MV and HV tiers - return 0 - end - return node_timeout[tier][minetest.hash_node_position(pos)] or 0 -end - -function technic.touch_node(tier, pos, timeout) - if node_timeout[tier] == nil then - -- this should get built up during registration - node_timeout[tier] = {} - end - node_timeout[tier][minetest.hash_node_position(pos)] = timeout or 2 -end - local function touch_nodes(list, tier) local touch_node = technic.touch_node for _, pos in ipairs(list) do @@ -324,21 +276,6 @@ end -- The action code for the switching station -- ----------------------------------------------- -technic.powerctrl_state = true - -minetest.register_chatcommand("powerctrl", { - params = "state", - description = "Enables or disables technic's switching station ABM", - privs = { basic_privs = true }, - func = function(name, state) - if state == "on" then - technic.powerctrl_state = true - else - technic.powerctrl_state = false - end - end -}) - -- Run all the nodes local function run_nodes(list, run_stage) for _, pos in ipairs(list) do From 79233d764a96d980add188263cf8d1bff9320b6a Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sun, 20 Sep 2020 23:07:33 +0300 Subject: [PATCH 02/20] Add busted unit testing for technic Fix test framework and luacheck, Add metadata cleanup ABM Added busted and contentdb badges --- .github/workflows/busted.yml | 18 + .luacheckrc | 4 + README.md | 2 + technic/machines/network.lua | 37 +- technic/machines/switching_station.lua | 2 - technic/spec/fixtures/minetest.cfg | 0 technic/spec/fixtures/minetest.lua | 73 ++ technic/spec/fixtures/minetest/game/misc.lua | 262 +++++++ .../spec/fixtures/minetest/misc_helpers.lua | 702 ++++++++++++++++++ technic/spec/fixtures/minetest/player.lua | 43 ++ technic/spec/fixtures/minetest/protection.lua | 18 + technic/spec/fixtures/network.lua | 5 + technic/spec/network_spec.lua | 326 ++++++++ technic/spec/test_helpers.lua | 26 + 14 files changed, 1512 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/busted.yml create mode 100644 technic/spec/fixtures/minetest.cfg create mode 100644 technic/spec/fixtures/minetest.lua create mode 100644 technic/spec/fixtures/minetest/game/misc.lua create mode 100644 technic/spec/fixtures/minetest/misc_helpers.lua create mode 100644 technic/spec/fixtures/minetest/player.lua create mode 100644 technic/spec/fixtures/minetest/protection.lua create mode 100644 technic/spec/fixtures/network.lua create mode 100644 technic/spec/network_spec.lua create mode 100644 technic/spec/test_helpers.lua diff --git a/.github/workflows/busted.yml b/.github/workflows/busted.yml new file mode 100644 index 0000000..d765392 --- /dev/null +++ b/.github/workflows/busted.yml @@ -0,0 +1,18 @@ +name: busted + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: apt + run: sudo apt-get install -y luarocks + - name: busted install + run: luarocks install --local busted + - name: busted run + working-directory: ./technic + run: $HOME/.luarocks/bin/busted diff --git a/.luacheckrc b/.luacheckrc index 5cfcb06..2b740cc 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,6 +1,10 @@ unused_args = false max_line_length = 999 +exclude_files = { + "**/spec/**", +} + globals = { "technic", "technic_cnc", "minetest", "wrench" } diff --git a/README.md b/README.md index 0eedd10..b80978f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Technic A mod for [minetest](http://www.minetest.net) ![integration-test](https://github.com/mt-mods/technic/workflows/integration-test/badge.svg) +![busted](https://github.com/mt-mods/technic/workflows/busted/badge.svg) ![luacheck](https://github.com/mt-mods/technic/workflows/luacheck/badge.svg) + [![License](https://img.shields.io/badge/license-LGPLv2.0%2B-purple.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.en.html) [![ContentDB](https://content.minetest.net/packages/mt-mods/technic_plus/shields/downloads/)](https://content.minetest.net/packages/mt-mods/technic_plus/) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index acaf377..fa3d664 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -1,6 +1,9 @@ --- +-- -- Power network specific functions and data should live here --- +-- + +technic.networks = {} +technic.cables = {} function technic.remove_network(network_id) local cables = technic.cables @@ -50,9 +53,9 @@ function technic.touch_node(tier, pos, timeout) node_timeout[tier][minetest.hash_node_position(pos)] = timeout or 2 end --- +-- -- Technic power network administrative functions --- +-- technic.powerctrl_state = true @@ -68,3 +71,29 @@ minetest.register_chatcommand("powerctrl", { end end }) + +-- +-- Metadata cleanup LBM, removes old metadata values from nodes +-- +--luacheck: ignore 511 +if false then + minetest.register_lbm({ + name = "technic:metadata-cleanup", + nodenames = { + "group:technic_machine", + "group:technic_all_tiers", + }, + action = function(pos, node) + -- Delete all listed metadata key/value pairs from technic machines + local keys = { + "LV_EU_timeout", "MV_EU_timeout", "HV_EU_timeout", + "LV_network", "MV_network", "HV_network", + } + local meta = minetest.get_meta(pos) + for _,key in ipairs(keys) do + -- Value of `""` will delete the key. + meta:set_string(key, "") + end + end, + }) +end diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 057b08a..e2043f4 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -1,7 +1,5 @@ -- See also technic/doc/api.md -technic.networks = {} -technic.cables = {} technic.redundant_warn = {} local overload_reset_time = tonumber(minetest.settings:get("technic.overload_reset_time") or "20") diff --git a/technic/spec/fixtures/minetest.cfg b/technic/spec/fixtures/minetest.cfg new file mode 100644 index 0000000..e69de29 diff --git a/technic/spec/fixtures/minetest.lua b/technic/spec/fixtures/minetest.lua new file mode 100644 index 0000000..3c61be9 --- /dev/null +++ b/technic/spec/fixtures/minetest.lua @@ -0,0 +1,73 @@ +local function noop(...) end +local function dummy_coords(...) return { x = 123, y = 123, z = 123 } end + +_G.core = {} +_G.minetest = _G.core + +local configuration_file = fixture_path("minetest.cfg") +_G.Settings = function(fname) + local settings = { + _data = {}, + get = function(self, key) + return self._data[key] + end, + get_bool = function(self, key, default) + return + end, + set = function(...)end, + set_bool = function(...)end, + write = function(...)end, + remove = function(self, key) + self._data[key] = nil + return true + end, + get_names = function(self) + local result = {} + for k,_ in pairs(t) do + table.insert(result, k) + end + return result + end, + to_table = function(self) + local result = {} + for k,v in pairs(self._data) do + result[k] = v + end + return result + end, + } + -- Not even nearly perfect config parser but should be good enough for now + file = assert(io.open(fname, "r")) + for line in file:lines() do + for key, value in string.gmatch(line, "([^= ]+) *= *(.-)$") do + settings._data[key] = value + end + end + return settings +end +_G.core.settings = _G.Settings(configuration_file) + +_G.core.register_on_joinplayer = noop +_G.core.register_on_leaveplayer = noop + +fixture("minetest/game/misc") +fixture("minetest/misc_helpers") + +_G.minetest.registered_nodes = { + testnode1 = {}, + testnode2 = {}, +} + +_G.minetest.registered_chatcommands = {} + +_G.minetest.register_lbm = noop +_G.minetest.register_abm = noop +_G.minetest.register_chatcommand = noop +_G.minetest.chat_send_player = noop +_G.minetest.register_craftitem = noop +_G.minetest.register_craft = noop +_G.minetest.register_on_placenode = noop +_G.minetest.register_on_dignode = noop +_G.minetest.item_drop = noop + +_G.minetest.get_pointed_thing_position = dummy_coords diff --git a/technic/spec/fixtures/minetest/game/misc.lua b/technic/spec/fixtures/minetest/game/misc.lua new file mode 100644 index 0000000..341e613 --- /dev/null +++ b/technic/spec/fixtures/minetest/game/misc.lua @@ -0,0 +1,262 @@ +-- Minetest: builtin/misc.lua + +-- +-- Misc. API functions +-- + +function core.check_player_privs(name, ...) + if core.is_player(name) then + name = name:get_player_name() + elseif type(name) ~= "string" then + error("core.check_player_privs expects a player or playername as " .. + "argument.", 2) + end + + local requested_privs = {...} + local player_privs = core.get_player_privs(name) + local missing_privileges = {} + + if type(requested_privs[1]) == "table" then + -- We were provided with a table like { privA = true, privB = true }. + for priv, value in pairs(requested_privs[1]) do + if value and not player_privs[priv] then + missing_privileges[#missing_privileges + 1] = priv + end + end + else + -- Only a list, we can process it directly. + for key, priv in pairs(requested_privs) do + if not player_privs[priv] then + missing_privileges[#missing_privileges + 1] = priv + end + end + end + + if #missing_privileges > 0 then + return false, missing_privileges + end + + return true, "" +end + + +function core.send_join_message(player_name) + if not core.is_singleplayer() then + core.chat_send_all("*** " .. player_name .. " joined the game.") + end +end + + +function core.send_leave_message(player_name, timed_out) + local announcement = "*** " .. player_name .. " left the game." + if timed_out then + announcement = announcement .. " (timed out)" + end + core.chat_send_all(announcement) +end + + +core.register_on_joinplayer(function(player) + local player_name = player:get_player_name() + if not core.is_singleplayer() then + local status = core.get_server_status(player_name, true) + if status and status ~= "" then + core.chat_send_player(player_name, status) + end + end + core.send_join_message(player_name) +end) + + +core.register_on_leaveplayer(function(player, timed_out) + local player_name = player:get_player_name() + core.send_leave_message(player_name, timed_out) +end) + + +function core.is_player(player) + -- a table being a player is also supported because it quacks sufficiently + -- like a player if it has the is_player function + local t = type(player) + return (t == "userdata" or t == "table") and + type(player.is_player) == "function" and player:is_player() +end + + +function core.player_exists(name) + return core.get_auth_handler().get_auth(name) ~= nil +end + + +-- Returns two position vectors representing a box of `radius` in each +-- direction centered around the player corresponding to `player_name` + +function core.get_player_radius_area(player_name, radius) + local player = core.get_player_by_name(player_name) + if player == nil then + return nil + end + + local p1 = player:get_pos() + local p2 = p1 + + if radius then + p1 = vector.subtract(p1, radius) + p2 = vector.add(p2, radius) + end + + return p1, p2 +end + + +function core.hash_node_position(pos) + return (pos.z + 32768) * 65536 * 65536 + + (pos.y + 32768) * 65536 + + pos.x + 32768 +end + + +function core.get_position_from_hash(hash) + local pos = {} + pos.x = (hash % 65536) - 32768 + hash = math.floor(hash / 65536) + pos.y = (hash % 65536) - 32768 + hash = math.floor(hash / 65536) + pos.z = (hash % 65536) - 32768 + return pos +end + + +function core.get_item_group(name, group) + if not core.registered_items[name] or not + core.registered_items[name].groups[group] then + return 0 + end + return core.registered_items[name].groups[group] +end + + +function core.get_node_group(name, group) + core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead") + return core.get_item_group(name, group) +end + + +function core.setting_get_pos(name) + local value = core.settings:get(name) + if not value then + return nil + end + return core.string_to_pos(value) +end + + +-- To be overriden by protection mods + +function core.is_protected(pos, name) + return false +end + + +function core.record_protection_violation(pos, name) + for _, func in pairs(core.registered_on_protection_violation) do + func(pos, name) + end +end + +-- To be overridden by Creative mods + +local creative_mode_cache = core.settings:get_bool("creative_mode") +function core.is_creative_enabled(name) + return creative_mode_cache +end + +-- Checks if specified volume intersects a protected volume + +function core.is_area_protected(minp, maxp, player_name, interval) + -- 'interval' is the largest allowed interval for the 3D lattice of checks. + + -- Compute the optimal float step 'd' for each axis so that all corners and + -- borders are checked. 'd' will be smaller or equal to 'interval'. + -- Subtracting 1e-4 ensures that the max co-ordinate will be reached by the + -- for loop (which might otherwise not be the case due to rounding errors). + + -- Default to 4 + interval = interval or 4 + local d = {} + + for _, c in pairs({"x", "y", "z"}) do + if minp[c] > maxp[c] then + -- Repair positions: 'minp' > 'maxp' + local tmp = maxp[c] + maxp[c] = minp[c] + minp[c] = tmp + end + + if maxp[c] > minp[c] then + d[c] = (maxp[c] - minp[c]) / + math.ceil((maxp[c] - minp[c]) / interval) - 1e-4 + else + d[c] = 1 -- Any value larger than 0 to avoid division by zero + end + end + + for zf = minp.z, maxp.z, d.z do + local z = math.floor(zf + 0.5) + for yf = minp.y, maxp.y, d.y do + local y = math.floor(yf + 0.5) + for xf = minp.x, maxp.x, d.x do + local x = math.floor(xf + 0.5) + local pos = {x = x, y = y, z = z} + if core.is_protected(pos, player_name) then + return pos + end + end + end + end + return false +end + + +local raillike_ids = {} +local raillike_cur_id = 0 +function core.raillike_group(name) + local id = raillike_ids[name] + if not id then + raillike_cur_id = raillike_cur_id + 1 + raillike_ids[name] = raillike_cur_id + id = raillike_cur_id + end + return id +end + + +-- HTTP callback interface + +function core.http_add_fetch(httpenv) + httpenv.fetch = function(req, callback) + local handle = httpenv.fetch_async(req) + + local function update_http_status() + local res = httpenv.fetch_async_get(handle) + if res.completed then + callback(res) + else + core.after(0, update_http_status) + end + end + core.after(0, update_http_status) + end + + return httpenv +end + + +function core.close_formspec(player_name, formname) + return core.show_formspec(player_name, formname, "") +end + + +function core.cancel_shutdown_requests() + core.request_shutdown("", false, -1) +end diff --git a/technic/spec/fixtures/minetest/misc_helpers.lua b/technic/spec/fixtures/minetest/misc_helpers.lua new file mode 100644 index 0000000..715f89b --- /dev/null +++ b/technic/spec/fixtures/minetest/misc_helpers.lua @@ -0,0 +1,702 @@ +-- Minetest: builtin/misc_helpers.lua + +-------------------------------------------------------------------------------- +-- Localize functions to avoid table lookups (better performance). +local string_sub, string_find = string.sub, string.find + +-------------------------------------------------------------------------------- +local function basic_dump(o) + local tp = type(o) + if tp == "number" then + return tostring(o) + elseif tp == "string" then + return string.format("%q", o) + elseif tp == "boolean" then + return tostring(o) + elseif tp == "nil" then + return "nil" + -- Uncomment for full function dumping support. + -- Not currently enabled because bytecode isn't very human-readable and + -- dump's output is intended for humans. + --elseif tp == "function" then + -- return string.format("loadstring(%q)", string.dump(o)) + else + return string.format("<%s>", tp) + end +end + +local keywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, -- Lua 5.2 + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, +} +local function is_valid_identifier(str) + if not str:find("^[a-zA-Z_][a-zA-Z0-9_]*$") or keywords[str] then + return false + end + return true +end + +-------------------------------------------------------------------------------- +-- Dumps values in a line-per-value format. +-- For example, {test = {"Testing..."}} becomes: +-- _["test"] = {} +-- _["test"][1] = "Testing..." +-- This handles tables as keys and circular references properly. +-- It also handles multiple references well, writing the table only once. +-- The dumped argument is internal-only. + +function dump2(o, name, dumped) + name = name or "_" + -- "dumped" is used to keep track of serialized tables to handle + -- multiple references and circular tables properly. + -- It only contains tables as keys. The value is the name that + -- the table has in the dump, eg: + -- {x = {"y"}} -> dumped[{"y"}] = '_["x"]' + dumped = dumped or {} + if type(o) ~= "table" then + return string.format("%s = %s\n", name, basic_dump(o)) + end + if dumped[o] then + return string.format("%s = %s\n", name, dumped[o]) + end + dumped[o] = name + -- This contains a list of strings to be concatenated later (because + -- Lua is slow at individual concatenation). + local t = {} + for k, v in pairs(o) do + local keyStr + if type(k) == "table" then + if dumped[k] then + keyStr = dumped[k] + else + -- Key tables don't have a name, so use one of + -- the form _G["table: 0xFFFFFFF"] + keyStr = string.format("_G[%q]", tostring(k)) + -- Dump key table + t[#t + 1] = dump2(k, keyStr, dumped) + end + else + keyStr = basic_dump(k) + end + local vname = string.format("%s[%s]", name, keyStr) + t[#t + 1] = dump2(v, vname, dumped) + end + return string.format("%s = {}\n%s", name, table.concat(t)) +end + +-------------------------------------------------------------------------------- +-- This dumps values in a one-statement format. +-- For example, {test = {"Testing..."}} becomes: +-- [[{ +-- test = { +-- "Testing..." +-- } +-- }]] +-- This supports tables as keys, but not circular references. +-- It performs poorly with multiple references as it writes out the full +-- table each time. +-- The indent field specifies a indentation string, it defaults to a tab. +-- Use the empty string to disable indentation. +-- The dumped and level arguments are internal-only. + +function dump(o, indent, nested, level) + local t = type(o) + if not level and t == "userdata" then + -- when userdata (e.g. player) is passed directly, print its metatable: + return "userdata metatable: " .. dump(getmetatable(o)) + end + if t ~= "table" then + return basic_dump(o) + end + + -- Contains table -> true/nil of currently nested tables + nested = nested or {} + if nested[o] then + return "" + end + nested[o] = true + indent = indent or "\t" + level = level or 1 + + local ret = {} + local dumped_indexes = {} + for i, v in ipairs(o) do + ret[#ret + 1] = dump(v, indent, nested, level + 1) + dumped_indexes[i] = true + end + for k, v in pairs(o) do + if not dumped_indexes[k] then + if type(k) ~= "string" or not is_valid_identifier(k) then + k = "["..dump(k, indent, nested, level + 1).."]" + end + v = dump(v, indent, nested, level + 1) + ret[#ret + 1] = k.." = "..v + end + end + nested[o] = nil + if indent ~= "" then + local indent_str = "\n"..string.rep(indent, level) + local end_indent_str = "\n"..string.rep(indent, level - 1) + return string.format("{%s%s%s}", + indent_str, + table.concat(ret, ","..indent_str), + end_indent_str) + end + return "{"..table.concat(ret, ", ").."}" +end + +-------------------------------------------------------------------------------- +function string.split(str, delim, include_empty, max_splits, sep_is_pattern) + delim = delim or "," + max_splits = max_splits or -2 + local items = {} + local pos, len = 1, #str + local plain = not sep_is_pattern + max_splits = max_splits + 1 + repeat + local np, npe = string_find(str, delim, pos, plain) + np, npe = (np or (len+1)), (npe or (len+1)) + if (not np) or (max_splits == 1) then + np = len + 1 + npe = np + end + local s = string_sub(str, pos, np - 1) + if include_empty or (s ~= "") then + max_splits = max_splits - 1 + items[#items + 1] = s + end + pos = npe + 1 + until (max_splits == 0) or (pos > (len + 1)) + return items +end + +-------------------------------------------------------------------------------- +function table.indexof(list, val) + for i, v in ipairs(list) do + if v == val then + return i + end + end + return -1 +end + +-------------------------------------------------------------------------------- +function string:trim() + return (self:gsub("^%s*(.-)%s*$", "%1")) +end + +-------------------------------------------------------------------------------- +function math.hypot(x, y) + local t + x = math.abs(x) + y = math.abs(y) + t = math.min(x, y) + x = math.max(x, y) + if x == 0 then return 0 end + t = t / x + return x * math.sqrt(1 + t * t) +end + +-------------------------------------------------------------------------------- +function math.sign(x, tolerance) + tolerance = tolerance or 0 + if x > tolerance then + return 1 + elseif x < -tolerance then + return -1 + end + return 0 +end + +-------------------------------------------------------------------------------- +function math.factorial(x) + assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer") + if x >= 171 then + -- 171! is greater than the biggest double, no need to calculate + return math.huge + end + local v = 1 + for k = 2, x do + v = v * k + end + return v +end + +function core.formspec_escape(text) + if text ~= nil then + text = string.gsub(text,"\\","\\\\") + text = string.gsub(text,"%]","\\]") + text = string.gsub(text,"%[","\\[") + text = string.gsub(text,";","\\;") + text = string.gsub(text,",","\\,") + end + return text +end + + +function core.wrap_text(text, max_length, as_table) + local result = {} + local line = {} + if #text <= max_length then + return as_table and {text} or text + end + + for word in text:gmatch('%S+') do + local cur_length = #table.concat(line, ' ') + if cur_length > 0 and cur_length + #word + 1 >= max_length then + -- word wouldn't fit on current line, move to next line + table.insert(result, table.concat(line, ' ')) + line = {} + end + table.insert(line, word) + end + + table.insert(result, table.concat(line, ' ')) + return as_table and result or table.concat(result, '\n') +end + +-------------------------------------------------------------------------------- + +if INIT == "game" then + local dirs1 = {9, 18, 7, 12} + local dirs2 = {20, 23, 22, 21} + + function core.rotate_and_place(itemstack, placer, pointed_thing, + infinitestacks, orient_flags, prevent_after_place) + orient_flags = orient_flags or {} + + local unode = core.get_node_or_nil(pointed_thing.under) + if not unode then + return + end + local undef = core.registered_nodes[unode.name] + if undef and undef.on_rightclick then + return undef.on_rightclick(pointed_thing.under, unode, placer, + itemstack, pointed_thing) + end + local fdir = placer and core.dir_to_facedir(placer:get_look_dir()) or 0 + + local above = pointed_thing.above + local under = pointed_thing.under + local iswall = (above.y == under.y) + local isceiling = not iswall and (above.y < under.y) + + if undef and undef.buildable_to then + iswall = false + end + + if orient_flags.force_floor then + iswall = false + isceiling = false + elseif orient_flags.force_ceiling then + iswall = false + isceiling = true + elseif orient_flags.force_wall then + iswall = true + isceiling = false + elseif orient_flags.invert_wall then + iswall = not iswall + end + + local param2 = fdir + if iswall then + param2 = dirs1[fdir + 1] + elseif isceiling then + if orient_flags.force_facedir then + param2 = 20 + else + param2 = dirs2[fdir + 1] + end + else -- place right side up + if orient_flags.force_facedir then + param2 = 0 + end + end + + local old_itemstack = ItemStack(itemstack) + local new_itemstack = core.item_place_node(itemstack, placer, + pointed_thing, param2, prevent_after_place) + return infinitestacks and old_itemstack or new_itemstack + end + + +-------------------------------------------------------------------------------- +--Wrapper for rotate_and_place() to check for sneak and assume Creative mode +--implies infinite stacks when performing a 6d rotation. +-------------------------------------------------------------------------------- + local creative_mode_cache = core.settings:get_bool("creative_mode") + local function is_creative(name) + return creative_mode_cache or + core.check_player_privs(name, {creative = true}) + end + + core.rotate_node = function(itemstack, placer, pointed_thing) + local name = placer and placer:get_player_name() or "" + local invert_wall = placer and placer:get_player_control().sneak or false + return core.rotate_and_place(itemstack, placer, pointed_thing, + is_creative(name), + {invert_wall = invert_wall}, true) + end +end + +-------------------------------------------------------------------------------- +function core.explode_table_event(evt) + if evt ~= nil then + local parts = evt:split(":") + if #parts == 3 then + local t = parts[1]:trim() + local r = tonumber(parts[2]:trim()) + local c = tonumber(parts[3]:trim()) + if type(r) == "number" and type(c) == "number" + and t ~= "INV" then + return {type=t, row=r, column=c} + end + end + end + return {type="INV", row=0, column=0} +end + +-------------------------------------------------------------------------------- +function core.explode_textlist_event(evt) + if evt ~= nil then + local parts = evt:split(":") + if #parts == 2 then + local t = parts[1]:trim() + local r = tonumber(parts[2]:trim()) + if type(r) == "number" and t ~= "INV" then + return {type=t, index=r} + end + end + end + return {type="INV", index=0} +end + +-------------------------------------------------------------------------------- +function core.explode_scrollbar_event(evt) + local retval = core.explode_textlist_event(evt) + + retval.value = retval.index + retval.index = nil + + return retval +end + +-------------------------------------------------------------------------------- +function core.rgba(r, g, b, a) + return a and string.format("#%02X%02X%02X%02X", r, g, b, a) or + string.format("#%02X%02X%02X", r, g, b) +end + +-------------------------------------------------------------------------------- +function core.pos_to_string(pos, decimal_places) + local x = pos.x + local y = pos.y + local z = pos.z + if decimal_places ~= nil then + x = string.format("%." .. decimal_places .. "f", x) + y = string.format("%." .. decimal_places .. "f", y) + z = string.format("%." .. decimal_places .. "f", z) + end + return "(" .. x .. "," .. y .. "," .. z .. ")" +end + +-------------------------------------------------------------------------------- +function core.string_to_pos(value) + if value == nil then + return nil + end + + local p = {} + p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + p = {} + p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + return nil +end + + +-------------------------------------------------------------------------------- +function core.string_to_area(value) + local p1, p2 = unpack(value:split(") (")) + if p1 == nil or p2 == nil then + return nil + end + + p1 = core.string_to_pos(p1 .. ")") + p2 = core.string_to_pos("(" .. p2) + if p1 == nil or p2 == nil then + return nil + end + + return p1, p2 +end + +local function test_string_to_area() + local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)") + assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2) + assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53) + + p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53") + assert(p1 == nil and p2 == nil) + + p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53") + assert(p1 == nil and p2 == nil) +end + +test_string_to_area() + +-------------------------------------------------------------------------------- +function table.copy(t, seen) + local n = {} + seen = seen or {} + seen[t] = n + for k, v in pairs(t) do + n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] = + (type(v) == "table" and (seen[v] or table.copy(v, seen))) or v + end + return n +end + + +function table.insert_all(t, other) + for i=1, #other do + t[#t + 1] = other[i] + end + return t +end + + +function table.key_value_swap(t) + local ti = {} + for k,v in pairs(t) do + ti[v] = k + end + return ti +end + + +function table.shuffle(t, from, to, random) + from = from or 1 + to = to or #t + random = random or math.random + local n = to - from + 1 + while n > 1 do + local r = from + n-1 + local l = from + random(0, n-1) + t[l], t[r] = t[r], t[l] + n = n-1 + end +end + + +-------------------------------------------------------------------------------- +-- mainmenu only functions +-------------------------------------------------------------------------------- +if INIT == "mainmenu" then + function core.get_game(index) + local games = core.get_games() + + if index > 0 and index <= #games then + return games[index] + end + + return nil + end +end + +if INIT == "client" or INIT == "mainmenu" then + function fgettext_ne(text, ...) + text = core.gettext(text) + local arg = {n=select('#', ...), ...} + if arg.n >= 1 then + -- Insert positional parameters ($1, $2, ...) + local result = '' + local pos = 1 + while pos <= text:len() do + local newpos = text:find('[$]', pos) + if newpos == nil then + result = result .. text:sub(pos) + pos = text:len() + 1 + else + local paramindex = + tonumber(text:sub(newpos+1, newpos+1)) + result = result .. text:sub(pos, newpos-1) + .. tostring(arg[paramindex]) + pos = newpos + 2 + end + end + text = result + end + return text + end + + function fgettext(text, ...) + return core.formspec_escape(fgettext_ne(text, ...)) + end +end + +local ESCAPE_CHAR = string.char(0x1b) + +function core.get_color_escape_sequence(color) + return ESCAPE_CHAR .. "(c@" .. color .. ")" +end + +function core.get_background_escape_sequence(color) + return ESCAPE_CHAR .. "(b@" .. color .. ")" +end + +function core.colorize(color, message) + local lines = tostring(message):split("\n", true) + local color_code = core.get_color_escape_sequence(color) + + for i, line in ipairs(lines) do + lines[i] = color_code .. line + end + + return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff") +end + + +function core.strip_foreground_colors(str) + return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", "")) +end + +function core.strip_background_colors(str) + return (str:gsub(ESCAPE_CHAR .. "%(b@[^)]+%)", "")) +end + +function core.strip_colors(str) + return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", "")) +end + +function core.translate(textdomain, str, ...) + local start_seq + if textdomain == "" then + start_seq = ESCAPE_CHAR .. "T" + else + start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")" + end + local arg = {n=select('#', ...), ...} + local end_seq = ESCAPE_CHAR .. "E" + local arg_index = 1 + local translated = str:gsub("@(.)", function(matched) + local c = string.byte(matched) + if string.byte("1") <= c and c <= string.byte("9") then + local a = c - string.byte("0") + if a ~= arg_index then + error("Escape sequences in string given to core.translate " .. + "are not in the correct order: got @" .. matched .. + "but expected @" .. tostring(arg_index)) + end + if a > arg.n then + error("Not enough arguments provided to core.translate") + end + arg_index = arg_index + 1 + return ESCAPE_CHAR .. "F" .. arg[a] .. ESCAPE_CHAR .. "E" + elseif matched == "n" then + return "\n" + else + return matched + end + end) + if arg_index < arg.n + 1 then + error("Too many arguments provided to core.translate") + end + return start_seq .. translated .. end_seq +end + +function core.get_translator(textdomain) + return function(str, ...) return core.translate(textdomain or "", str, ...) end +end + +-------------------------------------------------------------------------------- +-- Returns the exact coordinate of a pointed surface +-------------------------------------------------------------------------------- +function core.pointed_thing_to_face_pos(placer, pointed_thing) + -- Avoid crash in some situations when player is inside a node, causing + -- 'above' to equal 'under'. + if vector.equals(pointed_thing.above, pointed_thing.under) then + return pointed_thing.under + end + + local eye_height = placer:get_properties().eye_height + local eye_offset_first = placer:get_eye_offset() + local node_pos = pointed_thing.under + local camera_pos = placer:get_pos() + local pos_off = vector.multiply( + vector.subtract(pointed_thing.above, node_pos), 0.5) + local look_dir = placer:get_look_dir() + local offset, nc + local oc = {} + + for c, v in pairs(pos_off) do + if nc or v == 0 then + oc[#oc + 1] = c + else + offset = v + nc = c + end + end + + local fine_pos = {[nc] = node_pos[nc] + offset} + camera_pos.y = camera_pos.y + eye_height + eye_offset_first.y / 10 + local f = (node_pos[nc] + offset - camera_pos[nc]) / look_dir[nc] + + for i = 1, #oc do + fine_pos[oc[i]] = camera_pos[oc[i]] + look_dir[oc[i]] * f + end + return fine_pos +end + +function core.string_to_privs(str, delim) + assert(type(str) == "string") + delim = delim or ',' + local privs = {} + for _, priv in pairs(string.split(str, delim)) do + privs[priv:trim()] = true + end + return privs +end + +function core.privs_to_string(privs, delim) + assert(type(privs) == "table") + delim = delim or ',' + local list = {} + for priv, bool in pairs(privs) do + if bool then + list[#list + 1] = priv + end + end + return table.concat(list, delim) +end diff --git a/technic/spec/fixtures/minetest/player.lua b/technic/spec/fixtures/minetest/player.lua new file mode 100644 index 0000000..72a6313 --- /dev/null +++ b/technic/spec/fixtures/minetest/player.lua @@ -0,0 +1,43 @@ + +fixture("minetest") + +local players = {} + +_G.minetest.check_player_privs = function(player_or_name, ...) + local player_privs + if type(player_or_name) == "table" then + player_privs = player_or_name._privs + else + player_privs = players[player_or_name]._privs + end + local missing_privs = {} + local has_priv = false + local arg={...} + for _,priv in ipairs(arg) do + if player_privs[priv] then + has_priv = true + else + table.insert(missing_privs, priv) + end + end + return has_priv, missing_privs +end + +_G.minetest.get_player_by_name = function(name) + return players[name] +end + +_G.Player = function(name, privs) + local player = { + _name = name or "SX", + _privs = privs or { test_priv=1 }, + get_player_control = function(self) + return {} + end, + get_player_name = function(self) + return self._name + end + } + table.insert(players, player) + return player +end diff --git a/technic/spec/fixtures/minetest/protection.lua b/technic/spec/fixtures/minetest/protection.lua new file mode 100644 index 0000000..4246ff9 --- /dev/null +++ b/technic/spec/fixtures/minetest/protection.lua @@ -0,0 +1,18 @@ + +fixture("minetest") + +_G.ProtectedPos = function() + return { x = 123, y = 123, z = 123 } +end + +_G.UnprotectedPos = function() + return { x = -123, y = -123, z = -123 } +end + +minetest.is_protected = function(pos, name) + return pos.x == 123 and pos.y == 123 and pos.z == 123 +end + +minetest.record_protection_violation = function(pos, name) + -- noop +end diff --git a/technic/spec/fixtures/network.lua b/technic/spec/fixtures/network.lua new file mode 100644 index 0000000..7f1af94 --- /dev/null +++ b/technic/spec/fixtures/network.lua @@ -0,0 +1,5 @@ + +_G.technic = {} +_G.technic.S = string.format + +sourcefile("register") diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua new file mode 100644 index 0000000..ffe1c23 --- /dev/null +++ b/technic/spec/network_spec.lua @@ -0,0 +1,326 @@ +dofile("spec/test_helpers.lua") +--[[ + Technic network unit tests. + Execute busted at technic source directory. +--]] + +-- Load fixtures required by tests +fixture("minetest") +fixture("minetest/player") +fixture("minetest/protection") + +fixture("network") + +sourcefile("machines/network") + +describe("Power network helper", function() + + -- Simple network position fixtures + local net_id = 65536 + local pos = { x = -32768, y = -32767, z = -32768 } + local sw_pos = { x = -32768, y = -32766, z = -32768 } + + describe("network lookup functions", function() + + it("does not fail if network missing", function() + assert.is_nil( technic.remove_network(9999) ) + end) + + it("returns correct position for network", function() + assert.same(pos, technic.network2pos(net_id) ) + assert.same(sw_pos, technic.network2sw_pos(net_id) ) + end) + + it("returns correct network for position", function() + pending("TODO: Test requires real network fixture") + assert.same(net_id, technic.pos2network(pos) ) + assert.same(net_id, technic.sw_pos2network(sw_pos) ) + end) + + end) + + --[[ TODO: + technic.remove_network(network_id) + technic.pos2network(pos) + technic.network2pos(network_id) + technic.network2sw_pos(network_id) + --]] + + describe("Power network timeout functions technic.touch_node and technic.get_timeout", function() + + it("returns zero if no data available", function() + assert.equals(0, + technic.get_timeout("LV", {x=9999,y=9999,z=9999}) + ) + assert.equals(0, + technic.get_timeout("HV", {x=9999,y=9999,z=9999}) + ) + end) + + it("returns timeout if data is available", function() + technic.touch_node("LV", {x=123,y=123,z=123}, 42) + assert.equals(42, + technic.get_timeout("LV", {x=123,y=123,z=123}) + ) + technic.touch_node("HV", {x=123,y=123,z=123}, 74) + assert.equals(74, + technic.get_timeout("HV", {x=123,y=123,z=123}) + ) + end) + + end) + +end) + +-- Clean up, left following here just for easy copy pasting stuff from previous proj + +--[[ +describe("Metatool API protection", function() + + it("metatool.is_protected bypass privileges", function() + local value = metatool.is_protected(ProtectedPos(), Player(), "test_priv", true) + assert.equals(false, value) + end) + + it("metatool.is_protected no bypass privileges", function() + local value = metatool.is_protected(ProtectedPos(), Player(), "test_priv2", true) + assert.equals(true, value) + end) + + it("metatool.is_protected bypass privileges, unprotected", function() + local value = metatool.is_protected(UnprotectedPos(), Player(), "test_priv", true) + assert.equals(false, value) + end) + + it("metatool.is_protected no bypass privileges, unprotected", function() + local value = metatool.is_protected(UnprotectedPos(), Player(), "test_priv2", true) + assert.equals(false, value) + end) + +end) + +describe("Metatool API tool namespace", function() + + it("Create invalid namespace", function() + local tool = { ns = metatool.ns, name = 'invalid' } + local value = tool:ns("invalid", { + testkey = "testvalue" + }) + assert.is_nil(metatool:ns("testns")) + end) + + it("Get nonexistent namespace", function() + assert.is_nil(metatool.ns("nonexistent")) + end) + + it("Create tool namespace", function() + -- FIXME: Hack to get fake tool available, replace with real tool + local tool = { ns = metatool.ns, name = 'mytool' } + metatool.tools["metatool:mytool"] = tool + -- Actual tests + local value = tool:ns({ + testkey = "testvalue" + }) + local expected = { + testkey = "testvalue" + } + assert.same(expected, metatool.ns("mytool")) + end) + +end) + +describe("Metatool API tool registration", function() + + it("Register tool default configuration", function() + -- Tool registration + local definition = { + description = 'UnitTestTool Description', + name = 'UnitTestTool', + texture = 'utt.png', + recipe = {{'air'},{'air'},{'air'}}, + on_read_node = function(tooldef, player, pointed_thing, node, pos) + local data, group = tooldef:copy(node, pos, player) + return data, group, "on_read_node description" + end, + on_write_node = function(tooldef, data, group, player, pointed_thing, node, pos) + tooldef:paste(node, pos, player, data, group) + end, + } + local tool = metatool:register_tool('testtool0', definition) + + assert.is_table(tool) + assert.equals("metatool:testtool0", tool.name) + + assert.is_table(tool) + assert.equals(definition.description, tool.description) + assert.equals(definition.name, tool.nice_name) + assert.equals(definition.on_read_node, tool.on_read_node) + assert.equals(definition.on_write_node, tool.on_write_node) + + -- Test configurable tool attributes + assert.is_nil(tool.privs) + assert.same({}, tool.settings) + + -- Namespace creation + local mult = function(a,b) return a * b end + tool:ns({ k1 = "v1", fn = mult }) + + -- Retrieve namespace and and execute tests + local ns = metatool.ns("testtool0") + assert.same({ k1 = "v1", fn = mult }, ns) + assert.equals(8, ns.fn(2,4)) + end) + + it("Register tool with configuration", function() + -- Tool registration + local definition = { + description = 'UnitTestTool Description', + name = 'UnitTestTool', + texture = 'utt.png', + recipe = {{'air'},{'air'},{'air'}}, + on_read_node = function(tooldef, player, pointed_thing, node, pos) + local data, group = tooldef:copy(node, pos, player) + return data, group, "on_read_node description" + end, + on_write_node = function(tooldef, data, group, player, pointed_thing, node, pos) + tooldef:paste(node, pos, player, data, group) + end, + } + local tool = metatool:register_tool('testtool2', definition) + + assert.is_table(tool) + assert.equals("metatool:testtool2", tool.name) + + assert.is_table(tool) + assert.equals(definition.description, tool.description) + assert.equals(definition.name, tool.nice_name) + assert.equals(definition.on_read_node, tool.on_read_node) + assert.equals(definition.on_write_node, tool.on_write_node) + + -- Test configurable tool attributes + assert.equals("test_testtool2_privs", tool.privs) + local expected_settings = { + extra_config_key = "testtool2_extra_config_value", + } + assert.same(expected_settings, tool.settings) + + -- Namespace creation + local sum = function(a,b) return a + b end + tool:ns({ k1 = "v1", fn = sum }) + + -- Retrieve namespace and and execute tests + local ns = metatool.ns("testtool2") + assert.same({ k1 = "v1", fn = sum }, ns) + assert.equals(9, ns.fn(2,7)) + end) + +end) + +describe("Metatool API node registration", function() + + it("Register node default configuration", function() + local tool = metatool.tool("testtool0") + assert.is_table(tool) + assert.equals("metatool:testtool0", tool.name) + assert.is_table(tool) + + local definition = { + name = 'testnode1', + nodes = { + "testnode1", + "nonexistent1", + "testnode2", + "nonexistent2", + }, + tooldef = { + group = 'test node', + protection_bypass_write = "default_bypass_write_priv", + copy = function(node, pos, player) + print("nodedef copy callback executed") + end, + paste = function(node, pos, player, data) + print("nodedef paste callback executed") + end, + } + } + tool:load_node_definition(definition) + + assert.is_table(tool.nodes) + assert.is_table(tool.nodes.testnode1) + assert.is_table(tool.nodes.testnode2) + assert.is_nil(tool.nodes.nonexistent1) + assert.is_nil(tool.nodes.nonexistent2) + + assert.is_function(tool.nodes.testnode1.before_read) + assert.is_function(tool.nodes.testnode2.before_write) + + assert.equals(definition.tooldef.copy, tool.nodes.testnode1.copy) + assert.equals(definition.tooldef.paste, tool.nodes.testnode2.paste) + assert.equals("default_bypass_write_priv", definition.tooldef.protection_bypass_write) + + local expected_settings = { + protection_bypass_write = "default_bypass_write_priv" + } + assert.same(expected_settings, tool.nodes.testnode1.settings) + assert.same(expected_settings, tool.nodes.testnode2.settings) + + end) + + it("Register node with configuration", function() + local tool = metatool.tool("testtool2") + assert.is_table(tool) + assert.equals("metatool:testtool2", tool.name) + assert.is_table(tool) + + local definition = { + name = 'testnode2', + nodes = { + "testnode1", + "nonexistent1", + "testnode2", + "nonexistent2", + }, + tooldef = { + group = 'test node', + protection_bypass_write = "default_bypass_write_priv", + copy = function(node, pos, player) + print("nodedef copy callback executed") + end, + paste = function(node, pos, player, data) + print("nodedef paste callback executed") + end, + } + } + tool:load_node_definition(definition) + + assert.is_table(tool.nodes) + assert.is_table(tool.nodes.testnode1) + assert.is_table(tool.nodes.testnode2) + assert.is_nil(tool.nodes.nonexistent1) + assert.is_nil(tool.nodes.nonexistent2) + + assert.is_function(tool.nodes.testnode1.before_read) + assert.is_function(tool.nodes.testnode2.before_write) + + assert.equals(definition.tooldef.copy, tool.nodes.testnode1.copy) + assert.equals(definition.tooldef.paste, tool.nodes.testnode2.paste) + assert.equals("testtool2_testnode2_bypass_write", tool.nodes.testnode1.protection_bypass_write) + assert.equals("testtool2_testnode2_bypass_write", tool.nodes.testnode2.protection_bypass_write) + assert.equals("testtool2_testnode2_bypass_info", tool.nodes.testnode1.protection_bypass_info) + assert.equals("testtool2_testnode2_bypass_info", tool.nodes.testnode2.protection_bypass_info) + assert.equals("testtool2_testnode2_bypass_read", tool.nodes.testnode1.protection_bypass_read) + assert.equals("testtool2_testnode2_bypass_read", tool.nodes.testnode2.protection_bypass_read) + + local expected_settings = { + protection_bypass_write = "testtool2_testnode2_bypass_write", + protection_bypass_info = "testtool2_testnode2_bypass_info", + protection_bypass_read = "testtool2_testnode2_bypass_read", + } + assert.same(expected_settings, tool.nodes.testnode1.settings) + assert.same(expected_settings, tool.nodes.testnode2.settings) + + end) + +end) + +--]] diff --git a/technic/spec/test_helpers.lua b/technic/spec/test_helpers.lua new file mode 100644 index 0000000..54a7800 --- /dev/null +++ b/technic/spec/test_helpers.lua @@ -0,0 +1,26 @@ + +package.path = "../?.lua;./?.lua;machines/?.lua;" .. package.path + +local _fixture_path = "spec/fixtures" + +function fixture_path(name) + return string.format("%s/%s", _fixture_path, name) +end + +local _fixtures = {} +function fixture(name) + if not _fixtures[name] then + dofile(fixture_path(name) .. ".lua") + end + _fixtures[name] = true +end + +local _source_path = "." + +function source_path(name) + return string.format("%s/%s", _source_path, name) +end + +function sourcefile(name) + dofile(source_path(name) .. ".lua") +end From a25957689404cd8b56542af802d7310011947846 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Wed, 23 Sep 2020 20:02:26 +0300 Subject: [PATCH 03/20] Network refactoring, proper multi switch support WIP Update network utils, globalstep use networks, update switch ABM Removing switching stations from network --- technic/machines/network.lua | 468 ++++++++++++++++- technic/machines/register/cables.lua | 2 +- technic/machines/switching_station.lua | 490 ++---------------- .../machines/switching_station_globalstep.lua | 40 +- 4 files changed, 534 insertions(+), 466 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index fa3d664..1916c07 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -1,10 +1,40 @@ -- -- Power network specific functions and data should live here -- +local S = technic.getter + +local switch_max_range = tonumber(minetest.settings:get("technic.switch_max_range") or "256") technic.networks = {} technic.cables = {} +local poshash = minetest.hash_node_position +local hashpos = minetest.get_position_from_hash + +function technic.create_network(sw_pos) + local network_id = poshash({x=sw_pos.x,y=sw_pos.y-1,z=sw_pos.z}) + technic.build_network(network_id) + return network_id +end + +function technic.activate_network(network_id, timeout) + assert(network_id) + local network = technic.networks[network_id] + if network and (timeout or network.timeout < 4) then + network.timeout = timeout or 4 + end +end + +function technic.sw_pos2tier(pos, use_vm) + -- Get cable tier for switching station or nil if no cable + -- use_vm true to use VoxelManip to load node + local cable_pos = {x=pos.x,y=pos.y-1,z=pos.z} + if use_vm then + technic.get_or_load_node(cable_pos) + end + return technic.get_cable_tier(minetest.get_node(cable_pos).name) +end + function technic.remove_network(network_id) local cables = technic.cables for pos_hash,cable_net_id in pairs(cables) do @@ -13,28 +43,42 @@ function technic.remove_network(network_id) end end technic.networks[network_id] = nil + print(string.format("NET DESTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) end function technic.sw_pos2network(pos) - return pos and technic.cables[minetest.hash_node_position({x=pos.x,y=pos.y-1,z=pos.z})] + return technic.cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})] +end + +function technic.sw_pos2network(pos) + return technic.cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})] end function technic.pos2network(pos) - return pos and technic.cables[minetest.hash_node_position(pos)] + return technic.cables[poshash(pos)] end function technic.network2pos(network_id) - return network_id and minetest.get_position_from_hash(network_id) + return hashpos(network_id) end function technic.network2sw_pos(network_id) -- Return switching station position for network. -- It is not guaranteed that position actually contains switching station. - local sw_pos = minetest.get_position_from_hash(network_id) + local sw_pos = hashpos(network_id) sw_pos.y = sw_pos.y + 1 return sw_pos end +function technic.network_infotext(network_id, text) + if technic.networks[network_id] == nil then return end + if text then + technic.networks[network_id].infotext = text + else + return technic.networks[network_id].infotext + end +end + local node_timeout = {} function technic.get_timeout(tier, pos) @@ -42,7 +86,7 @@ function technic.get_timeout(tier, pos) -- it is normal that some multi tier nodes always drop here when checking all LV, MV and HV tiers return 0 end - return node_timeout[tier][minetest.hash_node_position(pos)] or 0 + return node_timeout[tier][poshash(pos)] or 0 end function technic.touch_node(tier, pos, timeout) @@ -50,7 +94,415 @@ function technic.touch_node(tier, pos, timeout) -- this should get built up during registration node_timeout[tier] = {} end - node_timeout[tier][minetest.hash_node_position(pos)] = timeout or 2 + node_timeout[tier][poshash(pos)] = timeout or 2 +end + +-- +-- Network overloading (incomplete cheat mitigation) +-- +local overload_reset_time = tonumber(minetest.settings:get("technic.overload_reset_time") or "20") +local overloaded_networks = {} + +function technic.overload_network(network_id) + overloaded_networks[network_id] = minetest.get_us_time() + (overload_reset_time * 1000 * 1000) +end + +function technic.reset_overloaded(network_id) + local remaining = math.max(0, overloaded_networks[network_id] - minetest.get_us_time()) + if remaining == 0 then + -- Clear cache, remove overload and restart network + technic.remove_network(network_id) + overloaded_networks[network_id] = nil + end + -- Returns 0 when network reset or remaining time if reset timer has not expired yet + return remaining +end + +function technic.is_overloaded(network_id) + return overloaded_networks[network_id] +end + +-- +-- Functions to traverse the electrical network +-- +local function flatten(map) + local list = {} + for key, value in pairs(map) do + list[#list + 1] = value + end + return list +end + +local function attach_network_machine(network_id, pos) + local pos_hash = poshash(pos) + local net_id_old = technic.cables[pos_hash] + if net_id_old == nil then + technic.cables[pos_hash] = network_id + elseif net_id_old ~= network_id then + -- do not allow running pos from multiple networks, also disable switch + technic.overload_network(network_id, pos) + technic.overload_network(net_id_old, pos) + technic.cables[pos_hash] = network_id + local meta = minetest.get_meta(pos) + meta:set_string("infotext",S("Network Overloaded")) + end +end + +-- Add a wire node to the LV/MV/HV network +local function add_network_node(nodes, pos, network_id) + local node_id = poshash(pos) + technic.cables[node_id] = network_id + if nodes[node_id] then + return false + end + nodes[node_id] = pos + return true +end + +local function add_cable_node(nodes, pos, network_id, queue) + if add_network_node(nodes, pos, network_id) then + queue[#queue + 1] = pos + end +end + +-- Generic function to add found connected nodes to the right classification array +local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, from_below, network_id, queue) + + local distance_to_switch = vector.distance(pos, sw_pos) + if distance_to_switch > switch_max_range then + -- max range exceeded + return + end + + technic.get_or_load_node(pos) + local name = minetest.get_node(pos).name + + if technic.is_tier_cable(name, tier) then + add_cable_node(all_nodes, pos, network_id, queue) + elseif machines[name] then + --dprint(name.." is a "..machines[name]) + + if machines[name] == technic.producer then + attach_network_machine(network_id, pos) + add_network_node(PR_nodes, pos, network_id) + elseif machines[name] == technic.receiver then + attach_network_machine(network_id, pos) + add_network_node(RE_nodes, pos, network_id) + elseif machines[name] == technic.producer_receiver then + --attach_network_machine(network_id, pos) + add_network_node(PR_nodes, pos, network_id) + add_network_node(RE_nodes, pos, network_id) + elseif machines[name] == "SPECIAL" and from_below then + -- Another switching station -> disable it + attach_network_machine(network_id, pos) + add_network_node(SP_nodes, pos, network_id) + elseif machines[name] == technic.battery then + attach_network_machine(network_id, pos) + add_network_node(BA_nodes, pos, network_id) + end + + technic.touch_node(tier, pos, 2) -- Touch node + end +end + +-- Traverse a network given a list of machines and a cable type name +local function traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, network_id, queue) + local positions = { + {x=pos.x+1, y=pos.y, z=pos.z}, + {x=pos.x-1, y=pos.y, z=pos.z}, + {x=pos.x, y=pos.y+1, z=pos.z}, + {x=pos.x, y=pos.y-1, z=pos.z}, + {x=pos.x, y=pos.y, z=pos.z+1}, + {x=pos.x, y=pos.y, z=pos.z-1}} + for i, cur_pos in pairs(positions) do + check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) + end +end + +local function touch_nodes(list, tier) + local touch_node = technic.touch_node + for _, pos in ipairs(list) do + touch_node(tier, pos, 2) -- Touch node + end +end + +local function get_network(network_id, tier) + local cached = technic.networks[network_id] + if cached and cached.tier == tier then + touch_nodes(cached.PR_nodes, tier) + touch_nodes(cached.BA_nodes, tier) + touch_nodes(cached.RE_nodes, tier) + return cached.PR_nodes, cached.BA_nodes, cached.RE_nodes + end + return technic.build_network(network_id) +end + +function technic.build_network(network_id) + print(string.format("NET CONSTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) + technic.remove_network(network_id) + local sw_pos = technic.network2sw_pos(network_id) + local tier = technic.sw_pos2tier(sw_pos) + if not tier then + print(string.format("Cannot build network, cannot get tier for switching station at %s", minetest.pos_to_string(sw_pos))) + return + end + local PR_nodes = {} + local BA_nodes = {} + local RE_nodes = {} + local SP_nodes = {} + local all_nodes = {} + local queue = {} + -- Add first cable (one that is holding network id) + add_cable_node(all_nodes, technic.network2pos(network_id), network_id, queue) + while next(queue) do + local to_visit = {} + for _, pos in ipairs(queue) do + traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, + pos, technic.machines[tier], tier, sw_pos, network_id, to_visit) + end + queue = to_visit + end + PR_nodes = flatten(PR_nodes) + BA_nodes = flatten(BA_nodes) + RE_nodes = flatten(RE_nodes) + --SP_nodes = flatten(SP_nodes) -- can this be removed from network data? + technic.networks[network_id] = {tier = tier, all_nodes = all_nodes, SP_nodes = SP_nodes, + PR_nodes = PR_nodes, RE_nodes = RE_nodes, BA_nodes = BA_nodes, timeout = 4} + return PR_nodes, BA_nodes, RE_nodes +end + +-- +-- Execute technic power network +-- + +local function run_nodes(list, run_stage) + for _, pos in ipairs(list) do + technic.get_or_load_node(pos) + local node = minetest.get_node_or_nil(pos) + if node and node.name then + local nodedef = minetest.registered_nodes[node.name] + if nodedef and nodedef.technic_run then + nodedef.technic_run(pos, node, run_stage) + end + end + end +end + +function technic.network_run(network_id) + -- + -- !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! + -- TODO: This function requires a lot of cleanup + -- It is moved here from switching_station.lua and still + -- contain a lot of switching station specific stuff which + -- should be removed and/or refactored. + -- + if not technic.powerctrl_state then return end + + local pos = technic.network2sw_pos(network_id) + local t0 = minetest.get_us_time() + local meta = minetest.get_meta(pos) + local meta1 + local pos1 = technic.network2pos(network_id) + + local tier = "" + local PR_nodes + local BA_nodes + local RE_nodes + + local network_id = poshash(pos1) + -- Check if network is overloaded / conflicts with another network + if technic.is_overloaded(network_id) then + -- TODO: Overload check should happen before technic.network_run is called, overloaded network should not generate any events + return + end + + local name = minetest.get_node(pos1).name + local tier = technic.get_cable_tier(name) + if tier then + PR_nodes, BA_nodes, RE_nodes = get_network(network_id, tier) + if technic.is_overloaded(network_id) then return end + else + --dprint("Not connected to a network") + technic.network_infotext(network_id, S("%s Has No Network"):format(S("Switching Station"))) + return + end + + run_nodes(PR_nodes, technic.producer) + run_nodes(RE_nodes, technic.receiver) + run_nodes(BA_nodes, technic.battery) + + -- Strings for the meta data + local eu_demand_str = tier.."_EU_demand" + local eu_input_str = tier.."_EU_input" + local eu_supply_str = tier.."_EU_supply" + + -- Distribute charge equally across multiple batteries. + local charge_total = 0 + local battery_count = 0 + + local BA_charge = 0 + local BA_charge_max = 0 + + for n, pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + local charge = meta1:get_int("internal_EU_charge") + local charge_max = meta1:get_int("internal_EU_charge_max") + + BA_charge = BA_charge + charge + BA_charge_max = BA_charge_max + charge_max + + if (meta1:get_int(eu_demand_str) ~= 0) then + charge_total = charge_total + charge + battery_count = battery_count + 1 + end + end + + local charge_distributed = math.floor(charge_total / battery_count) + + for n, pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + + if (meta1:get_int(eu_demand_str) ~= 0) then + meta1:set_int("internal_EU_charge", charge_distributed) + end + end + + -- Get all the power from the PR nodes + local PR_eu_supply = 0 -- Total power + for _, pos1 in pairs(PR_nodes) do + meta1 = minetest.get_meta(pos1) + PR_eu_supply = PR_eu_supply + meta1:get_int(eu_supply_str) + end + --dprint("Total PR supply:"..PR_eu_supply) + + -- Get all the demand from the RE nodes + local RE_eu_demand = 0 + for _, pos1 in pairs(RE_nodes) do + meta1 = minetest.get_meta(pos1) + RE_eu_demand = RE_eu_demand + meta1:get_int(eu_demand_str) + end + --dprint("Total RE demand:"..RE_eu_demand) + + -- Get all the power from the BA nodes + local BA_eu_supply = 0 + for _, pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + BA_eu_supply = BA_eu_supply + meta1:get_int(eu_supply_str) + end + --dprint("Total BA supply:"..BA_eu_supply) + + -- Get all the demand from the BA nodes + local BA_eu_demand = 0 + for _, pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + BA_eu_demand = BA_eu_demand + meta1:get_int(eu_demand_str) + end + --dprint("Total BA demand:"..BA_eu_demand) + + technic.network_infotext(network_id, S("@1. Supply: @2 Demand: @3", + S("Switching Station"), technic.EU_string(PR_eu_supply), + technic.EU_string(RE_eu_demand))) + + -- If mesecon signal and power supply or demand changed then + -- send them via digilines. + if mesecons_path and digilines_path and mesecon.is_powered(pos) then + if PR_eu_supply ~= meta:get_int("supply") or + RE_eu_demand ~= meta:get_int("demand") then + local channel = meta:get_string("channel") + digilines.receptor_send(pos, technic.digilines.rules, channel, { + supply = PR_eu_supply, + demand = RE_eu_demand + }) + end + end + + -- Data that will be used by the power monitor + meta:set_int("supply",PR_eu_supply) + meta:set_int("demand",RE_eu_demand) + meta:set_int("battery_count",#BA_nodes) + meta:set_int("battery_charge",BA_charge) + meta:set_int("battery_charge_max",BA_charge_max) + + -- If the PR supply is enough for the RE demand supply them all + if PR_eu_supply >= RE_eu_demand then + --dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand) + for _, pos1 in pairs(RE_nodes) do + meta1 = minetest.get_meta(pos1) + local eu_demand = meta1:get_int(eu_demand_str) + meta1:set_int(eu_input_str, eu_demand) + end + -- We have a surplus, so distribute the rest equally to the BA nodes + -- Let's calculate the factor of the demand + PR_eu_supply = PR_eu_supply - RE_eu_demand + local charge_factor = 0 -- Assume all batteries fully charged + if BA_eu_demand > 0 then + charge_factor = PR_eu_supply / BA_eu_demand + end + for n, pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + local eu_demand = meta1:get_int(eu_demand_str) + meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor)) + --dprint("Charging battery:"..math.floor(eu_demand*charge_factor)) + end + local t1 = minetest.get_us_time() + local diff = t1 - t0 + if diff > 50000 then + minetest.log("warning", "[technic] [+supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) + end + + return + end + + -- If the PR supply is not enough for the RE demand we will discharge the batteries too + if PR_eu_supply + BA_eu_supply >= RE_eu_demand then + --dprint("PR_eu_supply "..PR_eu_supply.."+BA_eu_supply "..BA_eu_supply.." >= RE_eu_demand"..RE_eu_demand) + for _, pos1 in pairs(RE_nodes) do + meta1 = minetest.get_meta(pos1) + local eu_demand = meta1:get_int(eu_demand_str) + meta1:set_int(eu_input_str, eu_demand) + end + -- We have a deficit, so distribute to the BA nodes + -- Let's calculate the factor of the supply + local charge_factor = 0 -- Assume all batteries depleted + if BA_eu_supply > 0 then + charge_factor = (PR_eu_supply - RE_eu_demand) / BA_eu_supply + end + for n,pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + local eu_supply = meta1:get_int(eu_supply_str) + meta1:set_int(eu_input_str, math.floor(eu_supply * charge_factor)) + --dprint("Discharging battery:"..math.floor(eu_supply*charge_factor)) + end + local t1 = minetest.get_us_time() + local diff = t1 - t0 + if diff > 50000 then + minetest.log("warning", "[technic] [-supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) + end + + return + end + + -- If the PR+BA supply is not enough for the RE demand: Power only the batteries + local charge_factor = 0 -- Assume all batteries fully charged + if BA_eu_demand > 0 then + charge_factor = PR_eu_supply / BA_eu_demand + end + for n, pos1 in pairs(BA_nodes) do + meta1 = minetest.get_meta(pos1) + local eu_demand = meta1:get_int(eu_demand_str) + meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor)) + end + for n, pos1 in pairs(RE_nodes) do + meta1 = minetest.get_meta(pos1) + meta1:set_int(eu_input_str, 0) + end + + local t1 = minetest.get_us_time() + local diff = t1 - t0 + if diff > 50000 then + minetest.log("warning", "[technic] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) + end + end -- @@ -88,12 +540,16 @@ if false then local keys = { "LV_EU_timeout", "MV_EU_timeout", "HV_EU_timeout", "LV_network", "MV_network", "HV_network", + "active_pos", } local meta = minetest.get_meta(pos) for _,key in ipairs(keys) do -- Value of `""` will delete the key. meta:set_string(key, "") end + if node.name == "technic:switching_station" then + meta:set_string("active", "") + end end, }) end diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 7b57f47..768c54f 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -76,7 +76,7 @@ local function clear_networks(pos) elseif technic.machines[tier][node.name] == "SPECIAL" and (pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) and from_below then - table.insert(network.SP_nodes,pos) + network.SP_nodes[pos_hash] = pos elseif technic.machines[tier][node.name] == technic.battery then table.insert(network.BA_nodes,pos) end diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index e2043f4..25c6fef 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -1,25 +1,5 @@ -- See also technic/doc/api.md -technic.redundant_warn = {} - -local overload_reset_time = tonumber(minetest.settings:get("technic.overload_reset_time") or "20") -local overloaded_networks = {} -local function overload_network(network_id) - overloaded_networks[network_id] = minetest.get_us_time() + (overload_reset_time * 1000 * 1000) -end -local function reset_overloaded(network_id) - local remaining = math.max(0, overloaded_networks[network_id] - minetest.get_us_time()) - if remaining == 0 then - -- Clear cache, remove overload and restart network - technic.remove_network(network_id) - overloaded_networks[network_id] = nil - end - -- Returns 0 when network reset or remaining time if reset timer has not expired yet - return remaining -end - -local switch_max_range = tonumber(minetest.settings:get("technic.switch_max_range") or "256") - local mesecons_path = minetest.get_modpath("mesecons") local digilines_path = minetest.get_modpath("digilines") @@ -36,6 +16,13 @@ minetest.register_craft({ } }) +local function start_network(pos) + local tier = technic.sw_pos2tier(pos) + if not tier then return end + local network_id = technic.sw_pos2network(pos) or technic.create_network(pos) + technic.activate_network(network_id) +end + local mesecon_def if mesecons_path then mesecon_def = {effector = { @@ -58,33 +45,25 @@ minetest.register_node("technic:switching_station",{ on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_string("infotext", S("Switching Station")) - local network_id = technic.sw_pos2network(pos) - local net_sw_pos = network_id and technic.network2sw_pos(network_id) - local net_sw_node = net_sw_pos and minetest.get_node_or_nil(net_sw_pos) - if net_sw_node then - -- There's already network with same id, check if it already has active switching station - if net_sw_node.name == "technic:switching_station" then - -- Another switch found set active to 0 for this switch if another is already active - local net_sw_meta = minetest.get_meta(net_sw_pos) - meta:set_string("active", net_sw_meta:get_int("active") == 1 and 0 or 1) - else - -- Network switching station disappeared, cleanup caches and start new network - technic.remove_network(network_id) - meta:set_string("active", 1) - end - else - -- Clean start, not previous networks, no other switching stations - meta:set_string("active", 1) - end meta:set_string("channel", "switching_station"..minetest.pos_to_string(pos)) meta:set_string("formspec", "field[channel;Channel;${channel}]") - local poshash = minetest.hash_node_position(pos) - technic.redundant_warn.poshash = nil + start_network(pos) end, after_dig_node = function(pos) - pos.y = pos.y - 1 - local poshash = minetest.hash_node_position(pos) - technic.redundant_warn.poshash = nil + -- Remove network when switching station is removed, if + -- there's another switching station network will be rebuilt. + local network_id = technic.sw_pos2network(pos) + local network = network_id and technic.networks[network_id] + if network then + if #network.SP_nodes <= 1 then + -- Last switching station, network collapses + technic.remove_network(network_id) + else + -- Remove switching station from network + network.SP_nodes[minetest.hash_node_position(pos)] = nil + network.all_nodes[minetest.hash_node_position(pos)] = nil + end + end end, on_receive_fields = function(pos, formname, fields, sender) if not fields.channel then @@ -124,402 +103,16 @@ minetest.register_node("technic:switching_station",{ }, }) --------------------------------------------------- --- Functions to traverse the electrical network --------------------------------------------------- -local function flatten(map) - local list = {} - for key, value in pairs(map) do - list[#list + 1] = value - end - return list -end - -local function attach_network_machine(network_id, pos) - local pos_hash = minetest.hash_node_position(pos) - local net_id_old = technic.cables[pos_hash] - if net_id_old == nil then - technic.cables[pos_hash] = network_id - elseif net_id_old ~= network_id then - -- do not allow running pos from multiple networks, also disable switch - overload_network(network_id, pos) - overload_network(net_id_old, pos) - technic.cables[pos_hash] = network_id - local meta = minetest.get_meta(pos) - meta:set_string("infotext",S("Network Overloaded")) - end -end - --- Add a wire node to the LV/MV/HV network -local function add_network_node(nodes, pos, network_id) - local node_id = minetest.hash_node_position(pos) - technic.cables[node_id] = network_id - if nodes[node_id] then - return false - end - nodes[node_id] = pos - return true -end - -local function add_cable_node(nodes, pos, network_id, queue) - if add_network_node(nodes, pos, network_id) then - queue[#queue + 1] = pos - end -end - --- Generic function to add found connected nodes to the right classification array -local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, from_below, network_id, queue) - - local distance_to_switch = vector.distance(pos, sw_pos) - if distance_to_switch > switch_max_range then - -- max range exceeded - return - end - - technic.get_or_load_node(pos) - local name = minetest.get_node(pos).name - - if technic.is_tier_cable(name, tier) then - add_cable_node(all_nodes, pos, network_id, queue) - elseif machines[name] then - --dprint(name.." is a "..machines[name]) - - if machines[name] == technic.producer then - attach_network_machine(network_id, pos) - add_network_node(PR_nodes, pos, network_id) - elseif machines[name] == technic.receiver then - attach_network_machine(network_id, pos) - add_network_node(RE_nodes, pos, network_id) - elseif machines[name] == technic.producer_receiver then - --attach_network_machine(network_id, pos) - add_network_node(PR_nodes, pos, network_id) - add_network_node(RE_nodes, pos, network_id) - elseif machines[name] == "SPECIAL" and - (pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) and - from_below then - -- Another switching station -> disable it - attach_network_machine(network_id, pos) - add_network_node(SP_nodes, pos, network_id) - local meta = minetest.get_meta(pos) - meta:set_int("active", 0) - elseif machines[name] == technic.battery then - attach_network_machine(network_id, pos) - add_network_node(BA_nodes, pos, network_id) - end - - technic.touch_node(tier, pos, 2) -- Touch node - end -end - --- Traverse a network given a list of machines and a cable type name -local function traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, network_id, queue) - local positions = { - {x=pos.x+1, y=pos.y, z=pos.z}, - {x=pos.x-1, y=pos.y, z=pos.z}, - {x=pos.x, y=pos.y+1, z=pos.z}, - {x=pos.x, y=pos.y-1, z=pos.z}, - {x=pos.x, y=pos.y, z=pos.z+1}, - {x=pos.x, y=pos.y, z=pos.z-1}} - for i, cur_pos in pairs(positions) do - check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) - end -end - -local function touch_nodes(list, tier) - local touch_node = technic.touch_node - for _, pos in ipairs(list) do - touch_node(tier, pos, 2) -- Touch node - end -end - -local function get_network(network_id, sw_pos, pos1, tier) - local cached = technic.networks[network_id] - if cached and cached.tier == tier then - touch_nodes(cached.PR_nodes, tier) - touch_nodes(cached.BA_nodes, tier) - touch_nodes(cached.RE_nodes, tier) - for _, pos in ipairs(cached.SP_nodes) do - local meta = minetest.get_meta(pos) - meta:set_int("active", 0) - meta:set_string("active_pos", minetest.serialize(sw_pos)) - technic.touch_node(tier, pos, 2) -- Touch node - end - return cached.PR_nodes, cached.BA_nodes, cached.RE_nodes - end - local PR_nodes = {} - local BA_nodes = {} - local RE_nodes = {} - local SP_nodes = {} - local all_nodes = {} - local queue = {} - add_cable_node(all_nodes, pos1, network_id, queue) - while next(queue) do - local to_visit = {} - for _, pos in ipairs(queue) do - traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, - pos, technic.machines[tier], tier, sw_pos, network_id, to_visit) - end - queue = to_visit - end - PR_nodes = flatten(PR_nodes) - BA_nodes = flatten(BA_nodes) - RE_nodes = flatten(RE_nodes) - SP_nodes = flatten(SP_nodes) - technic.networks[network_id] = {tier = tier, all_nodes = all_nodes, SP_nodes = SP_nodes, - PR_nodes = PR_nodes, RE_nodes = RE_nodes, BA_nodes = BA_nodes} - return PR_nodes, BA_nodes, RE_nodes -end - ----------------------------------------------- -- The action code for the switching station -- ----------------------------------------------- --- Run all the nodes -local function run_nodes(list, run_stage) - for _, pos in ipairs(list) do - technic.get_or_load_node(pos) - local node = minetest.get_node_or_nil(pos) - if node and node.name then - local nodedef = minetest.registered_nodes[node.name] - if nodedef and nodedef.technic_run then - nodedef.technic_run(pos, node, run_stage) - end - end - end -end - function technic.switching_station_run(pos) - if not technic.powerctrl_state then return end - - local t0 = minetest.get_us_time() - local meta = minetest.get_meta(pos) - local meta1 - local pos1 = {} - - local tier = "" - local PR_nodes - local BA_nodes - local RE_nodes - local machine_name = S("Switching Station") - - -- Which kind of network are we on: - pos1 = {x=pos.x, y=pos.y-1, z=pos.z} - - --Disable if necessary - if meta:get_int("active") ~= 1 then - meta:set_string("infotext",S("%s Already Present"):format(machine_name)) - - local poshash = minetest.hash_node_position(pos) - - if not technic.redundant_warn[poshash] then - technic.redundant_warn[poshash] = true - print("[TECHNIC] Warning: redundant switching station found near "..minetest.pos_to_string(pos)) - end - return + local network_id = technic.sw_pos2network(pos) + if network_id then + return technic.network_run(network_id) end - - local network_id = minetest.hash_node_position(pos1) - -- Check if network is overloaded / conflicts with another network - if overloaded_networks[network_id] then - local remaining = reset_overloaded(network_id) - if remaining > 0 then - meta:set_string("infotext",S("%s Network Overloaded, Restart in %dms"):format(machine_name, remaining / 1000)) - -- Set switching station supply value to zero to clean up power monitor supply info - meta:set_int("supply",0) - return - end - meta:set_string("infotext",S("%s Restarting Network"):format(machine_name)) - return - end - - local name = minetest.get_node(pos1).name - local tier = technic.get_cable_tier(name) - if tier then - PR_nodes, BA_nodes, RE_nodes = get_network(network_id, pos, pos1, tier) - if overloaded_networks[network_id] then return end - else - --dprint("Not connected to a network") - meta:set_string("infotext", S("%s Has No Network"):format(machine_name)) - return - end - - run_nodes(PR_nodes, technic.producer) - run_nodes(RE_nodes, technic.receiver) - run_nodes(BA_nodes, technic.battery) - - -- Strings for the meta data - local eu_demand_str = tier.."_EU_demand" - local eu_input_str = tier.."_EU_input" - local eu_supply_str = tier.."_EU_supply" - - -- Distribute charge equally across multiple batteries. - local charge_total = 0 - local battery_count = 0 - - local BA_charge = 0 - local BA_charge_max = 0 - - for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - local charge = meta1:get_int("internal_EU_charge") - local charge_max = meta1:get_int("internal_EU_charge_max") - - BA_charge = BA_charge + charge - BA_charge_max = BA_charge_max + charge_max - - if (meta1:get_int(eu_demand_str) ~= 0) then - charge_total = charge_total + charge - battery_count = battery_count + 1 - end - end - - local charge_distributed = math.floor(charge_total / battery_count) - - for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - - if (meta1:get_int(eu_demand_str) ~= 0) then - meta1:set_int("internal_EU_charge", charge_distributed) - end - end - - -- Get all the power from the PR nodes - local PR_eu_supply = 0 -- Total power - for _, pos1 in pairs(PR_nodes) do - meta1 = minetest.get_meta(pos1) - PR_eu_supply = PR_eu_supply + meta1:get_int(eu_supply_str) - end - --dprint("Total PR supply:"..PR_eu_supply) - - -- Get all the demand from the RE nodes - local RE_eu_demand = 0 - for _, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) - RE_eu_demand = RE_eu_demand + meta1:get_int(eu_demand_str) - end - --dprint("Total RE demand:"..RE_eu_demand) - - -- Get all the power from the BA nodes - local BA_eu_supply = 0 - for _, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - BA_eu_supply = BA_eu_supply + meta1:get_int(eu_supply_str) - end - --dprint("Total BA supply:"..BA_eu_supply) - - -- Get all the demand from the BA nodes - local BA_eu_demand = 0 - for _, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - BA_eu_demand = BA_eu_demand + meta1:get_int(eu_demand_str) - end - --dprint("Total BA demand:"..BA_eu_demand) - - meta:set_string("infotext", S("@1. Supply: @2 Demand: @3", - machine_name, technic.EU_string(PR_eu_supply), - technic.EU_string(RE_eu_demand))) - - -- If mesecon signal and power supply or demand changed then - -- send them via digilines. - if mesecons_path and digilines_path and mesecon.is_powered(pos) then - if PR_eu_supply ~= meta:get_int("supply") or - RE_eu_demand ~= meta:get_int("demand") then - local channel = meta:get_string("channel") - digilines.receptor_send(pos, technic.digilines.rules, channel, { - supply = PR_eu_supply, - demand = RE_eu_demand - }) - end - end - - -- Data that will be used by the power monitor - meta:set_int("supply",PR_eu_supply) - meta:set_int("demand",RE_eu_demand) - meta:set_int("battery_count",#BA_nodes) - meta:set_int("battery_charge",BA_charge) - meta:set_int("battery_charge_max",BA_charge_max) - - -- If the PR supply is enough for the RE demand supply them all - if PR_eu_supply >= RE_eu_demand then - --dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand) - for _, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) - local eu_demand = meta1:get_int(eu_demand_str) - meta1:set_int(eu_input_str, eu_demand) - end - -- We have a surplus, so distribute the rest equally to the BA nodes - -- Let's calculate the factor of the demand - PR_eu_supply = PR_eu_supply - RE_eu_demand - local charge_factor = 0 -- Assume all batteries fully charged - if BA_eu_demand > 0 then - charge_factor = PR_eu_supply / BA_eu_demand - end - for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - local eu_demand = meta1:get_int(eu_demand_str) - meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor)) - --dprint("Charging battery:"..math.floor(eu_demand*charge_factor)) - end - local t1 = minetest.get_us_time() - local diff = t1 - t0 - if diff > 50000 then - minetest.log("warning", "[technic] [+supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) - end - - return - end - - -- If the PR supply is not enough for the RE demand we will discharge the batteries too - if PR_eu_supply + BA_eu_supply >= RE_eu_demand then - --dprint("PR_eu_supply "..PR_eu_supply.."+BA_eu_supply "..BA_eu_supply.." >= RE_eu_demand"..RE_eu_demand) - for _, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) - local eu_demand = meta1:get_int(eu_demand_str) - meta1:set_int(eu_input_str, eu_demand) - end - -- We have a deficit, so distribute to the BA nodes - -- Let's calculate the factor of the supply - local charge_factor = 0 -- Assume all batteries depleted - if BA_eu_supply > 0 then - charge_factor = (PR_eu_supply - RE_eu_demand) / BA_eu_supply - end - for n,pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - local eu_supply = meta1:get_int(eu_supply_str) - meta1:set_int(eu_input_str, math.floor(eu_supply * charge_factor)) - --dprint("Discharging battery:"..math.floor(eu_supply*charge_factor)) - end - local t1 = minetest.get_us_time() - local diff = t1 - t0 - if diff > 50000 then - minetest.log("warning", "[technic] [-supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) - end - - return - end - - -- If the PR+BA supply is not enough for the RE demand: Power only the batteries - local charge_factor = 0 -- Assume all batteries fully charged - if BA_eu_demand > 0 then - charge_factor = PR_eu_supply / BA_eu_demand - end - for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) - local eu_demand = meta1:get_int(eu_demand_str) - meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor)) - end - for n, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) - meta1:set_int(eu_input_str, 0) - end - - local t1 = minetest.get_us_time() - local diff = t1 - t0 - if diff > 50000 then - minetest.log("warning", "[technic] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) - end - - + --print(string.format("technic.switching_station_run(%s) failed, no network available", minetest.pos_to_string(pos))) end -- Timeout ABM @@ -560,19 +153,38 @@ minetest.register_abm({ end, }) ---Re-enable disabled switching station if necessary, similar to the timeout above +--Re-enable network of switching station if necessary, similar to the timeout above minetest.register_abm({ label = "Machines: re-enable check", nodenames = {"technic:switching_station"}, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) - local pos1 = {x=pos.x,y=pos.y-1,z=pos.z} - local tier = technic.get_cable_tier(minetest.get_node(pos1).name) - if not tier then return end - if switching_station_timeout_count(pos, tier) then + local network_id = technic.sw_pos2network(pos) + -- Check if network is overloaded / conflicts with another network + if network_id then + local infotext local meta = minetest.get_meta(pos) - meta:set_int("active",1) + if technic.is_overloaded(network_id) then + local remaining = technic.reset_overloaded(network_id) + if remaining > 0 then + infotext = S("%s Network Overloaded, Restart in %dms"):format(S("Switching Station"), remaining / 1000) + -- Set switching station supply value to zero to clean up power monitor supply info + -- TODO: This should be saved with network and removed from metadata + meta:set_int("supply",0) + else + infotext = S("%s Restarting Network"):format(S("Switching Station")) + end + technic.network_infotext(network_id, infotext) + else + -- Network exists and is not overloaded, reactivate for 4 seconds + technic.activate_network(network_id) + infotext = technic.network_infotext(network_id) + end + meta:set_string("infotext", infotext) + else + -- Network does not exist yet, attempt to create new network here + start_network(pos) end end, }) diff --git a/technic/machines/switching_station_globalstep.lua b/technic/machines/switching_station_globalstep.lua index bbd5ea5..e83f0df 100644 --- a/technic/machines/switching_station_globalstep.lua +++ b/technic/machines/switching_station_globalstep.lua @@ -3,16 +3,15 @@ local has_monitoring_mod = minetest.get_modpath("monitoring") local switches = {} -- pos_hash -> { time = time_us } -local function get_switch_data(pos) - local hash = minetest.hash_node_position(pos) - local switch = switches[hash] +local function get_switch_data(network_id) + local switch = switches[network_id] if not switch then switch = { time = 0, skip = 0 } - switches[hash] = switch + switches[network_id] = switch end return switch @@ -39,16 +38,23 @@ minetest.register_abm({ interval = 1, chance = 1, action = function(pos) - local switch = get_switch_data(pos) - switch.time = minetest.get_us_time() + local network_id = technic.sw_pos2network(pos) + if network_id then + if technic.is_overloaded(network_id) then + switches[network_id] = nil + else + local switch = get_switch_data(network_id) + switch.time = minetest.get_us_time() + end + end end }) - -- the interval between technic_run calls local technic_run_interval = 1.0 -- iterate over all collected switching stations and execute the technic_run function +local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800") local timer = 0 minetest.register_globalstep(function(dtime) timer = timer + dtime @@ -74,13 +80,12 @@ minetest.register_globalstep(function(dtime) local now = minetest.get_us_time() - local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800") local off_delay_micros = off_delay_seconds*1000*1000 local active_switches = 0 - for hash, switch in pairs(switches) do - local pos = minetest.get_position_from_hash(hash) + for network_id, switch in pairs(switches) do + local pos = technic.network2sw_pos(network_id) local diff = now - switch.time minetest.get_voxel_manip(pos, pos) @@ -88,7 +93,7 @@ minetest.register_globalstep(function(dtime) if node.name ~= "technic:switching_station" then -- station vanished - switches[hash] = nil + switches[network_id] = nil elseif diff < off_delay_micros then -- station active @@ -100,7 +105,6 @@ minetest.register_globalstep(function(dtime) technic.switching_station_run(pos) local switch_diff = minetest.get_us_time() - start - local meta = minetest.get_meta(pos) -- set lag in microseconds into the "lag" meta field @@ -120,38 +124,34 @@ minetest.register_globalstep(function(dtime) if switch.skip > 0 then -- calculate efficiency in percent and display it local efficiency = math.floor(1/switch.skip*100) - meta:set_string("infotext", "Polyfuse triggered, current efficiency: " .. + technic.network_infotext(network_id, "Polyfuse triggered, current efficiency: " .. efficiency .. "% generated lag : " .. math.floor(switch_diff/1000) .. " ms") -- remove laggy switching station from active index -- it will be reactivated when a player is near it -- laggy switching stations won't work well in unloaded areas this way - switches[hash] = nil + switches[network_id] = nil end else switch.skip = math.max(switch.skip - 1, 0) end - else -- station timed out - switches[hash] = nil + switches[network_id] = nil end end - local time_usage = minetest.get_us_time() - now - if has_monitoring_mod then + local time_usage = minetest.get_us_time() - now active_switching_stations_metric.set(active_switches) switching_stations_usage_metric.inc(time_usage) end - end) - minetest.register_chatcommand("technic_flush_switch_cache", { description = "removes all loaded switching stations from the cache", privs = { server = true }, From c08732cd87e9cc844c5780a9f24b12c7c9fbcf7b Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 26 Sep 2020 01:33:39 +0300 Subject: [PATCH 04/20] Fix digiline event and luacheck warnings Add fake get_modpath for busted --- technic/machines/network.lua | 51 +++++++++++++------------- technic/machines/switching_station.lua | 1 - technic/spec/fixtures/minetest.lua | 2 + 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 1916c07..71ff05d 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -288,6 +288,9 @@ local function run_nodes(list, run_stage) end end +local mesecons_path = minetest.get_modpath("mesecons") +local digilines_path = minetest.get_modpath("digilines") + function technic.network_run(network_id) -- -- !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! @@ -298,26 +301,20 @@ function technic.network_run(network_id) -- if not technic.powerctrl_state then return end - local pos = technic.network2sw_pos(network_id) - local t0 = minetest.get_us_time() - local meta = minetest.get_meta(pos) - local meta1 - local pos1 = technic.network2pos(network_id) - - local tier = "" - local PR_nodes - local BA_nodes - local RE_nodes - - local network_id = poshash(pos1) -- Check if network is overloaded / conflicts with another network if technic.is_overloaded(network_id) then -- TODO: Overload check should happen before technic.network_run is called, overloaded network should not generate any events return end - local name = minetest.get_node(pos1).name - local tier = technic.get_cable_tier(name) + local pos = technic.network2sw_pos(network_id) + local t0 = minetest.get_us_time() + + local PR_nodes + local BA_nodes + local RE_nodes + + local tier = technic.sw_pos2tier(pos) if tier then PR_nodes, BA_nodes, RE_nodes = get_network(network_id, tier) if technic.is_overloaded(network_id) then return end @@ -344,7 +341,7 @@ function technic.network_run(network_id) local BA_charge_max = 0 for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) local charge = meta1:get_int("internal_EU_charge") local charge_max = meta1:get_int("internal_EU_charge_max") @@ -360,7 +357,7 @@ function technic.network_run(network_id) local charge_distributed = math.floor(charge_total / battery_count) for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) if (meta1:get_int(eu_demand_str) ~= 0) then meta1:set_int("internal_EU_charge", charge_distributed) @@ -370,7 +367,7 @@ function technic.network_run(network_id) -- Get all the power from the PR nodes local PR_eu_supply = 0 -- Total power for _, pos1 in pairs(PR_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) PR_eu_supply = PR_eu_supply + meta1:get_int(eu_supply_str) end --dprint("Total PR supply:"..PR_eu_supply) @@ -378,7 +375,7 @@ function technic.network_run(network_id) -- Get all the demand from the RE nodes local RE_eu_demand = 0 for _, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) RE_eu_demand = RE_eu_demand + meta1:get_int(eu_demand_str) end --dprint("Total RE demand:"..RE_eu_demand) @@ -386,7 +383,7 @@ function technic.network_run(network_id) -- Get all the power from the BA nodes local BA_eu_supply = 0 for _, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) BA_eu_supply = BA_eu_supply + meta1:get_int(eu_supply_str) end --dprint("Total BA supply:"..BA_eu_supply) @@ -394,7 +391,7 @@ function technic.network_run(network_id) -- Get all the demand from the BA nodes local BA_eu_demand = 0 for _, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) BA_eu_demand = BA_eu_demand + meta1:get_int(eu_demand_str) end --dprint("Total BA demand:"..BA_eu_demand) @@ -403,6 +400,8 @@ function technic.network_run(network_id) S("Switching Station"), technic.EU_string(PR_eu_supply), technic.EU_string(RE_eu_demand))) + local meta = minetest.get_meta(pos) + -- If mesecon signal and power supply or demand changed then -- send them via digilines. if mesecons_path and digilines_path and mesecon.is_powered(pos) then @@ -427,7 +426,7 @@ function technic.network_run(network_id) if PR_eu_supply >= RE_eu_demand then --dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand) for _, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) local eu_demand = meta1:get_int(eu_demand_str) meta1:set_int(eu_input_str, eu_demand) end @@ -439,7 +438,7 @@ function technic.network_run(network_id) charge_factor = PR_eu_supply / BA_eu_demand end for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) local eu_demand = meta1:get_int(eu_demand_str) meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor)) --dprint("Charging battery:"..math.floor(eu_demand*charge_factor)) @@ -457,7 +456,7 @@ function technic.network_run(network_id) if PR_eu_supply + BA_eu_supply >= RE_eu_demand then --dprint("PR_eu_supply "..PR_eu_supply.."+BA_eu_supply "..BA_eu_supply.." >= RE_eu_demand"..RE_eu_demand) for _, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) local eu_demand = meta1:get_int(eu_demand_str) meta1:set_int(eu_input_str, eu_demand) end @@ -468,7 +467,7 @@ function technic.network_run(network_id) charge_factor = (PR_eu_supply - RE_eu_demand) / BA_eu_supply end for n,pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) local eu_supply = meta1:get_int(eu_supply_str) meta1:set_int(eu_input_str, math.floor(eu_supply * charge_factor)) --dprint("Discharging battery:"..math.floor(eu_supply*charge_factor)) @@ -488,12 +487,12 @@ function technic.network_run(network_id) charge_factor = PR_eu_supply / BA_eu_demand end for n, pos1 in pairs(BA_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) local eu_demand = meta1:get_int(eu_demand_str) meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor)) end for n, pos1 in pairs(RE_nodes) do - meta1 = minetest.get_meta(pos1) + local meta1 = minetest.get_meta(pos1) meta1:set_int(eu_input_str, 0) end diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 25c6fef..2953219 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -1,7 +1,6 @@ -- See also technic/doc/api.md local mesecons_path = minetest.get_modpath("mesecons") -local digilines_path = minetest.get_modpath("digilines") local S = technic.getter diff --git a/technic/spec/fixtures/minetest.lua b/technic/spec/fixtures/minetest.lua index 3c61be9..f035b28 100644 --- a/technic/spec/fixtures/minetest.lua +++ b/technic/spec/fixtures/minetest.lua @@ -70,4 +70,6 @@ _G.minetest.register_on_placenode = noop _G.minetest.register_on_dignode = noop _G.minetest.item_drop = noop +_G.minetest.get_modpath = function(...) return "./unit_test_modpath" end + _G.minetest.get_pointed_thing_position = dummy_coords From f66c644fceb08d1e40e8b684b3a95b0e30fd9c94 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 26 Sep 2020 04:37:20 +0300 Subject: [PATCH 05/20] Remove metadata for supply,demand,battery_count,battery_charge,battery_charge_max Remove switching station from networks Disable few debug prints, add removed machines to LBM clear_networks refatoring #95 Functions remove_network_node and add_network_branch #95 Fix undef network_id, remove flatten #95 --- technic/machines/network.lua | 149 ++++++++----- technic/machines/power_monitor.lua | 40 ++-- technic/machines/register/cables.lua | 209 ++++++++++-------- technic/machines/switching_station.lua | 34 ++- .../machines/switching_station_globalstep.lua | 9 +- technic/spec/fixtures/minetest.lua | 21 ++ technic/spec/fixtures/network.lua | 32 +++ technic/spec/network_spec.lua | 11 + 8 files changed, 299 insertions(+), 206 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 71ff05d..08182b3 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -35,6 +35,7 @@ function technic.sw_pos2tier(pos, use_vm) return technic.get_cable_tier(minetest.get_node(cable_pos).name) end +-- Destroy network data function technic.remove_network(network_id) local cables = technic.cables for pos_hash,cable_net_id in pairs(cables) do @@ -43,7 +44,23 @@ function technic.remove_network(network_id) end end technic.networks[network_id] = nil - print(string.format("NET DESTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) + --print(string.format("NET DESTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) +end + +-- Remove machine or cable from network +local network_node_tables = {"PR_nodes","BA_nodes","RE_nodes","SP_nodes","all_nodes"} +function technic.remove_network_node(network_id, pos) + local network = technic.networks[network_id] + if not network then return end + technic.cables[poshash(pos)] = nil + for _,tblname in ipairs(network_node_tables) do + local table = network[tblname] + for id,mpos in pairs(table) do + if mpos.x == pos.x and mpos.y == pos.y and mpos.z == pos.z then + table[id] = nil + end + end + end end function technic.sw_pos2network(pos) @@ -104,6 +121,11 @@ local overload_reset_time = tonumber(minetest.settings:get("technic.overload_res local overloaded_networks = {} function technic.overload_network(network_id) + local network = technic.networks[network_id] + if network then + network.supply = 0 + network.battery_charge = 0 + end overloaded_networks[network_id] = minetest.get_us_time() + (overload_reset_time * 1000 * 1000) end @@ -125,13 +147,6 @@ end -- -- Functions to traverse the electrical network -- -local function flatten(map) - local list = {} - for key, value in pairs(map) do - list[#list + 1] = value - end - return list -end local function attach_network_machine(network_id, pos) local pos_hash = poshash(pos) @@ -148,31 +163,26 @@ local function attach_network_machine(network_id, pos) end end --- Add a wire node to the LV/MV/HV network +-- Add a machine node to the LV/MV/HV network local function add_network_node(nodes, pos, network_id) + technic.cables[poshash(pos)] = network_id + table.insert(nodes, pos) +end + +-- Add a wire node to the LV/MV/HV network +local function add_cable_node(nodes, pos, network_id, queue) local node_id = poshash(pos) technic.cables[node_id] = network_id if nodes[node_id] then return false end nodes[node_id] = pos - return true -end - -local function add_cable_node(nodes, pos, network_id, queue) - if add_network_node(nodes, pos, network_id) then - queue[#queue + 1] = pos - end + -- Also add cables to queue + queue[#queue + 1] = pos end -- Generic function to add found connected nodes to the right classification array -local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, from_below, network_id, queue) - - local distance_to_switch = vector.distance(pos, sw_pos) - if distance_to_switch > switch_max_range then - -- max range exceeded - return - end +local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, sw_pos, from_below, network_id, queue) technic.get_or_load_node(pos) local name = minetest.get_node(pos).name @@ -192,10 +202,6 @@ local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes --attach_network_machine(network_id, pos) add_network_node(PR_nodes, pos, network_id) add_network_node(RE_nodes, pos, network_id) - elseif machines[name] == "SPECIAL" and from_below then - -- Another switching station -> disable it - attach_network_machine(network_id, pos) - add_network_node(SP_nodes, pos, network_id) elseif machines[name] == technic.battery then attach_network_machine(network_id, pos) add_network_node(BA_nodes, pos, network_id) @@ -206,7 +212,7 @@ local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes end -- Traverse a network given a list of machines and a cable type name -local function traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos, network_id, queue) +local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, sw_pos, network_id, queue) local positions = { {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x-1, y=pos.y, z=pos.z}, @@ -215,7 +221,7 @@ local function traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_node {x=pos.x, y=pos.y, z=pos.z+1}, {x=pos.x, y=pos.y, z=pos.z-1}} for i, cur_pos in pairs(positions) do - check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) + check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) end end @@ -237,6 +243,28 @@ local function get_network(network_id, tier) return technic.build_network(network_id) end +function technic.add_network_branch(queue, sw_pos, network) + -- Adds whole branch to network, queue positions can be used to bypass sub branches + local PR_nodes = network.PR_nodes -- Indexed array + local BA_nodes = network.BA_nodes -- Indexed array + local RE_nodes = network.RE_nodes -- Indexed array + local all_nodes = network.all_nodes -- Hash table + local network_id = network.id + local tier = network.tier + while next(queue) do + local to_visit = {} + for _, pos in ipairs(queue) do + if vector.distance(pos, sw_pos) > switch_max_range then + -- max range exceeded + return + end + traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, + technic.machines[tier], tier, sw_pos, network_id, to_visit) + end + queue = to_visit + end +end + function technic.build_network(network_id) print(string.format("NET CONSTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) technic.remove_network(network_id) @@ -246,29 +274,24 @@ function technic.build_network(network_id) print(string.format("Cannot build network, cannot get tier for switching station at %s", minetest.pos_to_string(sw_pos))) return end - local PR_nodes = {} - local BA_nodes = {} - local RE_nodes = {} - local SP_nodes = {} - local all_nodes = {} + local network = { + id = network_id, tier = tier, all_nodes = {}, + SP_nodes = {}, PR_nodes = {}, RE_nodes = {}, BA_nodes = {}, + supply = 0, demand = 0, timeout = 4, battery_charge = 0, battery_charge_max = 0, + } + -- Add first cable (one that is holding network id) and build network local queue = {} - -- Add first cable (one that is holding network id) - add_cable_node(all_nodes, technic.network2pos(network_id), network_id, queue) - while next(queue) do - local to_visit = {} - for _, pos in ipairs(queue) do - traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, - pos, technic.machines[tier], tier, sw_pos, network_id, to_visit) - end - queue = to_visit - end - PR_nodes = flatten(PR_nodes) - BA_nodes = flatten(BA_nodes) - RE_nodes = flatten(RE_nodes) - --SP_nodes = flatten(SP_nodes) -- can this be removed from network data? - technic.networks[network_id] = {tier = tier, all_nodes = all_nodes, SP_nodes = SP_nodes, - PR_nodes = PR_nodes, RE_nodes = RE_nodes, BA_nodes = BA_nodes, timeout = 4} - return PR_nodes, BA_nodes, RE_nodes + add_cable_node(network.all_nodes, technic.network2pos(network_id), network_id, queue) + technic.add_network_branch(queue, sw_pos, network) + -- Flatten hashes to optimize iterations + --network.PR_nodes = flatten(network.PR_nodes) -- Already processed as indexed array + --network.BA_nodes = flatten(network.BA_nodes) -- Already processed as indexed array + --network.RE_nodes = flatten(network.RE_nodes) -- Already processed as indexed array + network.battery_count = #network.BA_nodes + -- Add newly built network to cache array + technic.networks[network_id] = network + -- And return producers, batteries and receivers (should this simply return network?) + return network.PR_nodes, network.BA_nodes, network.RE_nodes end -- @@ -315,9 +338,11 @@ function technic.network_run(network_id) local RE_nodes local tier = technic.sw_pos2tier(pos) + local network if tier then PR_nodes, BA_nodes, RE_nodes = get_network(network_id, tier) if technic.is_overloaded(network_id) then return end + network = technic.networks[network_id] else --dprint("Not connected to a network") technic.network_infotext(network_id, S("%s Has No Network"):format(S("Switching Station"))) @@ -400,13 +425,12 @@ function technic.network_run(network_id) S("Switching Station"), technic.EU_string(PR_eu_supply), technic.EU_string(RE_eu_demand))) - local meta = minetest.get_meta(pos) - -- If mesecon signal and power supply or demand changed then -- send them via digilines. if mesecons_path and digilines_path and mesecon.is_powered(pos) then - if PR_eu_supply ~= meta:get_int("supply") or - RE_eu_demand ~= meta:get_int("demand") then + if PR_eu_supply ~= network.supply or + RE_eu_demand ~= network.demand then + local meta = minetest.get_meta(pos) local channel = meta:get_string("channel") digilines.receptor_send(pos, technic.digilines.rules, channel, { supply = PR_eu_supply, @@ -416,11 +440,11 @@ function technic.network_run(network_id) end -- Data that will be used by the power monitor - meta:set_int("supply",PR_eu_supply) - meta:set_int("demand",RE_eu_demand) - meta:set_int("battery_count",#BA_nodes) - meta:set_int("battery_charge",BA_charge) - meta:set_int("battery_charge_max",BA_charge_max) + network.supply = PR_eu_supply + network.demand = RE_eu_demand + network.battery_count = #BA_nodes + network.battery_charge = BA_charge + network.battery_charge_max = BA_charge_max -- If the PR supply is enough for the RE demand supply them all if PR_eu_supply >= RE_eu_demand then @@ -533,13 +557,16 @@ if false then nodenames = { "group:technic_machine", "group:technic_all_tiers", + "technic:switching_station", + "technic:power_monitor", }, action = function(pos, node) -- Delete all listed metadata key/value pairs from technic machines local keys = { "LV_EU_timeout", "MV_EU_timeout", "HV_EU_timeout", "LV_network", "MV_network", "HV_network", - "active_pos", + "active_pos", "supply", "demand", + "battery_count", "battery_charge", "battery_charge_max", } local meta = minetest.get_meta(pos) for _,key in ipairs(keys) do diff --git a/technic/machines/power_monitor.lua b/technic/machines/power_monitor.lua index e17a790..3618cf8 100644 --- a/technic/machines/power_monitor.lua +++ b/technic/machines/power_monitor.lua @@ -13,6 +13,7 @@ local function get_cable(pos) end -- return the position of connected cable or nil +-- TODO: Make it support every possible orientation local function get_connected_cable_network(pos) local param2 = minetest.get_node(pos).param2 -- should probably also work sideways or upside down but for now it wont @@ -26,18 +27,16 @@ local function get_connected_cable_network(pos) -- Behind? checkpos = vector.add(minetest.facedir_to_dir(param2),pos) network_id = get_cable(checkpos) and technic.pos2network(checkpos) - if network_id then - return network_id - end + return network_id end -- return the position of the associated switching station or nil -local function get_swpos(pos) +local function get_network(pos) local network_id = get_connected_cable_network(pos) local network = network_id and technic.networks[network_id] local swpos = network and technic.network2sw_pos(network_id) local is_powermonitor = swpos and minetest.get_node(swpos).name == "technic:switching_station" - return (is_powermonitor and network.all_nodes[network_id]) and swpos + return (is_powermonitor and network.all_nodes[network_id]) and network end minetest.register_craft({ @@ -97,19 +96,16 @@ minetest.register_node("technic:power_monitor",{ return end - local sw_pos = get_swpos(pos) - if not sw_pos then - return - end + local network = get_network(pos) + if not network then return end - local sw_meta = minetest.get_meta(sw_pos) digilines.receptor_send(pos, technic.digilines.rules, channel, { - supply = sw_meta:get_int("supply"), - demand = sw_meta:get_int("demand"), - lag = sw_meta:get_int("lag"), - battery_count = sw_meta:get_int("battery_count"), - battery_charge = sw_meta:get_int("battery_charge"), - battery_charge_max = sw_meta:get_int("battery_charge_max"), + supply = network.supply, + demand = network.demand, + lag = network.lag, + battery_count = network.battery_count, + battery_charge = network.battery_charge, + battery_charge_max = network.battery_charge_max, }) end }, @@ -123,14 +119,10 @@ minetest.register_abm({ chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) local meta = minetest.get_meta(pos) - local sw_pos = get_swpos(pos) - if sw_pos then - local sw_meta = minetest.get_meta(sw_pos) - local supply = sw_meta:get_int("supply") - local demand = sw_meta:get_int("demand") - meta:set_string("infotext", - S("Power Monitor. Supply: @1 Demand: @2", - technic.EU_string(supply), technic.EU_string(demand))) + local network = get_network(pos) + if network then + meta:set_string("infotext", S("Power Monitor. Supply: @1 Demand: @2", + technic.EU_string(network.supply), technic.EU_string(network.demand))) else meta:set_string("infotext",S("Power Monitor Has No Network")) end diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 768c54f..e625422 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -13,6 +13,9 @@ end local function check_connections(pos) -- Build a table of all machines + -- TODO: Move this to network.lua + -- TODO: Build table for current tier only, we do not want to test other tiers. + -- Make sure that multi tier machines work (currently supply converter). local machines = {} for tier,list in pairs(technic.machines) do for k,v in pairs(list) do @@ -36,81 +39,94 @@ local function check_connections(pos) return connections end -local function clear_networks(pos) - local node = minetest.get_node(pos) - local placed = node.name ~= "air" +local function connect_networks(pos, positions) + -- TODO: Allow connecting networks: + -- If neighbor branch does not belong to any network attach it to this network + -- If neighbor branch belongs to another network check which one has least #all_nodes and rebuild that + for _,connected_pos in pairs(positions) do + local net = technic.pos2network(connected_pos) + if net and technic.networks[net] then + -- Not a dead end, so the whole network needs to be recalculated + technic.remove_network(net) + end + end +end + +local function place_network_node(pos, node) local positions = check_connections(pos) if #positions < 1 then return end local dead_end = #positions == 1 - for _,connected_pos in pairs(positions) do - local net = technic.cables[minetest.hash_node_position(connected_pos)] - if net and technic.networks[net] then - if dead_end and placed then - -- Dead end placed, add it to the network - -- Get the network - local network_id = technic.cables[minetest.hash_node_position(positions[1])] - if not network_id then - -- We're evidently not on a network, nothing to add ourselves to - return - end - local sw_pos = minetest.get_position_from_hash(network_id) - sw_pos.y = sw_pos.y + 1 - local network = technic.networks[network_id] - local tier = network.tier - -- Actually add it to the (cached) network - -- This is similar to check_node_subp - local pos_hash = minetest.hash_node_position(pos) - technic.cables[pos_hash] = network_id - pos.visited = 1 - if technic.is_tier_cable(name, tier) then - network.all_nodes[pos_hash] = pos - elseif technic.machines[tier][node.name] then - if technic.machines[tier][node.name] == technic.producer then - table.insert(network.PR_nodes,pos) - elseif technic.machines[tier][node.name] == technic.receiver then - table.insert(network.RE_nodes,pos) - elseif technic.machines[tier][node.name] == technic.producer_receiver then - table.insert(network.PR_nodes,pos) - table.insert(network.RE_nodes,pos) - elseif technic.machines[tier][node.name] == "SPECIAL" and - (pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) and - from_below then - network.SP_nodes[pos_hash] = pos - elseif technic.machines[tier][node.name] == technic.battery then - table.insert(network.BA_nodes,pos) - end - end - elseif dead_end and not placed then - -- Dead end removed, remove it from the network - -- Get the network - local network_id = technic.cables[minetest.hash_node_position(positions[1])] - if not network_id then - -- We're evidently not on a network, nothing to add ourselves to - return - end - local network = technic.networks[network_id] + -- Dead end placed, add it to the network + -- Get the network + local network_id = technic.pos2network(positions[1]) + if not network_id then + -- We're evidently not on a network, nothing to add ourselves to + return + end + local network = technic.networks[network_id] + local tier = network.tier - -- Search for and remove machine - technic.cables[minetest.hash_node_position(pos)] = nil - for tblname,table in pairs(network) do - if tblname ~= "tier" then - for machinenum,machine in pairs(table) do - if machine.x == pos.x - and machine.y == pos.y - and machine.z == pos.z then - table[machinenum] = nil - end - end - end + if not dead_end then + return connect_networks(pos, positions) + end + + -- Actually add it to the (cached) network + -- This is similar to check_node_subp + local pos_hash = minetest.hash_node_position(pos) + technic.cables[pos_hash] = network_id + pos.visited = 1 + if technic.is_tier_cable(name, tier) then + network.all_nodes[pos_hash] = pos + elseif technic.machines[tier][node.name] then + if technic.machines[tier][node.name] == technic.producer then + table.insert(network.PR_nodes,pos) + elseif technic.machines[tier][node.name] == technic.receiver then + table.insert(network.RE_nodes,pos) + elseif technic.machines[tier][node.name] == technic.producer_receiver then + table.insert(network.PR_nodes,pos) + table.insert(network.RE_nodes,pos) + elseif technic.machines[tier][node.name] == technic.battery then + table.insert(network.BA_nodes,pos) + end + end +end + +local function remove_network_node(pos) + -- Get the network + local network_id = technic.pos2network(pos) + if not network_id then + -- We're evidently not on a network, nothing to add ourselves to + return + end + + local positions = check_connections(pos) + if #positions < 1 then return end + local dead_end = #positions == 1 + + if not dead_end then + -- TODO: Check branches around and switching stations for branches: + -- remove branches that do not have switching station. + -- remove branches not connected to another branch. + -- do not rebuild networks here, leave that for ABM to reduce unnecessary cache building. + -- For now remove network like how it was done before: + technic.remove_network(network_id) + return + end + + -- Dead end removed, remove it from the network + local network = technic.networks[network_id] + technic.cables[minetest.hash_node_position(pos)] = nil + -- TODO: Looping over all keys in network is not right way to do this, should fix to use known machine types. + -- Better to add network function that knows what to remove, something like technic.remove_node(network_id, pos) + for tblname,table in pairs(network) do + if type(table) == "table" then + for machinenum,machine in pairs(table) do + if machine.x == pos.x + and machine.y == pos.y + and machine.z == pos.z then + table[machinenum] = nil end - else - -- Not a dead end, so the whole network needs to be recalculated - for _,v in pairs(technic.networks[net].all_nodes) do - local pos1 = minetest.hash_node_position(v) - technic.cables[pos1] = nil - end - technic.networks[net] = nil end end end @@ -142,7 +158,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, prefix = prefix or "" override_cable_plate = override_cable_plate or override_cable local ltier = string.lower(tier) - cable_tier["technic:"..ltier..prefix.."_cable"] = tier + local node_name = "technic:"..ltier..prefix.."_cable" + cable_tier[node_name] = tier local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, ["technic_"..ltier.."_cable"] = 1} @@ -158,22 +175,22 @@ function technic.register_cable(tier, size, description, prefix, override_cable, connect_right = {-size, -size, -size, 0.5, size, size}, -- x+ } - minetest.register_node("technic:"..ltier..prefix.."_cable", override_table({ + minetest.register_node(node_name, override_table({ description = S("%s Cable"):format(tier), tiles = {"technic_"..ltier..prefix.."_cable.png"}, inventory_image = "technic_"..ltier..prefix.."_cable_wield.png", wield_image = "technic_"..ltier..prefix.."_cable_wield.png", groups = groups, sounds = default.node_sound_wood_defaults(), - drop = "technic:"..ltier..prefix.."_cable", + drop = node_name, paramtype = "light", sunlight_propagates = true, drawtype = "nodebox", node_box = node_box, connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = clear_networks, - on_destruct = clear_networks, + on_construct = function(pos) place_network_node(pos, node_name) end, + on_destruct = remove_network_node, }, override_cable)) local xyz = { @@ -205,15 +222,15 @@ function technic.register_cable(tier, size, description, prefix, override_cable, tiles = {"technic_"..ltier..prefix.."_cable.png"}, groups = table.copy(groups), sounds = default.node_sound_wood_defaults(), - drop = "technic:"..ltier..prefix.."_cable_plate_1", + drop = node_name .. "_plate_1", paramtype = "light", sunlight_propagates = true, drawtype = "nodebox", node_box = table.copy(node_box), connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = clear_networks, - on_destruct = clear_networks, + on_construct = function(pos) place_network_node(pos, node_name.."_plate_"..i) end, + on_destruct = remove_network_node, } def.node_box.fixed = { {-size, -size, -size, size, size, size}, @@ -254,7 +271,7 @@ function technic.register_cable(tier, size, description, prefix, override_cable, if num == nil then num = 1 end return item_place_override_node( itemstack, placer, pointed_thing, - {name = "technic:"..ltier..prefix.."_cable_plate_"..num} + {name = node_name.."_plate_"..num} ) end else @@ -271,39 +288,41 @@ function technic.register_cable(tier, size, description, prefix, override_cable, num = num + dir num = (num >= 1 and num) or num + 6 num = (num <= 6 and num) or num - 6 - minetest.swap_node(pos, {name = "technic:"..ltier..prefix.."_cable_plate_"..num}) + minetest.swap_node(pos, {name = node_name.."_plate_"..num}) end - minetest.register_node("technic:"..ltier..prefix.."_cable_plate_"..i, override_table(def, override_cable_plate)) - cable_tier["technic:"..ltier..prefix.."_cable_plate_"..i] = tier + minetest.register_node(node_name.."_plate_"..i, override_table(def, override_cable_plate)) + cable_tier[node_name.."_plate_"..i] = tier end - local c = "technic:"..ltier..prefix.."_cable" minetest.register_craft({ - output = "technic:"..ltier..prefix.."_cable_plate_1 5", + output = node_name.."_plate_1 5", recipe = { - {"", "", c}, - {c , c , c}, - {"", "", c}, + {"" , "" , node_name}, + {node_name, node_name, node_name}, + {"" , "" , node_name}, } }) minetest.register_craft({ - output = c, + output = node_name, recipe = { - {"technic:"..ltier..prefix.."_cable_plate_1"}, + {node_name.."_plate_1"}, } }) end - -local function clear_nets_if_machine(pos, node) +minetest.register_on_placenode(function(pos, node) for tier, machine_list in pairs(technic.machines) do if machine_list[node.name] ~= nil then - return clear_networks(pos) + return place_network_node(pos, node) end end -end - -minetest.register_on_placenode(clear_nets_if_machine) -minetest.register_on_dignode(clear_nets_if_machine) +end) +minetest.register_on_dignode(function(pos, node) + for tier, machine_list in pairs(technic.machines) do + if machine_list[node.name] ~= nil then + return remove_network_node(pos) + end + end +end) diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 2953219..0bd8ef1 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -92,11 +92,19 @@ minetest.register_node("technic:switching_station",{ if channel ~= meta:get_string("channel") then return end - digilines.receptor_send(pos, technic.digilines.rules, channel, { - supply = meta:get_int("supply"), - demand = meta:get_int("demand"), - lag = meta:get_int("lag") - }) + local network_id = technic.sw_pos2network(pos) + local network = network_id and technic.networks[network_id] + if network then + digilines.receptor_send(pos, technic.digilines.rules, channel, { + supply = network.supply, + demand = network.demand, + lag = network.lag + }) + else + digilines.receptor_send(pos, technic.digilines.rules, channel, { + error = "No network", + }) + end end }, }, @@ -106,14 +114,6 @@ minetest.register_node("technic:switching_station",{ -- The action code for the switching station -- ----------------------------------------------- -function technic.switching_station_run(pos) - local network_id = technic.sw_pos2network(pos) - if network_id then - return technic.network_run(network_id) - end - --print(string.format("technic.switching_station_run(%s) failed, no network available", minetest.pos_to_string(pos))) -end - -- Timeout ABM -- Timeout for a node in case it was disconnected from the network -- A node must be touched by the station continuously in order to function @@ -168,9 +168,6 @@ minetest.register_abm({ local remaining = technic.reset_overloaded(network_id) if remaining > 0 then infotext = S("%s Network Overloaded, Restart in %dms"):format(S("Switching Station"), remaining / 1000) - -- Set switching station supply value to zero to clean up power monitor supply info - -- TODO: This should be saved with network and removed from metadata - meta:set_int("supply",0) else infotext = S("%s Restarting Network"):format(S("Switching Station")) end @@ -187,8 +184,3 @@ minetest.register_abm({ end end, }) - -for tier, machines in pairs(technic.machines) do - -- SPECIAL will not be traversed - technic.register_machine(tier, "technic:switching_station", "SPECIAL") -end diff --git a/technic/machines/switching_station_globalstep.lua b/technic/machines/switching_station_globalstep.lua index e83f0df..bed6f07 100644 --- a/technic/machines/switching_station_globalstep.lua +++ b/technic/machines/switching_station_globalstep.lua @@ -86,12 +86,13 @@ minetest.register_globalstep(function(dtime) for network_id, switch in pairs(switches) do local pos = technic.network2sw_pos(network_id) + local network = technic.networks[network_id] local diff = now - switch.time minetest.get_voxel_manip(pos, pos) local node = minetest.get_node(pos) - if node.name ~= "technic:switching_station" then + if network == nil or node.name ~= "technic:switching_station" then -- station vanished switches[network_id] = nil @@ -102,13 +103,11 @@ minetest.register_globalstep(function(dtime) if switch.skip < 1 then local start = minetest.get_us_time() - technic.switching_station_run(pos) + technic.network_run(network_id) local switch_diff = minetest.get_us_time() - start - local meta = minetest.get_meta(pos) - -- set lag in microseconds into the "lag" meta field - meta:set_int("lag", switch_diff) + network.lag = switch_diff -- overload detection if switch_diff > 250000 then diff --git a/technic/spec/fixtures/minetest.lua b/technic/spec/fixtures/minetest.lua index f035b28..b759cda 100644 --- a/technic/spec/fixtures/minetest.lua +++ b/technic/spec/fixtures/minetest.lua @@ -1,6 +1,13 @@ local function noop(...) end local function dummy_coords(...) return { x = 123, y = 123, z = 123 } end +_G.world = { nodes = {} } +local world = _G.world +_G.world.set_node = function(pos, node) + local hash = minetest.hash_node_position(pos) + world.nodes[hash] = node +end + _G.core = {} _G.minetest = _G.core @@ -64,12 +71,26 @@ _G.minetest.register_lbm = noop _G.minetest.register_abm = noop _G.minetest.register_chatcommand = noop _G.minetest.chat_send_player = noop +_G.minetest.register_alias = noop _G.minetest.register_craftitem = noop _G.minetest.register_craft = noop +_G.minetest.register_node = noop _G.minetest.register_on_placenode = noop _G.minetest.register_on_dignode = noop _G.minetest.item_drop = noop +_G.minetest.get_node = function(pos) + local hash = minetest.hash_node_position(pos) + return world.nodes[hash] or {name="IGNORE",param2=0} +end + _G.minetest.get_modpath = function(...) return "./unit_test_modpath" end _G.minetest.get_pointed_thing_position = dummy_coords + +-- +-- Minetest default noop table +-- +local default = { __index = function(...) return function(...)end end } +_G.default = {} +setmetatable(_G.default, default) diff --git a/technic/spec/fixtures/network.lua b/technic/spec/fixtures/network.lua index 7f1af94..ef06a92 100644 --- a/technic/spec/fixtures/network.lua +++ b/technic/spec/fixtures/network.lua @@ -1,5 +1,37 @@ +local world = { + {{x=100,y=100,z=100}, "technic:lv_cable"}, + {{x=101,y=100,z=100}, "technic:lv_cable"}, + {{x=102,y=100,z=100}, "technic:lv_cable"}, + {{x=103,y=100,z=100}, "technic:lv_cable"}, + {{x=100,y=101,z=100}, "technic:switching_station"}, + + {{x=100,y=200,z=100}, "technic:mv_cable"}, + {{x=101,y=200,z=100}, "technic:mv_cable"}, + {{x=102,y=200,z=100}, "technic:mv_cable"}, + {{x=103,y=200,z=100}, "technic:mv_cable"}, + {{x=100,y=201,z=100}, "technic:switching_station"}, + + {{x=100,y=300,z=100}, "technic:hv_cable"}, + {{x=101,y=300,z=100}, "technic:hv_cable"}, + {{x=102,y=300,z=100}, "technic:hv_cable"}, + {{x=103,y=300,z=100}, "technic:hv_cable"}, + {{x=100,y=301,z=100}, "technic:switching_station"}, +} + +-- Build world for tests +for _,node in ipairs(world) do + _G.world.set_node(node[1], {name=node[2], param2=0}) +end + _G.technic = {} _G.technic.S = string.format +_G.technic.getter = function(...) return "" end sourcefile("register") + +sourcefile("machines/register/cables") + +sourcefile("machines/LV/cables") +sourcefile("machines/MV/cables") +sourcefile("machines/HV/cables") diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua index ffe1c23..575b150 100644 --- a/technic/spec/network_spec.lua +++ b/technic/spec/network_spec.lua @@ -37,6 +37,17 @@ describe("Power network helper", function() assert.same(net_id, technic.sw_pos2network(sw_pos) ) end) + it("returns nil tier for empty position", function() + assert.is_nil(technic.sw_pos2tier({x=9999,y=9999,z=9999})) + end) + + it("returns correct tier for switching station position", function() + -- World is defined in fixtures/network.lua + assert.same("LV", technic.sw_pos2tier({x=100,y=101,z=100})) + assert.same("MV", technic.sw_pos2tier({x=100,y=201,z=100})) + assert.same("HV", technic.sw_pos2tier({x=100,y=301,z=100})) + end) + end) --[[ TODO: From 193447ea205886f2dea6cb84048ac09c8c365a00 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 3 Oct 2020 16:34:42 +0300 Subject: [PATCH 06/20] Tests for network constructor Cleanup network.lua Add minetest vector.lua Add tests for network constructor --- technic/machines/network.lua | 15 +- technic/spec/fixtures/minetest.lua | 3 +- .../minetest/{ => common}/misc_helpers.lua | 0 .../spec/fixtures/minetest/common/vector.lua | 242 ++++++++++++++++++ technic/spec/fixtures/network.lua | 25 +- technic/spec/fixtures/pipeworks.lua | 2 + technic/spec/network_spec.lua | 50 +++- technic/spec/test_helpers.lua | 42 +++ 8 files changed, 359 insertions(+), 20 deletions(-) rename technic/spec/fixtures/minetest/{ => common}/misc_helpers.lua (100%) create mode 100644 technic/spec/fixtures/minetest/common/vector.lua create mode 100644 technic/spec/fixtures/pipeworks.lua diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 08182b3..64b3d9b 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -173,12 +173,10 @@ end local function add_cable_node(nodes, pos, network_id, queue) local node_id = poshash(pos) technic.cables[node_id] = network_id - if nodes[node_id] then - return false + if not nodes[node_id] then + nodes[node_id] = pos + queue[#queue + 1] = pos end - nodes[node_id] = pos - -- Also add cables to queue - queue[#queue + 1] = pos end -- Generic function to add found connected nodes to the right classification array @@ -251,6 +249,7 @@ function technic.add_network_branch(queue, sw_pos, network) local all_nodes = network.all_nodes -- Hash table local network_id = network.id local tier = network.tier + local machines = technic.machines[tier] while next(queue) do local to_visit = {} for _, pos in ipairs(queue) do @@ -259,7 +258,7 @@ function technic.add_network_branch(queue, sw_pos, network) return end traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, - technic.machines[tier], tier, sw_pos, network_id, to_visit) + machines, tier, sw_pos, network_id, to_visit) end queue = to_visit end @@ -283,10 +282,6 @@ function technic.build_network(network_id) local queue = {} add_cable_node(network.all_nodes, technic.network2pos(network_id), network_id, queue) technic.add_network_branch(queue, sw_pos, network) - -- Flatten hashes to optimize iterations - --network.PR_nodes = flatten(network.PR_nodes) -- Already processed as indexed array - --network.BA_nodes = flatten(network.BA_nodes) -- Already processed as indexed array - --network.RE_nodes = flatten(network.RE_nodes) -- Already processed as indexed array network.battery_count = #network.BA_nodes -- Add newly built network to cache array technic.networks[network_id] = network diff --git a/technic/spec/fixtures/minetest.lua b/technic/spec/fixtures/minetest.lua index b759cda..67c3de8 100644 --- a/technic/spec/fixtures/minetest.lua +++ b/technic/spec/fixtures/minetest.lua @@ -58,7 +58,8 @@ _G.core.register_on_joinplayer = noop _G.core.register_on_leaveplayer = noop fixture("minetest/game/misc") -fixture("minetest/misc_helpers") +fixture("minetest/common/misc_helpers") +fixture("minetest/common/vector") _G.minetest.registered_nodes = { testnode1 = {}, diff --git a/technic/spec/fixtures/minetest/misc_helpers.lua b/technic/spec/fixtures/minetest/common/misc_helpers.lua similarity index 100% rename from technic/spec/fixtures/minetest/misc_helpers.lua rename to technic/spec/fixtures/minetest/common/misc_helpers.lua diff --git a/technic/spec/fixtures/minetest/common/vector.lua b/technic/spec/fixtures/minetest/common/vector.lua new file mode 100644 index 0000000..d6437de --- /dev/null +++ b/technic/spec/fixtures/minetest/common/vector.lua @@ -0,0 +1,242 @@ + +vector = {} + +function vector.new(a, b, c) + if type(a) == "table" then + assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()") + return {x=a.x, y=a.y, z=a.z} + elseif a then + assert(b and c, "Invalid arguments for vector.new()") + return {x=a, y=b, z=c} + end + return {x=0, y=0, z=0} +end + +function vector.equals(a, b) + return a.x == b.x and + a.y == b.y and + a.z == b.z +end + +function vector.length(v) + return math.hypot(v.x, math.hypot(v.y, v.z)) +end + +function vector.normalize(v) + local len = vector.length(v) + if len == 0 then + return {x=0, y=0, z=0} + else + return vector.divide(v, len) + end +end + +function vector.floor(v) + return { + x = math.floor(v.x), + y = math.floor(v.y), + z = math.floor(v.z) + } +end + +function vector.round(v) + return { + x = math.floor(v.x + 0.5), + y = math.floor(v.y + 0.5), + z = math.floor(v.z + 0.5) + } +end + +function vector.apply(v, func) + return { + x = func(v.x), + y = func(v.y), + z = func(v.z) + } +end + +function vector.distance(a, b) + local x = a.x - b.x + local y = a.y - b.y + local z = a.z - b.z + return math.hypot(x, math.hypot(y, z)) +end + +function vector.direction(pos1, pos2) + return vector.normalize({ + x = pos2.x - pos1.x, + y = pos2.y - pos1.y, + z = pos2.z - pos1.z + }) +end + +function vector.angle(a, b) + local dotp = vector.dot(a, b) + local cp = vector.cross(a, b) + local crossplen = vector.length(cp) + return math.atan2(crossplen, dotp) +end + +function vector.dot(a, b) + return a.x * b.x + a.y * b.y + a.z * b.z +end + +function vector.cross(a, b) + return { + x = a.y * b.z - a.z * b.y, + y = a.z * b.x - a.x * b.z, + z = a.x * b.y - a.y * b.x + } +end + +function vector.add(a, b) + if type(b) == "table" then + return {x = a.x + b.x, + y = a.y + b.y, + z = a.z + b.z} + else + return {x = a.x + b, + y = a.y + b, + z = a.z + b} + end +end + +function vector.subtract(a, b) + if type(b) == "table" then + return {x = a.x - b.x, + y = a.y - b.y, + z = a.z - b.z} + else + return {x = a.x - b, + y = a.y - b, + z = a.z - b} + end +end + +function vector.multiply(a, b) + if type(b) == "table" then + return {x = a.x * b.x, + y = a.y * b.y, + z = a.z * b.z} + else + return {x = a.x * b, + y = a.y * b, + z = a.z * b} + end +end + +function vector.divide(a, b) + if type(b) == "table" then + return {x = a.x / b.x, + y = a.y / b.y, + z = a.z / b.z} + else + return {x = a.x / b, + y = a.y / b, + z = a.z / b} + end +end + +function vector.offset(v, x, y, z) + return {x = v.x + x, + y = v.y + y, + z = v.z + z} +end + +function vector.sort(a, b) + return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, + {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} +end + +local function sin(x) + if x % math.pi == 0 then + return 0 + else + return math.sin(x) + end +end + +local function cos(x) + if x % math.pi == math.pi / 2 then + return 0 + else + return math.cos(x) + end +end + +function vector.rotate_around_axis(v, axis, angle) + local cosangle = cos(angle) + local sinangle = sin(angle) + axis = vector.normalize(axis) + -- https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula + local dot_axis = vector.multiply(axis, vector.dot(axis, v)) + local cross = vector.cross(v, axis) + return vector.new( + cross.x * sinangle + (v.x - dot_axis.x) * cosangle + dot_axis.x, + cross.y * sinangle + (v.y - dot_axis.y) * cosangle + dot_axis.y, + cross.z * sinangle + (v.z - dot_axis.z) * cosangle + dot_axis.z + ) +end + +function vector.rotate(v, rot) + local sinpitch = sin(-rot.x) + local sinyaw = sin(-rot.y) + local sinroll = sin(-rot.z) + local cospitch = cos(rot.x) + local cosyaw = cos(rot.y) + local cosroll = math.cos(rot.z) + -- Rotation matrix that applies yaw, pitch and roll + local matrix = { + { + sinyaw * sinpitch * sinroll + cosyaw * cosroll, + sinyaw * sinpitch * cosroll - cosyaw * sinroll, + sinyaw * cospitch, + }, + { + cospitch * sinroll, + cospitch * cosroll, + -sinpitch, + }, + { + cosyaw * sinpitch * sinroll - sinyaw * cosroll, + cosyaw * sinpitch * cosroll + sinyaw * sinroll, + cosyaw * cospitch, + }, + } + -- Compute matrix multiplication: `matrix` * `v` + return vector.new( + matrix[1][1] * v.x + matrix[1][2] * v.y + matrix[1][3] * v.z, + matrix[2][1] * v.x + matrix[2][2] * v.y + matrix[2][3] * v.z, + matrix[3][1] * v.x + matrix[3][2] * v.y + matrix[3][3] * v.z + ) +end + +function vector.dir_to_rotation(forward, up) + forward = vector.normalize(forward) + local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0} + if not up then + return rot + end + assert(vector.dot(forward, up) < 0.000001, + "Invalid vectors passed to vector.dir_to_rotation().") + up = vector.normalize(up) + -- Calculate vector pointing up with roll = 0, just based on forward vector. + local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot) + -- 'forwup' and 'up' are now in a plane with 'forward' as normal. + -- The angle between them is the absolute of the roll value we're looking for. + rot.z = vector.angle(forwup, up) + + -- Since vector.angle never returns a negative value or a value greater + -- than math.pi, rot.z has to be inverted sometimes. + -- To determine wether this is the case, we rotate the up vector back around + -- the forward vector and check if it worked out. + local back = vector.rotate_around_axis(up, forward, -rot.z) + + -- We don't use vector.equals for this because of floating point imprecision. + if (back.x - forwup.x) * (back.x - forwup.x) + + (back.y - forwup.y) * (back.y - forwup.y) + + (back.z - forwup.z) * (back.z - forwup.z) > 0.0000001 then + rot.z = -rot.z + end + return rot +end diff --git a/technic/spec/fixtures/network.lua b/technic/spec/fixtures/network.lua index ef06a92..dc3d37f 100644 --- a/technic/spec/fixtures/network.lua +++ b/technic/spec/fixtures/network.lua @@ -4,19 +4,34 @@ local world = { {{x=101,y=100,z=100}, "technic:lv_cable"}, {{x=102,y=100,z=100}, "technic:lv_cable"}, {{x=103,y=100,z=100}, "technic:lv_cable"}, + {{x=104,y=100,z=100}, "technic:lv_cable"}, {{x=100,y=101,z=100}, "technic:switching_station"}, {{x=100,y=200,z=100}, "technic:mv_cable"}, {{x=101,y=200,z=100}, "technic:mv_cable"}, {{x=102,y=200,z=100}, "technic:mv_cable"}, {{x=103,y=200,z=100}, "technic:mv_cable"}, + {{x=104,y=200,z=100}, "technic:mv_cable"}, {{x=100,y=201,z=100}, "technic:switching_station"}, {{x=100,y=300,z=100}, "technic:hv_cable"}, {{x=101,y=300,z=100}, "technic:hv_cable"}, {{x=102,y=300,z=100}, "technic:hv_cable"}, {{x=103,y=300,z=100}, "technic:hv_cable"}, + {{x=104,y=300,z=100}, "technic:hv_cable"}, {{x=100,y=301,z=100}, "technic:switching_station"}, + + -- For network lookup function -> returns correct network for position + {{x=100,y=500,z=100}, "technic:hv_cable"}, + {{x=101,y=500,z=100}, "technic:hv_cable"}, + {{x=102,y=500,z=100}, "technic:hv_cable"}, + {{x=103,y=500,z=100}, "technic:hv_cable"}, + {{x=104,y=500,z=100}, "technic:hv_cable"}, + {{x=100,y=501,z=100}, "technic:hv_generator"}, + {{x=101,y=501,z=100}, "technic:hv_cable"}, + {{x=102,y=501,z=100}, "technic:switching_station"}, + {{x=100,y=502,z=100}, "technic:hv_cable"}, + {{x=101,y=502,z=100}, "technic:hv_cable"}, } -- Build world for tests @@ -27,11 +42,9 @@ end _G.technic = {} _G.technic.S = string.format _G.technic.getter = function(...) return "" end +_G.technic.get_or_load_node = minetest.get_node sourcefile("register") - -sourcefile("machines/register/cables") - -sourcefile("machines/LV/cables") -sourcefile("machines/MV/cables") -sourcefile("machines/HV/cables") +technic.register_tier("LV", "Busted LV") +technic.register_tier("MV", "Busted MV") +technic.register_tier("HV", "Busted HV") diff --git a/technic/spec/fixtures/pipeworks.lua b/technic/spec/fixtures/pipeworks.lua new file mode 100644 index 0000000..80a4441 --- /dev/null +++ b/technic/spec/fixtures/pipeworks.lua @@ -0,0 +1,2 @@ + +_G.pipeworks = {} diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua index 575b150..353fd7d 100644 --- a/technic/spec/network_spec.lua +++ b/technic/spec/network_spec.lua @@ -9,10 +9,19 @@ fixture("minetest") fixture("minetest/player") fixture("minetest/protection") +fixture("pipeworks") fixture("network") sourcefile("machines/network") +sourcefile("machines/register/cables") +sourcefile("machines/LV/cables") +sourcefile("machines/MV/cables") +sourcefile("machines/HV/cables") + +sourcefile("machines/register/generator") +sourcefile("machines/HV/generator") + describe("Power network helper", function() -- Simple network position fixtures @@ -32,9 +41,9 @@ describe("Power network helper", function() end) it("returns correct network for position", function() - pending("TODO: Test requires real network fixture") - assert.same(net_id, technic.pos2network(pos) ) - assert.same(net_id, technic.sw_pos2network(sw_pos) ) + local net_id = technic.create_network({x=100,y=501,z=100}) + assert.same(net_id, technic.pos2network({x=100,y=500,z=100}) ) + assert.same(net_id, technic.sw_pos2network({x=100,y=501,z=100}) ) end) it("returns nil tier for empty position", function() @@ -50,6 +59,41 @@ describe("Power network helper", function() end) + describe("network constructors/destructors", function() + + -- Build network + local net_id = technic.create_network({x=100,y=501,z=100}) + assert.is_number(net_id) + + it("creates network", function() + assert.is_hashed(technic.networks[net_id]) + end) + + it("builds network", function() + local net = technic.networks[net_id] + -- Network table is valid + assert.is_indexed(net.SP_nodes) + assert.is_indexed(net.PR_nodes) + assert.is_indexed(net.RE_nodes) + assert.is_indexed(net.BA_nodes) + assert.equals(9, count(net.all_nodes)) + assert.is_hashed(net.all_nodes) + end) + + it("does not add duplicates to network", function() + local net = technic.networks[net_id] + -- Local network table is still valid + assert.equals(0, count(net.SP_nodes)) + assert.equals(1, count(net.PR_nodes)) + assert.equals(0, count(net.RE_nodes)) + assert.equals(0, count(net.BA_nodes)) + assert.equals(9, count(net.all_nodes)) + -- FIXME: This might be wrong if technic.cables should contain only cables and not machines + assert.equals(9, count(technic.cables)) + end) + + end) + --[[ TODO: technic.remove_network(network_id) technic.pos2network(pos) diff --git a/technic/spec/test_helpers.lua b/technic/spec/test_helpers.lua index 54a7800..ae3be41 100644 --- a/technic/spec/test_helpers.lua +++ b/technic/spec/test_helpers.lua @@ -24,3 +24,45 @@ end function sourcefile(name) dofile(source_path(name) .. ".lua") end + +function count(t) + if type(t) == "table" or type(t) == "userdata" then + local c = 0 + for a,b in pairs(t) do + c = c + 1 + end + return c + end +end + +local function sequential(t) + local p = 1 + for i,_ in pairs(t) do + if i ~= p then return false end + p = p +1 + end + return true +end + +local function tabletype(t) + if type(t) == "table" or type(t) == "userdata" then + if count(t) == #t and sequential(t) then + return "array" + else + return "hash" + end + end +end + +-- Busted test framework extensions + +local assert = require('luassert.assert') +local say = require("say") + +local function is_array(_,args) return tabletype(args[1]) == "array" end +say:set("assertion.is_indexed.negative", "Expected %s to be indexed array") +assert:register("assertion", "is_indexed", is_array, "assertion.is_indexed.negative") + +local function is_hash(_,args) return tabletype(args[1]) == "hash" end +say:set("assertion.is_hashed.negative", "Expected %s to be hash table") +assert:register("assertion", "is_hashed", is_hash, "assertion.is_hashed.negative") From 11fe7a7bf771317264bf2e1801db5431ed6ca58c Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sun, 4 Oct 2020 03:22:35 +0300 Subject: [PATCH 07/20] Add machines to all_nodes, no holes in arrays, check if node is added already Comment out debug prints Network node place/dig, simple remove_network test Luacheck warnings Fix network check Fix overridden global table --- technic/machines/network.lua | 45 ++++++++++------ technic/machines/register/cables.lua | 80 ++++++++++++---------------- technic/spec/network_spec.lua | 11 ++-- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 64b3d9b..b445aa4 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -44,20 +44,26 @@ function technic.remove_network(network_id) end end technic.networks[network_id] = nil - --print(string.format("NET DESTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) + --print(string.format("technic.remove_network(%.17g) at %s", network_id, minetest.pos_to_string(technic.network2pos(network_id)))) end -- Remove machine or cable from network -local network_node_tables = {"PR_nodes","BA_nodes","RE_nodes","SP_nodes","all_nodes"} +local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes","SP_nodes"} function technic.remove_network_node(network_id, pos) local network = technic.networks[network_id] if not network then return end - technic.cables[poshash(pos)] = nil - for _,tblname in ipairs(network_node_tables) do - local table = network[tblname] - for id,mpos in pairs(table) do + -- Clear hash tables, cannot use table.remove + local node_id = poshash(pos) + technic.cables[node_id] = nil + network.all_nodes[node_id] = nil + -- Clear indexed arrays, do NOT leave holes + for _,tblname in ipairs(network_node_arrays) do + local tbl = network[tblname] + for i=#tbl,1,-1 do + local mpos = tbl[i] if mpos.x == pos.x and mpos.y == pos.y and mpos.z == pos.z then - table[id] = nil + table.remove(tbl, i) + break end end end @@ -164,9 +170,11 @@ local function attach_network_machine(network_id, pos) end -- Add a machine node to the LV/MV/HV network -local function add_network_node(nodes, pos, network_id) - technic.cables[poshash(pos)] = network_id +local function add_network_node(nodes, pos, network_id, all_nodes) table.insert(nodes, pos) + local node_id = poshash(pos) + technic.cables[node_id] = network_id + all_nodes[node_id] = pos end -- Add a wire node to the LV/MV/HV network @@ -192,17 +200,17 @@ local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, mac if machines[name] == technic.producer then attach_network_machine(network_id, pos) - add_network_node(PR_nodes, pos, network_id) + add_network_node(PR_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.receiver then attach_network_machine(network_id, pos) - add_network_node(RE_nodes, pos, network_id) + add_network_node(RE_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.producer_receiver then --attach_network_machine(network_id, pos) - add_network_node(PR_nodes, pos, network_id) - add_network_node(RE_nodes, pos, network_id) + add_network_node(PR_nodes, pos, network_id, all_nodes) + add_network_node(RE_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.battery then attach_network_machine(network_id, pos) - add_network_node(BA_nodes, pos, network_id) + add_network_node(BA_nodes, pos, network_id, all_nodes) end technic.touch_node(tier, pos, 2) -- Touch node @@ -219,7 +227,9 @@ local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, ma {x=pos.x, y=pos.y, z=pos.z+1}, {x=pos.x, y=pos.y, z=pos.z-1}} for i, cur_pos in pairs(positions) do - check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) + if not all_nodes[poshash(cur_pos)] then + check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) + end end end @@ -242,6 +252,7 @@ local function get_network(network_id, tier) end function technic.add_network_branch(queue, sw_pos, network) + --print(string.format("technic.add_network_branch(%s, %s, %.17g)",queue,minetest.pos_to_string(sw_pos),network.id)) -- Adds whole branch to network, queue positions can be used to bypass sub branches local PR_nodes = network.PR_nodes -- Indexed array local BA_nodes = network.BA_nodes -- Indexed array @@ -265,12 +276,12 @@ function technic.add_network_branch(queue, sw_pos, network) end function technic.build_network(network_id) - print(string.format("NET CONSTRUCT %s (%.17g)", minetest.pos_to_string(technic.network2pos(network_id)), network_id)) + --print(string.format("technic.build_network(%.17g) at %s", network_id, minetest.pos_to_string(technic.network2pos(network_id)))) technic.remove_network(network_id) local sw_pos = technic.network2sw_pos(network_id) local tier = technic.sw_pos2tier(sw_pos) if not tier then - print(string.format("Cannot build network, cannot get tier for switching station at %s", minetest.pos_to_string(sw_pos))) + --print(string.format("Cannot build network, cannot get tier for switching station at %s", minetest.pos_to_string(sw_pos))) return end local network = { diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index e625422..994f82e 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -39,40 +39,47 @@ local function check_connections(pos) return connections end -local function connect_networks(pos, positions) - -- TODO: Allow connecting networks: - -- If neighbor branch does not belong to any network attach it to this network - -- If neighbor branch belongs to another network check which one has least #all_nodes and rebuild that - for _,connected_pos in pairs(positions) do - local net = technic.pos2network(connected_pos) - if net and technic.networks[net] then - -- Not a dead end, so the whole network needs to be recalculated - technic.remove_network(net) - end - end -end - local function place_network_node(pos, node) local positions = check_connections(pos) if #positions < 1 then return end local dead_end = #positions == 1 - -- Dead end placed, add it to the network - -- Get the network - local network_id = technic.pos2network(positions[1]) - if not network_id then + -- Get the network if there's any + local network_id + for _,connect_pos in ipairs(positions) do + network_id = technic.pos2network(connect_pos) + if network_id then break end + end + local network = technic.networks[network_id] + if not network then -- We're evidently not on a network, nothing to add ourselves to return end - local network = technic.networks[network_id] - local tier = network.tier if not dead_end then - return connect_networks(pos, positions) + -- TODO: Allow connecting networks: + -- If neighbor branch belongs to another network check which one has least #all_nodes and rebuild that branch + local removed = 0 + for _,connect_pos in ipairs(positions) do + local net = technic.pos2network(connect_pos) + if net and net ~= network_id then + -- Remove network if position belongs to another network + technic.remove_network(network_id) + removed = removed + 1 + end + end + -- Do not build whole network here if something was cleaned up, instead allow switch ABM to take care of it + if removed > 0 then return end + -- Nodes around do not belong to another network but missing branches must be added as whole + local sw_pos = technic.network2sw_pos(network_id) + technic.add_network_branch({pos}, sw_pos, network) + return end + -- Dead end placed, add it to the network -- Actually add it to the (cached) network - -- This is similar to check_node_subp + -- TODO: This should use check_node_subp or add_network_branch + local tier = network.tier local pos_hash = minetest.hash_node_position(pos) technic.cables[pos_hash] = network_id pos.visited = 1 @@ -95,40 +102,23 @@ end local function remove_network_node(pos) -- Get the network local network_id = technic.pos2network(pos) - if not network_id then - -- We're evidently not on a network, nothing to add ourselves to - return - end + if not network_id then return end local positions = check_connections(pos) if #positions < 1 then return end local dead_end = #positions == 1 - if not dead_end then + if dead_end then + -- Dead end machine or cable removed, remove it from the network + technic.remove_network_node(network_id, pos) + else -- TODO: Check branches around and switching stations for branches: -- remove branches that do not have switching station. -- remove branches not connected to another branch. -- do not rebuild networks here, leave that for ABM to reduce unnecessary cache building. - -- For now remove network like how it was done before: + -- To do all this network must be aware of individual branches, might not be worth it... + -- For now remove whole network and let ABM rebuild it technic.remove_network(network_id) - return - end - - -- Dead end removed, remove it from the network - local network = technic.networks[network_id] - technic.cables[minetest.hash_node_position(pos)] = nil - -- TODO: Looping over all keys in network is not right way to do this, should fix to use known machine types. - -- Better to add network function that knows what to remove, something like technic.remove_node(network_id, pos) - for tblname,table in pairs(network) do - if type(table) == "table" then - for machinenum,machine in pairs(table) do - if machine.x == pos.x - and machine.y == pos.y - and machine.z == pos.z then - table[machinenum] = nil - end - end - end end end diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua index 353fd7d..afb7280 100644 --- a/technic/spec/network_spec.lua +++ b/technic/spec/network_spec.lua @@ -92,13 +92,16 @@ describe("Power network helper", function() assert.equals(9, count(technic.cables)) end) + it("removes network", function() + technic.remove_network(net_id) + assert.is_nil(technic.networks[net_id]) + -- TODO: Verify that there's no lefover positions in technic.cables + end) + end) --[[ TODO: - technic.remove_network(network_id) - technic.pos2network(pos) - technic.network2pos(network_id) - technic.network2sw_pos(network_id) + technic.remove_network_node --]] describe("Power network timeout functions technic.touch_node and technic.get_timeout", function() From c6aa02897bc7e5eeb680fa6f4c48f034cf2acfcc Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Mon, 12 Oct 2020 17:00:23 +0300 Subject: [PATCH 08/20] Network handles timeouts, remove second switch ABM --- technic/machines/network.lua | 18 ++++- technic/machines/switching_station.lua | 2 +- .../machines/switching_station_globalstep.lua | 81 +++++-------------- 3 files changed, 34 insertions(+), 67 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index b445aa4..981bb5f 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -4,7 +4,9 @@ local S = technic.getter local switch_max_range = tonumber(minetest.settings:get("technic.switch_max_range") or "256") +local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800") +technic.active_networks = {} technic.networks = {} technic.cables = {} @@ -18,10 +20,12 @@ function technic.create_network(sw_pos) end function technic.activate_network(network_id, timeout) - assert(network_id) + -- timeout is optional ttl for network in seconds, if not specified use default local network = technic.networks[network_id] - if network and (timeout or network.timeout < 4) then - network.timeout = timeout or 4 + if network then + -- timeout is absolute time in microseconds + network.timeout = minetest.get_us_time() + ((timeout or off_delay_seconds) * 1000 * 1000) + technic.active_networks[network_id] = network end end @@ -44,6 +48,7 @@ function technic.remove_network(network_id) end end technic.networks[network_id] = nil + technic.active_networks[network_id] = nil --print(string.format("technic.remove_network(%.17g) at %s", network_id, minetest.pos_to_string(technic.network2pos(network_id)))) end @@ -285,9 +290,14 @@ function technic.build_network(network_id) return end local network = { + -- Basic network data and lookup table for attached nodes (no switching stations) id = network_id, tier = tier, all_nodes = {}, + -- Indexed arrays for iteration by machine type SP_nodes = {}, PR_nodes = {}, RE_nodes = {}, BA_nodes = {}, - supply = 0, demand = 0, timeout = 4, battery_charge = 0, battery_charge_max = 0, + -- Power generation, usage and capacity related variables + supply = 0, demand = 0, battery_charge = 0, battery_charge_max = 0, + -- Network activation and excution control + timeout = 0, skip = 0, } -- Add first cable (one that is holding network id) and build network local queue = {} diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 0bd8ef1..f0e66cd 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -173,7 +173,7 @@ minetest.register_abm({ end technic.network_infotext(network_id, infotext) else - -- Network exists and is not overloaded, reactivate for 4 seconds + -- Network exists and is not overloaded, reactivate network technic.activate_network(network_id) infotext = technic.network_infotext(network_id) end diff --git a/technic/machines/switching_station_globalstep.lua b/technic/machines/switching_station_globalstep.lua index bed6f07..98ee41a 100644 --- a/technic/machines/switching_station_globalstep.lua +++ b/technic/machines/switching_station_globalstep.lua @@ -1,22 +1,6 @@ local has_monitoring_mod = minetest.get_modpath("monitoring") -local switches = {} -- pos_hash -> { time = time_us } - -local function get_switch_data(network_id) - local switch = switches[network_id] - - if not switch then - switch = { - time = 0, - skip = 0 - } - switches[network_id] = switch - end - - return switch -end - local active_switching_stations_metric, switching_stations_usage_metric if has_monitoring_mod then @@ -31,30 +15,10 @@ if has_monitoring_mod then ) end --- collect all active switching stations -minetest.register_abm({ - nodenames = {"technic:switching_station"}, - label = "Switching Station", - interval = 1, - chance = 1, - action = function(pos) - local network_id = technic.sw_pos2network(pos) - if network_id then - if technic.is_overloaded(network_id) then - switches[network_id] = nil - else - local switch = get_switch_data(network_id) - switch.time = minetest.get_us_time() - end - end - end -}) - -- the interval between technic_run calls local technic_run_interval = 1.0 -- iterate over all collected switching stations and execute the technic_run function -local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800") local timer = 0 minetest.register_globalstep(function(dtime) timer = timer + dtime @@ -80,27 +44,24 @@ minetest.register_globalstep(function(dtime) local now = minetest.get_us_time() - local off_delay_micros = off_delay_seconds*1000*1000 - local active_switches = 0 - for network_id, switch in pairs(switches) do + for network_id, network in pairs(technic.active_networks) do local pos = technic.network2sw_pos(network_id) - local network = technic.networks[network_id] - local diff = now - switch.time - minetest.get_voxel_manip(pos, pos) - local node = minetest.get_node(pos) + local node = technic.get_or_load_node(pos) or minetest.get_node(pos) - if network == nil or node.name ~= "technic:switching_station" then + if node.name ~= "technic:switching_station" then -- station vanished - switches[network_id] = nil + technic.active_networks[network_id] = nil - elseif diff < off_delay_micros then + elseif network.timeout > now then -- station active active_switches = active_switches + 1 - if switch.skip < 1 then + if network.skip > 0 then + network.skip = network.skip - 1 + else local start = minetest.get_us_time() technic.network_run(network_id) @@ -111,34 +72,30 @@ minetest.register_globalstep(function(dtime) -- overload detection if switch_diff > 250000 then - switch.skip = 30 + network.skip = 30 elseif switch_diff > 150000 then - switch.skip = 20 + network.skip = 20 elseif switch_diff > 75000 then - switch.skip = 10 + network.skip = 10 elseif switch_diff > 50000 then - switch.skip = 2 + network.skip = 2 end - if switch.skip > 0 then + if network.skip > 0 then -- calculate efficiency in percent and display it - local efficiency = math.floor(1/switch.skip*100) + local efficiency = math.floor(1/network.skip*100) technic.network_infotext(network_id, "Polyfuse triggered, current efficiency: " .. efficiency .. "% generated lag : " .. math.floor(switch_diff/1000) .. " ms") - -- remove laggy switching station from active index + -- remove laggy network from active index -- it will be reactivated when a player is near it - -- laggy switching stations won't work well in unloaded areas this way - switches[network_id] = nil + technic.active_networks[network_id] = nil end - - else - switch.skip = math.max(switch.skip - 1, 0) end else -- station timed out - switches[network_id] = nil + technic.active_networks[network_id] = nil end end @@ -152,9 +109,9 @@ minetest.register_globalstep(function(dtime) end) minetest.register_chatcommand("technic_flush_switch_cache", { - description = "removes all loaded switching stations from the cache", + description = "removes all loaded networks from the cache", privs = { server = true }, func = function() - switches = {} + technic.active_networks = {} end }) From 5efafaceb9c7687c459fa1deb0617b33d7710aeb Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Thu, 15 Oct 2020 14:28:25 +0300 Subject: [PATCH 09/20] (#100) Add compatibility hack for digtron --- technic/machines/compat/digtron.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/technic/machines/compat/digtron.lua b/technic/machines/compat/digtron.lua index ce8f74e..a3daaae 100644 --- a/technic/machines/compat/digtron.lua +++ b/technic/machines/compat/digtron.lua @@ -9,9 +9,8 @@ local function power_connector_compat() local digtron_technic_run = minetest.registered_nodes["digtron:power_connector"].technic_run minetest.override_item("digtron:power_connector",{ technic_run = function(pos, node) - local network_id = technic.cables[minetest.hash_node_position(pos)] - local sw_pos = network_id and minetest.get_position_from_hash(network_id) - if sw_pos then sw_pos.y = sw_pos.y + 1 end + local network_id = technic.pos2network(pos) + local sw_pos = network_id and technic.network2sw_pos(network_id) local meta = minetest.get_meta(pos) meta:set_string("HV_network", sw_pos and minetest.pos_to_string(sw_pos) or "") return digtron_technic_run(pos, node) From 16f0683c2145bc3cc457b30b9280ed34e33d1c79 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Fri, 16 Oct 2020 03:39:12 +0300 Subject: [PATCH 10/20] Network node placement, utilize locals for frequent names (#95) Place/remove network nodes. Cleanup sw_pos (#96) SC infotext / reduce timeout ABM workload --- technic/machines/network.lua | 106 ++++++++++++-------- technic/machines/register/cables.lua | 132 ++++++++++++------------- technic/machines/switching_station.lua | 66 ++++++++----- technic/spec/test_helpers.lua | 11 +++ 4 files changed, 179 insertions(+), 136 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 981bb5f..e5f9dad 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -7,8 +7,10 @@ local switch_max_range = tonumber(minetest.settings:get("technic.switch_max_rang local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800") technic.active_networks = {} -technic.networks = {} -technic.cables = {} +local networks = {} +technic.networks = networks +local cables = {} +technic.cables = cables local poshash = minetest.hash_node_position local hashpos = minetest.get_position_from_hash @@ -21,7 +23,7 @@ end function technic.activate_network(network_id, timeout) -- timeout is optional ttl for network in seconds, if not specified use default - local network = technic.networks[network_id] + local network = networks[network_id] if network then -- timeout is absolute time in microseconds network.timeout = minetest.get_us_time() + ((timeout or off_delay_seconds) * 1000 * 1000) @@ -41,13 +43,12 @@ end -- Destroy network data function technic.remove_network(network_id) - local cables = technic.cables for pos_hash,cable_net_id in pairs(cables) do if cable_net_id == network_id then cables[pos_hash] = nil end end - technic.networks[network_id] = nil + networks[network_id] = nil technic.active_networks[network_id] = nil --print(string.format("technic.remove_network(%.17g) at %s", network_id, minetest.pos_to_string(technic.network2pos(network_id)))) end @@ -55,11 +56,11 @@ end -- Remove machine or cable from network local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes","SP_nodes"} function technic.remove_network_node(network_id, pos) - local network = technic.networks[network_id] + local network = networks[network_id] if not network then return end -- Clear hash tables, cannot use table.remove local node_id = poshash(pos) - technic.cables[node_id] = nil + cables[node_id] = nil network.all_nodes[node_id] = nil -- Clear indexed arrays, do NOT leave holes for _,tblname in ipairs(network_node_arrays) do @@ -75,15 +76,15 @@ function technic.remove_network_node(network_id, pos) end function technic.sw_pos2network(pos) - return technic.cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})] + return cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})] end function technic.sw_pos2network(pos) - return technic.cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})] + return cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})] end function technic.pos2network(pos) - return technic.cables[poshash(pos)] + return cables[poshash(pos)] end function technic.network2pos(network_id) @@ -99,11 +100,11 @@ function technic.network2sw_pos(network_id) end function technic.network_infotext(network_id, text) - if technic.networks[network_id] == nil then return end + if networks[network_id] == nil then return end if text then - technic.networks[network_id].infotext = text + networks[network_id].infotext = text else - return technic.networks[network_id].infotext + return networks[network_id].infotext end end @@ -117,13 +118,14 @@ function technic.get_timeout(tier, pos) return node_timeout[tier][poshash(pos)] or 0 end -function technic.touch_node(tier, pos, timeout) +local function touch_node(tier, pos, timeout) if node_timeout[tier] == nil then -- this should get built up during registration node_timeout[tier] = {} end node_timeout[tier][poshash(pos)] = timeout or 2 end +technic.touch_node = touch_node -- -- Network overloading (incomplete cheat mitigation) @@ -131,16 +133,17 @@ end local overload_reset_time = tonumber(minetest.settings:get("technic.overload_reset_time") or "20") local overloaded_networks = {} -function technic.overload_network(network_id) - local network = technic.networks[network_id] +local function overload_network(network_id) + local network = networks[network_id] if network then network.supply = 0 network.battery_charge = 0 end overloaded_networks[network_id] = minetest.get_us_time() + (overload_reset_time * 1000 * 1000) end +technic.overload_network = overload_network -function technic.reset_overloaded(network_id) +local function reset_overloaded(network_id) local remaining = math.max(0, overloaded_networks[network_id] - minetest.get_us_time()) if remaining == 0 then -- Clear cache, remove overload and restart network @@ -150,10 +153,12 @@ function technic.reset_overloaded(network_id) -- Returns 0 when network reset or remaining time if reset timer has not expired yet return remaining end +technic.reset_overloaded = reset_overloaded -function technic.is_overloaded(network_id) +local function is_overloaded(network_id) return overloaded_networks[network_id] end +technic.is_overloaded = is_overloaded -- -- Functions to traverse the electrical network @@ -161,31 +166,31 @@ end local function attach_network_machine(network_id, pos) local pos_hash = poshash(pos) - local net_id_old = technic.cables[pos_hash] + local net_id_old = cables[pos_hash] if net_id_old == nil then - technic.cables[pos_hash] = network_id + cables[pos_hash] = network_id elseif net_id_old ~= network_id then -- do not allow running pos from multiple networks, also disable switch - technic.overload_network(network_id, pos) - technic.overload_network(net_id_old, pos) - technic.cables[pos_hash] = network_id + overload_network(network_id, pos) + overload_network(net_id_old, pos) + cables[pos_hash] = network_id local meta = minetest.get_meta(pos) meta:set_string("infotext",S("Network Overloaded")) end end -- Add a machine node to the LV/MV/HV network -local function add_network_node(nodes, pos, network_id, all_nodes) +local function add_network_machine(nodes, pos, network_id, all_nodes) table.insert(nodes, pos) local node_id = poshash(pos) - technic.cables[node_id] = network_id + cables[node_id] = network_id all_nodes[node_id] = pos end -- Add a wire node to the LV/MV/HV network local function add_cable_node(nodes, pos, network_id, queue) local node_id = poshash(pos) - technic.cables[node_id] = network_id + cables[node_id] = network_id if not nodes[node_id] then nodes[node_id] = pos queue[#queue + 1] = pos @@ -193,7 +198,7 @@ local function add_cable_node(nodes, pos, network_id, queue) end -- Generic function to add found connected nodes to the right classification array -local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, sw_pos, from_below, network_id, queue) +local function add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, network_id, queue) technic.get_or_load_node(pos) local name = minetest.get_node(pos).name @@ -205,25 +210,40 @@ local function check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, mac if machines[name] == technic.producer then attach_network_machine(network_id, pos) - add_network_node(PR_nodes, pos, network_id, all_nodes) + add_network_machine(PR_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.receiver then attach_network_machine(network_id, pos) - add_network_node(RE_nodes, pos, network_id, all_nodes) + add_network_machine(RE_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.producer_receiver then --attach_network_machine(network_id, pos) - add_network_node(PR_nodes, pos, network_id, all_nodes) - add_network_node(RE_nodes, pos, network_id, all_nodes) + add_network_machine(PR_nodes, pos, network_id, all_nodes) + add_network_machine(RE_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.battery then attach_network_machine(network_id, pos) - add_network_node(BA_nodes, pos, network_id, all_nodes) + add_network_machine(BA_nodes, pos, network_id, all_nodes) end - technic.touch_node(tier, pos, 2) -- Touch node + touch_node(tier, pos, 2) -- Touch node end end +-- Generic function to add single nodes to the right classification array of existing network +function technic.add_network_node(pos, network) + return add_network_node( + network.PR_nodes, + network.RE_nodes, + network.BA_nodes, + network.all_nodes, + pos, + technic.machines[network.tier], + network.tier, + network.id, + {} + ) +end + -- Traverse a network given a list of machines and a cable type name -local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, sw_pos, network_id, queue) +local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, network_id, queue) local positions = { {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x-1, y=pos.y, z=pos.z}, @@ -233,20 +253,19 @@ local function traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, ma {x=pos.x, y=pos.y, z=pos.z-1}} for i, cur_pos in pairs(positions) do if not all_nodes[poshash(cur_pos)] then - check_node_subp(PR_nodes, RE_nodes, BA_nodes, all_nodes, cur_pos, machines, tier, sw_pos, i == 3, network_id, queue) + add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, cur_pos, machines, tier, network_id, queue) end end end local function touch_nodes(list, tier) - local touch_node = technic.touch_node for _, pos in ipairs(list) do touch_node(tier, pos, 2) -- Touch node end end local function get_network(network_id, tier) - local cached = technic.networks[network_id] + local cached = networks[network_id] if cached and cached.tier == tier then touch_nodes(cached.PR_nodes, tier) touch_nodes(cached.BA_nodes, tier) @@ -256,8 +275,7 @@ local function get_network(network_id, tier) return technic.build_network(network_id) end -function technic.add_network_branch(queue, sw_pos, network) - --print(string.format("technic.add_network_branch(%s, %s, %.17g)",queue,minetest.pos_to_string(sw_pos),network.id)) +function technic.add_network_branch(queue, network) -- Adds whole branch to network, queue positions can be used to bypass sub branches local PR_nodes = network.PR_nodes -- Indexed array local BA_nodes = network.BA_nodes -- Indexed array @@ -266,6 +284,8 @@ function technic.add_network_branch(queue, sw_pos, network) local network_id = network.id local tier = network.tier local machines = technic.machines[tier] + local sw_pos = technic.network2sw_pos(network_id) + --print(string.format("technic.add_network_branch(%s, %s, %.17g)",queue,minetest.pos_to_string(sw_pos),network.id)) while next(queue) do local to_visit = {} for _, pos in ipairs(queue) do @@ -274,7 +294,7 @@ function technic.add_network_branch(queue, sw_pos, network) return end traverse_network(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, - machines, tier, sw_pos, network_id, to_visit) + machines, tier, network_id, to_visit) end queue = to_visit end @@ -302,10 +322,10 @@ function technic.build_network(network_id) -- Add first cable (one that is holding network id) and build network local queue = {} add_cable_node(network.all_nodes, technic.network2pos(network_id), network_id, queue) - technic.add_network_branch(queue, sw_pos, network) + technic.add_network_branch(queue, network) network.battery_count = #network.BA_nodes -- Add newly built network to cache array - technic.networks[network_id] = network + networks[network_id] = network -- And return producers, batteries and receivers (should this simply return network?) return network.PR_nodes, network.BA_nodes, network.RE_nodes end @@ -358,7 +378,7 @@ function technic.network_run(network_id) if tier then PR_nodes, BA_nodes, RE_nodes = get_network(network_id, tier) if technic.is_overloaded(network_id) then return end - network = technic.networks[network_id] + network = networks[network_id] else --dprint("Not connected to a network") technic.network_infotext(network_id, S("%s Has No Network"):format(S("Switching Station"))) diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 994f82e..94fd0ae 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -11,17 +11,17 @@ function technic.get_cable_tier(name) return cable_tier[name] end -local function check_connections(pos) +local function get_neighbors(pos, tier) -- Build a table of all machines -- TODO: Move this to network.lua -- TODO: Build table for current tier only, we do not want to test other tiers. - -- Make sure that multi tier machines work (currently supply converter). - local machines = {} - for tier,list in pairs(technic.machines) do - for k,v in pairs(list) do - machines[k] = v - end - end + -- TODO: Consider adding only single position for given network + -- TEST: Make sure that multi tier machines work (currently supply converter) + -- these should not be a problem but can return multiple tiers, currently + -- machines collector below only handles single tier. + -- TODO: technic.get_cable_tier works only with cables, should get tier of any network node here. + -- This is to convert this function into general neighbor lookup tool, not just from cable PoV. + local machines = technic.machines[tier] local connections = {} local positions = { {x=pos.x+1, y=pos.y, z=pos.z}, @@ -29,84 +29,75 @@ local function check_connections(pos) {x=pos.x, y=pos.y+1, z=pos.z}, {x=pos.x, y=pos.y-1, z=pos.z}, {x=pos.x, y=pos.y, z=pos.z+1}, - {x=pos.x, y=pos.y, z=pos.z-1}} - for _,connected_pos in pairs(positions) do + {x=pos.x, y=pos.y, z=pos.z-1}, + } + for _,connected_pos in ipairs(positions) do local name = minetest.get_node(connected_pos).name - if machines[name] or technic.get_cable_tier(name) then - table.insert(connections,connected_pos) + if machines[name] or tier == technic.get_cable_tier(name) then + table.insert(connections,{ + pos = connected_pos, + network = technic.networks[technic.pos2network(connected_pos)], + }) end end return connections end -local function place_network_node(pos, node) - local positions = check_connections(pos) - if #positions < 1 then return end - local dead_end = #positions == 1 - - -- Get the network if there's any - local network_id - for _,connect_pos in ipairs(positions) do - network_id = technic.pos2network(connect_pos) - if network_id then break end +local function place_network_node(pos, tier) + -- Get connections and primary network if there's any + local connections = get_neighbors(pos, tier) + if #connections < 1 then + return end - local network = technic.networks[network_id] + + -- Get first network from connections, this will be our primary network + local network + for _,connection in ipairs(connections) do + if connection.network then + network = connection.network + break + end + end + if not network then -- We're evidently not on a network, nothing to add ourselves to return end - if not dead_end then - -- TODO: Allow connecting networks: - -- If neighbor branch belongs to another network check which one has least #all_nodes and rebuild that branch - local removed = 0 - for _,connect_pos in ipairs(positions) do - local net = technic.pos2network(connect_pos) - if net and net ~= network_id then - -- Remove network if position belongs to another network - technic.remove_network(network_id) - removed = removed + 1 - end - end - -- Do not build whole network here if something was cleaned up, instead allow switch ABM to take care of it - if removed > 0 then return end - -- Nodes around do not belong to another network but missing branches must be added as whole - local sw_pos = technic.network2sw_pos(network_id) - technic.add_network_branch({pos}, sw_pos, network) + -- Attach to primary network, this must be done before building branches from this position + technic.add_network_node(pos, network) + if #connections == 1 then + -- Only single connected position and we are already attached to it return end - -- Dead end placed, add it to the network - -- Actually add it to the (cached) network - -- TODO: This should use check_node_subp or add_network_branch - local tier = network.tier - local pos_hash = minetest.hash_node_position(pos) - technic.cables[pos_hash] = network_id - pos.visited = 1 - if technic.is_tier_cable(name, tier) then - network.all_nodes[pos_hash] = pos - elseif technic.machines[tier][node.name] then - if technic.machines[tier][node.name] == technic.producer then - table.insert(network.PR_nodes,pos) - elseif technic.machines[tier][node.name] == technic.receiver then - table.insert(network.RE_nodes,pos) - elseif technic.machines[tier][node.name] == technic.producer_receiver then - table.insert(network.PR_nodes,pos) - table.insert(network.RE_nodes,pos) - elseif technic.machines[tier][node.name] == technic.battery then - table.insert(network.BA_nodes,pos) + -- Check if there's any neighbors that do not belong to chosen primary network and handle those + local removed = 0 + for _,connection in ipairs(connections) do + if connection.network then + if connection.network.id ~= network.id then + -- Remove network if position belongs to another network + -- FIXME: Network requires rebuild but avoid doing it here if possible. + technic.remove_network(connection.network.id) + connection.network = nil + removed = removed + 1 + end + else + -- There's node that does not belong to any network, attach whole branch + technic.add_network_branch({connection.pos}, network) end end + end -local function remove_network_node(pos) +local function remove_network_node(pos, tier) -- Get the network local network_id = technic.pos2network(pos) if not network_id then return end - local positions = check_connections(pos) - if #positions < 1 then return end - local dead_end = #positions == 1 + local connections = get_neighbors(pos, tier) + if #connections < 1 then return end + local dead_end = #connections == 1 if dead_end then -- Dead end machine or cable removed, remove it from the network @@ -179,8 +170,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, node_box = node_box, connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = function(pos) place_network_node(pos, node_name) end, - on_destruct = remove_network_node, + on_construct = function(pos) place_network_node(pos, tier) end, + on_destruct = function(pos) remove_network_node(pos, tier) end, }, override_cable)) local xyz = { @@ -206,6 +197,7 @@ function technic.register_cable(tier, size, description, prefix, override_cable, return "-"..p end end + -- TODO: Does this really need 6 different nodes? Use single node for cable plate if possible. for p, i in pairs(xyz) do local def = { description = S("%s Cable Plate"):format(tier), @@ -219,8 +211,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, node_box = table.copy(node_box), connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = function(pos) place_network_node(pos, node_name.."_plate_"..i) end, - on_destruct = remove_network_node, + on_construct = function(pos) place_network_node(pos, tier) end, + on_destruct = function(pos) remove_network_node(pos, tier) end, } def.node_box.fixed = { {-size, -size, -size, size, size, size}, @@ -301,18 +293,20 @@ function technic.register_cable(tier, size, description, prefix, override_cable, }) end +-- TODO: Instead of universal callback either require machines to call place_network_node or patch all nodedefs minetest.register_on_placenode(function(pos, node) for tier, machine_list in pairs(technic.machines) do if machine_list[node.name] ~= nil then - return place_network_node(pos, node) + return place_network_node(pos, tier) end end end) +-- TODO: Instead of universal callback either require machines to call remove_network_node or patch all nodedefs minetest.register_on_dignode(function(pos, node) for tier, machine_list in pairs(technic.machines) do if machine_list[node.name] ~= nil then - return remove_network_node(pos) + return remove_network_node(pos, tier) end end end) diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index f0e66cd..6c24607 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -114,39 +114,57 @@ minetest.register_node("technic:switching_station",{ -- The action code for the switching station -- ----------------------------------------------- +-- Lookup table for machine tiers +local machine_tiers = {} +minetest.register_on_mods_loaded(function() + for tier, machines in pairs(technic.machines) do + for name,_ in pairs(machines) do + if not machine_tiers[name] then machine_tiers[name] = {} end + table.insert(machine_tiers[name], tier) + end + end +end) + -- Timeout ABM -- Timeout for a node in case it was disconnected from the network -- A node must be touched by the station continuously in order to function -local function switching_station_timeout_count(pos, tier) - local timeout = technic.get_timeout(tier, pos) - if timeout <= 0 then - local meta = minetest.get_meta(pos) - meta:set_int(tier.."_EU_input", 0) -- Not needed anymore <-- actually, it is for supply converter - return true - else - technic.touch_node(tier, pos, timeout - 1) - return false - end -end +-- TODO: If possible replace this with something that does not need ABM, preferably without any timers minetest.register_abm({ label = "Machines: timeout check", nodenames = {"group:technic_machine"}, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) - for tier, machines in pairs(technic.machines) do - if machines[node.name] and switching_station_timeout_count(pos, tier) then - local nodedef = minetest.registered_nodes[node.name] - if nodedef and nodedef.technic_disabled_machine_name then - node.name = nodedef.technic_disabled_machine_name - minetest.swap_node(pos, node) - elseif nodedef and nodedef.technic_on_disable then - nodedef.technic_on_disable(pos, node) - end - if nodedef then - local meta = minetest.get_meta(pos) - meta:set_string("infotext", S("%s Has No Network"):format(nodedef.description)) - end + local tiers = machine_tiers[node.name] + if not tiers then + -- This should not happen ever, machines without at least single tier should not exist. Consider removing check? + return + end + -- Check for machine timeouts for all tiers + local timed_out = true + for _, tier in ipairs(tiers) do + local timeout = technic.get_timeout(tier, pos) + if timeout <= 0 then + local meta = minetest.get_meta(pos) + meta:set_int(tier.."_EU_input", 0) -- Not needed anymore <-- actually, it is for supply converter <-- Actually supply converter could handle this alone too + else + technic.touch_node(tier, pos, timeout - 1) + timed_out = false + break + end + end + -- If all tiers for machine timed out take action + if timed_out then + local nodedef = minetest.registered_nodes[node.name] + if nodedef and nodedef.technic_disabled_machine_name then + node.name = nodedef.technic_disabled_machine_name + minetest.swap_node(pos, node) + elseif nodedef and nodedef.technic_on_disable then + nodedef.technic_on_disable(pos, node) + end + if nodedef then + local meta = minetest.get_meta(pos) + meta:set_string("infotext", S("%s Has No Network"):format(nodedef.description)) end end end, diff --git a/technic/spec/test_helpers.lua b/technic/spec/test_helpers.lua index ae3be41..f9a0075 100644 --- a/technic/spec/test_helpers.lua +++ b/technic/spec/test_helpers.lua @@ -25,6 +25,17 @@ function sourcefile(name) dofile(source_path(name) .. ".lua") end +function timeit(count, func, ...) + local socket = require 'socket' + local t1 = socket.gettime() * 1000 + for i=0,count do + func(...) + end + local diff = (socket.gettime() * 1000) - t1 + local info = debug.getinfo(func,'S') + print(string.format("\nTimeit: %s:%d took %d ticks", info.short_src, info.linedefined, diff)) +end + function count(t) if type(t) == "table" or type(t) == "userdata" then local c = 0 From 8c5aca585640a49fd2f0532d8720eaba0c99f183 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Thu, 22 Oct 2020 03:52:23 +0300 Subject: [PATCH 11/20] Net building tests Fix few cable place/dig issues Tests for machine build/dig Fix machine build/dig bugs Fix test for machine building Fix machines acting like cables when placed Inline fixtures for building/digging tests fix ignored luacheck warnings (#105) fix long lines (#105) Add more tests for bugs found Fix tests, add minetest.get_us_time (implement using socket.gettime) --- .luacheckrc | 19 +- concrete/init.lua | 7 - extranodes/init.lua | 61 ++-- technic/init.lua | 17 +- technic/machines/HV/forcefield.lua | 6 +- technic/machines/HV/quarry.lua | 6 +- technic/machines/MV/power_radiator.lua | 2 +- technic/machines/MV/tool_workshop.lua | 4 +- technic/machines/network.lua | 29 +- technic/machines/other/anchor.lua | 10 +- technic/machines/other/frames.lua | 22 +- technic/machines/register/alloy_recipes.lua | 3 +- technic/machines/register/battery_box.lua | 14 +- technic/machines/register/cables.lua | 106 +++---- technic/machines/register/common.lua | 2 +- technic/machines/register/generator.lua | 2 +- technic/machines/register/grindings.lua | 40 +-- technic/radiation.lua | 2 +- technic/register.lua | 5 +- technic/spec/building_spec.lua | 325 ++++++++++++++++++++ technic/spec/fixtures/minetest.lua | 17 + technic/tools/cans.lua | 6 +- technic/tools/mining_drill.lua | 25 +- technic/tools/prospector.lua | 12 +- technic/tools/tree_tap.lua | 4 +- technic_cnc/cnc.lua | 2 +- technic_cnc/cnc_api.lua | 14 +- technic_cnc/init.lua | 17 +- wrench/init.lua | 16 +- 29 files changed, 585 insertions(+), 210 deletions(-) create mode 100644 technic/spec/building_spec.lua diff --git a/.luacheckrc b/.luacheckrc index 2b740cc..f6143ef 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,5 +1,4 @@ unused_args = false -max_line_length = 999 exclude_files = { "**/spec/**", @@ -33,19 +32,11 @@ read_globals = { "monitoring", "drawers" } -files["concrete/init.lua"].ignore = { "steel_ingot" } -files["technic/machines/MV/tool_workshop.lua"].ignore = { "pos" } -files["technic/machines/other/frames.lua"].ignore = { "item_texture", "item_type", "adj", "connected", "" } -files["technic/machines/register/battery_box.lua"].ignore = { "pos", "tube_upgrade" } +-- Remove after network update files["technic/machines/register/cables.lua"].ignore = { "name", "from_below", "p" } -files["technic/machines/register/common.lua"].ignore = { "result" } - -files["technic/machines/register/generator.lua"].ignore = { "node" } files["technic/machines/switching_station.lua"].ignore = { "pos1", "tier", "poshash" } -files["technic/radiation.lua"].ignore = { "LAVA_VISC" } -files["technic/tools/chainsaw.lua"].ignore = { "pos" } -files["technic/tools/mining_drill.lua"].ignore = { "mode" } -files["technic_chests/register.lua"].ignore = { "fs_helpers", "name", "locked_after_place" } +files["technic/machines/switching_station.lua"].max_line_length = false -files["technic_cnc/cnc.lua"].ignore = { "multiplier" } -files["wrench/init.lua"].ignore = { "name", "stack" } +-- Remove after chests update +files["technic_chests/register.lua"].ignore = { "fs_helpers", "name", "locked_after_place" } +files["technic_chests/register.lua"].max_line_length = false diff --git a/concrete/init.lua b/concrete/init.lua index ced0471..82b4098 100644 --- a/concrete/init.lua +++ b/concrete/init.lua @@ -16,13 +16,6 @@ for i = 32, 63 do "technic:concrete_post_with_platform") end -local steel_ingot -if minetest.get_modpath("technic_worldgen") then - steel_ingot = "technic:carbon_steel_ingot" -else - steel_ingot = "default:steel_ingot" -end - minetest.register_craft({ output = 'technic:concrete_post_platform 6', recipe = { diff --git a/extranodes/init.lua b/extranodes/init.lua index 55b4e8b..f8fb9fc 100644 --- a/extranodes/init.lua +++ b/extranodes/init.lua @@ -57,35 +57,56 @@ if minetest.get_modpath("moreblocks") then tiles={"technic_stainless_steel_block.png"}, }) + -- FIXME: Clean this function up somehow local function register_technic_stairs_alias(modname, origname, newmod, newname) minetest.register_alias(modname .. ":slab_" .. origname, newmod..":slab_" .. newname) - minetest.register_alias(modname .. ":slab_" .. origname .. "_inverted", newmod..":slab_" .. newname .. "_inverted") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_inverted", newmod..":slab_" .. newname .. "_inverted") minetest.register_alias(modname .. ":slab_" .. origname .. "_wall", newmod..":slab_" .. newname .. "_wall") - minetest.register_alias(modname .. ":slab_" .. origname .. "_quarter", newmod..":slab_" .. newname .. "_quarter") - minetest.register_alias(modname .. ":slab_" .. origname .. "_quarter_inverted", newmod..":slab_" .. newname .. "_quarter_inverted") - minetest.register_alias(modname .. ":slab_" .. origname .. "_quarter_wall", newmod..":slab_" .. newname .. "_quarter_wall") - minetest.register_alias(modname .. ":slab_" .. origname .. "_three_quarter", newmod..":slab_" .. newname .. "_three_quarter") - minetest.register_alias(modname .. ":slab_" .. origname .. "_three_quarter_inverted", newmod..":slab_" .. newname .. "_three_quarter_inverted") - minetest.register_alias(modname .. ":slab_" .. origname .. "_three_quarter_wall", newmod..":slab_" .. newname .. "_three_quarter_wall") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_quarter", newmod..":slab_" .. newname .. "_quarter") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_quarter_inverted", newmod..":slab_" .. newname .. "_quarter_inverted") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_quarter_wall", newmod..":slab_" .. newname .. "_quarter_wall") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_three_quarter", newmod..":slab_" .. newname .. "_three_quarter") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_three_quarter_inverted", newmod..":slab_" .. newname .. "_three_quarter_inverted") + minetest.register_alias(modname .. ":slab_" .. origname .. + "_three_quarter_wall", newmod..":slab_" .. newname .. "_three_quarter_wall") minetest.register_alias(modname .. ":stair_" .. origname, newmod..":stair_" .. newname) - minetest.register_alias(modname .. ":stair_" .. origname .. "_inverted", newmod..":stair_" .. newname .. "_inverted") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_inverted", newmod..":stair_" .. newname .. "_inverted") minetest.register_alias(modname .. ":stair_" .. origname .. "_wall", newmod..":stair_" .. newname .. "_wall") - minetest.register_alias(modname .. ":stair_" .. origname .. "_wall_half", newmod..":stair_" .. newname .. "_wall_half") - minetest.register_alias(modname .. ":stair_" .. origname .. "_wall_half_inverted", newmod..":stair_" .. newname .. "_wall_half_inverted") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_wall_half", newmod..":stair_" .. newname .. "_wall_half") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_wall_half_inverted", newmod..":stair_" .. newname .. "_wall_half_inverted") minetest.register_alias(modname .. ":stair_" .. origname .. "_half", newmod..":stair_" .. newname .. "_half") - minetest.register_alias(modname .. ":stair_" .. origname .. "_half_inverted", newmod..":stair_" .. newname .. "_half_inverted") - minetest.register_alias(modname .. ":stair_" .. origname .. "_right_half", newmod..":stair_" .. newname .. "_right_half") - minetest.register_alias(modname .. ":stair_" .. origname .. "_right_half_inverted", newmod..":stair_" .. newname .. "_right_half_inverted") - minetest.register_alias(modname .. ":stair_" .. origname .. "_wall_half", newmod..":stair_" .. newname .. "_wall_half") - minetest.register_alias(modname .. ":stair_" .. origname .. "_wall_half_inverted", newmod..":stair_" .. newname .. "_wall_half_inverted") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_half_inverted", newmod..":stair_" .. newname .. "_half_inverted") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_right_half", newmod..":stair_" .. newname .. "_right_half") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_right_half_inverted", newmod..":stair_" .. newname .. "_right_half_inverted") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_wall_half", newmod..":stair_" .. newname .. "_wall_half") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_wall_half_inverted", newmod..":stair_" .. newname .. "_wall_half_inverted") minetest.register_alias(modname .. ":stair_" .. origname .. "_inner", newmod..":stair_" .. newname .. "_inner") - minetest.register_alias(modname .. ":stair_" .. origname .. "_inner_inverted", newmod..":stair_" .. newname .. "_inner_inverted") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_inner_inverted", newmod..":stair_" .. newname .. "_inner_inverted") minetest.register_alias(modname .. ":stair_" .. origname .. "_outer", newmod..":stair_" .. newname .. "_outer") - minetest.register_alias(modname .. ":stair_" .. origname .. "_outer_inverted", newmod..":stair_" .. newname .. "_outer_inverted") - minetest.register_alias(modname .. ":panel_" .. origname .. "_bottom", newmod..":panel_" .. newname .. "_bottom") + minetest.register_alias(modname .. ":stair_" .. origname .. + "_outer_inverted", newmod..":stair_" .. newname .. "_outer_inverted") + minetest.register_alias(modname .. ":panel_" .. origname .. + "_bottom", newmod..":panel_" .. newname .. "_bottom") minetest.register_alias(modname .. ":panel_" .. origname .. "_top", newmod..":panel_" .. newname .. "_top") - minetest.register_alias(modname .. ":panel_" .. origname .. "_vertical", newmod..":panel_" .. newname .. "_vertical") - minetest.register_alias(modname .. ":micro_" .. origname .. "_bottom", newmod..":micro_" .. newname .. "_bottom") + minetest.register_alias(modname .. ":panel_" .. origname .. + "_vertical", newmod..":panel_" .. newname .. "_vertical") + minetest.register_alias(modname .. ":micro_" .. origname .. + "_bottom", newmod..":micro_" .. newname .. "_bottom") minetest.register_alias(modname .. ":micro_" .. origname .. "_top", newmod..":micro_" .. newname .. "_top") end diff --git a/technic/init.lua b/technic/init.lua index 79d4b64..f5ed615 100644 --- a/technic/init.lua +++ b/technic/init.lua @@ -16,7 +16,22 @@ technic.modpath = modpath if rawget(_G, "intllib") then technic.getter = intllib.Getter() else - technic.getter = function(s,a,...)if a==nil then return s end a={a,...}return s:gsub("(@?)@(%(?)(%d+)(%)?)",function(e,o,n,c)if e==""then return a[tonumber(n)]..(o==""and c or"")else return"@"..o..n..c end end) end + technic.getter = function(s, a, ...) + if a == nil then + return s + end + a = {a, ...} + return s:gsub( + "(@?)@(%(?)(%d+)(%)?)", + function(e, o, n, c) + if e == "" then + return a[tonumber(n)] .. (o == "" and c or "") + else + return "@" .. o .. n .. c + end + end + ) + end end local S = technic.getter diff --git a/technic/machines/HV/forcefield.lua b/technic/machines/HV/forcefield.lua index f4be8ba..bc755df 100644 --- a/technic/machines/HV/forcefield.lua +++ b/technic/machines/HV/forcefield.lua @@ -121,9 +121,11 @@ local function set_forcefield_formspec(meta) formspec = formspec.."button[0,1;5,1;mesecon_mode_0;"..S("Controlled by Mesecon Signal").."]" end if meta:get_int("enabled") == 0 then - formspec = formspec.."button[0,1.75;5,1;enable;"..S("%s Disabled"):format(S("%s Forcefield Emitter"):format("HV")).."]" + formspec = formspec.. + "button[0,1.75;5,1;enable;"..S("%s Disabled"):format(S("%s Forcefield Emitter"):format("HV")).."]" else - formspec = formspec.."button[0,1.75;5,1;disable;"..S("%s Enabled"):format(S("%s Forcefield Emitter"):format("HV")).."]" + formspec = formspec.. + "button[0,1.75;5,1;disable;"..S("%s Enabled"):format(S("%s Forcefield Emitter"):format("HV")).."]" end meta:set_string("formspec", formspec) end diff --git a/technic/machines/HV/quarry.lua b/technic/machines/HV/quarry.lua index b8114c1..edd9d39 100644 --- a/technic/machines/HV/quarry.lua +++ b/technic/machines/HV/quarry.lua @@ -82,7 +82,11 @@ local function set_quarry_status(pos) else local rel_y = meta:get_int("dig_level") - pos.y status = S("Digging %d m "..(rel_y > 0 and "above" or "below").." machine"):format(math.abs(rel_y)) - meta:set_string("infotext", S(meta:get_int("HV_EU_input") >= quarry_demand and "%s Active" or "%s Unpowered"):format(machine_name)) + if meta:get_int("HV_EU_input") >= quarry_demand then + meta:set_string("infotext", S("%s Active"):format(machine_name)) + else + meta:set_string("infotext", S("%s Unpowered"):format(machine_name)) + end meta:set_int("HV_EU_demand", quarry_demand) end else diff --git a/technic/machines/MV/power_radiator.lua b/technic/machines/MV/power_radiator.lua index a5d2ed6..49d81ec 100644 --- a/technic/machines/MV/power_radiator.lua +++ b/technic/machines/MV/power_radiator.lua @@ -36,7 +36,7 @@ technic.register_inductive_machine = function(name) end -- Appliances: --- has_supply: pos of supply node if the appliance has a power radiator near with sufficient power for the demand else "" +-- has_supply: pos of supply node if the appliance has a power radiator near with sufficient power for the demand -- EU_demand: The power demand of the device. -- EU_charge: Actual use. set to EU_demand if active==1 -- active: set to 1 if the device is on diff --git a/technic/machines/MV/tool_workshop.lua b/technic/machines/MV/tool_workshop.lua index 678a1fd..eefa8b3 100644 --- a/technic/machines/MV/tool_workshop.lua +++ b/technic/machines/MV/tool_workshop.lua @@ -60,9 +60,9 @@ local run = function(pos, node) repairable = true end end - technic.handle_machine_pipeworks(pos, tube_upgrade, function (pos, x_velocity, z_velocity) + technic.handle_machine_pipeworks(pos, tube_upgrade, function(pos2, x_velocity, z_velocity) if not repairable then - technic.send_items(pos, x_velocity, z_velocity, "src") + technic.send_items(pos2, x_velocity, z_velocity, "src") end end) if not repairable then diff --git a/technic/machines/network.lua b/technic/machines/network.lua index e5f9dad..c77a871 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -164,25 +164,18 @@ technic.is_overloaded = is_overloaded -- Functions to traverse the electrical network -- -local function attach_network_machine(network_id, pos) - local pos_hash = poshash(pos) - local net_id_old = cables[pos_hash] - if net_id_old == nil then - cables[pos_hash] = network_id - elseif net_id_old ~= network_id then +-- Add a machine node to the LV/MV/HV network +local function add_network_machine(nodes, pos, network_id, all_nodes, no_overload) + local node_id = poshash(pos) + local net_id_old = cables[node_id] + if not no_overload and net_id_old and net_id_old ~= network_id then -- do not allow running pos from multiple networks, also disable switch overload_network(network_id, pos) overload_network(net_id_old, pos) - cables[pos_hash] = network_id local meta = minetest.get_meta(pos) meta:set_string("infotext",S("Network Overloaded")) end -end - --- Add a machine node to the LV/MV/HV network -local function add_network_machine(nodes, pos, network_id, all_nodes) table.insert(nodes, pos) - local node_id = poshash(pos) cables[node_id] = network_id all_nodes[node_id] = pos end @@ -193,33 +186,27 @@ local function add_cable_node(nodes, pos, network_id, queue) cables[node_id] = network_id if not nodes[node_id] then nodes[node_id] = pos - queue[#queue + 1] = pos + table.insert(queue, pos) end end -- Generic function to add found connected nodes to the right classification array local function add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, machines, tier, network_id, queue) - technic.get_or_load_node(pos) local name = minetest.get_node(pos).name if technic.is_tier_cable(name, tier) then add_cable_node(all_nodes, pos, network_id, queue) elseif machines[name] then - --dprint(name.." is a "..machines[name]) if machines[name] == technic.producer then - attach_network_machine(network_id, pos) add_network_machine(PR_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.receiver then - attach_network_machine(network_id, pos) add_network_machine(RE_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.producer_receiver then - --attach_network_machine(network_id, pos) - add_network_machine(PR_nodes, pos, network_id, all_nodes) - add_network_machine(RE_nodes, pos, network_id, all_nodes) + add_network_machine(PR_nodes, pos, network_id, all_nodes, true) + table.insert(RE_nodes, pos) elseif machines[name] == technic.battery then - attach_network_machine(network_id, pos) add_network_machine(BA_nodes, pos, network_id, all_nodes) end diff --git a/technic/machines/other/anchor.lua b/technic/machines/other/anchor.lua index 5a1ac35..59a9482 100644 --- a/technic/machines/other/anchor.lua +++ b/technic/machines/other/anchor.lua @@ -61,7 +61,8 @@ local function set_display(pos, meta) (meta:get_int("enabled") == 0 and "button[3,2;2,1;enable;"..minetest.formspec_escape(S("Disabled")).."]" or "button[3,2;2,1;disable;"..minetest.formspec_escape(S("Enabled")).."]").. - "label[0,3;"..minetest.formspec_escape(S("Keeping %d/%d map blocks loaded"):format(#currently_forceloaded_positions(meta), #compute_forceload_positions(pos, meta))).."]") + "label[0,3;"..minetest.formspec_escape(S("Keeping %d/%d map blocks loaded"):format( + #currently_forceloaded_positions(meta), #compute_forceload_positions(pos, meta))).."]") end minetest.register_node("technic:admin_anchor", { @@ -80,7 +81,8 @@ minetest.register_node("technic:admin_anchor", { end, can_dig = function (pos, player) local meta = minetest.get_meta(pos) - return meta:get_int("locked") == 0 or (player and player:is_player() and player:get_player_name() == meta:get_string("owner")) + return meta:get_int("locked") == 0 or (player and player:is_player() + and player:get_player_name() == meta:get_string("owner")) end, on_destruct = function (pos) local meta = minetest.get_meta(pos) @@ -99,7 +101,9 @@ minetest.register_node("technic:admin_anchor", { forceload_off(meta) if fields.disable then meta:set_int("enabled", 0) end if fields.enable then meta:set_int("enabled", 1) end - if fields.radius and string.find(fields.radius, "^[0-9]+$") and tonumber(fields.radius) < 256 then meta:set_int("radius", fields.radius) end + if fields.radius and string.find(fields.radius, "^[0-9]+$") and tonumber(fields.radius) < 256 then + meta:set_int("radius", fields.radius) + end if meta:get_int("enabled") ~= 0 then forceload_on(pos, meta) end diff --git a/technic/machines/other/frames.lua b/technic/machines/other/frames.lua index e65902c..0163a6c 100644 --- a/technic/machines/other/frames.lua +++ b/technic/machines/other/frames.lua @@ -88,13 +88,6 @@ local function pos_in_list(l, pos) return false end -local function table_empty(table) - for _, __ in pairs(table) do - return false - end - return true -end - local function add_table(table, toadd) local i = 1 while true do @@ -398,6 +391,8 @@ minetest.register_entity("technic:frame_entity", { local pos = vector.round(self.object:getpos()) frames_pos[pos_to_string(pos)] = node.name +-- This code does nothing currently, so it is disabled to stop luacheck warnings +--[[ local stack = ItemStack(node.name) local itemtable = stack:to_table() local itemname = nil @@ -412,6 +407,7 @@ minetest.register_entity("technic:frame_entity", { item_texture = minetest.registered_items[itemname].inventory_image item_type = minetest.registered_items[itemname].type end +--]] local prop = { is_visible = true, textures = { node.name }, @@ -579,8 +575,8 @@ local function connected(pos, c, adj) minetest.registered_nodes[nodename].frames_can_connect(pos1, vect)) then c[#c + 1] = pos1 if minetest.registered_nodes[nodename].frame == 1 then - local adj = minetest.registered_nodes[nodename].frame_connect_all(nodename) - connected(pos1, c, adj) + local adj2 = minetest.registered_nodes[nodename].frame_connect_all(nodename) + connected(pos1, c, adj2) end end end @@ -798,7 +794,7 @@ local function template_drops(pos, node, oldmeta, digger) drops = { "technic:template 1" } else local dcc = minetest.deserialize(cc) - if not table_empty(dcc) then + if next(dcc) ~= nil then drops = {} for sp, _ in pairs(dcc) do local ssp = pos_from_string(sp) @@ -918,9 +914,9 @@ minetest.register_tool("technic:template_tool", { -- Template motor local function get_template_nodes(pos) local meta = minetest.get_meta(pos) - local connected = meta:get_string("connected") - if connected == "" then return {} end - local adj = minetest.deserialize(connected) + local connected_to = meta:get_string("connected") + if connected_to == "" then return {} end + local adj = minetest.deserialize(connected_to) local c = {} for _, vect in ipairs(adj) do local pos1 = vector.add(pos, vect) diff --git a/technic/machines/register/alloy_recipes.lua b/technic/machines/register/alloy_recipes.lua index a971965..0165ad9 100644 --- a/technic/machines/register/alloy_recipes.lua +++ b/technic/machines/register/alloy_recipes.lua @@ -26,7 +26,8 @@ local recipes = { {"technic:silicon_wafer", "technic:gold_dust", "technic:doped_silicon_wafer"}, -- from https://en.wikipedia.org/wiki/Carbon_black -- The highest volume use of carbon black is as a reinforcing filler in rubber products, especially tires. - -- "[Compounding a] pure gum vulcanizate … with 50% of its weight of carbon black improves its tensile strength and wear resistance …" + -- "[Compounding a] pure gum vulcanizate … with 50% of its weight of carbon black + -- improves its tensile strength and wear resistance …" {"technic:raw_latex 4", "technic:coal_dust 2", "technic:rubber 6", 2}, {"default:ice", "bucket:bucket_empty", "bucket:bucket_water", 1 }, {"default:obsidian", "bucket:bucket_empty", "bucket:bucket_lava", 1 }, diff --git a/technic/machines/register/battery_box.lua b/technic/machines/register/battery_box.lua index 9f5f414..858c404 100644 --- a/technic/machines/register/battery_box.lua +++ b/technic/machines/register/battery_box.lua @@ -225,11 +225,11 @@ function technic.register_battery_box(data) if data.tube then local inv = meta:get_inventory() technic.handle_machine_pipeworks(pos, tube_upgrade, - function(pos, x_velocity, z_velocity) + function(pos2, x_velocity, z_velocity) if tool_full and not inv:is_empty("src") then - technic.send_items(pos, x_velocity, z_velocity, "src") + technic.send_items(pos2, x_velocity, z_velocity, "src") elseif tool_empty and not inv:is_empty("dst") then - technic.send_items(pos, x_velocity, z_velocity, "dst") + technic.send_items(pos2, x_velocity, z_velocity, "dst") end end) end @@ -303,9 +303,9 @@ function technic.register_battery_box(data) drop = "technic:"..ltier.."_battery_box0", on_construct = function(pos) local meta = minetest.get_meta(pos) - local EU_upgrade, tube_upgrade = 0, 0 + local EU_upgrade = 0 if data.upgrade then - EU_upgrade, tube_upgrade = technic.handle_machine_upgrades(meta) + EU_upgrade = technic.handle_machine_upgrades(meta) end local max_charge = data.max_charge * (1 + EU_upgrade / 10) local charge = meta:get_int("internal_EU_charge") @@ -344,9 +344,9 @@ function technic.register_battery_box(data) meta = minetest.get_meta(pos) if not pipeworks.may_configure(pos, sender) then return end fs_helpers.on_receive_fields(pos, fields) - local EU_upgrade, tube_upgrade = 0, 0 + local EU_upgrade = 0 if data.upgrade then - EU_upgrade, tube_upgrade = technic.handle_machine_upgrades(meta) + EU_upgrade = technic.handle_machine_upgrades(meta) end local max_charge = data.max_charge * (1 + EU_upgrade / 10) local charge = meta:get_int("internal_EU_charge") diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 94fd0ae..08f5116 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -12,17 +12,14 @@ function technic.get_cable_tier(name) end local function get_neighbors(pos, tier) - -- Build a table of all machines -- TODO: Move this to network.lua - -- TODO: Build table for current tier only, we do not want to test other tiers. - -- TODO: Consider adding only single position for given network -- TEST: Make sure that multi tier machines work (currently supply converter) -- these should not be a problem but can return multiple tiers, currently -- machines collector below only handles single tier. - -- TODO: technic.get_cable_tier works only with cables, should get tier of any network node here. - -- This is to convert this function into general neighbor lookup tool, not just from cable PoV. - local machines = technic.machines[tier] - local connections = {} + local tier_machines = technic.machines[tier] + local network = technic.networks[technic.pos2network(pos)] + local cables = {} + local machines = {} local positions = { {x=pos.x+1, y=pos.y, z=pos.z}, {x=pos.x-1, y=pos.y, z=pos.z}, @@ -33,32 +30,23 @@ local function get_neighbors(pos, tier) } for _,connected_pos in ipairs(positions) do local name = minetest.get_node(connected_pos).name - if machines[name] or tier == technic.get_cable_tier(name) then - table.insert(connections,{ + if tier_machines[name] then + table.insert(machines, connected_pos) + elseif tier == technic.get_cable_tier(name) then + local cable_network = technic.networks[technic.pos2network(connected_pos)] + table.insert(cables,{ pos = connected_pos, - network = technic.networks[technic.pos2network(connected_pos)], + network = cable_network, }) + if not network then network = cable_network end end end - return connections + return network, cables, machines end -local function place_network_node(pos, tier) +local function place_network_node(pos, tier, name) -- Get connections and primary network if there's any - local connections = get_neighbors(pos, tier) - if #connections < 1 then - return - end - - -- Get first network from connections, this will be our primary network - local network - for _,connection in ipairs(connections) do - if connection.network then - network = connection.network - break - end - end - + local network, cables, machines = get_neighbors(pos, tier) if not network then -- We're evidently not on a network, nothing to add ourselves to return @@ -66,42 +54,52 @@ local function place_network_node(pos, tier) -- Attach to primary network, this must be done before building branches from this position technic.add_network_node(pos, network) - if #connections == 1 then - -- Only single connected position and we are already attached to it + if not technic.is_tier_cable(name, tier) then + -- Machine added, skip all network building return end - -- Check if there's any neighbors that do not belong to chosen primary network and handle those - local removed = 0 - for _,connection in ipairs(connections) do + -- Attach neighbor machines if cable was added + for _,machine_pos in ipairs(machines) do + technic.add_network_node(machine_pos, network) + end + + -- Attach neighbor cables + for _,connection in ipairs(cables) do if connection.network then if connection.network.id ~= network.id then -- Remove network if position belongs to another network - -- FIXME: Network requires rebuild but avoid doing it here if possible. + -- FIXME: Network requires partial rebuild but avoid doing it here if possible. + -- This might cause problems when merging two active networks into one technic.remove_network(connection.network.id) connection.network = nil - removed = removed + 1 end else - -- There's node that does not belong to any network, attach whole branch + -- There's cable that does not belong to any network, attach whole branch + technic.add_network_node(connection.pos, network) technic.add_network_branch({connection.pos}, network) end end - end +-- NOTE: Exported for tests but should probably be moved to network.lua +technic.network_node_on_placenode = place_network_node -local function remove_network_node(pos, tier) - -- Get the network - local network_id = technic.pos2network(pos) - if not network_id then return end +local function remove_network_node(pos, tier, name) + -- Get the network and neighbors + local network, cables, machines = get_neighbors(pos, tier) + if not network then return end - local connections = get_neighbors(pos, tier) - if #connections < 1 then return end - local dead_end = #connections == 1 - - if dead_end then - -- Dead end machine or cable removed, remove it from the network - technic.remove_network_node(network_id, pos) + if #cables == 1 then + -- Dead end cable removed, remove it from the network + technic.remove_network_node(network.id, pos) + -- Remove neighbor machines from network if cable was removed + -- FIXME: this is not compatible with machines that have multiple connections to network + -- TEST: this should have test in building_spec.lua for multiple connections + if technic.is_tier_cable(name, tier) then + for _,machine_pos in ipairs(machines) do + technic.remove_network_node(network.id, machine_pos) + end + end else -- TODO: Check branches around and switching stations for branches: -- remove branches that do not have switching station. @@ -109,9 +107,11 @@ local function remove_network_node(pos, tier) -- do not rebuild networks here, leave that for ABM to reduce unnecessary cache building. -- To do all this network must be aware of individual branches, might not be worth it... -- For now remove whole network and let ABM rebuild it - technic.remove_network(network_id) + technic.remove_network(network.id) end end +-- NOTE: Exported for tests but should probably be moved to network.lua +technic.network_node_on_dignode = remove_network_node local function item_place_override_node(itemstack, placer, pointed, node) -- Call the default on_place function with a fake itemstack @@ -170,8 +170,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, node_box = node_box, connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = function(pos) place_network_node(pos, tier) end, - on_destruct = function(pos) remove_network_node(pos, tier) end, + on_construct = function(pos) place_network_node(pos, tier, node_name) end, + on_destruct = function(pos) remove_network_node(pos, tier, node_name) end, }, override_cable)) local xyz = { @@ -211,8 +211,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, node_box = table.copy(node_box), connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = function(pos) place_network_node(pos, tier) end, - on_destruct = function(pos) remove_network_node(pos, tier) end, + on_construct = function(pos) place_network_node(pos, tier, node_name.."_plate_"..i) end, + on_destruct = function(pos) remove_network_node(pos, tier, node_name.."_plate_"..i) end, } def.node_box.fixed = { {-size, -size, -size, size, size, size}, @@ -297,7 +297,7 @@ end minetest.register_on_placenode(function(pos, node) for tier, machine_list in pairs(technic.machines) do if machine_list[node.name] ~= nil then - return place_network_node(pos, tier) + return place_network_node(pos, tier, node.name) end end end) @@ -306,7 +306,7 @@ end) minetest.register_on_dignode(function(pos, node) for tier, machine_list in pairs(technic.machines) do if machine_list[node.name] ~= nil then - return remove_network_node(pos, tier) + return remove_network_node(pos, tier, node.name) end end end) diff --git a/technic/machines/register/common.lua b/technic/machines/register/common.lua index e53e762..b7917f4 100644 --- a/technic/machines/register/common.lua +++ b/technic/machines/register/common.lua @@ -84,13 +84,13 @@ function technic.send_items(pos, x_velocity, z_velocity, output_name) end end +-- FIXME: This funtion is never used anywhere, what is it for? function technic.smelt_item(meta, result, speed) local inv = meta:get_inventory() meta:set_int("cook_time", meta:get_int("cook_time") + 1) if meta:get_int("cook_time") < result.time / speed then return end - local result local afterfuel result, afterfuel = minetest.get_craft_result({method = "cooking", width = 1, items = inv:get_list("src")}) diff --git a/technic/machines/register/generator.lua b/technic/machines/register/generator.lua index 91a942e..ec002dd 100644 --- a/technic/machines/register/generator.lua +++ b/technic/machines/register/generator.lua @@ -205,7 +205,7 @@ function technic.register_generator(data) local timer = minetest.get_node_timer(pos) timer:start(1) end, - on_timer = function(pos, node) + on_timer = function(pos) -- Connected back? if technic.get_timeout(tier, pos) > 0 then return false end diff --git a/technic/machines/register/grindings.lua b/technic/machines/register/grindings.lua index fe9fa4b..f4c7e27 100644 --- a/technic/machines/register/grindings.lua +++ b/technic/machines/register/grindings.lua @@ -51,29 +51,23 @@ local default_extract = dye and "dye:brown 2" -- https://en.wikipedia.org/wiki/Catechu ancient brown dye from the wood of acacia trees local acacia_extract = dye and "dye:brown 8" -register_tree_grinding("Common Tree", "group:tree", "group:wood", default_extract) -register_tree_grinding("Common Tree", "default:tree", "default:wood", default_extract) -register_tree_grinding("Common Tree", "default:aspen_tree", "default:aspen_wood", default_extract) -register_tree_grinding("Common Tree", "default:jungletree", "default:junglewood", default_extract) -register_tree_grinding("Common Tree", "default:pine_tree", "default:pine_wood", default_extract) -register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk", rubber_tree_planks, "technic:raw_latex") -register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk_empty", nil, "technic:raw_latex") +-- technic recipes don't support groups yet :/ +--register_tree_grinding("Common Tree", "group:tree", "group:wood", default_extract) + +register_tree_grinding("Acacia", "default:acacia_tree", "default:acacia_wood", acacia_extract) +register_tree_grinding("Common Tree", "default:tree", "default:wood", default_extract) +register_tree_grinding("Common Tree", "default:aspen_tree", "default:aspen_wood", default_extract) +register_tree_grinding("Common Tree", "default:jungletree", "default:junglewood", default_extract) +register_tree_grinding("Common Tree", "default:pine_tree", "default:pine_wood", default_extract) +register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk", rubber_tree_planks, "technic:raw_latex") +register_tree_grinding("Rubber Tree", "moretrees:rubber_tree_trunk_empty", nil, "technic:raw_latex") if moretrees then - register_tree_grinding("Common Tree", "moretrees:beech_tree_trunk", "moretrees:beech_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:apple_tree_trunk", "moretrees:apple_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:oak_tree_trunk", "moretrees:oak_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:giant_sequoia_trunk", "moretrees:giant_sequoia_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:birch_tree_trunk", "moretrees:birch_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:palm_tree_trunk", "moretrees:palm_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:date_palm_tree_trunk", "moretrees:date_palm_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:spruce_tree_trunk", "moretrees:spruce_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:ceder_tree_trunk", "moretrees:ceder_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:poplar_tree_trunk", "moretrees:poplar_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:wollow_tree_trunk", "moretrees:wollow_tree_planks", default_extract) - register_tree_grinding("Common Tree", "moretrees:douglas_fir_trunk", "moretrees:douglas_fir_planks", default_extract) - - register_tree_grinding("Acacia", "moretrees:acacia_trunk", "moretrees:acacia_planks", acacia_extract) -else - register_tree_grinding("Acacia", "default:acacia_tree", "default:acacia_wood", acacia_extract) + local trees = { + "beech", "apple_tree", "oak", "sequoia", "birch", "palm", + "date_palm", "spruce", "cedar", "poplar", "willow", "fir" + } + for _,tree in pairs(trees) do + register_tree_grinding("Common Tree", "moretrees:"..tree.."_trunk", "moretrees:"..tree.."_planks", default_extract) + end end diff --git a/technic/radiation.lua b/technic/radiation.lua index 50810db..8cfbe1b 100644 --- a/technic/radiation.lua +++ b/technic/radiation.lua @@ -465,7 +465,7 @@ for _, state in pairs({"flowing", "source"}) do liquidtype = state, liquid_alternative_flowing = "technic:corium_flowing", liquid_alternative_source = "technic:corium_source", - liquid_viscosity = LAVA_VISC, + liquid_viscosity = 7, liquid_renewable = false, damage_per_second = 6, post_effect_color = {a=192, r=80, g=160, b=80}, diff --git a/technic/register.lua b/technic/register.lua index 8f75b81..9b4f9be 100644 --- a/technic/register.lua +++ b/technic/register.lua @@ -44,7 +44,10 @@ end -- Wear down a tool depending on the remaining charge. function technic.set_RE_wear(itemstack, item_load, max_load) - if (minetest.registered_items[itemstack:get_name()].wear_represents or "mechanical_wear") ~= "technic_RE_charge" then return itemstack end + if (minetest.registered_items[itemstack:get_name()].wear_represents + or "mechanical_wear") ~= "technic_RE_charge" then + return itemstack + end local temp if item_load == 0 then temp = 0 diff --git a/technic/spec/building_spec.lua b/technic/spec/building_spec.lua new file mode 100644 index 0000000..7bb3137 --- /dev/null +++ b/technic/spec/building_spec.lua @@ -0,0 +1,325 @@ +dofile("spec/test_helpers.lua") +--[[ + Technic network unit tests. + Execute busted at technic source directory. +--]] + +-- Load fixtures required by tests +fixture("minetest") +fixture("minetest/player") +fixture("minetest/protection") + +fixture("pipeworks") +fixture("network") + +sourcefile("machines/network") + +sourcefile("machines/register/cables") +sourcefile("machines/LV/cables") +sourcefile("machines/MV/cables") +sourcefile("machines/HV/cables") + +sourcefile("machines/register/generator") +sourcefile("machines/HV/generator") + +function get_network_fixture(sw_pos) + -- Build network + local net_id = technic.create_network(sw_pos) + assert.is_number(net_id) + local net = technic.networks[net_id] + assert.is_table(net) + return net +end + +describe("Power network building", function() + + describe("cable building", function() + + world.add_layout({ + {{x=100,y=800,z=100}, "technic:hv_cable"}, + {{x=100,y=801,z=100}, "technic:switching_station"}, + {{x=101,y=800,z=100}, "technic:hv_cable"}, + {{x=101,y=801,z=100}, "technic:hv_generator"}, + --{{x=102,y=800,z=100}, "technic:hv_cable"}, -- This cable is built + --{{x=102,y=801,z=100}, "technic:hv_cable"}, -- TODO: Add this cable as test case? + {{x=103,y=800,z=100}, "technic:hv_cable"}, -- This should appear + {{x=103,y=801,z=100}, "technic:hv_generator"}, -- This should appear + }) + -- Build network + local net = get_network_fixture({x=100,y=801,z=100}) + local build_pos = {x=102,y=800,z=100} + + it("does not crash", function() + assert.equals(1, #net.PR_nodes) + assert.equals(3, count(net.all_nodes)) + world.set_node(build_pos, {name="technic:hv_cable", param2=0}) + technic.network_node_on_placenode(build_pos, "HV", "technic:hv_cable") + end) + + it("is added to network", function() + assert.same(build_pos, net.all_nodes[minetest.hash_node_position(build_pos)]) + end) + + it("adds all network nodes", function() + assert.equals(6, count(net.all_nodes)) + end) + + it("adds connected machines to network without duplicates", function() + assert.equals(2, #net.PR_nodes) + --assert.equals({x=103,y=801,z=100}, net.PR_nodes[2]) + end) + + end) + + describe("cable building to machine", function() + + world.add_layout({ + {{x=100,y=810,z=100}, "technic:hv_cable"}, + {{x=100,y=811,z=100}, "technic:switching_station"}, + {{x=101,y=810,z=100}, "technic:hv_cable"}, + {{x=101,y=811,z=100}, "technic:hv_generator"}, + {{x=102,y=810,z=100}, "technic:hv_cable"}, + --{{x=102,y=811,z=100}, "technic:hv_cable"}, -- This cable is built + --{{x=103,y=810,z=100}, "technic:hv_cable"}, -- This cable is built + {{x=103,y=811,z=100}, "technic:hv_generator"}, -- This should appear + {{x=103,y=812,z=100}, "technic:hv_cable"}, -- Unconnected cable + }) + -- Build network + local net = get_network_fixture({x=100,y=811,z=100}) + local build_pos = {x=103,y=810,z=100} + local build_pos2 = {x=102,y=811,z=100} + + it("does not crash", function() + assert.equals(1, #net.PR_nodes) + assert.equals(4, count(net.all_nodes)) + world.set_node(build_pos, {name="technic:hv_cable", param2=0}) + technic.network_node_on_placenode(build_pos, "HV", "technic:hv_cable") + end) + + it("is added to network", function() + assert.same(build_pos, net.all_nodes[minetest.hash_node_position(build_pos)]) + end) + + it("adds all network nodes", function() + assert.equals(6, count(net.all_nodes)) + end) + + it("adds connected machines to network without duplicates", function() + assert.equals(2, #net.PR_nodes) + --assert.equals({x=103,y=801,z=100}, net.PR_nodes[2]) + end) + + it("does not add unconnected cables to network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position({x=103,y=812,z=100})]) + end) + + it("does not duplicate already added machine", function() + world.set_node(build_pos2, {name="technic:hv_cable", param2=0}) + technic.network_node_on_placenode(build_pos2, "HV", "technic:hv_cable") + assert.equals(2, #net.PR_nodes) + assert.equals(7, count(net.all_nodes)) + end) + + end) + + describe("machine building", function() + + world.add_layout({ + {{x=100,y=820,z=100}, "technic:hv_cable"}, + {{x=100,y=821,z=100}, "technic:switching_station"}, + {{x=101,y=820,z=100}, "technic:hv_cable"}, + {{x=101,y=821,z=100}, "technic:hv_generator"}, + {{x=102,y=820,z=100}, "technic:hv_cable"}, + -- {{x=102,y=821,z=100}, "technic:hv_generator"}, -- This machine is built + {{x=102,y=821,z= 99}, "technic:hv_cable"}, -- This should not be added to network + {{x=102,y=821,z=101}, "technic:hv_cable"}, -- This should not be added to network + {{x=103,y=820,z=100}, "technic:hv_cable"}, + {{x=103,y=821,z=100}, "technic:hv_generator"}, + -- Second network for overload test + {{x=100,y=820,z=102}, "technic:hv_cable"}, + {{x=100,y=821,z=102}, "technic:switching_station"}, + -- {{x=100,y=820,z=101}, "technic:hv_generator"}, -- This machine is built, it should overload + }) + -- Build network + local net = get_network_fixture({x=100,y=821,z=100}) + local net2 = get_network_fixture({x=100,y=821,z=102}) + local build_pos = {x=102,y=821,z=100} + local build_pos2 = {x=100,y=820,z=101} + + it("does not crash", function() + assert.equals(2, #net.PR_nodes) + assert.equals(6, count(net.all_nodes)) + world.set_node(build_pos, {name="technic:hv_generator",param2=0}) + technic.network_node_on_placenode(build_pos, "HV", "technic:hv_generator") + end) + + it("is added to network without duplicates", function() + assert.same(build_pos, net.all_nodes[minetest.hash_node_position(build_pos)]) + assert.equals(7, count(net.all_nodes)) + assert.equals(3, #net.PR_nodes) + assert.is_nil(technic.is_overloaded(net.id)) + assert.is_nil(technic.is_overloaded(net2.id)) + end) + + it("does not remove connected machines from network", function() + assert.same({x=101,y=821,z=100},net.all_nodes[minetest.hash_node_position({x=101,y=821,z=100})]) + assert.same({x=103,y=821,z=100},net.all_nodes[minetest.hash_node_position({x=103,y=821,z=100})]) + end) + + it("does not remove network", function() + assert.is_hashed(technic.networks[net.id]) + end) + + it("does not add cables to network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position({x=102,y=821,z=99})]) + assert.is_nil(net.all_nodes[minetest.hash_node_position({x=102,y=821,z=101})]) + end) + + it("overloads network", function() + world.set_node(build_pos2, {name="technic:hv_generator",param2=0}) + technic.network_node_on_placenode(build_pos2, "HV", "technic:hv_generator") + assert.not_nil(technic.is_overloaded(net.id)) + assert.not_nil(technic.is_overloaded(net2.id)) + end) + + end) + + describe("cable cutting", function() + + world.add_layout({ + {{x=100,y=900,z=100}, "technic:hv_cable"}, + {{x=100,y=901,z=100}, "technic:switching_station"}, + {{x=101,y=900,z=100}, "technic:hv_cable"}, + {{x=101,y=901,z=100}, "technic:hv_generator"}, + {{x=102,y=900,z=100}, "technic:hv_cable"}, -- This cable is digged + {{x=103,y=900,z=100}, "technic:hv_cable"}, -- This should disappear + {{x=103,y=901,z=100}, "technic:hv_generator"}, -- This should disappear + }) + -- Build network + local net = get_network_fixture({x=100,y=901,z=100}) + local build_pos = {x=102,y=900,z=100} + + it("does not crash", function() + assert.equals(2, #net.PR_nodes) + assert.equals(6, count(net.all_nodes)) + world.set_node(build_pos, {name="air",param2=0}) + technic.network_node_on_dignode(build_pos, "HV", "technic:hv_cable") + end) + + --[[ NOTE: Whole network is currently removed when cutting cables + + it("is removed from network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position(build_pos)]) + end) + + it("removes connected cables from network", function() + --assert.is_nil(net.all_nodes[minetest.hash_node_position({x=103,y=900,z=100})]) + assert.equals(3, count(net.all_nodes)) + end) + + it("removes connected machines from network", function() + --assert.is_nil(net.all_nodes[minetest.hash_node_position({x=103,y=901,z=100})]) + assert.equals(1, #net.PR_nodes) + end) + --]] + + it("removes network", function() + assert.is_nil(technic.networks[net.id]) + end) + + end) + + describe("cable digging below machine", function() + + world.add_layout({ + {{x=100,y=910,z=100}, "technic:hv_cable"}, + {{x=100,y=911,z=100}, "technic:switching_station"}, + {{x=101,y=910,z=100}, "technic:hv_cable"}, + {{x=101,y=911,z=100}, "technic:hv_generator"}, + {{x=102,y=910,z=100}, "technic:hv_cable"}, + {{x=103,y=910,z=100}, "technic:hv_cable"}, -- This cable is digged + {{x=103,y=911,z=100}, "technic:hv_generator"}, -- This should disappear + -- Multiple cable connections to machine at x 101, vertical cable + {{x=101,y=910,z=101}, "technic:hv_cable"}, -- cables for second connection + {{x=101,y=911,z=101}, "technic:hv_cable"}, -- cables for second connection, this cable is digged + }) + -- Build network + local net = get_network_fixture({x=100,y=911,z=100}) + local build_pos = {x=103,y=910,z=100} + local build_pos2 = {x=101,y=911,z=101} + + it("does not crash", function() + assert.equals(2, #net.PR_nodes) + assert.equals(8, count(net.all_nodes)) + world.set_node(build_pos, {name="air",param2=0}) + technic.network_node_on_dignode(build_pos, "HV", "technic:hv_cable") + end) + + it("is removed from network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position(build_pos)]) + assert.equals(6, count(net.all_nodes)) + end) + + it("removes connected machines from network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position({x=103,y=911,z=100})]) + assert.equals(1, #net.PR_nodes) + end) + + it("does not remove network", function() + assert.is_hashed(technic.networks[net.id]) + end) + + it("keeps connected machines in network", function() + world.set_node(build_pos2, {name="air",param2=0}) + technic.network_node_on_dignode(build_pos2, "HV", "technic:hv_cable") + assert.same({x=101,y=911,z=100}, net.all_nodes[minetest.hash_node_position({x=101,y=911,z=100})]) + assert.equals(1, #net.PR_nodes) + assert.equals(5, count(net.all_nodes)) + end) + + end) + + describe("machine digging", function() + + world.add_layout({ + {{x=100,y=920,z=100}, "technic:hv_cable"}, + {{x=100,y=921,z=100}, "technic:switching_station"}, + {{x=101,y=920,z=100}, "technic:hv_cable"}, + {{x=101,y=921,z=100}, "technic:hv_generator"}, + {{x=102,y=920,z=100}, "technic:hv_cable"}, + {{x=102,y=921,z=100}, "technic:hv_generator"}, -- This machine is digged + {{x=103,y=920,z=100}, "technic:hv_cable"}, + {{x=103,y=921,z=100}, "technic:hv_generator"}, + }) + -- Build network + local net = get_network_fixture({x=100,y=921,z=100}) + local build_pos = {x=102,y=921,z=100} + + it("does not crash", function() + assert.equals(3, #net.PR_nodes) + assert.equals(7, count(net.all_nodes)) + world.set_node(build_pos, {name="air",param2=0}) + technic.network_node_on_dignode(build_pos, "HV", "technic:hv_generator") + end) + + it("is removed from network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position(build_pos)]) + end) + + it("does not remove other nodes from network", function() + assert.equals(6, count(net.all_nodes)) + end) + + it("does not remove connected machines from network", function() + assert.same({x=101,y=921,z=100},net.all_nodes[minetest.hash_node_position({x=101,y=921,z=100})]) + assert.same({x=103,y=921,z=100},net.all_nodes[minetest.hash_node_position({x=103,y=921,z=100})]) + assert.equals(2, #net.PR_nodes) + end) + + it("does not remove network", function() + assert.is_hashed(technic.networks[net.id]) + end) + + end) + +end) diff --git a/technic/spec/fixtures/minetest.lua b/technic/spec/fixtures/minetest.lua index 67c3de8..e6d12ca 100644 --- a/technic/spec/fixtures/minetest.lua +++ b/technic/spec/fixtures/minetest.lua @@ -7,6 +7,17 @@ _G.world.set_node = function(pos, node) local hash = minetest.hash_node_position(pos) world.nodes[hash] = node end +_G.world.add_layout = function(layout, offset) + for _, node in ipairs(layout) do + local pos = node[1] + if offset then + pos.x = pos.x + offset.x + pos.y = pos.y + offset.y + pos.z = pos.z + offset.z + end + _G.world.set_node(pos, {name=node[2], param2=0}) + end +end _G.core = {} _G.minetest = _G.core @@ -80,6 +91,12 @@ _G.minetest.register_on_placenode = noop _G.minetest.register_on_dignode = noop _G.minetest.item_drop = noop +_G.minetest.get_us_time = function() + local socket = require 'socket' + -- FIXME: Returns the time in seconds, relative to the origin of the universe. + return socket.gettime() * 1000 * 1000 +end + _G.minetest.get_node = function(pos) local hash = minetest.hash_node_position(pos) return world.nodes[hash] or {name="IGNORE",param2=0} diff --git a/technic/tools/cans.lua b/technic/tools/cans.lua index c79093f..e6bc93c 100644 --- a/technic/tools/cans.lua +++ b/technic/tools/cans.lua @@ -36,7 +36,8 @@ function technic.register_can(d) local charge = get_can_level(itemstack) if charge == data.can_capacity then return end if minetest.is_protected(pointed_thing.under, user:get_player_name()) then - minetest.log("action", user:get_player_name().." tried to take "..node.name.." at protected position "..minetest.pos_to_string(pointed_thing.under).." with a "..data.can_name) + minetest.log("action", user:get_player_name().." tried to take "..node.name.. + " at protected position "..minetest.pos_to_string(pointed_thing.under).." with a "..data.can_name) return end minetest.remove_node(pointed_thing.under) @@ -63,7 +64,8 @@ function technic.register_can(d) local charge = get_can_level(itemstack) if charge == 0 then return end if minetest.is_protected(pos, user:get_player_name()) then - minetest.log("action", user:get_player_name().." tried to place "..data.liquid_source_name.." at protected position "..minetest.pos_to_string(pos).." with a "..data.can_name) + minetest.log("action", user:get_player_name().." tried to place "..data.liquid_source_name.. + " at protected position "..minetest.pos_to_string(pos).." with a "..data.can_name) return end minetest.set_node(pos, {name=data.liquid_source_name}) diff --git a/technic/tools/mining_drill.lua b/technic/tools/mining_drill.lua index b9147e4..dabbdee 100644 --- a/technic/tools/mining_drill.lua +++ b/technic/tools/mining_drill.lua @@ -242,21 +242,16 @@ end local function mining_drill_mk2_setmode(user,itemstack) local player_name=user:get_player_name() local item=itemstack:to_table() - local mode = nil - local meta=minetest.deserialize(item["metadata"]) - if meta==nil then - meta={} - mode=0 - end + local meta=minetest.deserialize(item["metadata"]) or {} if meta["mode"]==nil then minetest.chat_send_player(player_name, S("Use while sneaking to change Mining Drill Mk%d modes."):format(2)) meta["mode"]=0 - mode=0 end - mode=(meta["mode"]) + local mode=meta["mode"] mode=mode+1 if mode>=5 then mode=1 end - minetest.chat_send_player(player_name, S("Mining Drill Mk%d Mode %d"):format(2, mode)..": "..mining_drill_mode_text[mode][1]) + minetest.chat_send_player(player_name, + S("Mining Drill Mk%d Mode %d"):format(2, mode)..": "..mining_drill_mode_text[mode][1]) itemstack:set_name("technic:mining_drill_mk2_"..mode); meta["mode"]=mode itemstack:set_metadata(minetest.serialize(meta)) @@ -266,20 +261,16 @@ end local function mining_drill_mk3_setmode(user,itemstack) local player_name=user:get_player_name() local item=itemstack:to_table() - local meta=minetest.deserialize(item["metadata"]) - if meta==nil then - meta={} - mode=0 - end + local meta=minetest.deserialize(item["metadata"]) or {} if meta["mode"]==nil then minetest.chat_send_player(player_name, S("Use while sneaking to change Mining Drill Mk%d modes."):format(3)) meta["mode"]=0 - mode=0 end - mode=(meta["mode"]) + local mode=meta["mode"] mode=mode+1 if mode>=6 then mode=1 end - minetest.chat_send_player(player_name, S("Mining Drill Mk%d Mode %d"):format(3, mode)..": "..mining_drill_mode_text[mode][1]) + minetest.chat_send_player(player_name, + S("Mining Drill Mk%d Mode %d"):format(3, mode)..": "..mining_drill_mode_text[mode][1]) itemstack:set_name("technic:mining_drill_mk3_"..mode); meta["mode"]=mode itemstack:set_metadata(minetest.serialize(meta)) diff --git a/technic/tools/prospector.lua b/technic/tools/prospector.lua index d76eb07..948294e 100644 --- a/technic/tools/prospector.lua +++ b/technic/tools/prospector.lua @@ -42,13 +42,19 @@ minetest.register_tool("technic:prospector", { for f = 0, toolmeta.look_depth-1 do for r = 0, look_diameter-1 do for u = 0, look_diameter-1 do - if minetest.get_node(vector.add(vector.add(vector.add(base_pos, vector.multiply(forward, f)), vector.multiply(right, r)), vector.multiply(up, u))).name == toolmeta.target then found = true end + if minetest.get_node(vector.add(vector.add(vector.add(base_pos, vector.multiply(forward, f)), + vector.multiply(right, r)), vector.multiply(up, u))).name == toolmeta.target then + found = true + end end end end if math.random() < 0.02 then found = not found end - minetest.chat_send_player(user:get_player_name(), minetest.registered_nodes[toolmeta.target].description.." is "..(found and "present" or "absent").." in "..look_diameter.."x"..look_diameter.."x"..toolmeta.look_depth.." region") - minetest.sound_play("technic_prospector_"..(found and "hit" or "miss"), { pos = vector.add(user:get_pos(), { x = 0, y = 1, z = 0 }), gain = 1.0, max_hear_distance = 10 }) + minetest.chat_send_player(user:get_player_name(), minetest.registered_nodes[toolmeta.target].description.. + " is "..(found and "present" or "absent").." in "..look_diameter.. + "x"..look_diameter.."x"..toolmeta.look_depth.." region") + minetest.sound_play("technic_prospector_"..(found and "hit" or "miss"), + { pos = vector.add(user:get_pos(), { x = 0, y = 1, z = 0 }), gain = 1.0, max_hear_distance = 10 }) return toolstack end, on_place = function(toolstack, user, pointed_thing) diff --git a/technic/tools/tree_tap.lua b/technic/tools/tree_tap.lua index ae68b56..eb9f36a 100644 --- a/technic/tools/tree_tap.lua +++ b/technic/tools/tree_tap.lua @@ -67,7 +67,9 @@ minetest.register_abm({ interval = 60, chance = 15, action = function(pos, node) - if minetest.find_node_near(pos, (moretrees and moretrees.leafdecay_radius) or 5, {"moretrees:rubber_tree_leaves"}) then + local radius = (moretrees and moretrees.leafdecay_radius) or 5 + local nodes = minetest.find_node_near(pos, radius, {"moretrees:rubber_tree_leaves"}) + if nodes then node.name = "moretrees:rubber_tree_trunk" minetest.swap_node(pos, node) end diff --git a/technic_cnc/cnc.lua b/technic_cnc/cnc.lua index eb158aa..d5cb023 100644 --- a/technic_cnc/cnc.lua +++ b/technic_cnc/cnc.lua @@ -163,7 +163,7 @@ local function form_handler(pos, formname, fields, sender) local inv = meta:get_inventory() local inputstack = inv:get_stack("src", 1) local inputname = inputstack:get_name() - local multiplier = 0 + local multiplier local size = meta:get_int("size") if size < 1 then size = 1 end diff --git a/technic_cnc/cnc_api.lua b/technic_cnc/cnc_api.lua index 3026b47..a2f70ba 100644 --- a/technic_cnc/cnc_api.lua +++ b/technic_cnc/cnc_api.lua @@ -326,9 +326,14 @@ function technic_cnc.register_all(recipeitem, groups, images, description) end --- REGISTER NEW TECHNIC_CNC_API's PART 2: technic_cnc..register_element_end(subname, recipeitem, groups, images, desc_element_xyz) ------------------------------------------------------------------------------------------------------------------------ -function technic_cnc.register_slope_edge_etc(recipeitem, groups, images, desc_slope, desc_slope_lying, desc_slope_upsdown, desc_slope_edge, desc_slope_inner_edge, desc_slope_upsdwn_edge, desc_slope_upsdwn_inner_edge, desc_pyramid, desc_spike, desc_onecurvededge, desc_twocurvededge, desc_cylinder, desc_cylinder_horizontal, desc_spheroid, desc_element_straight, desc_element_edge, desc_element_t, desc_element_cross, desc_element_end) +-- REGISTER NEW TECHNIC_CNC_API's PART 2: +-- technic_cnc..register_element_end(subname, recipeitem, groups, images, desc_element_xyz) +------------------------------------------------------------------------------------------------------------ +function technic_cnc.register_slope_edge_etc(recipeitem, groups, images, desc_slope, desc_slope_lying, + desc_slope_upsdown, desc_slope_edge, desc_slope_inner_edge, desc_slope_upsdwn_edge, + desc_slope_upsdwn_inner_edge, desc_pyramid, desc_spike, desc_onecurvededge, desc_twocurvededge, + desc_cylinder, desc_cylinder_horizontal, desc_spheroid, desc_element_straight, desc_element_edge, + desc_element_t, desc_element_cross, desc_element_end) technic_cnc.register_slope(recipeitem, groups, images, desc_slope) technic_cnc.register_slope_lying(recipeitem, groups, images, desc_slope_lying) @@ -357,7 +362,8 @@ function technic_cnc.register_stick_etc(recipeitem, groups, images, desc_stick) technic_cnc.register_stick(recipeitem, groups, images, desc_stick) end -function technic_cnc.register_elements(recipeitem, groups, images, desc_element_straight_double, desc_element_edge_double, desc_element_t_double, desc_element_cross_double, desc_element_end_double) +function technic_cnc.register_elements(recipeitem, groups, images, desc_element_straight_double, + desc_element_edge_double, desc_element_t_double, desc_element_cross_double, desc_element_end_double) technic_cnc.register_element_straight_double(recipeitem, groups, images, desc_element_straight_double) technic_cnc.register_element_edge_double(recipeitem, groups, images, desc_element_edge_double) technic_cnc.register_element_t_double(recipeitem, groups, images, desc_element_t_double) diff --git a/technic_cnc/init.lua b/technic_cnc/init.lua index 3c4da3e..12b67af 100644 --- a/technic_cnc/init.lua +++ b/technic_cnc/init.lua @@ -10,7 +10,22 @@ technic_cnc.use_technic = technic_cnc.technic_modpath if rawget(_G, "intllib") then technic_cnc.getter = intllib.Getter() else - technic_cnc.getter = function(s,a,...)if a==nil then return s end a={a,...}return s:gsub("(@?)@(%(?)(%d+)(%)?)",function(e,o,n,c)if e==""then return a[tonumber(n)]..(o==""and c or"")else return"@"..o..n..c end end) end + technic_cnc.getter = function(s, a, ...) + if a == nil then + return s + end + a = {a, ...} + return s:gsub( + "(@?)@(%(?)(%d+)(%)?)", + function(e, o, n, c) + if e == "" then + return a[tonumber(n)] .. (o == "" and c or "") + else + return "@" .. o .. n .. c + end + end + ) + end end dofile(modpath.."/cnc.lua") diff --git a/wrench/init.lua b/wrench/init.lua index 3d1dc8e..031c714 100644 --- a/wrench/init.lua +++ b/wrench/init.lua @@ -33,7 +33,7 @@ local function get_pickup_name(name) end local function restore(pos, placer, itemstack) - local name = itemstack:get_name() + local itemname = itemstack:get_name() local node = minetest.get_node(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() @@ -43,7 +43,7 @@ local function restore(pos, placer, itemstack) if not data then minetest.remove_node(pos) minetest.log("error", placer:get_player_name().." wanted to place ".. - name.." at "..minetest.pos_to_string(pos).. + itemname.." at "..minetest.pos_to_string(pos).. ", but it had no data.") minetest.log("verbose", "itemstack: "..itemstack:to_string()) return true @@ -120,13 +120,13 @@ minetest.register_tool("wrench:wrench", { minetest.record_protection_violation(pos, player_name) return end - local name = minetest.get_node(pos).name - local def = wrench.registered_nodes[name] + local nname = minetest.get_node(pos).name + local def = wrench.registered_nodes[nname] if not def then return end - local stack = ItemStack(get_pickup_name(name)) + local stack = ItemStack(get_pickup_name(nname)) local player_inv = placer:get_inventory() if not player_inv:room_for_item("main", stack) then return @@ -144,15 +144,15 @@ minetest.register_tool("wrench:wrench", { end local metadata = {} - metadata.name = name + metadata.name = nname metadata.version = LATEST_SERIALIZATION_VERSION local inv = meta:get_inventory() local lists = {} for _, listname in pairs(def.lists or {}) do local list = inv:get_list(listname) - for i, stack in pairs(list) do - list[i] = stack:to_string() + for i, s in pairs(list) do + list[i] = s:to_string() end lists[listname] = list end From 6bbe8e1da5b96298f12f43af90b72b78553891b0 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sun, 25 Oct 2020 00:19:46 +0300 Subject: [PATCH 12/20] Fix SC/build/dig bugs, some cleanup --- technic/machines/network.lua | 70 +++++++++++++++++--------- technic/machines/register/cables.lua | 29 +++++++---- technic/machines/supply_converter.lua | 4 ++ technic/machines/switching_station.lua | 25 ++------- 4 files changed, 73 insertions(+), 55 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index c77a871..7dfc06f 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -50,7 +50,6 @@ function technic.remove_network(network_id) end networks[network_id] = nil technic.active_networks[network_id] = nil - --print(string.format("technic.remove_network(%.17g) at %s", network_id, minetest.pos_to_string(technic.network2pos(network_id)))) end -- Remove machine or cable from network @@ -62,17 +61,27 @@ function technic.remove_network_node(network_id, pos) local node_id = poshash(pos) cables[node_id] = nil network.all_nodes[node_id] = nil + -- TODO: All following things can be skipped if node is not machine + -- check here if it is or is not cable + -- or add separate function to remove cables and move responsibility to caller -- Clear indexed arrays, do NOT leave holes + local machine_removed = false for _,tblname in ipairs(network_node_arrays) do local tbl = network[tblname] for i=#tbl,1,-1 do local mpos = tbl[i] if mpos.x == pos.x and mpos.y == pos.y and mpos.z == pos.z then table.remove(tbl, i) + machine_removed = true break end end end + if machine_removed then + -- Machine can still be in world, just not connected to any network. If so then disable it. + local node = minetest.get_node(pos) + technic.disable_machine(pos, node) + end end function technic.sw_pos2network(pos) @@ -123,10 +132,28 @@ local function touch_node(tier, pos, timeout) -- this should get built up during registration node_timeout[tier] = {} end - node_timeout[tier][poshash(pos)] = timeout or 2 + node_timeout[tier][poshash(pos)] = timeout or 5 end technic.touch_node = touch_node +function technic.disable_machine(pos, node) + local nodedef = minetest.registered_nodes[node.name] + if nodedef and nodedef.technic_disabled_machine_name then + node.name = nodedef.technic_disabled_machine_name + minetest.swap_node(pos, node) + elseif nodedef and nodedef.technic_on_disable then + nodedef.technic_on_disable(pos, node) + end + if nodedef then + local meta = minetest.get_meta(pos) + meta:set_string("infotext", S("%s Has No Network"):format(nodedef.description)) + end + local node_id = poshash(pos) + for _,nodes in pairs(node_timeout) do + nodes[node_id] = nil + end +end + -- -- Network overloading (incomplete cheat mitigation) -- @@ -165,26 +192,28 @@ technic.is_overloaded = is_overloaded -- -- Add a machine node to the LV/MV/HV network -local function add_network_machine(nodes, pos, network_id, all_nodes, no_overload) +local function add_network_machine(nodes, pos, network_id, all_nodes, multitier) local node_id = poshash(pos) local net_id_old = cables[node_id] - if not no_overload and net_id_old and net_id_old ~= network_id then - -- do not allow running pos from multiple networks, also disable switch - overload_network(network_id, pos) - overload_network(net_id_old, pos) + if net_id_old == nil then + -- Add machine to network only if it is not already added + table.insert(nodes, pos) + cables[node_id] = network_id + all_nodes[node_id] = pos + elseif not multitier and net_id_old ~= network_id then + -- Do not allow running from multiple networks, trigger overload + overload_network(network_id) + overload_network(net_id_old) local meta = minetest.get_meta(pos) meta:set_string("infotext",S("Network Overloaded")) end - table.insert(nodes, pos) - cables[node_id] = network_id - all_nodes[node_id] = pos end -- Add a wire node to the LV/MV/HV network local function add_cable_node(nodes, pos, network_id, queue) local node_id = poshash(pos) - cables[node_id] = network_id - if not nodes[node_id] then + if not cables[node_id] then + cables[node_id] = network_id nodes[node_id] = pos table.insert(queue, pos) end @@ -198,7 +227,6 @@ local function add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, ma if technic.is_tier_cable(name, tier) then add_cable_node(all_nodes, pos, network_id, queue) elseif machines[name] then - if machines[name] == technic.producer then add_network_machine(PR_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.receiver then @@ -209,14 +237,12 @@ local function add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, ma elseif machines[name] == technic.battery then add_network_machine(BA_nodes, pos, network_id, all_nodes) end - - touch_node(tier, pos, 2) -- Touch node end end -- Generic function to add single nodes to the right classification array of existing network function technic.add_network_node(pos, network) - return add_network_node( + add_network_node( network.PR_nodes, network.RE_nodes, network.BA_nodes, @@ -247,7 +273,7 @@ end local function touch_nodes(list, tier) for _, pos in ipairs(list) do - touch_node(tier, pos, 2) -- Touch node + touch_node(tier, pos) -- Touch node end end @@ -288,12 +314,10 @@ function technic.add_network_branch(queue, network) end function technic.build_network(network_id) - --print(string.format("technic.build_network(%.17g) at %s", network_id, minetest.pos_to_string(technic.network2pos(network_id)))) technic.remove_network(network_id) local sw_pos = technic.network2sw_pos(network_id) local tier = technic.sw_pos2tier(sw_pos) if not tier then - --print(string.format("Cannot build network, cannot get tier for switching station at %s", minetest.pos_to_string(sw_pos))) return end local network = { @@ -349,7 +373,7 @@ function technic.network_run(network_id) -- Check if network is overloaded / conflicts with another network if technic.is_overloaded(network_id) then - -- TODO: Overload check should happen before technic.network_run is called, overloaded network should not generate any events + -- TODO: Overload check should happen before technic.network_run is called return end @@ -493,7 +517,7 @@ function technic.network_run(network_id) local t1 = minetest.get_us_time() local diff = t1 - t0 if diff > 50000 then - minetest.log("warning", "[technic] [+supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) + minetest.log("warning", "[technic] [+supply] technic_run took " .. diff .. " us at " .. minetest.pos_to_string(pos)) end return @@ -522,7 +546,7 @@ function technic.network_run(network_id) local t1 = minetest.get_us_time() local diff = t1 - t0 if diff > 50000 then - minetest.log("warning", "[technic] [-supply] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) + minetest.log("warning", "[technic] [-supply] technic_run took " .. diff .. " us at " .. minetest.pos_to_string(pos)) end return @@ -546,7 +570,7 @@ function technic.network_run(network_id) local t1 = minetest.get_us_time() local diff = t1 - t0 if diff > 50000 then - minetest.log("warning", "[technic] switching station abm took " .. diff .. " us at " .. minetest.pos_to_string(pos)) + minetest.log("warning", "[technic] technic_run took " .. diff .. " us at " .. minetest.pos_to_string(pos)) end end diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 08f5116..1ca5ab0 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -13,11 +13,9 @@ end local function get_neighbors(pos, tier) -- TODO: Move this to network.lua - -- TEST: Make sure that multi tier machines work (currently supply converter) - -- these should not be a problem but can return multiple tiers, currently - -- machines collector below only handles single tier. local tier_machines = technic.machines[tier] - local network = technic.networks[technic.pos2network(pos)] + local is_cable = technic.is_tier_cable(minetest.get_node(pos).name, tier) + local network = is_cable and technic.networks[technic.pos2network(pos)] local cables = {} local machines = {} local positions = { @@ -32,7 +30,7 @@ local function get_neighbors(pos, tier) local name = minetest.get_node(connected_pos).name if tier_machines[name] then table.insert(machines, connected_pos) - elseif tier == technic.get_cable_tier(name) then + elseif technic.is_tier_cable(name, tier) then local cable_network = technic.networks[technic.pos2network(connected_pos)] table.insert(cables,{ pos = connected_pos, @@ -55,6 +53,13 @@ local function place_network_node(pos, tier, name) -- Attach to primary network, this must be done before building branches from this position technic.add_network_node(pos, network) if not technic.is_tier_cable(name, tier) then + -- Check connected cables for foreign networks + for _, connection in ipairs(cables) do + if connection.network and connection.network.id ~= network.id then + technic.overload_network(connection.network.id) + technic.overload_network(network.id) + end + end -- Machine added, skip all network building return end @@ -97,15 +102,19 @@ local function remove_network_node(pos, tier, name) -- TEST: this should have test in building_spec.lua for multiple connections if technic.is_tier_cable(name, tier) then for _,machine_pos in ipairs(machines) do - technic.remove_network_node(network.id, machine_pos) + local net, _, _ = get_neighbors(machine_pos, tier) + if not net then + -- Remove machine from network if it does not have other connected cables + technic.remove_network_node(network.id, machine_pos) + end end end else -- TODO: Check branches around and switching stations for branches: - -- remove branches that do not have switching station. - -- remove branches not connected to another branch. - -- do not rebuild networks here, leave that for ABM to reduce unnecessary cache building. - -- To do all this network must be aware of individual branches, might not be worth it... + -- remove branches that do not have switching station. Switching stations not tracked but could be easily tracked. + -- remove branches not connected to another branch. Individual branches not tracked, requires simple AI heuristics. + -- move branches that have switching station to new networks without checking or loading actual nodes in world. + -- To do all this network must be aware of individual branches and switching stations, might not be worth it... -- For now remove whole network and let ABM rebuild it technic.remove_network(network.id) end diff --git a/technic/machines/supply_converter.lua b/technic/machines/supply_converter.lua index 19afb98..787c912 100644 --- a/technic/machines/supply_converter.lua +++ b/technic/machines/supply_converter.lua @@ -143,6 +143,10 @@ local run = function(pos, node, run_stage) if from and to then local input = meta:get_int(from.."_EU_input") + if (technic.get_timeout(from, pos) <= 0) or (technic.get_timeout(to, pos) <= 0) then + -- Supply converter timed out, either RE or PR network is not running anymore + input = 0 + end meta:set_int(from.."_EU_demand", demand) meta:set_int(from.."_EU_supply", 0) meta:set_int(to.."_EU_demand", 0) diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 6c24607..e0af8b8 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -128,44 +128,25 @@ end) -- Timeout ABM -- Timeout for a node in case it was disconnected from the network -- A node must be touched by the station continuously in order to function --- TODO: If possible replace this with something that does not need ABM, preferably without any timers minetest.register_abm({ label = "Machines: timeout check", nodenames = {"group:technic_machine"}, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) - local tiers = machine_tiers[node.name] - if not tiers then - -- This should not happen ever, machines without at least single tier should not exist. Consider removing check? - return - end -- Check for machine timeouts for all tiers + local tiers = machine_tiers[node.name] local timed_out = true for _, tier in ipairs(tiers) do local timeout = technic.get_timeout(tier, pos) - if timeout <= 0 then - local meta = minetest.get_meta(pos) - meta:set_int(tier.."_EU_input", 0) -- Not needed anymore <-- actually, it is for supply converter <-- Actually supply converter could handle this alone too - else + if timeout > 0 then technic.touch_node(tier, pos, timeout - 1) timed_out = false - break end end -- If all tiers for machine timed out take action if timed_out then - local nodedef = minetest.registered_nodes[node.name] - if nodedef and nodedef.technic_disabled_machine_name then - node.name = nodedef.technic_disabled_machine_name - minetest.swap_node(pos, node) - elseif nodedef and nodedef.technic_on_disable then - nodedef.technic_on_disable(pos, node) - end - if nodedef then - local meta = minetest.get_meta(pos) - meta:set_string("infotext", S("%s Has No Network"):format(nodedef.description)) - end + technic.disable_machine(pos, node) end end, }) From bdf8d2e0cc1a92d76a5496a130fb80fdc66757a1 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sun, 25 Oct 2020 03:46:37 +0300 Subject: [PATCH 13/20] Make default_timeout dynamic, remove power_monitor from machine group --- technic/machines/network.lua | 7 ++++++- technic/machines/power_monitor.lua | 2 +- technic/machines/register/cables.lua | 2 -- technic/machines/switching_station_globalstep.lua | 2 ++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 7dfc06f..f8f5d02 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -118,6 +118,11 @@ function technic.network_infotext(network_id, text) end local node_timeout = {} +local default_timeout = 2 + +function technic.set_default_timeout(timeout) + default_timeout = timeout or 2 +end function technic.get_timeout(tier, pos) if node_timeout[tier] == nil then @@ -132,7 +137,7 @@ local function touch_node(tier, pos, timeout) -- this should get built up during registration node_timeout[tier] = {} end - node_timeout[tier][poshash(pos)] = timeout or 5 + node_timeout[tier][poshash(pos)] = timeout or default_timeout end technic.touch_node = touch_node diff --git a/technic/machines/power_monitor.lua b/technic/machines/power_monitor.lua index 3618cf8..7d6ca99 100644 --- a/technic/machines/power_monitor.lua +++ b/technic/machines/power_monitor.lua @@ -59,7 +59,7 @@ minetest.register_node("technic:power_monitor",{ "technic_power_monitor_front.png" }, paramtype2 = "facedir", - groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_all_tiers=1, technic_machine=1}, + groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_all_tiers=1}, connect_sides = {"bottom", "back"}, sounds = default.node_sound_wood_defaults(), on_construct = function(pos) diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 1ca5ab0..c554a92 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -98,8 +98,6 @@ local function remove_network_node(pos, tier, name) -- Dead end cable removed, remove it from the network technic.remove_network_node(network.id, pos) -- Remove neighbor machines from network if cable was removed - -- FIXME: this is not compatible with machines that have multiple connections to network - -- TEST: this should have test in building_spec.lua for multiple connections if technic.is_tier_cable(name, tier) then for _,machine_pos in ipairs(machines) do local net, _, _ = get_neighbors(machine_pos, tier) diff --git a/technic/machines/switching_station_globalstep.lua b/technic/machines/switching_station_globalstep.lua index 98ee41a..bb92eb3 100644 --- a/technic/machines/switching_station_globalstep.lua +++ b/technic/machines/switching_station_globalstep.lua @@ -17,6 +17,7 @@ end -- the interval between technic_run calls local technic_run_interval = 1.0 +local set_default_timeout = technic.set_default_timeout -- iterate over all collected switching stations and execute the technic_run function local timer = 0 @@ -41,6 +42,7 @@ minetest.register_globalstep(function(dtime) -- normal run_interval technic_run_interval = 1.0 end + set_default_timeout(math.ceil(technic_run_interval) + 1) local now = minetest.get_us_time() From d20695681023294de8bdc6b847b4d16756fdecc4 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sun, 25 Oct 2020 09:26:50 +0200 Subject: [PATCH 14/20] Destroy network if ref switch disappears, cleanup switching station code Remove rest of SP_nodes from code, it does not contain anything Reset switch infotext, export machine_tiers Add tests for cable building between active networks Remove all networks when cable is placed between networks Fix SC connectivity issues --- technic/machines/network.lua | 9 ++-- technic/machines/register/cables.lua | 41 +++++++++++++++---- technic/machines/switching_station.lua | 23 +++++------ .../machines/switching_station_globalstep.lua | 2 +- technic/spec/building_spec.lua | 29 +++++++++++++ technic/spec/network_spec.lua | 2 - 6 files changed, 77 insertions(+), 29 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index f8f5d02..1dd58ce 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -53,7 +53,7 @@ function technic.remove_network(network_id) end -- Remove machine or cable from network -local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes","SP_nodes"} +local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes"} function technic.remove_network_node(network_id, pos) local network = networks[network_id] if not network then return end @@ -200,12 +200,13 @@ technic.is_overloaded = is_overloaded local function add_network_machine(nodes, pos, network_id, all_nodes, multitier) local node_id = poshash(pos) local net_id_old = cables[node_id] - if net_id_old == nil then + if net_id_old == nil or (multitier and net_id_old ~= network_id and all_nodes[node_id] == nil) then -- Add machine to network only if it is not already added table.insert(nodes, pos) + -- FIXME: Machines connecting to multiple networks should have way to store multiple network ids cables[node_id] = network_id all_nodes[node_id] = pos - elseif not multitier and net_id_old ~= network_id then + elseif net_id_old ~= network_id then -- Do not allow running from multiple networks, trigger overload overload_network(network_id) overload_network(net_id_old) @@ -329,7 +330,7 @@ function technic.build_network(network_id) -- Basic network data and lookup table for attached nodes (no switching stations) id = network_id, tier = tier, all_nodes = {}, -- Indexed arrays for iteration by machine type - SP_nodes = {}, PR_nodes = {}, RE_nodes = {}, BA_nodes = {}, + PR_nodes = {}, RE_nodes = {}, BA_nodes = {}, -- Power generation, usage and capacity related variables supply = 0, demand = 0, battery_charge = 0, battery_charge_max = 0, -- Network activation and excution control diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index c554a92..528c08d 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -11,10 +11,17 @@ function technic.get_cable_tier(name) return cable_tier[name] end +local function match_cable_tier_filter(name, tier) + -- Helper for get_neighbors to check for specific or any tier cable + if tier then + return cable_tier[name] == tier + end + return cable_tier[name] ~= nil +end local function get_neighbors(pos, tier) -- TODO: Move this to network.lua - local tier_machines = technic.machines[tier] - local is_cable = technic.is_tier_cable(minetest.get_node(pos).name, tier) + local tier_machines = tier and technic.machines[tier] + local is_cable = match_cable_tier_filter(minetest.get_node(pos).name, tier) local network = is_cable and technic.networks[technic.pos2network(pos)] local cables = {} local machines = {} @@ -28,9 +35,9 @@ local function get_neighbors(pos, tier) } for _,connected_pos in ipairs(positions) do local name = minetest.get_node(connected_pos).name - if tier_machines[name] then + if tier_machines and tier_machines[name] then table.insert(machines, connected_pos) - elseif technic.is_tier_cable(name, tier) then + elseif match_cable_tier_filter(name, tier) then local cable_network = technic.networks[technic.pos2network(connected_pos)] table.insert(cables,{ pos = connected_pos, @@ -53,11 +60,26 @@ local function place_network_node(pos, tier, name) -- Attach to primary network, this must be done before building branches from this position technic.add_network_node(pos, network) if not technic.is_tier_cable(name, tier) then - -- Check connected cables for foreign networks - for _, connection in ipairs(cables) do - if connection.network and connection.network.id ~= network.id then - technic.overload_network(connection.network.id) - technic.overload_network(network.id) + if technic.machines[tier][name] == technic.producer_receiver then + -- FIXME: Multi tier machine like supply converter should also attach to other networks around pos. + -- Preferably also with connection rules defined for machine. + -- nodedef.connect_sides could be used to generate these rules. + -- For now, assume that all multi network machines belong to technic.producer_receiver group: + -- Get cables and networks around PR_RE machine + local _, machine_cables, _ = get_neighbors(pos) + for _,connection in ipairs(machine_cables) do + if connection.network and connection.network ~= network then + -- Attach PR_RE machine to secondary networks (last added is primary until above note is resolved) + technic.add_network_node(pos, connection.network) + end + end + else + -- Check connected cables for foreign networks, overload if machine was connected to multiple networks + for _, connection in ipairs(cables) do + if connection.network and connection.network.id ~= network.id then + technic.overload_network(connection.network.id) + technic.overload_network(network.id) + end end end -- Machine added, skip all network building @@ -76,6 +98,7 @@ local function place_network_node(pos, tier, name) -- Remove network if position belongs to another network -- FIXME: Network requires partial rebuild but avoid doing it here if possible. -- This might cause problems when merging two active networks into one + technic.remove_network(network.id) technic.remove_network(connection.network.id) connection.network = nil end diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index e0af8b8..6ac5a5a 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -17,7 +17,11 @@ minetest.register_craft({ local function start_network(pos) local tier = technic.sw_pos2tier(pos) - if not tier then return end + if not tier then + local meta = minetest.get_meta(pos) + meta:set_string("infotext", S("%s Has No Network"):format(S("Switching Station"))) + return + end local network_id = technic.sw_pos2network(pos) or technic.create_network(pos) technic.activate_network(network_id) end @@ -48,20 +52,12 @@ minetest.register_node("technic:switching_station",{ meta:set_string("formspec", "field[channel;Channel;${channel}]") start_network(pos) end, - after_dig_node = function(pos) + on_destruct = function(pos) -- Remove network when switching station is removed, if -- there's another switching station network will be rebuilt. local network_id = technic.sw_pos2network(pos) - local network = network_id and technic.networks[network_id] - if network then - if #network.SP_nodes <= 1 then - -- Last switching station, network collapses - technic.remove_network(network_id) - else - -- Remove switching station from network - network.SP_nodes[minetest.hash_node_position(pos)] = nil - network.all_nodes[minetest.hash_node_position(pos)] = nil - end + if technic.networks[network_id] then + technic.remove_network(network_id) end end, on_receive_fields = function(pos, formname, fields, sender) @@ -114,8 +110,9 @@ minetest.register_node("technic:switching_station",{ -- The action code for the switching station -- ----------------------------------------------- --- Lookup table for machine tiers +-- Lookup table for machine tiers, export for external use local machine_tiers = {} +technic.machine_tiers = machine_tiers minetest.register_on_mods_loaded(function() for tier, machines in pairs(technic.machines) do for name,_ in pairs(machines) do diff --git a/technic/machines/switching_station_globalstep.lua b/technic/machines/switching_station_globalstep.lua index bb92eb3..8932aa0 100644 --- a/technic/machines/switching_station_globalstep.lua +++ b/technic/machines/switching_station_globalstep.lua @@ -55,7 +55,7 @@ minetest.register_globalstep(function(dtime) if node.name ~= "technic:switching_station" then -- station vanished - technic.active_networks[network_id] = nil + technic.remove_network(network_id) elseif network.timeout > now then -- station active diff --git a/technic/spec/building_spec.lua b/technic/spec/building_spec.lua index 7bb3137..a81aef3 100644 --- a/technic/spec/building_spec.lua +++ b/technic/spec/building_spec.lua @@ -184,6 +184,35 @@ describe("Power network building", function() end) + describe("cable building between networks", function() + + world.add_layout({ + {{x=100,y=830,z=100}, "technic:hv_cable"}, + {{x=100,y=831,z=100}, "technic:switching_station"}, + --{{x=101,y=830,z=100}, "technic:hv_cable"}, -- This cable is built + --{{x=101,y=831,z=100}, "technic:hv_cable"}, -- TODO: Add this cable as test case? + {{x=102,y=830,z=100}, "technic:hv_cable"}, + {{x=102,y=831,z=100}, "technic:switching_station"}, + }) + -- Build network + local net = get_network_fixture({x=100,y=831,z=100}) + local net2 = get_network_fixture({x=102,y=831,z=100}) + local build_pos = {x=101,y=830,z=100} + + it("does not crash", function() + assert.equals(1, count(net.all_nodes)) + assert.equals(1, count(net2.all_nodes)) + world.set_node(build_pos, {name="technic:hv_cable", param2=0}) + technic.network_node_on_placenode(build_pos, "HV", "technic:hv_cable") + end) + + it("removes network", function() + assert.is_nil(technic.networks[net.id]) + assert.is_nil(technic.networks[net2.id]) + end) + + end) + describe("cable cutting", function() world.add_layout({ diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua index afb7280..87569b6 100644 --- a/technic/spec/network_spec.lua +++ b/technic/spec/network_spec.lua @@ -72,7 +72,6 @@ describe("Power network helper", function() it("builds network", function() local net = technic.networks[net_id] -- Network table is valid - assert.is_indexed(net.SP_nodes) assert.is_indexed(net.PR_nodes) assert.is_indexed(net.RE_nodes) assert.is_indexed(net.BA_nodes) @@ -83,7 +82,6 @@ describe("Power network helper", function() it("does not add duplicates to network", function() local net = technic.networks[net_id] -- Local network table is still valid - assert.equals(0, count(net.SP_nodes)) assert.equals(1, count(net.PR_nodes)) assert.equals(0, count(net.RE_nodes)) assert.equals(0, count(net.BA_nodes)) From 977913c785f740f7dd327104834ed900c1138990 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Fri, 30 Oct 2020 20:03:04 +0200 Subject: [PATCH 15/20] Basic tests for SC, cleaner world fixture management --- technic/spec/building_spec.lua | 34 +++---- technic/spec/fixtures/minetest.lua | 6 ++ technic/spec/fixtures/network.lua | 53 +++------- technic/spec/network_spec.lua | 35 +++++++ technic/spec/supply_converter_spec.lua | 136 +++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 57 deletions(-) create mode 100644 technic/spec/supply_converter_spec.lua diff --git a/technic/spec/building_spec.lua b/technic/spec/building_spec.lua index a81aef3..83c5c02 100644 --- a/technic/spec/building_spec.lua +++ b/technic/spec/building_spec.lua @@ -35,7 +35,7 @@ describe("Power network building", function() describe("cable building", function() - world.add_layout({ + world.layout({ {{x=100,y=800,z=100}, "technic:hv_cable"}, {{x=100,y=801,z=100}, "technic:switching_station"}, {{x=101,y=800,z=100}, "technic:hv_cable"}, @@ -53,7 +53,7 @@ describe("Power network building", function() assert.equals(1, #net.PR_nodes) assert.equals(3, count(net.all_nodes)) world.set_node(build_pos, {name="technic:hv_cable", param2=0}) - technic.network_node_on_placenode(build_pos, "HV", "technic:hv_cable") + technic.network_node_on_placenode(build_pos, {"HV"}, "technic:hv_cable") end) it("is added to network", function() @@ -73,7 +73,7 @@ describe("Power network building", function() describe("cable building to machine", function() - world.add_layout({ + world.layout({ {{x=100,y=810,z=100}, "technic:hv_cable"}, {{x=100,y=811,z=100}, "technic:switching_station"}, {{x=101,y=810,z=100}, "technic:hv_cable"}, @@ -93,7 +93,7 @@ describe("Power network building", function() assert.equals(1, #net.PR_nodes) assert.equals(4, count(net.all_nodes)) world.set_node(build_pos, {name="technic:hv_cable", param2=0}) - technic.network_node_on_placenode(build_pos, "HV", "technic:hv_cable") + technic.network_node_on_placenode(build_pos, {"HV"}, "technic:hv_cable") end) it("is added to network", function() @@ -115,7 +115,7 @@ describe("Power network building", function() it("does not duplicate already added machine", function() world.set_node(build_pos2, {name="technic:hv_cable", param2=0}) - technic.network_node_on_placenode(build_pos2, "HV", "technic:hv_cable") + technic.network_node_on_placenode(build_pos2, {"HV"}, "technic:hv_cable") assert.equals(2, #net.PR_nodes) assert.equals(7, count(net.all_nodes)) end) @@ -124,7 +124,7 @@ describe("Power network building", function() describe("machine building", function() - world.add_layout({ + world.layout({ {{x=100,y=820,z=100}, "technic:hv_cable"}, {{x=100,y=821,z=100}, "technic:switching_station"}, {{x=101,y=820,z=100}, "technic:hv_cable"}, @@ -150,7 +150,7 @@ describe("Power network building", function() assert.equals(2, #net.PR_nodes) assert.equals(6, count(net.all_nodes)) world.set_node(build_pos, {name="technic:hv_generator",param2=0}) - technic.network_node_on_placenode(build_pos, "HV", "technic:hv_generator") + technic.network_node_on_placenode(build_pos, {"HV"}, "technic:hv_generator") end) it("is added to network without duplicates", function() @@ -177,7 +177,7 @@ describe("Power network building", function() it("overloads network", function() world.set_node(build_pos2, {name="technic:hv_generator",param2=0}) - technic.network_node_on_placenode(build_pos2, "HV", "technic:hv_generator") + technic.network_node_on_placenode(build_pos2, {"HV"}, "technic:hv_generator") assert.not_nil(technic.is_overloaded(net.id)) assert.not_nil(technic.is_overloaded(net2.id)) end) @@ -186,7 +186,7 @@ describe("Power network building", function() describe("cable building between networks", function() - world.add_layout({ + world.layout({ {{x=100,y=830,z=100}, "technic:hv_cable"}, {{x=100,y=831,z=100}, "technic:switching_station"}, --{{x=101,y=830,z=100}, "technic:hv_cable"}, -- This cable is built @@ -203,7 +203,7 @@ describe("Power network building", function() assert.equals(1, count(net.all_nodes)) assert.equals(1, count(net2.all_nodes)) world.set_node(build_pos, {name="technic:hv_cable", param2=0}) - technic.network_node_on_placenode(build_pos, "HV", "technic:hv_cable") + technic.network_node_on_placenode(build_pos, {"HV"}, "technic:hv_cable") end) it("removes network", function() @@ -215,7 +215,7 @@ describe("Power network building", function() describe("cable cutting", function() - world.add_layout({ + world.layout({ {{x=100,y=900,z=100}, "technic:hv_cable"}, {{x=100,y=901,z=100}, "technic:switching_station"}, {{x=101,y=900,z=100}, "technic:hv_cable"}, @@ -232,7 +232,7 @@ describe("Power network building", function() assert.equals(2, #net.PR_nodes) assert.equals(6, count(net.all_nodes)) world.set_node(build_pos, {name="air",param2=0}) - technic.network_node_on_dignode(build_pos, "HV", "technic:hv_cable") + technic.network_node_on_dignode(build_pos, {"HV"}, "technic:hv_cable") end) --[[ NOTE: Whole network is currently removed when cutting cables @@ -260,7 +260,7 @@ describe("Power network building", function() describe("cable digging below machine", function() - world.add_layout({ + world.layout({ {{x=100,y=910,z=100}, "technic:hv_cable"}, {{x=100,y=911,z=100}, "technic:switching_station"}, {{x=101,y=910,z=100}, "technic:hv_cable"}, @@ -281,7 +281,7 @@ describe("Power network building", function() assert.equals(2, #net.PR_nodes) assert.equals(8, count(net.all_nodes)) world.set_node(build_pos, {name="air",param2=0}) - technic.network_node_on_dignode(build_pos, "HV", "technic:hv_cable") + technic.network_node_on_dignode(build_pos, {"HV"}, "technic:hv_cable") end) it("is removed from network", function() @@ -300,7 +300,7 @@ describe("Power network building", function() it("keeps connected machines in network", function() world.set_node(build_pos2, {name="air",param2=0}) - technic.network_node_on_dignode(build_pos2, "HV", "technic:hv_cable") + technic.network_node_on_dignode(build_pos2, {"HV"}, "technic:hv_cable") assert.same({x=101,y=911,z=100}, net.all_nodes[minetest.hash_node_position({x=101,y=911,z=100})]) assert.equals(1, #net.PR_nodes) assert.equals(5, count(net.all_nodes)) @@ -310,7 +310,7 @@ describe("Power network building", function() describe("machine digging", function() - world.add_layout({ + world.layout({ {{x=100,y=920,z=100}, "technic:hv_cable"}, {{x=100,y=921,z=100}, "technic:switching_station"}, {{x=101,y=920,z=100}, "technic:hv_cable"}, @@ -328,7 +328,7 @@ describe("Power network building", function() assert.equals(3, #net.PR_nodes) assert.equals(7, count(net.all_nodes)) world.set_node(build_pos, {name="air",param2=0}) - technic.network_node_on_dignode(build_pos, "HV", "technic:hv_generator") + technic.network_node_on_dignode(build_pos, {"HV"}, "technic:hv_generator") end) it("is removed from network", function() diff --git a/technic/spec/fixtures/minetest.lua b/technic/spec/fixtures/minetest.lua index e6d12ca..ed6a552 100644 --- a/technic/spec/fixtures/minetest.lua +++ b/technic/spec/fixtures/minetest.lua @@ -7,6 +7,11 @@ _G.world.set_node = function(pos, node) local hash = minetest.hash_node_position(pos) world.nodes[hash] = node end +_G.world.clear = function() _G.world.nodes = {} end +_G.world.layout = function(layout, offset) + _G.world.clear() + _G.world.add_layout(layout, offset) +end _G.world.add_layout = function(layout, offset) for _, node in ipairs(layout) do local pos = node[1] @@ -89,6 +94,7 @@ _G.minetest.register_craft = noop _G.minetest.register_node = noop _G.minetest.register_on_placenode = noop _G.minetest.register_on_dignode = noop +_G.minetest.register_on_mods_loaded = noop _G.minetest.item_drop = noop _G.minetest.get_us_time = function() diff --git a/technic/spec/fixtures/network.lua b/technic/spec/fixtures/network.lua index dc3d37f..bf9815e 100644 --- a/technic/spec/fixtures/network.lua +++ b/technic/spec/fixtures/network.lua @@ -1,48 +1,21 @@ -local world = { - {{x=100,y=100,z=100}, "technic:lv_cable"}, - {{x=101,y=100,z=100}, "technic:lv_cable"}, - {{x=102,y=100,z=100}, "technic:lv_cable"}, - {{x=103,y=100,z=100}, "technic:lv_cable"}, - {{x=104,y=100,z=100}, "technic:lv_cable"}, - {{x=100,y=101,z=100}, "technic:switching_station"}, - - {{x=100,y=200,z=100}, "technic:mv_cable"}, - {{x=101,y=200,z=100}, "technic:mv_cable"}, - {{x=102,y=200,z=100}, "technic:mv_cable"}, - {{x=103,y=200,z=100}, "technic:mv_cable"}, - {{x=104,y=200,z=100}, "technic:mv_cable"}, - {{x=100,y=201,z=100}, "technic:switching_station"}, - - {{x=100,y=300,z=100}, "technic:hv_cable"}, - {{x=101,y=300,z=100}, "technic:hv_cable"}, - {{x=102,y=300,z=100}, "technic:hv_cable"}, - {{x=103,y=300,z=100}, "technic:hv_cable"}, - {{x=104,y=300,z=100}, "technic:hv_cable"}, - {{x=100,y=301,z=100}, "technic:switching_station"}, - - -- For network lookup function -> returns correct network for position - {{x=100,y=500,z=100}, "technic:hv_cable"}, - {{x=101,y=500,z=100}, "technic:hv_cable"}, - {{x=102,y=500,z=100}, "technic:hv_cable"}, - {{x=103,y=500,z=100}, "technic:hv_cable"}, - {{x=104,y=500,z=100}, "technic:hv_cable"}, - {{x=100,y=501,z=100}, "technic:hv_generator"}, - {{x=101,y=501,z=100}, "technic:hv_cable"}, - {{x=102,y=501,z=100}, "technic:switching_station"}, - {{x=100,y=502,z=100}, "technic:hv_cable"}, - {{x=101,y=502,z=100}, "technic:hv_cable"}, -} - --- Build world for tests -for _,node in ipairs(world) do - _G.world.set_node(node[1], {name=node[2], param2=0}) -end - _G.technic = {} _G.technic.S = string.format _G.technic.getter = function(...) return "" end _G.technic.get_or_load_node = minetest.get_node +_G.technic.digilines = { + rules = { + -- digilines.rules.default + {x= 1,y= 0,z= 0},{x=-1,y= 0,z= 0}, -- along x beside + {x= 0,y= 0,z= 1},{x= 0,y= 0,z=-1}, -- along z beside + {x= 1,y= 1,z= 0},{x=-1,y= 1,z= 0}, -- 1 node above along x diagonal + {x= 0,y= 1,z= 1},{x= 0,y= 1,z=-1}, -- 1 node above along z diagonal + {x= 1,y=-1,z= 0},{x=-1,y=-1,z= 0}, -- 1 node below along x diagonal + {x= 0,y=-1,z= 1},{x= 0,y=-1,z=-1}, -- 1 node below along z diagonal + -- added rules for digi cable + {x = 0, y = -1, z = 0}, -- along y below + } +} sourcefile("register") technic.register_tier("LV", "Busted LV") diff --git a/technic/spec/network_spec.lua b/technic/spec/network_spec.lua index 87569b6..0970d51 100644 --- a/technic/spec/network_spec.lua +++ b/technic/spec/network_spec.lua @@ -22,6 +22,41 @@ sourcefile("machines/HV/cables") sourcefile("machines/register/generator") sourcefile("machines/HV/generator") +world.layout({ + {{x=100,y=100,z=100}, "technic:lv_cable"}, + {{x=101,y=100,z=100}, "technic:lv_cable"}, + {{x=102,y=100,z=100}, "technic:lv_cable"}, + {{x=103,y=100,z=100}, "technic:lv_cable"}, + {{x=104,y=100,z=100}, "technic:lv_cable"}, + {{x=100,y=101,z=100}, "technic:switching_station"}, + + {{x=100,y=200,z=100}, "technic:mv_cable"}, + {{x=101,y=200,z=100}, "technic:mv_cable"}, + {{x=102,y=200,z=100}, "technic:mv_cable"}, + {{x=103,y=200,z=100}, "technic:mv_cable"}, + {{x=104,y=200,z=100}, "technic:mv_cable"}, + {{x=100,y=201,z=100}, "technic:switching_station"}, + + {{x=100,y=300,z=100}, "technic:hv_cable"}, + {{x=101,y=300,z=100}, "technic:hv_cable"}, + {{x=102,y=300,z=100}, "technic:hv_cable"}, + {{x=103,y=300,z=100}, "technic:hv_cable"}, + {{x=104,y=300,z=100}, "technic:hv_cable"}, + {{x=100,y=301,z=100}, "technic:switching_station"}, + + -- For network lookup function -> returns correct network for position + {{x=100,y=500,z=100}, "technic:hv_cable"}, + {{x=101,y=500,z=100}, "technic:hv_cable"}, + {{x=102,y=500,z=100}, "technic:hv_cable"}, + {{x=103,y=500,z=100}, "technic:hv_cable"}, + {{x=104,y=500,z=100}, "technic:hv_cable"}, + {{x=100,y=501,z=100}, "technic:hv_generator"}, + {{x=101,y=501,z=100}, "technic:hv_cable"}, + {{x=102,y=501,z=100}, "technic:switching_station"}, + {{x=100,y=502,z=100}, "technic:hv_cable"}, + {{x=101,y=502,z=100}, "technic:hv_cable"}, +}) + describe("Power network helper", function() -- Simple network position fixtures diff --git a/technic/spec/supply_converter_spec.lua b/technic/spec/supply_converter_spec.lua new file mode 100644 index 0000000..d2db426 --- /dev/null +++ b/technic/spec/supply_converter_spec.lua @@ -0,0 +1,136 @@ +dofile("spec/test_helpers.lua") +--[[ + Technic network unit tests. + Execute busted at technic source directory. +--]] + +-- Load fixtures required by tests +fixture("minetest") +fixture("minetest/player") +fixture("minetest/protection") + +fixture("pipeworks") +fixture("network") + +sourcefile("machines/network") + +sourcefile("machines/register/cables") +sourcefile("machines/LV/cables") +sourcefile("machines/MV/cables") +sourcefile("machines/HV/cables") + +sourcefile("machines/supply_converter") + +function get_network_fixture(sw_pos) + -- Build network + local net_id = technic.create_network(sw_pos) + assert.is_number(net_id) + local net = technic.networks[net_id] + assert.is_table(net) + return net +end + +describe("Supply converter", function() + + describe("building", function() + + world.layout({ + {{x=100,y=820,z=100}, "technic:hv_cable"}, + {{x=100,y=821,z=100}, "technic:switching_station"}, + {{x=101,y=820,z=100}, "technic:hv_cable"}, + {{x=101,y=821,z=100}, "technic:supply_converter"}, + {{x=102,y=820,z=100}, "technic:hv_cable"}, + -- {{x=102,y=821,z=100}, "technic:supply_converter"}, -- This machine is built + {{x=102,y=821,z= 99}, "technic:hv_cable"}, -- This should not be added to network + {{x=102,y=821,z=101}, "technic:hv_cable"}, -- This should not be added to network + {{x=103,y=820,z=100}, "technic:hv_cable"}, + -- Second network for overload test + {{x=100,y=820,z=102}, "technic:hv_cable"}, + {{x=100,y=821,z=102}, "technic:switching_station"}, + -- {{x=100,y=820,z=101}, "technic:supply_converter"}, -- This machine is built, it should overload + }) + -- Build network + local net = get_network_fixture({x=100,y=821,z=100}) + local net2 = get_network_fixture({x=100,y=821,z=102}) + local build_pos = {x=102,y=821,z=100} + local build_pos2 = {x=100,y=820,z=101} + + it("does not crash", function() + assert.equals(1, #net.PR_nodes) + assert.equals(1, #net.RE_nodes) + assert.equals(5, count(net.all_nodes)) + world.set_node(build_pos, {name="technic:supply_converter",param2=0}) + technic.network_node_on_placenode(build_pos, {"HV"}, "technic:supply_converter") + end) + + it("is added to network without duplicates", function() + assert.same(build_pos, net.all_nodes[minetest.hash_node_position(build_pos)]) + assert.equals(6, count(net.all_nodes)) + assert.equals(2, #net.PR_nodes) + assert.equals(2, #net.RE_nodes) + assert.is_nil(technic.is_overloaded(net.id)) + assert.is_nil(technic.is_overloaded(net2.id)) + end) + + it("does not remove connected machines from network", function() + assert.same({x=101,y=821,z=100},net.all_nodes[minetest.hash_node_position({x=101,y=821,z=100})]) + end) + + it("does not remove networks", function() + assert.is_hashed(technic.networks[net.id]) + assert.is_hashed(technic.networks[net2.id]) + end) + + it("does not add cables to network", function() + assert.is_nil(net.all_nodes[minetest.hash_node_position({x=102,y=821,z=99})]) + assert.is_nil(net.all_nodes[minetest.hash_node_position({x=102,y=821,z=101})]) + end) + + it("overloads network", function() + pending("overload does not work with supply converter") + world.set_node(build_pos2, {name="technic:supply_converter",param2=0}) + technic.network_node_on_placenode(build_pos2, {"HV"}, "technic:supply_converter") + assert.not_nil(technic.is_overloaded(net.id)) + assert.not_nil(technic.is_overloaded(net2.id)) + end) + + end) + + describe("digging", function() + + world.layout({ + {{x=100,y=990,z=100}, "technic:hv_cable"}, + {{x=100,y=991,z=100}, "technic:switching_station"}, + {{x=101,y=990,z=100}, "technic:hv_cable"}, + {{x=102,y=990,z=100}, "technic:hv_cable"}, + {{x=102,y=991,z=100}, "technic:supply_converter"}, -- This machine is digged + }) + -- Build network + local net = get_network_fixture({x=100,y=991,z=100}) + local build_pos = {x=102,y=991,z=100} + + it("does not crash", function() + assert.equals(1, #net.PR_nodes) + assert.equals(1, #net.RE_nodes) + assert.equals(4, count(net.all_nodes)) + world.set_node(build_pos, {name="air",param2=0}) + technic.network_node_on_dignode(build_pos, {"HV"}, "technic:supply_converter") + end) + + it("is removed from network", function() + assert.is_nil(technic.pos2network(build_pos)) + assert.is_nil(technic.cables[minetest.hash_node_position(build_pos)]) + assert.is_nil(net.all_nodes[minetest.hash_node_position(build_pos)]) + end) + + it("does not remove other nodes from network", function() + assert.equals(3, count(net.all_nodes)) + end) + + it("does not remove network", function() + assert.is_hashed(technic.networks[net.id]) + end) + + end) + +end) From 9887a5950b5ab7d8adb1d9329940a4685145a133 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Fri, 30 Oct 2020 22:43:19 +0200 Subject: [PATCH 16/20] Remove global dig/place handlers. Better multi tier support for dig/place Fix on_construct/on_destruct registration --- technic/machines/register/cables.lua | 83 +++++++++++++++----------- technic/machines/switching_station.lua | 14 +---- technic/register.lua | 8 ++- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 528c08d..9707e27 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -11,17 +11,19 @@ function technic.get_cable_tier(name) return cable_tier[name] end -local function match_cable_tier_filter(name, tier) - -- Helper for get_neighbors to check for specific or any tier cable - if tier then - return cable_tier[name] == tier +local function match_cable_tier_filter(name, tiers) + -- Helper to check for set of cable tiers + if tiers then + for _, tier in ipairs(tiers) do if cable_tier[name] == tier then return true end end + return false end return cable_tier[name] ~= nil end -local function get_neighbors(pos, tier) + +local function get_neighbors(pos, tiers) -- TODO: Move this to network.lua - local tier_machines = tier and technic.machines[tier] - local is_cable = match_cable_tier_filter(minetest.get_node(pos).name, tier) + local tier_machines = tiers and technic.machines[tiers[1]] + local is_cable = match_cable_tier_filter(minetest.get_node(pos).name, tiers) local network = is_cable and technic.networks[technic.pos2network(pos)] local cables = {} local machines = {} @@ -37,7 +39,7 @@ local function get_neighbors(pos, tier) local name = minetest.get_node(connected_pos).name if tier_machines and tier_machines[name] then table.insert(machines, connected_pos) - elseif match_cable_tier_filter(name, tier) then + elseif match_cable_tier_filter(name, tiers) then local cable_network = technic.networks[technic.pos2network(connected_pos)] table.insert(cables,{ pos = connected_pos, @@ -49,9 +51,9 @@ local function get_neighbors(pos, tier) return network, cables, machines end -local function place_network_node(pos, tier, name) +local function place_network_node(pos, tiers, name) -- Get connections and primary network if there's any - local network, cables, machines = get_neighbors(pos, tier) + local network, cables, machines = get_neighbors(pos, tiers) if not network then -- We're evidently not on a network, nothing to add ourselves to return @@ -59,8 +61,8 @@ local function place_network_node(pos, tier, name) -- Attach to primary network, this must be done before building branches from this position technic.add_network_node(pos, network) - if not technic.is_tier_cable(name, tier) then - if technic.machines[tier][name] == technic.producer_receiver then + if not match_cable_tier_filter(name, tiers) then + if technic.machines[tiers[1]][name] == technic.producer_receiver then -- FIXME: Multi tier machine like supply converter should also attach to other networks around pos. -- Preferably also with connection rules defined for machine. -- nodedef.connect_sides could be used to generate these rules. @@ -68,7 +70,7 @@ local function place_network_node(pos, tier, name) -- Get cables and networks around PR_RE machine local _, machine_cables, _ = get_neighbors(pos) for _,connection in ipairs(machine_cables) do - if connection.network and connection.network ~= network then + if connection.network and connection.network.id ~= network.id then -- Attach PR_RE machine to secondary networks (last added is primary until above note is resolved) technic.add_network_node(pos, connection.network) end @@ -112,18 +114,27 @@ end -- NOTE: Exported for tests but should probably be moved to network.lua technic.network_node_on_placenode = place_network_node -local function remove_network_node(pos, tier, name) +local function remove_network_node(pos, tiers, name) -- Get the network and neighbors - local network, cables, machines = get_neighbors(pos, tier) + local network, cables, machines = get_neighbors(pos, tiers) if not network then return end + if not match_cable_tier_filter(name, tiers) then + -- Machine removed, skip cable checks to prevent unnecessary network cleanups + for _,connection in ipairs(cables) do + -- Remove machine from all networks around it + technic.remove_network_node(connection.network.id, pos) + end + return + end + if #cables == 1 then -- Dead end cable removed, remove it from the network technic.remove_network_node(network.id, pos) -- Remove neighbor machines from network if cable was removed - if technic.is_tier_cable(name, tier) then + if match_cable_tier_filter(name, tiers) then for _,machine_pos in ipairs(machines) do - local net, _, _ = get_neighbors(machine_pos, tier) + local net, _, _ = get_neighbors(machine_pos, tiers) if not net then -- Remove machine from network if it does not have other connected cables technic.remove_network_node(network.id, machine_pos) @@ -200,8 +211,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, node_box = node_box, connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = function(pos) place_network_node(pos, tier, node_name) end, - on_destruct = function(pos) remove_network_node(pos, tier, node_name) end, + on_construct = function(pos) place_network_node(pos, {tier}, node_name) end, + on_destruct = function(pos) remove_network_node(pos, {tier}, node_name) end, }, override_cable)) local xyz = { @@ -241,8 +252,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable, node_box = table.copy(node_box), connects_to = {"group:technic_"..ltier.."_cable", "group:technic_"..ltier, "group:technic_all_tiers"}, - on_construct = function(pos) place_network_node(pos, tier, node_name.."_plate_"..i) end, - on_destruct = function(pos) remove_network_node(pos, tier, node_name.."_plate_"..i) end, + on_construct = function(pos) place_network_node(pos, {tier}, node_name.."_plate_"..i) end, + on_destruct = function(pos) remove_network_node(pos, {tier}, node_name.."_plate_"..i) end, } def.node_box.fixed = { {-size, -size, -size, size, size, size}, @@ -323,20 +334,20 @@ function technic.register_cable(tier, size, description, prefix, override_cable, }) end --- TODO: Instead of universal callback either require machines to call place_network_node or patch all nodedefs -minetest.register_on_placenode(function(pos, node) - for tier, machine_list in pairs(technic.machines) do - if machine_list[node.name] ~= nil then - return place_network_node(pos, tier, node.name) - end - end -end) - --- TODO: Instead of universal callback either require machines to call remove_network_node or patch all nodedefs -minetest.register_on_dignode(function(pos, node) - for tier, machine_list in pairs(technic.machines) do - if machine_list[node.name] ~= nil then - return remove_network_node(pos, tier, node.name) - end +minetest.register_on_mods_loaded(function() + -- FIXME: Move this to register.lua or somewhere else where register_on_mods_loaded is not required. + -- Possible better option would be to inject these when machine is registered in register.lua. + for name, tiers in pairs(technic.machine_tiers) do + local nodedef = minetest.registered_nodes[name] + local on_construct = type(nodedef.on_construct) == "function" and nodedef.on_construct + local on_destruct = type(nodedef.on_destruct) == "function" and nodedef.on_destruct + minetest.override_item(name,{ + on_construct = on_construct + and function(pos) on_construct(pos) place_network_node(pos, tiers, name) end + or function(pos) place_network_node(pos, tiers, name) end, + on_destruct = on_destruct + and function(pos) on_destruct(pos) remove_network_node(pos, tiers, name) end + or function(pos) remove_network_node(pos, tiers, name) end, + }) end end) diff --git a/technic/machines/switching_station.lua b/technic/machines/switching_station.lua index 6ac5a5a..9e3e246 100644 --- a/technic/machines/switching_station.lua +++ b/technic/machines/switching_station.lua @@ -110,18 +110,6 @@ minetest.register_node("technic:switching_station",{ -- The action code for the switching station -- ----------------------------------------------- --- Lookup table for machine tiers, export for external use -local machine_tiers = {} -technic.machine_tiers = machine_tiers -minetest.register_on_mods_loaded(function() - for tier, machines in pairs(technic.machines) do - for name,_ in pairs(machines) do - if not machine_tiers[name] then machine_tiers[name] = {} end - table.insert(machine_tiers[name], tier) - end - end -end) - -- Timeout ABM -- Timeout for a node in case it was disconnected from the network -- A node must be touched by the station continuously in order to function @@ -132,7 +120,7 @@ minetest.register_abm({ chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) -- Check for machine timeouts for all tiers - local tiers = machine_tiers[node.name] + local tiers = technic.machine_tiers[node.name] local timed_out = true for _, tier in ipairs(tiers) do local timeout = technic.get_timeout(tier, pos) diff --git a/technic/register.lua b/technic/register.lua index 9b4f9be..cc60cd5 100644 --- a/technic/register.lua +++ b/technic/register.lua @@ -9,17 +9,23 @@ technic.battery = "BA" technic.machines = {} technic.power_tools = {} technic.networks = {} - +technic.machine_tiers = {} function technic.register_tier(tier, description) technic.machines[tier] = {} end function technic.register_machine(tier, nodename, machine_type) + -- Lookup table to get compatible node names and machine type by tier if not technic.machines[tier] then return end technic.machines[tier][nodename] = machine_type + -- Lookup table to get compatible tiers by node name + if not technic.machine_tiers[nodename] then + technic.machine_tiers[nodename] = {} + end + table.insert(technic.machine_tiers[nodename], tier) end function technic.register_power_tool(craftitem, max_charge) From 1a83a03ad012ec403e7c5bdb6a5f49605c4d45e5 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 31 Oct 2020 01:05:18 +0200 Subject: [PATCH 17/20] Test SC digging for nil cable network use Check network table before attempting to use it --- technic/machines/register/cables.lua | 6 ++++-- technic/spec/building_spec.lua | 1 + technic/spec/supply_converter_spec.lua | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/technic/machines/register/cables.lua b/technic/machines/register/cables.lua index 9707e27..05cb68a 100644 --- a/technic/machines/register/cables.lua +++ b/technic/machines/register/cables.lua @@ -122,8 +122,10 @@ local function remove_network_node(pos, tiers, name) if not match_cable_tier_filter(name, tiers) then -- Machine removed, skip cable checks to prevent unnecessary network cleanups for _,connection in ipairs(cables) do - -- Remove machine from all networks around it - technic.remove_network_node(connection.network.id, pos) + if connection.network then + -- Remove machine from all networks around it + technic.remove_network_node(connection.network.id, pos) + end end return end diff --git a/technic/spec/building_spec.lua b/technic/spec/building_spec.lua index 83c5c02..48093ff 100644 --- a/technic/spec/building_spec.lua +++ b/technic/spec/building_spec.lua @@ -336,6 +336,7 @@ describe("Power network building", function() end) it("does not remove other nodes from network", function() + assert.equals(2, #net.PR_nodes) assert.equals(6, count(net.all_nodes)) end) diff --git a/technic/spec/supply_converter_spec.lua b/technic/spec/supply_converter_spec.lua index d2db426..605fbbd 100644 --- a/technic/spec/supply_converter_spec.lua +++ b/technic/spec/supply_converter_spec.lua @@ -104,6 +104,7 @@ describe("Supply converter", function() {{x=101,y=990,z=100}, "technic:hv_cable"}, {{x=102,y=990,z=100}, "technic:hv_cable"}, {{x=102,y=991,z=100}, "technic:supply_converter"}, -- This machine is digged + {{x=102,y=991,z=101}, "technic:hv_cable"}, }) -- Build network local net = get_network_fixture({x=100,y=991,z=100}) From 7b658a429178f5537c3877020eb254e880954747 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 31 Oct 2020 01:09:36 +0200 Subject: [PATCH 18/20] Do not add duplicates to PR_nodes when cable placed near SC --- technic/machines/network.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/technic/machines/network.lua b/technic/machines/network.lua index 1dd58ce..eaaa666 100644 --- a/technic/machines/network.lua +++ b/technic/machines/network.lua @@ -206,7 +206,8 @@ local function add_network_machine(nodes, pos, network_id, all_nodes, multitier) -- FIXME: Machines connecting to multiple networks should have way to store multiple network ids cables[node_id] = network_id all_nodes[node_id] = pos - elseif net_id_old ~= network_id then + return true + elseif not multitier and net_id_old ~= network_id then -- Do not allow running from multiple networks, trigger overload overload_network(network_id) overload_network(net_id_old) @@ -238,8 +239,9 @@ local function add_network_node(PR_nodes, RE_nodes, BA_nodes, all_nodes, pos, ma elseif machines[name] == technic.receiver then add_network_machine(RE_nodes, pos, network_id, all_nodes) elseif machines[name] == technic.producer_receiver then - add_network_machine(PR_nodes, pos, network_id, all_nodes, true) - table.insert(RE_nodes, pos) + if add_network_machine(PR_nodes, pos, network_id, all_nodes, true) then + table.insert(RE_nodes, pos) + end elseif machines[name] == technic.battery then add_network_machine(BA_nodes, pos, network_id, all_nodes) end From 93c509d1d94e678edddb970a185a0b4183bd4508 Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 31 Oct 2020 01:26:33 +0200 Subject: [PATCH 19/20] Better tests for SC placement between networks --- technic/spec/supply_converter_spec.lua | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/technic/spec/supply_converter_spec.lua b/technic/spec/supply_converter_spec.lua index 605fbbd..6426da5 100644 --- a/technic/spec/supply_converter_spec.lua +++ b/technic/spec/supply_converter_spec.lua @@ -41,8 +41,10 @@ describe("Supply converter", function() {{x=101,y=821,z=100}, "technic:supply_converter"}, {{x=102,y=820,z=100}, "technic:hv_cable"}, -- {{x=102,y=821,z=100}, "technic:supply_converter"}, -- This machine is built - {{x=102,y=821,z= 99}, "technic:hv_cable"}, -- This should not be added to network - {{x=102,y=821,z=101}, "technic:hv_cable"}, -- This should not be added to network + {{x=102,y=822,z=100}, "technic:mv_cable"}, -- Supply network for placed SC + {{x=102,y=823,z=100}, "technic:switching_station"}, -- Supply network for placed SC + {{x=102,y=821,z= 99}, "technic:hv_cable"}, -- This should not be added to network + {{x=102,y=821,z=101}, "technic:hv_cable"}, -- This should not be added to network {{x=103,y=820,z=100}, "technic:hv_cable"}, -- Second network for overload test {{x=100,y=820,z=102}, "technic:hv_cable"}, @@ -50,8 +52,9 @@ describe("Supply converter", function() -- {{x=100,y=820,z=101}, "technic:supply_converter"}, -- This machine is built, it should overload }) -- Build network - local net = get_network_fixture({x=100,y=821,z=100}) - local net2 = get_network_fixture({x=100,y=821,z=102}) + local net = get_network_fixture({x=100,y=821,z=100}) -- Output network for SC + local net2 = get_network_fixture({x=102,y=823,z=100}) -- Input network for SC + local net3 = get_network_fixture({x=100,y=821,z=102}) -- Overload test network (tests currently disabled) local build_pos = {x=102,y=821,z=100} local build_pos2 = {x=100,y=820,z=101} @@ -59,6 +62,9 @@ describe("Supply converter", function() assert.equals(1, #net.PR_nodes) assert.equals(1, #net.RE_nodes) assert.equals(5, count(net.all_nodes)) + assert.equals(0, #net2.PR_nodes) + assert.equals(0, #net2.RE_nodes) + assert.equals(1, count(net2.all_nodes)) world.set_node(build_pos, {name="technic:supply_converter",param2=0}) technic.network_node_on_placenode(build_pos, {"HV"}, "technic:supply_converter") end) @@ -68,6 +74,9 @@ describe("Supply converter", function() assert.equals(6, count(net.all_nodes)) assert.equals(2, #net.PR_nodes) assert.equals(2, #net.RE_nodes) + assert.equals(2, count(net2.all_nodes)) + assert.equals(1, #net2.PR_nodes) + assert.equals(1, #net2.RE_nodes) assert.is_nil(technic.is_overloaded(net.id)) assert.is_nil(technic.is_overloaded(net2.id)) end) @@ -91,7 +100,8 @@ describe("Supply converter", function() world.set_node(build_pos2, {name="technic:supply_converter",param2=0}) technic.network_node_on_placenode(build_pos2, {"HV"}, "technic:supply_converter") assert.not_nil(technic.is_overloaded(net.id)) - assert.not_nil(technic.is_overloaded(net2.id)) + assert.is_nil(technic.is_overloaded(net2.id)) + assert.not_nil(technic.is_overloaded(net3.id)) end) end) From 7661ea582d075fb4c7e1dd9b3b0e3221130217bf Mon Sep 17 00:00:00 2001 From: SX <50966843+S-S-X@users.noreply.github.com> Date: Sat, 31 Oct 2020 03:05:33 +0200 Subject: [PATCH 20/20] Fix digtron again after removing last SC hacks --- technic/machines/compat/digtron.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/technic/machines/compat/digtron.lua b/technic/machines/compat/digtron.lua index a3daaae..c5aeddd 100644 --- a/technic/machines/compat/digtron.lua +++ b/technic/machines/compat/digtron.lua @@ -15,6 +15,11 @@ local function power_connector_compat() meta:set_string("HV_network", sw_pos and minetest.pos_to_string(sw_pos) or "") return digtron_technic_run(pos, node) end, + technic_on_disable = function(pos, node) + local meta = minetest.get_meta(pos) + meta:set_string("HV_network", "") + meta:set_string("HV_EU_input", "") + end, }) end