commit
8f88ec9e16
18
.github/workflows/busted.yml
vendored
Normal file
18
.github/workflows/busted.yml
vendored
Normal file
@ -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
|
@ -1,5 +1,10 @@
|
|||||||
unused_args = false
|
unused_args = false
|
||||||
|
|
||||||
|
-- Exclude regression tests / unit tests
|
||||||
|
exclude_files = {
|
||||||
|
"**/spec/**",
|
||||||
|
}
|
||||||
|
|
||||||
globals = {
|
globals = {
|
||||||
"technic", "technic_cnc", "minetest", "wrench"
|
"technic", "technic_cnc", "minetest", "wrench"
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ Technic
|
|||||||
A mod for [minetest](http://www.minetest.net)
|
A mod for [minetest](http://www.minetest.net)
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||

|

|
||||||
|
|
||||||
[](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.en.html)
|
[](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.en.html)
|
||||||
[](https://content.minetest.net/packages/mt-mods/technic_plus/)
|
[](https://content.minetest.net/packages/mt-mods/technic_plus/)
|
||||||
|
|
||||||
|
@ -9,13 +9,17 @@ local function power_connector_compat()
|
|||||||
local digtron_technic_run = minetest.registered_nodes["digtron:power_connector"].technic_run
|
local digtron_technic_run = minetest.registered_nodes["digtron:power_connector"].technic_run
|
||||||
minetest.override_item("digtron:power_connector",{
|
minetest.override_item("digtron:power_connector",{
|
||||||
technic_run = function(pos, node)
|
technic_run = function(pos, node)
|
||||||
local network_id = technic.cables[minetest.hash_node_position(pos)]
|
local network_id = technic.pos2network(pos)
|
||||||
local sw_pos = network_id and minetest.get_position_from_hash(network_id)
|
local sw_pos = network_id and technic.network2sw_pos(network_id)
|
||||||
if sw_pos then sw_pos.y = sw_pos.y + 1 end
|
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
meta:set_string("HV_network", sw_pos and minetest.pos_to_string(sw_pos) or "")
|
meta:set_string("HV_network", sw_pos and minetest.pos_to_string(sw_pos) or "")
|
||||||
return digtron_technic_run(pos, node)
|
return digtron_technic_run(pos, node)
|
||||||
end,
|
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
|
end
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ technic.digilines = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dofile(path.."/network.lua")
|
||||||
|
|
||||||
dofile(path.."/register/init.lua")
|
dofile(path.."/register/init.lua")
|
||||||
|
|
||||||
-- Tiers
|
-- Tiers
|
||||||
|
636
technic/machines/network.lua
Normal file
636
technic/machines/network.lua
Normal file
@ -0,0 +1,636 @@
|
|||||||
|
--
|
||||||
|
-- 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")
|
||||||
|
local off_delay_seconds = tonumber(minetest.settings:get("technic.switch.off_delay_seconds") or "1800")
|
||||||
|
|
||||||
|
technic.active_networks = {}
|
||||||
|
local networks = {}
|
||||||
|
technic.networks = networks
|
||||||
|
local cables = {}
|
||||||
|
technic.cables = 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)
|
||||||
|
-- timeout is optional ttl for network in seconds, if not specified use default
|
||||||
|
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)
|
||||||
|
technic.active_networks[network_id] = network
|
||||||
|
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
|
||||||
|
|
||||||
|
-- Destroy network data
|
||||||
|
function technic.remove_network(network_id)
|
||||||
|
for pos_hash,cable_net_id in pairs(cables) do
|
||||||
|
if cable_net_id == network_id then
|
||||||
|
cables[pos_hash] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
networks[network_id] = nil
|
||||||
|
technic.active_networks[network_id] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove machine or cable from network
|
||||||
|
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
|
||||||
|
-- Clear hash tables, cannot use table.remove
|
||||||
|
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)
|
||||||
|
return cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})]
|
||||||
|
end
|
||||||
|
|
||||||
|
function technic.sw_pos2network(pos)
|
||||||
|
return cables[poshash({x=pos.x,y=pos.y-1,z=pos.z})]
|
||||||
|
end
|
||||||
|
|
||||||
|
function technic.pos2network(pos)
|
||||||
|
return cables[poshash(pos)]
|
||||||
|
end
|
||||||
|
|
||||||
|
function technic.network2pos(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 = hashpos(network_id)
|
||||||
|
sw_pos.y = sw_pos.y + 1
|
||||||
|
return sw_pos
|
||||||
|
end
|
||||||
|
|
||||||
|
function technic.network_infotext(network_id, text)
|
||||||
|
if networks[network_id] == nil then return end
|
||||||
|
if text then
|
||||||
|
networks[network_id].infotext = text
|
||||||
|
else
|
||||||
|
return networks[network_id].infotext
|
||||||
|
end
|
||||||
|
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
|
||||||
|
-- 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][poshash(pos)] or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
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 default_timeout
|
||||||
|
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)
|
||||||
|
--
|
||||||
|
local overload_reset_time = tonumber(minetest.settings:get("technic.overload_reset_time") or "20")
|
||||||
|
local overloaded_networks = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
technic.reset_overloaded = reset_overloaded
|
||||||
|
|
||||||
|
local function is_overloaded(network_id)
|
||||||
|
return overloaded_networks[network_id]
|
||||||
|
end
|
||||||
|
technic.is_overloaded = is_overloaded
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Functions to traverse the electrical network
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Add a machine node to the LV/MV/HV network
|
||||||
|
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 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
|
||||||
|
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)
|
||||||
|
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_cable_node(nodes, pos, network_id, queue)
|
||||||
|
local node_id = poshash(pos)
|
||||||
|
if not cables[node_id] then
|
||||||
|
cables[node_id] = network_id
|
||||||
|
nodes[node_id] = 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
|
||||||
|
if machines[name] == technic.producer then
|
||||||
|
add_network_machine(PR_nodes, pos, network_id, all_nodes)
|
||||||
|
elseif machines[name] == technic.receiver then
|
||||||
|
add_network_machine(RE_nodes, pos, network_id, all_nodes)
|
||||||
|
elseif machines[name] == technic.producer_receiver then
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Generic function to add single nodes to the right classification array of existing network
|
||||||
|
function technic.add_network_node(pos, network)
|
||||||
|
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, 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
|
||||||
|
if not all_nodes[poshash(cur_pos)] then
|
||||||
|
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)
|
||||||
|
for _, pos in ipairs(list) do
|
||||||
|
touch_node(tier, pos) -- Touch node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_network(network_id, tier)
|
||||||
|
local cached = 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.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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
machines, tier, network_id, to_visit)
|
||||||
|
end
|
||||||
|
queue = to_visit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function technic.build_network(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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
timeout = 0, skip = 0,
|
||||||
|
}
|
||||||
|
-- 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, network)
|
||||||
|
network.battery_count = #network.BA_nodes
|
||||||
|
-- Add newly built network to cache array
|
||||||
|
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
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
local mesecons_path = minetest.get_modpath("mesecons")
|
||||||
|
local digilines_path = minetest.get_modpath("digilines")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 = networks[network_id]
|
||||||
|
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
|
||||||
|
local 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
|
||||||
|
local 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
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- Get all the demand from the RE nodes
|
||||||
|
local RE_eu_demand = 0
|
||||||
|
for _, pos1 in pairs(RE_nodes) do
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- Get all the power from the BA nodes
|
||||||
|
local BA_eu_supply = 0
|
||||||
|
for _, pos1 in pairs(BA_nodes) do
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- Get all the demand from the BA nodes
|
||||||
|
local BA_eu_demand = 0
|
||||||
|
for _, pos1 in pairs(BA_nodes) do
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 ~= 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,
|
||||||
|
demand = RE_eu_demand
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Data that will be used by the power monitor
|
||||||
|
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
|
||||||
|
--dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
|
||||||
|
for _, pos1 in pairs(RE_nodes) do
|
||||||
|
local 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
|
||||||
|
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))
|
||||||
|
end
|
||||||
|
local t1 = minetest.get_us_time()
|
||||||
|
local diff = t1 - t0
|
||||||
|
if diff > 50000 then
|
||||||
|
minetest.log("warning", "[technic] [+supply] technic_run 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
|
||||||
|
local 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
|
||||||
|
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))
|
||||||
|
end
|
||||||
|
local t1 = minetest.get_us_time()
|
||||||
|
local diff = t1 - t0
|
||||||
|
if diff > 50000 then
|
||||||
|
minetest.log("warning", "[technic] [-supply] technic_run 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
|
||||||
|
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
|
||||||
|
local 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] technic_run took " .. diff .. " us at " .. minetest.pos_to_string(pos))
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 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",
|
||||||
|
"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", "supply", "demand",
|
||||||
|
"battery_count", "battery_charge", "battery_charge_max",
|
||||||
|
}
|
||||||
|
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
|
@ -13,6 +13,7 @@ local function get_cable(pos)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- return the position of connected cable or nil
|
-- return the position of connected cable or nil
|
||||||
|
-- TODO: Make it support every possible orientation
|
||||||
local function get_connected_cable_network(pos)
|
local function get_connected_cable_network(pos)
|
||||||
local param2 = minetest.get_node(pos).param2
|
local param2 = minetest.get_node(pos).param2
|
||||||
-- should probably also work sideways or upside down but for now it wont
|
-- 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?
|
-- Behind?
|
||||||
checkpos = vector.add(minetest.facedir_to_dir(param2),pos)
|
checkpos = vector.add(minetest.facedir_to_dir(param2),pos)
|
||||||
network_id = get_cable(checkpos) and technic.pos2network(checkpos)
|
network_id = get_cable(checkpos) and technic.pos2network(checkpos)
|
||||||
if network_id then
|
|
||||||
return network_id
|
return network_id
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return the position of the associated switching station or nil
|
-- 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_id = get_connected_cable_network(pos)
|
||||||
local network = network_id and technic.networks[network_id]
|
local network = network_id and technic.networks[network_id]
|
||||||
local swpos = network and technic.network2sw_pos(network_id)
|
local swpos = network and technic.network2sw_pos(network_id)
|
||||||
local is_powermonitor = swpos and minetest.get_node(swpos).name == "technic:switching_station"
|
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
|
end
|
||||||
|
|
||||||
minetest.register_craft({
|
minetest.register_craft({
|
||||||
@ -60,7 +59,7 @@ minetest.register_node("technic:power_monitor",{
|
|||||||
"technic_power_monitor_front.png"
|
"technic_power_monitor_front.png"
|
||||||
},
|
},
|
||||||
paramtype2 = "facedir",
|
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"},
|
connect_sides = {"bottom", "back"},
|
||||||
sounds = default.node_sound_wood_defaults(),
|
sounds = default.node_sound_wood_defaults(),
|
||||||
on_construct = function(pos)
|
on_construct = function(pos)
|
||||||
@ -97,19 +96,16 @@ minetest.register_node("technic:power_monitor",{
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local sw_pos = get_swpos(pos)
|
local network = get_network(pos)
|
||||||
if not sw_pos then
|
if not network then return end
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local sw_meta = minetest.get_meta(sw_pos)
|
|
||||||
digilines.receptor_send(pos, technic.digilines.rules, channel, {
|
digilines.receptor_send(pos, technic.digilines.rules, channel, {
|
||||||
supply = sw_meta:get_int("supply"),
|
supply = network.supply,
|
||||||
demand = sw_meta:get_int("demand"),
|
demand = network.demand,
|
||||||
lag = sw_meta:get_int("lag"),
|
lag = network.lag,
|
||||||
battery_count = sw_meta:get_int("battery_count"),
|
battery_count = network.battery_count,
|
||||||
battery_charge = sw_meta:get_int("battery_charge"),
|
battery_charge = network.battery_charge,
|
||||||
battery_charge_max = sw_meta:get_int("battery_charge_max"),
|
battery_charge_max = network.battery_charge_max,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
@ -123,14 +119,10 @@ minetest.register_abm({
|
|||||||
chance = 1,
|
chance = 1,
|
||||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
local sw_pos = get_swpos(pos)
|
local network = get_network(pos)
|
||||||
if sw_pos then
|
if network then
|
||||||
local sw_meta = minetest.get_meta(sw_pos)
|
meta:set_string("infotext", S("Power Monitor. Supply: @1 Demand: @2",
|
||||||
local supply = sw_meta:get_int("supply")
|
technic.EU_string(network.supply), technic.EU_string(network.demand)))
|
||||||
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)))
|
|
||||||
else
|
else
|
||||||
meta:set_string("infotext",S("Power Monitor Has No Network"))
|
meta:set_string("infotext",S("Power Monitor Has No Network"))
|
||||||
end
|
end
|
||||||
|
@ -11,110 +11,150 @@ function technic.get_cable_tier(name)
|
|||||||
return cable_tier[name]
|
return cable_tier[name]
|
||||||
end
|
end
|
||||||
|
|
||||||
local function check_connections(pos)
|
local function match_cable_tier_filter(name, tiers)
|
||||||
-- Build a table of all machines
|
-- 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, tiers)
|
||||||
|
-- TODO: Move this to network.lua
|
||||||
|
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 = {}
|
local machines = {}
|
||||||
for tier,list in pairs(technic.machines) do
|
|
||||||
for k,v in pairs(list) do
|
|
||||||
machines[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local connections = {}
|
|
||||||
local positions = {
|
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-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-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},
|
||||||
{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
|
}
|
||||||
|
for _,connected_pos in ipairs(positions) do
|
||||||
local name = minetest.get_node(connected_pos).name
|
local name = minetest.get_node(connected_pos).name
|
||||||
if machines[name] or technic.get_cable_tier(name) then
|
if tier_machines and tier_machines[name] then
|
||||||
table.insert(connections,connected_pos)
|
table.insert(machines, connected_pos)
|
||||||
|
elseif match_cable_tier_filter(name, tiers) then
|
||||||
|
local cable_network = technic.networks[technic.pos2network(connected_pos)]
|
||||||
|
table.insert(cables,{
|
||||||
|
pos = connected_pos,
|
||||||
|
network = cable_network,
|
||||||
|
})
|
||||||
|
if not network then network = cable_network end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return connections
|
return network, cables, machines
|
||||||
end
|
end
|
||||||
|
|
||||||
local function clear_networks(pos)
|
local function place_network_node(pos, tiers, name)
|
||||||
local node = minetest.get_node(pos)
|
-- Get connections and primary network if there's any
|
||||||
local placed = node.name ~= "air"
|
local network, cables, machines = get_neighbors(pos, tiers)
|
||||||
local positions = check_connections(pos)
|
if not network then
|
||||||
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
|
-- We're evidently not on a network, nothing to add ourselves to
|
||||||
return
|
return
|
||||||
end
|
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
|
-- Attach to primary network, this must be done before building branches from this position
|
||||||
-- This is similar to check_node_subp
|
technic.add_network_node(pos, network)
|
||||||
local pos_hash = minetest.hash_node_position(pos)
|
if not match_cable_tier_filter(name, tiers) then
|
||||||
technic.cables[pos_hash] = network_id
|
if technic.machines[tiers[1]][name] == technic.producer_receiver then
|
||||||
pos.visited = 1
|
-- FIXME: Multi tier machine like supply converter should also attach to other networks around pos.
|
||||||
if technic.is_tier_cable(name, tier) then
|
-- Preferably also with connection rules defined for machine.
|
||||||
network.all_nodes[pos_hash] = pos
|
-- nodedef.connect_sides could be used to generate these rules.
|
||||||
elseif technic.machines[tier][node.name] then
|
-- For now, assume that all multi network machines belong to technic.producer_receiver group:
|
||||||
if technic.machines[tier][node.name] == technic.producer then
|
-- Get cables and networks around PR_RE machine
|
||||||
table.insert(network.PR_nodes,pos)
|
local _, machine_cables, _ = get_neighbors(pos)
|
||||||
elseif technic.machines[tier][node.name] == technic.receiver then
|
for _,connection in ipairs(machine_cables) do
|
||||||
table.insert(network.RE_nodes,pos)
|
if connection.network and connection.network.id ~= network.id then
|
||||||
elseif technic.machines[tier][node.name] == technic.producer_receiver then
|
-- Attach PR_RE machine to secondary networks (last added is primary until above note is resolved)
|
||||||
table.insert(network.PR_nodes,pos)
|
technic.add_network_node(pos, connection.network)
|
||||||
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
|
|
||||||
table.insert(network.SP_nodes,pos)
|
|
||||||
elseif technic.machines[tier][node.name] == technic.battery then
|
|
||||||
table.insert(network.BA_nodes,pos)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif dead_end and not placed then
|
else
|
||||||
-- Dead end removed, remove it from the network
|
-- Check connected cables for foreign networks, overload if machine was connected to multiple networks
|
||||||
-- Get the network
|
for _, connection in ipairs(cables) do
|
||||||
local network_id = technic.cables[minetest.hash_node_position(positions[1])]
|
if connection.network and connection.network.id ~= network.id then
|
||||||
if not network_id then
|
technic.overload_network(connection.network.id)
|
||||||
-- We're evidently not on a network, nothing to add ourselves to
|
technic.overload_network(network.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Machine added, skip all network building
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local network = technic.networks[network_id]
|
|
||||||
|
|
||||||
-- Search for and remove machine
|
-- Attach neighbor machines if cable was added
|
||||||
technic.cables[minetest.hash_node_position(pos)] = nil
|
for _,machine_pos in ipairs(machines) do
|
||||||
for tblname,table in pairs(network) do
|
technic.add_network_node(machine_pos, network)
|
||||||
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
|
||||||
|
|
||||||
|
-- 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 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
|
||||||
|
else
|
||||||
|
-- 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, tiers, name)
|
||||||
|
-- Get the network and neighbors
|
||||||
|
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
|
||||||
|
if connection.network then
|
||||||
|
-- Remove machine from all networks around it
|
||||||
|
technic.remove_network_node(connection.network.id, pos)
|
||||||
|
end
|
||||||
|
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 match_cable_tier_filter(name, tiers) then
|
||||||
|
for _,machine_pos in ipairs(machines) do
|
||||||
|
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)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Not a dead end, so the whole network needs to be recalculated
|
-- TODO: Check branches around and switching stations for branches:
|
||||||
for _,v in pairs(technic.networks[net].all_nodes) do
|
-- remove branches that do not have switching station. Switching stations not tracked but could be easily tracked.
|
||||||
local pos1 = minetest.hash_node_position(v)
|
-- remove branches not connected to another branch. Individual branches not tracked, requires simple AI heuristics.
|
||||||
technic.cables[pos1] = nil
|
-- move branches that have switching station to new networks without checking or loading actual nodes in world.
|
||||||
end
|
-- To do all this network must be aware of individual branches and switching stations, might not be worth it...
|
||||||
technic.networks[net] = nil
|
-- For now remove whole network and let ABM rebuild it
|
||||||
end
|
technic.remove_network(network.id)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
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)
|
local function item_place_override_node(itemstack, placer, pointed, node)
|
||||||
-- Call the default on_place function with a fake itemstack
|
-- Call the default on_place function with a fake itemstack
|
||||||
@ -142,7 +182,8 @@ function technic.register_cable(tier, size, description, prefix, override_cable,
|
|||||||
prefix = prefix or ""
|
prefix = prefix or ""
|
||||||
override_cable_plate = override_cable_plate or override_cable
|
override_cable_plate = override_cable_plate or override_cable
|
||||||
local ltier = string.lower(tier)
|
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,
|
local groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2,
|
||||||
["technic_"..ltier.."_cable"] = 1}
|
["technic_"..ltier.."_cable"] = 1}
|
||||||
@ -158,22 +199,22 @@ function technic.register_cable(tier, size, description, prefix, override_cable,
|
|||||||
connect_right = {-size, -size, -size, 0.5, size, size}, -- x+
|
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),
|
description = S("%s Cable"):format(tier),
|
||||||
tiles = {"technic_"..ltier..prefix.."_cable.png"},
|
tiles = {"technic_"..ltier..prefix.."_cable.png"},
|
||||||
inventory_image = "technic_"..ltier..prefix.."_cable_wield.png",
|
inventory_image = "technic_"..ltier..prefix.."_cable_wield.png",
|
||||||
wield_image = "technic_"..ltier..prefix.."_cable_wield.png",
|
wield_image = "technic_"..ltier..prefix.."_cable_wield.png",
|
||||||
groups = groups,
|
groups = groups,
|
||||||
sounds = default.node_sound_wood_defaults(),
|
sounds = default.node_sound_wood_defaults(),
|
||||||
drop = "technic:"..ltier..prefix.."_cable",
|
drop = node_name,
|
||||||
paramtype = "light",
|
paramtype = "light",
|
||||||
sunlight_propagates = true,
|
sunlight_propagates = true,
|
||||||
drawtype = "nodebox",
|
drawtype = "nodebox",
|
||||||
node_box = node_box,
|
node_box = node_box,
|
||||||
connects_to = {"group:technic_"..ltier.."_cable",
|
connects_to = {"group:technic_"..ltier.."_cable",
|
||||||
"group:technic_"..ltier, "group:technic_all_tiers"},
|
"group:technic_"..ltier, "group:technic_all_tiers"},
|
||||||
on_construct = clear_networks,
|
on_construct = function(pos) place_network_node(pos, {tier}, node_name) end,
|
||||||
on_destruct = clear_networks,
|
on_destruct = function(pos) remove_network_node(pos, {tier}, node_name) end,
|
||||||
}, override_cable))
|
}, override_cable))
|
||||||
|
|
||||||
local xyz = {
|
local xyz = {
|
||||||
@ -199,21 +240,22 @@ function technic.register_cable(tier, size, description, prefix, override_cable,
|
|||||||
return "-"..p
|
return "-"..p
|
||||||
end
|
end
|
||||||
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
|
for p, i in pairs(xyz) do
|
||||||
local def = {
|
local def = {
|
||||||
description = S("%s Cable Plate"):format(tier),
|
description = S("%s Cable Plate"):format(tier),
|
||||||
tiles = {"technic_"..ltier..prefix.."_cable.png"},
|
tiles = {"technic_"..ltier..prefix.."_cable.png"},
|
||||||
groups = table.copy(groups),
|
groups = table.copy(groups),
|
||||||
sounds = default.node_sound_wood_defaults(),
|
sounds = default.node_sound_wood_defaults(),
|
||||||
drop = "technic:"..ltier..prefix.."_cable_plate_1",
|
drop = node_name .. "_plate_1",
|
||||||
paramtype = "light",
|
paramtype = "light",
|
||||||
sunlight_propagates = true,
|
sunlight_propagates = true,
|
||||||
drawtype = "nodebox",
|
drawtype = "nodebox",
|
||||||
node_box = table.copy(node_box),
|
node_box = table.copy(node_box),
|
||||||
connects_to = {"group:technic_"..ltier.."_cable",
|
connects_to = {"group:technic_"..ltier.."_cable",
|
||||||
"group:technic_"..ltier, "group:technic_all_tiers"},
|
"group:technic_"..ltier, "group:technic_all_tiers"},
|
||||||
on_construct = clear_networks,
|
on_construct = function(pos) place_network_node(pos, {tier}, node_name.."_plate_"..i) end,
|
||||||
on_destruct = clear_networks,
|
on_destruct = function(pos) remove_network_node(pos, {tier}, node_name.."_plate_"..i) end,
|
||||||
}
|
}
|
||||||
def.node_box.fixed = {
|
def.node_box.fixed = {
|
||||||
{-size, -size, -size, size, size, size},
|
{-size, -size, -size, size, size, size},
|
||||||
@ -254,7 +296,7 @@ function technic.register_cable(tier, size, description, prefix, override_cable,
|
|||||||
if num == nil then num = 1 end
|
if num == nil then num = 1 end
|
||||||
return item_place_override_node(
|
return item_place_override_node(
|
||||||
itemstack, placer, pointed_thing,
|
itemstack, placer, pointed_thing,
|
||||||
{name = "technic:"..ltier..prefix.."_cable_plate_"..num}
|
{name = node_name.."_plate_"..num}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -271,39 +313,43 @@ function technic.register_cable(tier, size, description, prefix, override_cable,
|
|||||||
num = num + dir
|
num = num + dir
|
||||||
num = (num >= 1 and num) or num + 6
|
num = (num >= 1 and num) or num + 6
|
||||||
num = (num <= 6 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
|
end
|
||||||
minetest.register_node("technic:"..ltier..prefix.."_cable_plate_"..i, override_table(def, override_cable_plate))
|
minetest.register_node(node_name.."_plate_"..i, override_table(def, override_cable_plate))
|
||||||
cable_tier["technic:"..ltier..prefix.."_cable_plate_"..i] = tier
|
cable_tier[node_name.."_plate_"..i] = tier
|
||||||
end
|
end
|
||||||
|
|
||||||
local c = "technic:"..ltier..prefix.."_cable"
|
|
||||||
minetest.register_craft({
|
minetest.register_craft({
|
||||||
output = "technic:"..ltier..prefix.."_cable_plate_1 5",
|
output = node_name.."_plate_1 5",
|
||||||
recipe = {
|
recipe = {
|
||||||
{"", "", c},
|
{"" , "" , node_name},
|
||||||
{c , c , c},
|
{node_name, node_name, node_name},
|
||||||
{"", "", c},
|
{"" , "" , node_name},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
minetest.register_craft({
|
minetest.register_craft({
|
||||||
output = c,
|
output = node_name,
|
||||||
recipe = {
|
recipe = {
|
||||||
{"technic:"..ltier..prefix.."_cable_plate_1"},
|
{node_name.."_plate_1"},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
minetest.register_on_mods_loaded(function()
|
||||||
local function clear_nets_if_machine(pos, node)
|
-- FIXME: Move this to register.lua or somewhere else where register_on_mods_loaded is not required.
|
||||||
for tier, machine_list in pairs(technic.machines) do
|
-- Possible better option would be to inject these when machine is registered in register.lua.
|
||||||
if machine_list[node.name] ~= nil then
|
for name, tiers in pairs(technic.machine_tiers) do
|
||||||
return clear_networks(pos)
|
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
|
||||||
end
|
end)
|
||||||
end
|
|
||||||
|
|
||||||
minetest.register_on_placenode(clear_nets_if_machine)
|
|
||||||
minetest.register_on_dignode(clear_nets_if_machine)
|
|
||||||
|
|
||||||
|
@ -143,6 +143,10 @@ local run = function(pos, node, run_stage)
|
|||||||
|
|
||||||
if from and to then
|
if from and to then
|
||||||
local input = meta:get_int(from.."_EU_input")
|
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_demand", demand)
|
||||||
meta:set_int(from.."_EU_supply", 0)
|
meta:set_int(from.."_EU_supply", 0)
|
||||||
meta:set_int(to.."_EU_demand", 0)
|
meta:set_int(to.."_EU_demand", 0)
|
||||||
|
@ -1,29 +1,6 @@
|
|||||||
-- See also technic/doc/api.md
|
-- 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")
|
|
||||||
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 mesecons_path = minetest.get_modpath("mesecons")
|
||||||
local digilines_path = minetest.get_modpath("digilines")
|
|
||||||
|
|
||||||
local S = technic.getter
|
local S = technic.getter
|
||||||
|
|
||||||
@ -38,6 +15,17 @@ minetest.register_craft({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
local function start_network(pos)
|
||||||
|
local tier = technic.sw_pos2tier(pos)
|
||||||
|
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
|
||||||
|
|
||||||
local mesecon_def
|
local mesecon_def
|
||||||
if mesecons_path then
|
if mesecons_path then
|
||||||
mesecon_def = {effector = {
|
mesecon_def = {effector = {
|
||||||
@ -60,33 +48,17 @@ minetest.register_node("technic:switching_station",{
|
|||||||
on_construct = function(pos)
|
on_construct = function(pos)
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
meta:set_string("infotext", S("Switching Station"))
|
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("channel", "switching_station"..minetest.pos_to_string(pos))
|
||||||
meta:set_string("formspec", "field[channel;Channel;${channel}]")
|
meta:set_string("formspec", "field[channel;Channel;${channel}]")
|
||||||
local poshash = minetest.hash_node_position(pos)
|
start_network(pos)
|
||||||
technic.redundant_warn.poshash = nil
|
|
||||||
end,
|
end,
|
||||||
after_dig_node = function(pos)
|
on_destruct = function(pos)
|
||||||
pos.y = pos.y - 1
|
-- Remove network when switching station is removed, if
|
||||||
local poshash = minetest.hash_node_position(pos)
|
-- there's another switching station network will be rebuilt.
|
||||||
technic.redundant_warn.poshash = nil
|
local network_id = technic.sw_pos2network(pos)
|
||||||
|
if technic.networks[network_id] then
|
||||||
|
technic.remove_network(network_id)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
on_receive_fields = function(pos, formname, fields, sender)
|
on_receive_fields = function(pos, formname, fields, sender)
|
||||||
if not fields.channel then
|
if not fields.channel then
|
||||||
@ -116,533 +88,83 @@ minetest.register_node("technic:switching_station",{
|
|||||||
if channel ~= meta:get_string("channel") then
|
if channel ~= meta:get_string("channel") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
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, {
|
digilines.receptor_send(pos, technic.digilines.rules, channel, {
|
||||||
supply = meta:get_int("supply"),
|
supply = network.supply,
|
||||||
demand = meta:get_int("demand"),
|
demand = network.demand,
|
||||||
lag = meta:get_int("lag")
|
lag = network.lag
|
||||||
})
|
})
|
||||||
|
else
|
||||||
|
digilines.receptor_send(pos, technic.digilines.rules, channel, {
|
||||||
|
error = "No network",
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
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 = 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
|
|
||||||
|
|
||||||
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
|
|
||||||
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 --
|
-- 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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Timeout ABM
|
-- Timeout ABM
|
||||||
-- Timeout for a node in case it was disconnected from the network
|
-- 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
|
-- 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
|
|
||||||
minetest.register_abm({
|
minetest.register_abm({
|
||||||
label = "Machines: timeout check",
|
label = "Machines: timeout check",
|
||||||
nodenames = {"group:technic_machine"},
|
nodenames = {"group:technic_machine"},
|
||||||
interval = 1,
|
interval = 1,
|
||||||
chance = 1,
|
chance = 1,
|
||||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||||
for tier, machines in pairs(technic.machines) do
|
-- Check for machine timeouts for all tiers
|
||||||
if machines[node.name] and switching_station_timeout_count(pos, tier) then
|
local tiers = technic.machine_tiers[node.name]
|
||||||
local nodedef = minetest.registered_nodes[node.name]
|
local timed_out = true
|
||||||
if nodedef and nodedef.technic_disabled_machine_name then
|
for _, tier in ipairs(tiers) do
|
||||||
node.name = nodedef.technic_disabled_machine_name
|
local timeout = technic.get_timeout(tier, pos)
|
||||||
minetest.swap_node(pos, node)
|
if timeout > 0 then
|
||||||
elseif nodedef and nodedef.technic_on_disable then
|
technic.touch_node(tier, pos, timeout - 1)
|
||||||
nodedef.technic_on_disable(pos, node)
|
timed_out = false
|
||||||
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
|
end
|
||||||
|
-- If all tiers for machine timed out take action
|
||||||
|
if timed_out then
|
||||||
|
technic.disable_machine(pos, node)
|
||||||
end
|
end
|
||||||
end,
|
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({
|
minetest.register_abm({
|
||||||
label = "Machines: re-enable check",
|
label = "Machines: re-enable check",
|
||||||
nodenames = {"technic:switching_station"},
|
nodenames = {"technic:switching_station"},
|
||||||
interval = 1,
|
interval = 1,
|
||||||
chance = 1,
|
chance = 1,
|
||||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||||
local pos1 = {x=pos.x,y=pos.y-1,z=pos.z}
|
local network_id = technic.sw_pos2network(pos)
|
||||||
local tier = technic.get_cable_tier(minetest.get_node(pos1).name)
|
-- Check if network is overloaded / conflicts with another network
|
||||||
if not tier then return end
|
if network_id then
|
||||||
if switching_station_timeout_count(pos, tier) then
|
local infotext
|
||||||
local meta = minetest.get_meta(pos)
|
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)
|
||||||
|
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 network
|
||||||
|
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
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
for tier, machines in pairs(technic.machines) do
|
|
||||||
-- SPECIAL will not be traversed
|
|
||||||
technic.register_machine(tier, "technic:switching_station", "SPECIAL")
|
|
||||||
end
|
|
||||||
|
@ -1,23 +1,6 @@
|
|||||||
|
|
||||||
local has_monitoring_mod = minetest.get_modpath("monitoring")
|
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]
|
|
||||||
|
|
||||||
if not switch then
|
|
||||||
switch = {
|
|
||||||
time = 0,
|
|
||||||
skip = 0
|
|
||||||
}
|
|
||||||
switches[hash] = switch
|
|
||||||
end
|
|
||||||
|
|
||||||
return switch
|
|
||||||
end
|
|
||||||
|
|
||||||
local active_switching_stations_metric, switching_stations_usage_metric
|
local active_switching_stations_metric, switching_stations_usage_metric
|
||||||
|
|
||||||
if has_monitoring_mod then
|
if has_monitoring_mod then
|
||||||
@ -32,21 +15,9 @@ if has_monitoring_mod then
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- collect all active switching stations
|
|
||||||
minetest.register_abm({
|
|
||||||
nodenames = {"technic:switching_station"},
|
|
||||||
label = "Switching Station",
|
|
||||||
interval = 1,
|
|
||||||
chance = 1,
|
|
||||||
action = function(pos)
|
|
||||||
local switch = get_switch_data(pos)
|
|
||||||
switch.time = minetest.get_us_time()
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
-- the interval between technic_run calls
|
-- the interval between technic_run calls
|
||||||
local technic_run_interval = 1.0
|
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
|
-- iterate over all collected switching stations and execute the technic_run function
|
||||||
local timer = 0
|
local timer = 0
|
||||||
@ -71,91 +42,78 @@ minetest.register_globalstep(function(dtime)
|
|||||||
-- normal run_interval
|
-- normal run_interval
|
||||||
technic_run_interval = 1.0
|
technic_run_interval = 1.0
|
||||||
end
|
end
|
||||||
|
set_default_timeout(math.ceil(technic_run_interval) + 1)
|
||||||
|
|
||||||
local now = minetest.get_us_time()
|
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
|
local active_switches = 0
|
||||||
|
|
||||||
for hash, switch in pairs(switches) do
|
for network_id, network in pairs(technic.active_networks) do
|
||||||
local pos = minetest.get_position_from_hash(hash)
|
local pos = technic.network2sw_pos(network_id)
|
||||||
local diff = now - switch.time
|
|
||||||
|
|
||||||
minetest.get_voxel_manip(pos, pos)
|
local node = technic.get_or_load_node(pos) or minetest.get_node(pos)
|
||||||
local node = minetest.get_node(pos)
|
|
||||||
|
|
||||||
if node.name ~= "technic:switching_station" then
|
if node.name ~= "technic:switching_station" then
|
||||||
-- station vanished
|
-- station vanished
|
||||||
switches[hash] = nil
|
technic.remove_network(network_id)
|
||||||
|
|
||||||
elseif diff < off_delay_micros then
|
elseif network.timeout > now then
|
||||||
-- station active
|
-- station active
|
||||||
active_switches = active_switches + 1
|
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()
|
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 switch_diff = minetest.get_us_time() - start
|
||||||
|
|
||||||
|
|
||||||
local meta = minetest.get_meta(pos)
|
|
||||||
|
|
||||||
-- set lag in microseconds into the "lag" meta field
|
-- set lag in microseconds into the "lag" meta field
|
||||||
meta:set_int("lag", switch_diff)
|
network.lag = switch_diff
|
||||||
|
|
||||||
-- overload detection
|
-- overload detection
|
||||||
if switch_diff > 250000 then
|
if switch_diff > 250000 then
|
||||||
switch.skip = 30
|
network.skip = 30
|
||||||
elseif switch_diff > 150000 then
|
elseif switch_diff > 150000 then
|
||||||
switch.skip = 20
|
network.skip = 20
|
||||||
elseif switch_diff > 75000 then
|
elseif switch_diff > 75000 then
|
||||||
switch.skip = 10
|
network.skip = 10
|
||||||
elseif switch_diff > 50000 then
|
elseif switch_diff > 50000 then
|
||||||
switch.skip = 2
|
network.skip = 2
|
||||||
end
|
end
|
||||||
|
|
||||||
if switch.skip > 0 then
|
if network.skip > 0 then
|
||||||
-- calculate efficiency in percent and display it
|
-- calculate efficiency in percent and display it
|
||||||
local efficiency = math.floor(1/switch.skip*100)
|
local efficiency = math.floor(1/network.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")
|
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
|
-- it will be reactivated when a player is near it
|
||||||
-- laggy switching stations won't work well in unloaded areas this way
|
technic.active_networks[network_id] = nil
|
||||||
switches[hash] = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
|
||||||
switch.skip = math.max(switch.skip - 1, 0)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
else
|
else
|
||||||
-- station timed out
|
-- station timed out
|
||||||
switches[hash] = nil
|
technic.active_networks[network_id] = nil
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local time_usage = minetest.get_us_time() - now
|
|
||||||
|
|
||||||
if has_monitoring_mod then
|
if has_monitoring_mod then
|
||||||
|
local time_usage = minetest.get_us_time() - now
|
||||||
active_switching_stations_metric.set(active_switches)
|
active_switching_stations_metric.set(active_switches)
|
||||||
switching_stations_usage_metric.inc(time_usage)
|
switching_stations_usage_metric.inc(time_usage)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
minetest.register_chatcommand("technic_flush_switch_cache", {
|
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 },
|
privs = { server = true },
|
||||||
func = function()
|
func = function()
|
||||||
switches = {}
|
technic.active_networks = {}
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
@ -9,17 +9,23 @@ technic.battery = "BA"
|
|||||||
technic.machines = {}
|
technic.machines = {}
|
||||||
technic.power_tools = {}
|
technic.power_tools = {}
|
||||||
technic.networks = {}
|
technic.networks = {}
|
||||||
|
technic.machine_tiers = {}
|
||||||
|
|
||||||
function technic.register_tier(tier, description)
|
function technic.register_tier(tier, description)
|
||||||
technic.machines[tier] = {}
|
technic.machines[tier] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
function technic.register_machine(tier, nodename, machine_type)
|
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
|
if not technic.machines[tier] then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
technic.machines[tier][nodename] = machine_type
|
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
|
end
|
||||||
|
|
||||||
function technic.register_power_tool(craftitem, max_charge)
|
function technic.register_power_tool(craftitem, max_charge)
|
||||||
|
355
technic/spec/building_spec.lua
Normal file
355
technic/spec/building_spec.lua
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
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.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.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.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 building between networks", function()
|
||||||
|
|
||||||
|
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
|
||||||
|
--{{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.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.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.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(2, #net.PR_nodes)
|
||||||
|
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)
|
0
technic/spec/fixtures/minetest.cfg
vendored
Normal file
0
technic/spec/fixtures/minetest.cfg
vendored
Normal file
120
technic/spec/fixtures/minetest.lua
vendored
Normal file
120
technic/spec/fixtures/minetest.lua
vendored
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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.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]
|
||||||
|
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
|
||||||
|
|
||||||
|
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/common/misc_helpers")
|
||||||
|
fixture("minetest/common/vector")
|
||||||
|
|
||||||
|
_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_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.register_on_mods_loaded = 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}
|
||||||
|
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)
|
702
technic/spec/fixtures/minetest/common/misc_helpers.lua
vendored
Normal file
702
technic/spec/fixtures/minetest/common/misc_helpers.lua
vendored
Normal file
@ -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 "<circular reference>"
|
||||||
|
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
|
242
technic/spec/fixtures/minetest/common/vector.lua
vendored
Normal file
242
technic/spec/fixtures/minetest/common/vector.lua
vendored
Normal file
@ -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
|
262
technic/spec/fixtures/minetest/game/misc.lua
vendored
Normal file
262
technic/spec/fixtures/minetest/game/misc.lua
vendored
Normal file
@ -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
|
43
technic/spec/fixtures/minetest/player.lua
vendored
Normal file
43
technic/spec/fixtures/minetest/player.lua
vendored
Normal file
@ -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
|
18
technic/spec/fixtures/minetest/protection.lua
vendored
Normal file
18
technic/spec/fixtures/minetest/protection.lua
vendored
Normal file
@ -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
|
23
technic/spec/fixtures/network.lua
vendored
Normal file
23
technic/spec/fixtures/network.lua
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
_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")
|
||||||
|
technic.register_tier("MV", "Busted MV")
|
||||||
|
technic.register_tier("HV", "Busted HV")
|
2
technic/spec/fixtures/pipeworks.lua
vendored
Normal file
2
technic/spec/fixtures/pipeworks.lua
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
_G.pipeworks = {}
|
417
technic/spec/network_spec.lua
Normal file
417
technic/spec/network_spec.lua
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
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")
|
||||||
|
|
||||||
|
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
|
||||||
|
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()
|
||||||
|
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()
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.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(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)
|
||||||
|
|
||||||
|
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_node
|
||||||
|
--]]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
--]]
|
147
technic/spec/supply_converter_spec.lua
Normal file
147
technic/spec/supply_converter_spec.lua
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
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=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"},
|
||||||
|
{{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}) -- 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}
|
||||||
|
|
||||||
|
it("does not crash", 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)
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
|
||||||
|
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.is_nil(technic.is_overloaded(net2.id))
|
||||||
|
assert.not_nil(technic.is_overloaded(net3.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
|
||||||
|
{{x=102,y=991,z=101}, "technic:hv_cable"},
|
||||||
|
})
|
||||||
|
-- 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)
|
79
technic/spec/test_helpers.lua
Normal file
79
technic/spec/test_helpers.lua
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
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")
|
Loading…
x
Reference in New Issue
Block a user