forked from MTSR/techage_modpack
442 lines
15 KiB
Lua
442 lines
15 KiB
Lua
|
--[[
|
||
|
|
||
|
Networks
|
||
|
========
|
||
|
|
||
|
Copyright (C) 2021 Joachim Stolberg
|
||
|
|
||
|
AGPL v3
|
||
|
See LICENSE.txt for more information
|
||
|
|
||
|
Power API for power consuming and generating nodes
|
||
|
|
||
|
]]--
|
||
|
|
||
|
-- for lazy programmers
|
||
|
local S2P = minetest.string_to_pos
|
||
|
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
||
|
local M = minetest.get_meta
|
||
|
local N = tubelib2.get_node_lvm
|
||
|
local OBS = networks.node_observer
|
||
|
local Flip = tubelib2.Turn180Deg
|
||
|
|
||
|
networks.power = {}
|
||
|
networks.registered_networks.power = {}
|
||
|
|
||
|
local DEFAULT_DATA = {
|
||
|
curr_load = 0, -- network storage value
|
||
|
max_capa = 0, -- network storage capacity
|
||
|
consumed = 0, -- consumed power by consumers
|
||
|
provided = 0, -- provided power by generators
|
||
|
available = 0, -- max. available generator power
|
||
|
netw_num = 0, -- network number
|
||
|
}
|
||
|
|
||
|
-- Storage parameters:
|
||
|
-- capa = maximum value in power units
|
||
|
-- load = current value in power units
|
||
|
-- level = ratio value (load/capa) (0..1)
|
||
|
|
||
|
local Power = {} -- {netID = {curr_load, max_capa, consumed, provided, available}}
|
||
|
|
||
|
|
||
|
|
||
|
-- Determine load, capa and other power network data
|
||
|
local function get_power_data(pos, tlib2, outdir, netID)
|
||
|
assert(outdir)
|
||
|
local netw = networks.get_network_table(pos, tlib2, outdir) or {}
|
||
|
local max_capa = 1 -- to prevent nan
|
||
|
local max_perf = 0
|
||
|
local curr_load = 0
|
||
|
-- Generators
|
||
|
for _,item in ipairs(netw.gen or {}) do
|
||
|
local ndef = minetest.registered_nodes[N(item.pos).name]
|
||
|
local data = ndef.get_generator_data and ndef.get_generator_data(item.pos, Flip[item.indir], tlib2)
|
||
|
if data then
|
||
|
OBS("get_power_data", item.pos, data)
|
||
|
max_capa = max_capa + (data.capa or 0)
|
||
|
max_perf = max_perf + (data.perf or 0)
|
||
|
curr_load = curr_load + ((data.level or 0) * (data.capa or 0))
|
||
|
end
|
||
|
end
|
||
|
-- Storage systems
|
||
|
for _,item in ipairs(netw.sto or {}) do
|
||
|
local ndef = minetest.registered_nodes[N(item.pos).name]
|
||
|
local data = ndef.get_storage_data and ndef.get_storage_data(item.pos, Flip[item.indir], tlib2)
|
||
|
if data then
|
||
|
OBS("get_power_data", item.pos, data)
|
||
|
max_capa = max_capa + (data.capa or 0)
|
||
|
curr_load = curr_load + ((data.level or 0) * (data.capa or 0))
|
||
|
end
|
||
|
end
|
||
|
Power[netID] = {
|
||
|
curr_load = curr_load, -- network storage value
|
||
|
max_capa = max_capa, -- network storage capacity
|
||
|
max_perf = max_perf, -- max. available power
|
||
|
consumed = 0, -- consumed power
|
||
|
provided = 0, -- provided power
|
||
|
available = 0, -- available power
|
||
|
num_nodes = netw.num_nodes,
|
||
|
}
|
||
|
return Power[netID]
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- For all types of nodes
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- names: list of node names
|
||
|
-- tlib2: tubelib2 instance
|
||
|
-- node_type: one of "gen", "con", "sto", "junc"
|
||
|
-- valid_sides: something like {"L", "R"} or nil
|
||
|
function networks.power.register_nodes(names, tlib2, node_type, valid_sides)
|
||
|
if node_type == "gen" then
|
||
|
assert(#valid_sides <= 2)
|
||
|
elseif node_type == "sto" then
|
||
|
assert(#valid_sides == 1)
|
||
|
elseif node_type == "con" or node_type == "junc" then
|
||
|
assert(not valid_sides or type(valid_sides) == "table")
|
||
|
valid_sides = valid_sides or {"B", "R", "F", "L", "D", "U"}
|
||
|
elseif node_type and type(node_type) == "string" then
|
||
|
valid_sides = valid_sides or {"B", "R", "F", "L", "D", "U"}
|
||
|
else
|
||
|
error("parameter error")
|
||
|
end
|
||
|
|
||
|
tlib2:add_secondary_node_names(names)
|
||
|
networks.registered_networks.power[tlib2.tube_type] = tlib2
|
||
|
|
||
|
for _, name in ipairs(names) do
|
||
|
local ndef = minetest.registered_nodes[name]
|
||
|
local tbl = ndef.networks or {}
|
||
|
assert(tbl[tlib2.tube_type] == nil, "more than one call of 'networks.power.register_nodes' for " .. names[1])
|
||
|
tbl[tlib2.tube_type] = {ntype = node_type}
|
||
|
minetest.override_item(name, {networks = tbl})
|
||
|
tlib2:set_valid_sides(name, valid_sides)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- To be called for each power network change via
|
||
|
-- tubelib2_on_update2 or register_on_tube_update2
|
||
|
function networks.power.update_network(pos, outdir, tlib2, node)
|
||
|
local ndef = networks.net_def(pos, tlib2.tube_type)
|
||
|
assert(ndef, "node " .. N(pos).name .. " has no 'networks." .. tlib2.tube_type .. "' table")
|
||
|
if ndef.ntype == "junc" then
|
||
|
outdir = 0
|
||
|
end
|
||
|
local netID = networks.get_netID(pos, outdir)
|
||
|
if netID then
|
||
|
Power[netID] = nil
|
||
|
end
|
||
|
networks.update_network(pos, outdir, tlib2, node)
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Consumer
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Function checks for a power grid, not for enough power
|
||
|
-- Param outdir is optional
|
||
|
function networks.power.power_available(pos, tlib2, outdir)
|
||
|
for _,outdir in ipairs(networks.get_outdirs(pos, tlib2, outdir)) do
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
if netID then
|
||
|
local pwr = Power[netID] or get_power_data(pos, tlib2, outdir, netID)
|
||
|
OBS("power_available", pos, pwr)
|
||
|
return pwr.curr_load > 0
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Param outdir is optional
|
||
|
function networks.power.consume_power(pos, tlib2, outdir, amount)
|
||
|
assert(amount)
|
||
|
for _,outdir in ipairs(networks.get_outdirs(pos, tlib2, outdir)) do
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
if netID then
|
||
|
local pwr = Power[netID] or get_power_data(pos, tlib2, outdir, netID)
|
||
|
OBS("consume_power", pos, {outdir = outdir, amount = amount}, pwr)
|
||
|
if pwr.curr_load >= amount then
|
||
|
pwr.curr_load = pwr.curr_load - amount
|
||
|
pwr.consumed = pwr.consumed + amount
|
||
|
return amount
|
||
|
else
|
||
|
local consumed = pwr.curr_load
|
||
|
pwr.curr_load = 0
|
||
|
pwr.consumed = pwr.consumed + consumed
|
||
|
return consumed
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return 0
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Generator
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- amount is the maximum power, the generator can provide.
|
||
|
-- cp1 and cp2 are control points for the charge regulator.
|
||
|
-- From cp1 the charging power is reduced more and more and reaches zero at cp2.
|
||
|
--
|
||
|
-- A
|
||
|
-- |
|
||
|
-- 100 % |-------------------__
|
||
|
-- | --__
|
||
|
-- | --__
|
||
|
-- | --__
|
||
|
-- --+------------------+---------------+---->
|
||
|
-- | cp1 cp2
|
||
|
--
|
||
|
function networks.power.provide_power(pos, tlib2, outdir, amount, cp1, cp2)
|
||
|
assert(outdir)
|
||
|
assert(amount and amount > 0)
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
if netID then
|
||
|
local pwr = Power[netID] or get_power_data(pos, tlib2, outdir, netID)
|
||
|
local x = pwr.curr_load / pwr.max_capa
|
||
|
OBS("provide_power", pos, {outdir = outdir, amount = amount}, pwr)
|
||
|
|
||
|
pwr.available = pwr.available + amount
|
||
|
amount = math.min(amount, pwr.max_capa - pwr.curr_load)
|
||
|
cp1 = cp1 or 0.8
|
||
|
cp2 = cp2 or 1.0
|
||
|
|
||
|
if x < cp1 then -- charge with full power
|
||
|
pwr.curr_load = pwr.curr_load + amount
|
||
|
pwr.provided = pwr.provided + amount
|
||
|
return amount
|
||
|
elseif x < cp2 then -- charge with reduced power
|
||
|
local factor = 1 - ((x - cp1) / (cp2 - cp1))
|
||
|
local provided = amount * factor
|
||
|
pwr.curr_load = pwr.curr_load + provided
|
||
|
pwr.provided = pwr.provided + provided
|
||
|
return provided
|
||
|
else -- turn off
|
||
|
return 0
|
||
|
end
|
||
|
end
|
||
|
return 0
|
||
|
end
|
||
|
|
||
|
-- Function for generators with storage capacity
|
||
|
function networks.power.get_storage_load(pos, tlib2, outdir, amount)
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
if netID then
|
||
|
local pwr = Power[netID] or get_power_data(pos, tlib2, outdir, netID)
|
||
|
OBS("get_storage_load", pos, pwr)
|
||
|
if pwr.max_capa and pwr.max_capa > 0 then
|
||
|
return pwr.curr_load / pwr.max_capa * amount
|
||
|
else
|
||
|
error("invalid pwr.max_capa", pwr.max_capa)
|
||
|
end
|
||
|
end
|
||
|
return 0
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Storage
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Function returns a table with storage level as ratio (0..1) and the
|
||
|
-- charging state (1 = charging, -1 = uncharging, or 0)
|
||
|
-- Function provides nil if no network is available
|
||
|
function networks.power.get_storage_data(pos, tlib2, outdir)
|
||
|
assert(outdir)
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
if netID then
|
||
|
local pwr = Power[netID] or get_power_data(pos, tlib2, outdir, netID)
|
||
|
OBS("get_storage_data", pos, pwr)
|
||
|
local charging = (pwr.provided > pwr.consumed and 1) or (pwr.provided < pwr.consumed and -1) or 0
|
||
|
return {level = pwr.curr_load / pwr.max_capa, charging = charging}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- To be called for each network storage change (turn on/off of storage/generator nodes)
|
||
|
function networks.power.start_storage_calc(pos, tlib2, outdir)
|
||
|
assert(outdir)
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
OBS("start_storage_calc", pos)
|
||
|
if netID then
|
||
|
Power[netID] = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Transformer
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Charge transfer in both directions between network 1 and network 2
|
||
|
-- 'netw1' and 'netw2' are tubelib2 network instances.
|
||
|
-- Function returns a table with result values for:
|
||
|
-- {curr_load1, curr_load2, max_capa1, max_capa2, moved}
|
||
|
function networks.power.transfer_duplex(pos, netw1, outdir1, netw2, outdir2, amount)
|
||
|
local netID1 = networks.determine_netID(pos, netw1, outdir1)
|
||
|
local netID2 = networks.determine_netID(pos, netw2, outdir2)
|
||
|
if netID1 and netID2 then
|
||
|
local pwr1 = Power[netID1] or get_power_data(pos, netw1, outdir1, netID1)
|
||
|
local pwr2 = Power[netID2] or get_power_data(pos, netw2, outdir2, netID2)
|
||
|
local lvl = pwr1.curr_load / pwr1.max_capa - pwr2.curr_load / pwr2.max_capa
|
||
|
local moved
|
||
|
|
||
|
pwr2.available = pwr2.available + amount
|
||
|
pwr1.available = pwr1.available + amount
|
||
|
if lvl > 0 then
|
||
|
-- transfer from netw1 to netw2
|
||
|
moved = math.min(amount, lvl * math.min(pwr1.max_capa, pwr2.max_capa))
|
||
|
moved = math.max(moved, 0)
|
||
|
pwr1.curr_load = pwr1.curr_load - moved
|
||
|
pwr2.curr_load = pwr2.curr_load + moved
|
||
|
pwr1.consumed = (pwr1.consumed or 0) + moved
|
||
|
pwr2.provided = (pwr2.provided or 0) + moved
|
||
|
elseif lvl < 0 then
|
||
|
-- transfer from netw2 to netw1
|
||
|
moved = math.min(amount, lvl * math.min(pwr1.max_capa, pwr2.max_capa))
|
||
|
moved = math.max(moved, 0)
|
||
|
pwr2.curr_load = pwr2.curr_load - moved
|
||
|
pwr1.curr_load = pwr1.curr_load + moved
|
||
|
pwr2.consumed = (pwr2.consumed or 0) + moved
|
||
|
pwr1.provided = (pwr1.provided or 0) + moved
|
||
|
else
|
||
|
moved = 0
|
||
|
end
|
||
|
OBS("transfer_duplex", pos, pwr1, pwr2)
|
||
|
return {
|
||
|
curr_load1 = pwr1.curr_load,
|
||
|
curr_load2 = pwr2.curr_load,
|
||
|
max_capa1 = pwr1.max_capa,
|
||
|
max_capa2 = pwr2.max_capa,
|
||
|
moved = moved}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Charge transfer in one direction from network 1 to network 2
|
||
|
-- 'netw1' and 'netw2' are tubelib2 network instances.
|
||
|
-- Function returns a table with result values for:
|
||
|
-- {curr_load1, curr_load2, max_capa1, max_capa2, moved}
|
||
|
function networks.power.transfer_simplex(pos, netw1, outdir1, netw2, outdir2, amount)
|
||
|
local netID1 = networks.determine_netID(pos, netw1, outdir1)
|
||
|
local netID2 = networks.determine_netID(pos, netw2, outdir2)
|
||
|
if netID1 and netID2 then
|
||
|
local pwr1 = Power[netID1] or get_power_data(pos, netw1, outdir1, netID1)
|
||
|
local pwr2 = Power[netID2] or get_power_data(pos, netw2, outdir2, netID2)
|
||
|
local lvl = pwr1.curr_load / pwr1.max_capa - pwr2.curr_load / pwr2.max_capa
|
||
|
local moved
|
||
|
|
||
|
pwr2.available = pwr2.available + amount
|
||
|
if lvl > 0 then
|
||
|
-- transfer from netw1 to netw2
|
||
|
moved = math.min(amount, lvl * math.min(pwr1.max_capa, pwr2.max_capa))
|
||
|
moved = math.max(moved, 0)
|
||
|
pwr1.curr_load = pwr1.curr_load - moved
|
||
|
pwr2.curr_load = pwr2.curr_load + moved
|
||
|
pwr1.consumed = (pwr1.consumed or 0) + moved
|
||
|
pwr2.provided = (pwr2.provided or 0) + moved
|
||
|
else
|
||
|
moved = 0
|
||
|
end
|
||
|
OBS("transfer_simplex", pos, pwr1, pwr2)
|
||
|
return {
|
||
|
curr_load1 = pwr1.curr_load,
|
||
|
curr_load2 = pwr2.curr_load,
|
||
|
max_capa1 = pwr1.max_capa,
|
||
|
max_capa2 = pwr2.max_capa,
|
||
|
moved = moved}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Switch
|
||
|
-------------------------------------------------------------------------------
|
||
|
function networks.power.turn_switch_on(pos, tlib2, name_off, name_on)
|
||
|
local node = N(pos)
|
||
|
local meta = M(pos)
|
||
|
local changed = false
|
||
|
|
||
|
if node.name == name_off then
|
||
|
node.name = name_on
|
||
|
changed = true
|
||
|
elseif meta:get_string("netw_name") == name_off then
|
||
|
meta:set_string("netw_name", name_on)
|
||
|
else
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if meta:contains("netw_param2") then
|
||
|
meta:set_int("netw_param2", meta:get_int("netw_param2_copy"))
|
||
|
else
|
||
|
node.param2 = meta:get_int("netw_param2_copy")
|
||
|
end
|
||
|
meta:set_int("netw_param2_copy", 0)
|
||
|
|
||
|
if changed then
|
||
|
minetest.swap_node(pos, node)
|
||
|
end
|
||
|
|
||
|
tlib2:after_place_tube(pos)
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function networks.power.turn_switch_off(pos, tlib2, name_off, name_on)
|
||
|
local node = N(pos)
|
||
|
local meta = M(pos)
|
||
|
local changed = false
|
||
|
|
||
|
if node.name == name_on then
|
||
|
node.name = name_off
|
||
|
changed = true
|
||
|
elseif meta:get_string("netw_name") == name_on then
|
||
|
meta:set_string("netw_name", name_off)
|
||
|
else
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if meta:contains("netw_param2") then
|
||
|
meta:set_int("netw_param2_copy", meta:get_int("netw_param2"))
|
||
|
--meta:set_int("netw_param2", 0)
|
||
|
else
|
||
|
meta:set_int("netw_param2_copy", node.param2)
|
||
|
end
|
||
|
|
||
|
if changed then
|
||
|
minetest.swap_node(pos, node)
|
||
|
end
|
||
|
|
||
|
if meta:contains("netw_param2") then
|
||
|
node.param2 = meta:get_int("netw_param2")
|
||
|
end
|
||
|
tlib2:after_dig_tube(pos, node)
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
-- Statistics
|
||
|
-------------------------------------------------------------------------------
|
||
|
function networks.power.get_network_data(pos, tlib2, outdir)
|
||
|
for _,outdir in ipairs(networks.get_outdirs(pos, tlib2, outdir)) do
|
||
|
local netID = networks.determine_netID(pos, tlib2, outdir)
|
||
|
if netID then
|
||
|
local pwr = Power[netID] or get_power_data(pos, tlib2, outdir, netID)
|
||
|
local consumed, provided, available
|
||
|
if pwr.available > 0 and pwr.max_perf > 0 then
|
||
|
local fac = pwr.max_perf / pwr.available
|
||
|
available = pwr.max_perf
|
||
|
provided = pwr.provided * fac
|
||
|
consumed = pwr.consumed * fac
|
||
|
else
|
||
|
available = pwr.max_perf
|
||
|
provided = 0
|
||
|
consumed = pwr.consumed
|
||
|
end
|
||
|
local res = {
|
||
|
curr_load = pwr.curr_load, -- network storage value
|
||
|
max_capa = pwr.max_capa, -- network storage capacity
|
||
|
consumed = consumed, -- consumed power by consumers
|
||
|
provided = provided, -- provided power by generators
|
||
|
available = available, -- max. available generator power
|
||
|
netw_num = networks.netw_num(netID), -- network number
|
||
|
}
|
||
|
pwr.consumed = 0
|
||
|
pwr.provided = 0
|
||
|
pwr.available = 0
|
||
|
return res
|
||
|
end
|
||
|
end
|
||
|
return DEFAULT_DATA
|
||
|
end
|