diff --git a/.test/akku.lua b/.test/akku.lua index f560eb7..02a714f 100644 --- a/.test/akku.lua +++ b/.test/akku.lua @@ -8,8 +8,7 @@ local PWR_PERF = 10 local PWR_CAPA = 300 local Cable = techage.ElectricCable -local secondary_power = techage.power.secondary_power -local power_switched = techage.power.power_switched +local power = techage.power local function in_range(val, min, max) if val < min then return min end @@ -32,52 +31,36 @@ end local function node_timer(pos, elapsed) --print("node_timer akku "..S(pos)) local mem = tubelib2.get_mem(pos) - mem.capa = mem.capa or 0 - if mem.generating then - local delivered - if mem.capa >= PWR_CAPA then - delivered = secondary_power(pos, PWR_PERF, 0) - elseif mem.capa <= 0 then - delivered = secondary_power(pos, 0, PWR_PERF) - else - delivered = secondary_power(pos, PWR_PERF, PWR_PERF) - end - if delivered ~= mem.delivered then - mem.delivered = delivered - --techage.power2.power_switched(pos) - end + if mem.running then + mem.delivered = power.secondary_alive(pos, mem, mem.capa, PWR_CAPA) + --print("provided", mem.delivered) mem.capa = mem.capa - mem.delivered mem.capa = in_range(mem.capa, 0, PWR_CAPA) - --print("delivered = "..mem.delivered) - return true end - mem.delivered = 0 - return false + return mem.running end + local function on_receive_fields(pos, formname, fields, player) if minetest.is_protected(pos, player:get_player_name()) then return end + local mem = tubelib2.get_mem(pos) + techage.power.network_changed(pos, mem) + if fields.update then - local mem = tubelib2.get_mem(pos) M(pos):set_string("formspec", formspec(pos, mem)) end end -local function on_rightclick(pos) - local mem = tubelib2.get_mem(pos) - M(pos):set_string("formspec", formspec(pos, mem)) -end - local function after_place_node(pos, placer) local mem = tubelib2.get_mem(pos) - mem.generating = true + mem.running = true mem.capa = 0 minetest.get_node_timer(pos):start(CYCLE_TIME) - techage.power.power_switched(pos) - on_rightclick(pos) + power.secondary_start(pos, mem, PWR_PERF, PWR_PERF) + M(pos):set_string("formspec", formspec(pos, mem)) end @@ -97,7 +80,6 @@ minetest.register_node("techage:akku", { is_ground_content = false, after_place_node = after_place_node, on_receive_fields = on_receive_fields, - on_rightclick = on_rightclick, on_timer = node_timer, }) diff --git a/.test/sink.lua b/.test/sink.lua index 2022a4d..f784ea7 100644 --- a/.test/sink.lua +++ b/.test/sink.lua @@ -7,7 +7,7 @@ local PWR_NEEDED = 5 local CYCLE_TIME = 4 local Cable = techage.ElectricCable -local consume_power = techage.power.consume_power +local power = techage.power local function swap_node(pos, name) local node = minetest.get_node(pos) @@ -18,45 +18,42 @@ local function swap_node(pos, name) minetest.swap_node(pos, node) end - -local function on_power(pos) - local mem = tubelib2.get_mem(pos) - --print("sink on_power", mem.running) - mem.timer_running = (mem.timer_running or 1) - 1 - if mem.running and mem.timer_running >= 0 then - local got = consume_power(pos, PWR_NEEDED) - if got < PWR_NEEDED then - swap_node(pos, "techage:sink") - else - swap_node(pos, "techage:sink_on") - mem.power_available = true - end +local function on_power(pos, mem) + print("on_power") + if mem.running then + swap_node(pos, "techage:sink_on") + minetest.get_node_timer(pos):start(CYCLE_TIME) + M(pos):set_string("infotext", "on") end end +local function on_nopower(pos, mem) + print("on_nopower") + swap_node(pos, "techage:sink") + M(pos):set_string("infotext", "nopower") +end + local function node_timer(pos, elapsed) --print("node_timer sink "..S(pos)) local mem = tubelib2.get_mem(pos) - if mem.running and not mem.power_available then - --print("sink not triggered") - swap_node(pos, "techage:sink") - end - mem.power_available = false - mem.timer_running = CYCLE_TIME/2 + 1 - return true + power.consumer_alive(pos, mem) + return mem.running end - local function on_rightclick(pos, node, clicker) local mem = tubelib2.get_mem(pos) - if not mem.running then + if not mem.running and power.power_available(pos, mem, PWR_NEEDED) then mem.running = true - node_timer(pos, 2) + swap_node(pos, "techage:sink_on") + power.consumer_start(pos, mem, CYCLE_TIME, PWR_NEEDED) minetest.get_node_timer(pos):start(CYCLE_TIME) + M(pos):set_string("infotext", "on") else - swap_node(pos, "techage:sink") - minetest.get_node_timer(pos):stop() mem.running = false + swap_node(pos, "techage:sink") + power.consumer_stop(pos, mem) + minetest.get_node_timer(pos):stop() + M(pos):set_string("infotext", "off") end end @@ -64,6 +61,11 @@ end minetest.register_node("techage:sink", { description = "Sink", tiles = {'techage_electric_button.png'}, + + after_place_node = function(pos) + M(pos):set_string("infotext", "off") + end, + on_timer = node_timer, on_rightclick = on_rightclick, @@ -96,4 +98,5 @@ minetest.register_node("techage:sink_on", { techage.power.register_node({"techage:sink", "techage:sink_on"}, { power_network = Cable, on_power = on_power, + on_nopower = on_nopower, }) diff --git a/.test/source.lua b/.test/source.lua index 1570065..2cdbc31 100644 --- a/.test/source.lua +++ b/.test/source.lua @@ -7,32 +7,33 @@ local CYCLE_TIME = 2 local PWR_CAPA = 15 local Cable = techage.ElectricCable -local provide_power = techage.power.provide_power +local power = techage.power local function node_timer(pos, elapsed) --print("node_timer source "..S(pos)) local mem = tubelib2.get_mem(pos) if mem.generating then - local provided = provide_power(pos, PWR_CAPA) - return true + local provided = power.generator_alive(pos, mem) + --print("provided", provided) end - return false + return mem.generating end local function on_rightclick(pos, node, clicker) local mem = tubelib2.get_mem(pos) if not mem.generating then mem.generating = true - node_timer(pos, 2) + power.generator_start(pos, mem, PWR_CAPA) minetest.get_node_timer(pos):start(CYCLE_TIME) + M(pos):set_string("infotext", "on") else - minetest.get_node_timer(pos):stop() mem.generating = false + power.generator_stop(pos, mem) + minetest.get_node_timer(pos):stop() + M(pos):set_string("infotext", "off") end - techage.power.power_switched(pos) end - minetest.register_node("techage:source", { description = "Source", tiles = { @@ -44,6 +45,10 @@ minetest.register_node("techage:source", { 'techage_electric_button.png^techage_appl_electronic_fab.png', 'techage_electric_button.png^techage_appl_electronic_fab.png', }, + after_place_node = function(pos) + M(pos):set_string("infotext", "off") + end, + paramtype2 = "facedir", groups = {cracky=2, crumbly=2, choppy=2}, is_ground_content = false, diff --git a/coal_power_station/generator.lua b/coal_power_station/generator.lua index 5bb3b24..d51c455 100644 --- a/coal_power_station/generator.lua +++ b/coal_power_station/generator.lua @@ -84,7 +84,7 @@ local function node_timer(pos, elapsed) State:keep_running(pos, mem, COUNTDOWN_TICKS) mem.power_available = (mem.power_available or 1) - 1 mem.triggered = (mem.triggered or 1) - 1 - if mem.power_available <= 0 or mem.triggered <= 0 then + if mem.power_available < 0 or mem.triggered < 0 then power_switched(pos) State:stop(pos, mem) mem.generating = false diff --git a/init.lua b/init.lua index d5d3a80..376e14e 100644 --- a/init.lua +++ b/init.lua @@ -43,6 +43,7 @@ else -- Power networks dofile(MP.."/power/power.lua") + dofile(MP.."/power/power2.lua") dofile(MP.."/power/junction.lua") dofile(MP.."/power/drive_axle.lua") dofile(MP.."/power/steam_pipe.lua") diff --git a/iron_age/charcoalpile.lua b/iron_age/charcoalpile.lua index d226e0d..ad18c11 100644 --- a/iron_age/charcoalpile.lua +++ b/iron_age/charcoalpile.lua @@ -171,6 +171,7 @@ minetest.register_node("techage:charcoal_burn", { return false end, drop = "", + paramtype = "light", light_source = 10, is_ground_content = false, groups = {crumbly = 2, falling_node = 1, not_in_creative_inventory=1}, diff --git a/power/junctionbox.lua b/power/junctionbox.lua index 523a59a..2c7a5ce 100644 --- a/power/junctionbox.lua +++ b/power/junctionbox.lua @@ -17,8 +17,7 @@ local M = minetest.get_meta local S = techage.S local Cable = techage.ElectricCable -local power_switched = techage.power.power_switched -local power_available = techage.power.power_available +local power = techage.power local size = 3/32 local Boxes = { @@ -40,10 +39,10 @@ techage.register_junction("techage:electric_junction", 2/8, Boxes, Cable, { after_tube_update = function(node, pos, out_dir, peer_pos, peer_in_dir) local name = "techage:electric_junction"..techage.junction_type(pos, Cable) minetest.swap_node(pos, {name = name, param2 = 0}) - power_switched(pos) + power.network_changed(pos, tubelib2.get_mem(pos)) end, is_power_available = function(pos) - return techage.power.power_accounting(pos) + return techage.power.power_accounting(pos, tubelib2.get_mem(pos)) end, }) diff --git a/power/power.lua b/power/power.lua index 0a9cb80..0e69a73 100644 --- a/power/power.lua +++ b/power/power.lua @@ -19,16 +19,17 @@ local P = minetest.string_to_pos local M = minetest.get_meta -- Techage Related Data local PWR = function(pos) return (minetest.registered_nodes[minetest.get_node(pos).name] or {}).power end -local PWRN = function(node) return (minetest.registered_nodes[node.name] or {}).power end -- Used to determine the already passed nodes while power distribution local Route = {} -local function in_range(val, min, max) - if val < min then return min end - if val > max then return max end - return val -end +techage.power = {} + +-- Consumer States +local STOPPED = 1 +local NOPOWER = 2 +local RUNNING = 3 + local function pos_already_reached(pos) local key = minetest.hash_node_position(pos) @@ -39,88 +40,21 @@ local function pos_already_reached(pos) return true end -local SideToDir = {B=1, R=2, F=3, L=4, D=5, U=6} - -local function side_to_dir(param2, side) - local dir = SideToDir[side] - if dir < 5 then - dir = (((dir - 1) + (param2 % 4)) % 4) + 1 - end - return dir -end - -function techage.get_pos(pos, side) - local node = minetest.get_node(pos) - local dir = nil - if node.name ~= "air" and node.name ~= "ignore" then - dir = side_to_dir(node.param2, side) - end - return tubelib2.get_pos(pos, dir) -end - -local function set_conn_dirs(pos, sides) - local tbl = {} - local node = minetest.get_node(pos) - if type(sides) == "function" then - tbl = sides(pos, node) - else - for _,side in ipairs(sides) do - tbl[#tbl+1] = tubelib2.Turn180Deg[side_to_dir(node.param2, side)] - end - end - M(pos):set_string("power_dirs", minetest.serialize(tbl)) -end - -local function valid_indir(pos, in_dir) - local s = M(pos):get_string("power_dirs") - if s == "" then - local pwr = PWR(pos) - if pwr then - set_conn_dirs(pos, pwr.conn_sides) - end - end - if s ~= "" then - for _,dir in ipairs(minetest.deserialize(s)) do - if dir == in_dir then - return true - end - end - end - return false -end - -local function valid_outdir(pos, out_dir) - return valid_indir(pos, tubelib2.Turn180Deg[out_dir]) -end - --- Both nodes are from the same power network type? -local function matching_nodes(pos, peer_pos) - local tube_type1 = pos and PWR(pos) and PWR(pos).power_network.tube_type - local tube_type2 = peer_pos and PWR(peer_pos) and PWR(peer_pos).power_network.tube_type - return not tube_type1 or not tube_type2 or tube_type1 == tube_type2 -end - local function min(val, max) if val < 0 then return 0 end if val > max then return max end return val end --- called from master every cycle (2 seconds) -local function accounting(mem) - -- defensive programming - mem.needed1 = mem.needed1 or 0 - mem.needed2 = mem.needed2 or 0 - mem.available1 = mem.available1 or 0 - mem.available2 = mem.available2 or 0 +local function accounting(pos, mem) -- calculate the primary and secondary supply and demand - mem.supply1 = min(mem.needed1 + mem.needed2, mem.available1) - mem.demand1 = min(mem.needed1, mem.available1 + mem.available2) - mem.supply2 = min(mem.demand1 - mem.supply1, mem.available2) - mem.demand2 = min(mem.supply1 - mem.demand1, mem.available1) - mem.reserve = (mem.available1 + mem.available2) > mem.needed1 - --print("needed = "..mem.needed1.."/"..mem.needed2..", available = "..mem.available1.."/"..mem.available2) - --print("supply = "..mem.supply1.."/"..mem.supply2..", demand = "..mem.demand1.."/"..mem.demand2..", reserve = "..dump(mem.reserve)) + mem.mst_supply1 = min(mem.mst_needed1 + mem.mst_needed2, mem.mst_available1) + mem.mst_demand1 = min(mem.mst_needed1, mem.mst_available1 + mem.mst_available2) + mem.mst_supply2 = min(mem.mst_demand1 - mem.mst_supply1, mem.mst_available2) + mem.mst_demand2 = min(mem.mst_supply1 - mem.mst_demand1, mem.mst_available1) + mem.mst_reserve = (mem.mst_available1 + mem.mst_available2) - mem.mst_needed1 + --print("needed = "..mem.mst_needed1.."/"..mem.mst_needed2..", available = "..mem.mst_available1.."/"..mem.mst_available2) + --print("supply = "..mem.mst_supply1.."/"..mem.mst_supply2..", demand = "..mem.mst_demand1.."/"..mem.mst_demand2..", reserve = "..mem.mst_reserve) end local function connection_walk(pos, clbk) @@ -137,6 +71,24 @@ local function connection_walk(pos, clbk) end end +local function consumer_turn_off(pos, mem) + local pwr = PWR(pos) + print("consumer_turn_off") + if pwr and pwr.on_nopower then + pwr.on_nopower(pos, mem) + end + mem.pwr_node_alive_cnt = 0 + mem.pwr_state = NOPOWER +end + +local function consumer_turn_on(pos, mem) + local pwr = PWR(pos) + print("consumer_turn_on") + if pwr and pwr.on_power then + pwr.on_power(pos, mem) + end + mem.pwr_state = RUNNING +end -- determine one "generating" node as master (largest hash number) local function determine_master(pos) @@ -145,15 +97,17 @@ local function determine_master(pos) local hash = 0 local master = nil connection_walk(pos, function(pos, mem) - mem.generator_available = (mem.generator_available or 1) - 1 - if mem.generating and mem.generator_available > 0 then - local new = minetest.hash_node_position(pos) - if hash <= new then - hash = new - master = pos - end + if (mem.pwr_node_alive_cnt or 0) >= 0 and + (mem.pwr_available or 0) > 0 or + (mem.pwr_available2 or 0) > 0 then -- active generator? + + local new = minetest.hash_node_position(pos) + if hash <= new then + hash = new + master = pos end - end) + end + end) return master end @@ -162,302 +116,256 @@ local function store_master(pos, master_pos) Route = {} pos_already_reached(pos) connection_walk(pos, function(pos, mem) - mem.master_pos = master_pos - mem.is_master = false + mem.pwr_master_pos = master_pos + mem.pwr_is_master = false end) end -local function trigger_nodes(pos) - Route = {} - pos_already_reached(pos) - connection_walk(pos, function(pos, mem) - local pwr = PWR(pos) - if pwr and pwr.on_power then - pwr.on_power(pos) - end - end) -end - --- Called from any generator on any power switching process --- and to determine a new master in case of a timeout -local function on_power_switch(pos) - --print("on_power_change"..S(pos)) - --local t = minetest.get_us_time() - local mem = tubelib2.get_mem(pos) - mem.master_pos = nil - mem.is_master = nil - -- used to check if generator is active - mem.generator_available = 2 - - local mpos = determine_master(pos) - store_master(pos, mpos) - --print("store_master", S(pos), S(mpos)) - if mpos then - local mem = tubelib2.get_mem(mpos) - mem.is_master = true - -- trigger all nodes so that we get a stable state again - -- reset values for nect cycle - mem.needed1 = 0 - mem.needed2 = 0 - mem.available1 = 0 - mem.available2 = 0 - trigger_nodes(mpos) - accounting(tubelib2.get_mem(mpos)) - --t = minetest.get_us_time() - t - --print("t = "..t) - return mem - end -end - --- --- Generic API functions --- -techage.power = {} - -techage.power.power_switched = on_power_switch - --- Used to turn on/off the power by means of a power switch -function techage.power.power_cut(pos, dir, cable, cut) - local npos = vector.add(pos, tubelib2.Dir6dToVector[dir or 0]) - - local node = minetest.get_node(npos) - if node.name ~= "techage:powerswitch_box" and - M(npos):get_string("techage_hidden_nodename") ~= "techage:powerswitch_box" then - return - end - - local mem = tubelib2.get_mem(npos) - mem.interrupted_dirs = mem.interrupted_dirs or {} - - if cut then - mem.interrupted_dirs = {true, true, true, true, true, true} - for dir,_ in pairs(mem.connections) do - mem.interrupted_dirs[dir] = false - on_power_switch(npos) - trigger_nodes(npos) - mem.interrupted_dirs[dir] = true - end - else - mem.interrupted_dirs = {} - on_power_switch(npos) - end -end - --- only for nodes with own 'conn_sides' and rotate function -function techage.power.after_rotate_node(pos, cable) - cable:after_dig_node(pos) - set_conn_dirs(pos, PWR(pos).conn_sides) - cable:after_place_node(pos) -end - -function techage.power.register_node(names, pwr_def) - for _,name in ipairs(names) do - local ndef = minetest.registered_nodes[name] - if ndef then - minetest.override_item(name, { - power = { - conn_sides = pwr_def.conn_sides or {"L", "R", "U", "D", "F", "B"}, - on_power = pwr_def.on_power, - power_network = pwr_def.power_network, - after_place_node = ndef.after_place_node, - after_dig_node = ndef.after_dig_node, - after_tube_update = ndef.after_tube_update, - }, - -- after_place_node decorator - after_place_node = function(pos, placer, itemstack, pointed_thing) - local pwr = PWR(pos) - set_conn_dirs(pos, pwr.conn_sides) - pwr.power_network:after_place_node(pos) - if pwr.after_place_node then - return pwr.after_place_node(pos, placer, itemstack, pointed_thing) - end - end, - -- after_dig_node decorator - after_dig_node = function(pos, oldnode, oldmetadata, digger) - local pwr = PWRN(oldnode) - pwr.power_network:after_dig_node(pos) - minetest.after(0.1, tubelib2.del_mem, pos) -- At latest... - if pwr.after_dig_node then - return pwr.after_dig_node(pos, oldnode, oldmetadata, digger) - end - end, - -- tubelib2 callback, called after any connection change - after_tube_update = function(node, pos, out_dir, peer_pos, peer_in_dir) - local pwr = PWR(pos) - local mem = tubelib2.get_mem(pos) - mem.connections = mem.connections or {} - if not peer_pos or not valid_indir(peer_pos, peer_in_dir) - or not valid_outdir(pos, out_dir) - or not matching_nodes(pos, peer_pos) then - mem.connections[out_dir] = nil -- del connection - else - mem.connections[out_dir] = {pos = peer_pos, in_dir = peer_in_dir} - end - -- To be called delayed, so that all network connections have been established - minetest.after(0.2, on_power_switch, pos) - if pwr.after_tube_update then - return pwr.after_tube_update(node, pos, out_dir, peer_pos, peer_in_dir) - end - end, - }) - pwr_def.power_network:add_secondary_node_names({name}) - end - end -end - -function techage.power.after_tube_update(node, pos, out_dir, peer_pos, peer_in_dir, power) - local mem = tubelib2.get_mem(pos) - mem.connections = mem.connections or {} - if not peer_pos or not valid_indir(peer_pos, peer_in_dir) - or not valid_outdir(pos, out_dir) - or not matching_nodes(pos, peer_pos) then - mem.connections[out_dir] = nil -- del connection - else - mem.connections[out_dir] = {pos = peer_pos, in_dir = peer_in_dir} - end - -- To be called delayed, so that all network connections have been established - minetest.after(0.2, on_power_switch, pos) - if power.after_tube_update then - return power.after_tube_update(node, pos, out_dir, peer_pos, peer_in_dir) - end -end - --- Called from every generator every 2 seconds -function techage.power.power_distribution(pos) - local mem = tubelib2.get_mem(pos) - -- used to check if generator is active - mem.generator_available = 2 - if mem.is_master then - -- reset values for nect cycle - mem.needed1 = 0 - mem.needed2 = 0 - mem.available1 = 0 - mem.available2 = 0 - trigger_nodes(pos, mem) - accounting(mem) - end -end - -function techage.power.consume_power(pos, needed) - local master_pos = tubelib2.get_mem(pos).master_pos - --print("consume_power", S(master_pos)) - if master_pos then - local mem = tubelib2.get_mem(master_pos) - -- for next cycle - mem.needed1 = (mem.needed1 or 0) + needed - -- current cycle - mem.demand1 = mem.demand1 or 0 - local val = math.min(needed, mem.demand1) - mem.demand1 = mem.demand1 - val - --return val - if mem.reserve then -- TODO ???? - return needed - end - end - return 0 -end - -function techage.power.provide_power(pos, provide) - local mem = tubelib2.get_mem(pos) - if mem.is_master then - -- nothing to do - elseif mem.master_pos then - mem = tubelib2.get_mem(mem.master_pos) - else - return 0 - end +local function handle_generator(mst_mem, mem, pos, power_available) -- for next cycle - mem.available1 = (mem.available1 or 0) + provide + mst_mem.mst_available1 = mst_mem.mst_available1 + power_available -- current cycle - mem.supply1 = mem.supply1 or 0 - local val = math.min(provide, mem.supply1) - mem.supply1 = mem.supply1 - val - return val -end - -function techage.power.secondary_power(pos, provide, needed) - local mem = tubelib2.get_mem(pos) - if mem.is_master then - -- nothing to do - elseif mem.master_pos then - mem = tubelib2.get_mem(mem.master_pos) + mst_mem.mst_supply1 = mst_mem.mst_supply1 or 0 + if mst_mem.mst_supply1 < power_available then + mem.pwr_provided = mst_mem.mst_supply1 + mst_mem.mst_supply1 = 0 else - return 0 + mst_mem.mst_supply1 = mst_mem.mst_supply1 - power_available + mem.pwr_provided = power_available end - -- for next cycle - mem.available2 = (mem.available2 or 0) + provide - mem.needed2 = (mem.needed2 or 0) + needed - - -- defensive programming - mem.supply2 = mem.supply2 or 0 - mem.demand2 = mem.demand2 or 0 - - -- check as generator - if mem.supply2 > 0 then - local val = math.min(provide, mem.supply2) - mem.supply2 = mem.supply2 - val - return val - end - -- check as consumer - if mem.demand2 > 0 then - local val = math.min(needed, mem.demand2) - mem.demand2 = mem.demand2 - val - return -val - end - return 0 end -function techage.power.power_available(pos, needed) - local mem = tubelib2.get_mem(pos) - if mem.master_pos then - mem = tubelib2.get_mem(mem.master_pos) - if needed then - -- for next cycle - mem.needed1 = (mem.needed1 or 0) + needed +local function handle_consumer(mst_mem, mem, pos, power_needed) + print("handle_consumer", mem.pwr_state) + if mem.pwr_state == NOPOWER then + --print("power_needed", power_needed,"mst_mem.demand1", mst_mem.mst_demand1) + -- for next cycle + mst_mem.mst_needed1 = mst_mem.mst_needed1 + power_needed + -- current cycle + if (mst_mem.mst_demand1 or 0) - power_needed >= 0 then + mst_mem.mst_demand1 = (mst_mem.mst_demand1 or 0) - power_needed + consumer_turn_on(pos, mem) + end + elseif mem.pwr_state == RUNNING then + -- for next cycle + mst_mem.mst_needed1 = mst_mem.mst_needed1 + power_needed + -- current cycle + mst_mem.mst_demand1 = (mst_mem.mst_demand1 or 0) - power_needed + if mst_mem.mst_demand1 < 0 then + mst_mem.mst_demand1 = 0 + consumer_turn_off(pos, mem) + end + end +end + +local function handle_secondary(mst_mem, mem, pos, provides, needed) + -- for next cycle + mst_mem.mst_available2 = (mst_mem.mst_available2 or 0) + provides + mst_mem.mst_needed2 = (mst_mem.mst_needed2 or 0) + needed + -- check as generator + mst_mem.mst_supply2 = mst_mem.mst_supply2 or 0 + mst_mem.mst_demand2 = mst_mem.mst_demand2 or 0 + if mst_mem.mst_supply2 > 0 then + local val = math.min(provides, mst_mem.mst_supply2) + mst_mem.mst_supply2 = mst_mem.mst_supply2 - val + mem.pwr_provided = val + -- check as consumer + elseif mst_mem.mst_demand2 > 0 then + local val = math.min(needed, mst_mem.mst_demand2) + mst_mem.mst_demand2 = mst_mem.mst_demand2 - val + mem.pwr_provided = -val + else + mem.pwr_provided = 0 + end + +end + +local function trigger_nodes(mst_pos, mst_mem) + Route = {} + pos_already_reached(mst_pos) + connection_walk(mst_pos, function(pos, mem) + mem.pwr_node_alive_cnt = (mem.pwr_node_alive_cnt or 1) - 1 + mem.pwr_power_provided_cnt = 2 + --print("trigger_nodes", mem.pwr_node_alive_cnt, mem.pwr_available2 or mem.pwr_available or mem.pwr_needed) + if mem.pwr_node_alive_cnt >= 0 then + if mem.pwr_available then + handle_generator(mst_mem, mem, pos, mem.pwr_available) + elseif mem.pwr_needed then + handle_consumer(mst_mem, mem, pos, mem.pwr_needed) + elseif mem.pwr_available2 then + handle_secondary(mst_mem, mem, pos, mem.pwr_available2, mem.pwr_needed2) + end + end + end) +end + +local function determine_new_master(pos, mem) + local was_master = mem.pwr_is_master + mem.pwr_is_master = false + local mpos = determine_master(pos) + --print("determine_new_master", S(mpos)) + store_master(pos, mpos) + if mpos then + tubelib2.get_mem(mpos).pwr_is_master = true + elseif was_master then -- no master any more + -- delete data + local mmem = tubelib2.get_mem(mpos) + mmem.mst_supply1 = 0 + mmem.mst_supply2 = 0 + mmem.mst_reserve = 0 + end + return was_master or mem.pwr_is_master +end + +-- called from master position +local function power_distribution(pos, mem) + mem.mst_needed1 = 0 + mem.mst_needed2 = 0 + mem.mst_available1 = 0 + mem.mst_available2 = 0 + trigger_nodes(pos, mem) + accounting(pos, mem) +end + +-- +-- Power API functions +-- + +-- To be called for each network change from any node +function techage.power.network_changed(pos, mem) + print("network_changed") + mem.pwr_node_alive_cnt = (mem.pwr_cycle_time or 2)/2 + 1 + if determine_new_master(pos, mem) then -- new master? + power_distribution(pos, mem) + elseif not next(mem.connections) then -- isolated? + if mem.pwr_needed then -- consumer? + consumer_turn_off(pos, mem) + end + end +end + +-- +-- Generator related functions +-- +function techage.power.generator_start(pos, mem, available) + mem.pwr_node_alive_cnt = 2 + mem.pwr_cycle_time = 2 + mem.pwr_available = available + if determine_new_master(pos, mem) then -- new master + power_distribution(pos, mem) + end +end + +function techage.power.generator_stop(pos, mem) + mem.pwr_node_alive_cnt = 0 + mem.pwr_available = 0 + if determine_new_master(pos, mem) then -- last available master + power_distribution(pos, mem) + end +end + +function techage.power.generator_alive(pos, mem) + mem.pwr_node_alive_cnt = 2 + if mem.pwr_is_master then + power_distribution(pos, mem) + end + return mem.pwr_provided +end + +-- +-- Consumer related functions +-- +function techage.power.consumer_alive(pos, mem) + print("consumer_alive", mem.pwr_power_provided_cnt) + mem.pwr_power_provided_cnt = (mem.pwr_power_provided_cnt or 0) - (mem.pwr_cycle_time or 2)/2 + if mem.pwr_power_provided_cnt >= 0 then + mem.pwr_node_alive_cnt = (mem.pwr_cycle_time or 2)/2 + 1 + else + consumer_turn_off(pos, mem) + end +end + +function techage.power.consumer_start(pos, mem, cycle_time, needed) + mem.pwr_cycle_time = cycle_time + mem.pwr_power_provided_cnt = 0 + mem.pwr_node_alive_cnt = 2 + mem.pwr_needed = needed + mem.pwr_state = NOPOWER +end + +function techage.power.consumer_stop(pos, mem) + mem.pwr_power_provided_cnt = 0 + mem.pwr_node_alive_cnt = 0 + mem.pwr_needed = 0 + mem.pwr_state = STOPPED +end + +-- Lamp related function to speed up the turn on +function techage.power.power_available(pos, mem, needed) + if mem.pwr_master_pos and (mem.pwr_power_provided_cnt or 0) > 0 then + mem = tubelib2.get_mem(mem.pwr_master_pos) + if (mem.mst_reserve or 0) - needed >= 0 then + mem.mst_reserve = mem.mst_reserve - needed + return true end - return mem.reserve end return false end - -function techage.power.power_accounting(pos) - local mem = tubelib2.get_mem(pos) - if mem.master_pos then - mem = tubelib2.get_mem(mem.master_pos) - return "\nGenerators = "..mem.available1.."\nAkkus = "..mem.available2.."\nMachines = "..mem.needed1.."\n" + +-- Power terminal function +function techage.power.power_accounting(pos, mem) + if mem.pwr_master_pos then + mem = tubelib2.get_mem(mem.pwr_master_pos) + return { + prim_available = mem.mst_available1, + sec_available = mem.mst_available2, + prim_needed = mem.mst_needed1, + sec_needed = mem.mst_needed2, + } end - return "no power" + return { + prim_available = 0, + sec_available = 0, + prim_needed = 0, + sec_needed = 0, + } end -function techage.power.percent(max_val, curr_val) - return math.min(math.ceil(((curr_val or 0) * 100.0) / (max_val or 1.0)), 100) +-- +-- Akku related functions +-- +function techage.power.secondary_start(pos, mem, available, needed) + mem.pwr_node_alive_cnt = 2 + mem.pwr_could_provide = available + mem.pwr_could_need = needed + if determine_new_master(pos, mem) then -- new master + power_distribution(pos, mem) + end end -function techage.power.formspec_load_bar(charging, max_val) - local percent - charging = charging or 0 - max_val = max_val or 1 - if charging ~= 0 then - percent = 50 + math.ceil((charging * 50.0) / max_val) +function techage.power.secondary_stop(pos, mem) + mem.pwr_node_alive_cnt = 0 + mem.pwr_could_provide = 0 + mem.pwr_could_need = 0 + if determine_new_master(pos, mem) then -- last available master + power_distribution(pos, mem) end +end - if charging > 0 then - return "techage_form_level_off.png^[lowpart:"..percent..":techage_form_level_charge.png" - elseif charging < 0 then - return "techage_form_level_unload.png^[lowpart:"..percent..":techage_form_level_off.png" +function techage.power.secondary_alive(pos, mem, capa_curr, capa_max) + --print("secondary_alive") + if capa_curr >= capa_max then + mem.pwr_available2, mem.pwr_needed2 = mem.pwr_could_provide, 0 -- can provide only + elseif capa_curr <= 0 then + mem.pwr_available2, mem.pwr_needed2 = 0, mem.pwr_could_need -- can deliver only else - return "techage_form_level_off.png" + mem.pwr_available2, mem.pwr_needed2 = mem.pwr_could_provide, mem.pwr_could_need end -end - -function techage.power.formspec_power_bar(max_power, current_power) - local percent = techage.power.percent(max_power, current_power) - percent = (percent + 5) / 1.22 -- texture correction - return "techage_form_level_bg.png^[lowpart:"..percent..":techage_form_level_fg.png" -end - - -function techage.power.side_to_outdir(pos, side) - local node = minetest.get_node(pos) - return side_to_dir(node.param2, side) -end + + mem.pwr_node_alive_cnt = 2 + if mem.pwr_is_master then + --print("secondary_alive is master") + power_distribution(pos, mem) + end + return mem.pwr_provided +end \ No newline at end of file diff --git a/power/power2.lua b/power/power2.lua new file mode 100644 index 0000000..a95952e --- /dev/null +++ b/power/power2.lua @@ -0,0 +1,224 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + Power helper functions +]]-- + +-- for lazy programmers +local S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P = minetest.string_to_pos +local M = minetest.get_meta +-- Techage Related Data +local PWR = function(pos) return (minetest.registered_nodes[minetest.get_node(pos).name] or {}).power end +local PWRN = function(node) return (minetest.registered_nodes[node.name] or {}).power end + +local network_changed = techage.power.network_changed + +local SideToDir = {B=1, R=2, F=3, L=4, D=5, U=6} + +local function side_to_dir(param2, side) + local dir = SideToDir[side] + if dir < 5 then + dir = (((dir - 1) + (param2 % 4)) % 4) + 1 + end + return dir +end + +local function set_conn_dirs(pos, sides) + local tbl = {} + local node = minetest.get_node(pos) + if type(sides) == "function" then + tbl = sides(pos, node) + else + for _,side in ipairs(sides) do + tbl[#tbl+1] = tubelib2.Turn180Deg[side_to_dir(node.param2, side)] + end + end + M(pos):set_string("power_dirs", minetest.serialize(tbl)) +end + +local function valid_indir(pos, in_dir) + local s = M(pos):get_string("power_dirs") + if s == "" then + local pwr = PWR(pos) + if pwr then + set_conn_dirs(pos, pwr.conn_sides) + end + end + if s ~= "" then + for _,dir in ipairs(minetest.deserialize(s)) do + if dir == in_dir then + return true + end + end + end + return false +end + +local function valid_outdir(pos, out_dir) + return valid_indir(pos, tubelib2.Turn180Deg[out_dir]) +end + +-- Both nodes are from the same power network type? +local function matching_nodes(pos, peer_pos) + local tube_type1 = pos and PWR(pos) and PWR(pos).power_network.tube_type + local tube_type2 = peer_pos and PWR(peer_pos) and PWR(peer_pos).power_network.tube_type + return not tube_type1 or not tube_type2 or tube_type1 == tube_type2 +end + +function techage.get_pos(pos, side) + local node = minetest.get_node(pos) + local dir = nil + if node.name ~= "air" and node.name ~= "ignore" then + dir = side_to_dir(node.param2, side) + end + return tubelib2.get_pos(pos, dir) +end + +-- only for nodes with own 'conn_sides' and rotate function +function techage.power.after_rotate_node(pos, cable) + cable:after_dig_node(pos) + set_conn_dirs(pos, PWR(pos).conn_sides) + cable:after_place_node(pos) +end + +function techage.power.register_node(names, pwr_def) + for _,name in ipairs(names) do + local ndef = minetest.registered_nodes[name] + if ndef then + minetest.override_item(name, { + power = { + conn_sides = pwr_def.conn_sides or {"L", "R", "U", "D", "F", "B"}, + on_power = pwr_def.on_power, + on_nopower = pwr_def.on_nopower, + power_network = pwr_def.power_network, + after_place_node = ndef.after_place_node, + after_dig_node = ndef.after_dig_node, + after_tube_update = ndef.after_tube_update, + }, + -- after_place_node decorator + after_place_node = function(pos, placer, itemstack, pointed_thing) + local pwr = PWR(pos) + set_conn_dirs(pos, pwr.conn_sides) + pwr.power_network:after_place_node(pos) + if pwr.after_place_node then + return pwr.after_place_node(pos, placer, itemstack, pointed_thing) + end + end, + -- after_dig_node decorator + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local pwr = PWRN(oldnode) + pwr.power_network:after_dig_node(pos) + minetest.after(0.1, tubelib2.del_mem, pos) -- At latest... + if pwr.after_dig_node then + return pwr.after_dig_node(pos, oldnode, oldmetadata, digger) + end + end, + -- tubelib2 callback, called after any connection change + after_tube_update = function(node, pos, out_dir, peer_pos, peer_in_dir) + local pwr = PWR(pos) + local mem = tubelib2.get_mem(pos) + mem.connections = mem.connections or {} + if not peer_pos or not valid_indir(peer_pos, peer_in_dir) + or not valid_outdir(pos, out_dir) + or not matching_nodes(pos, peer_pos) then + mem.connections[out_dir] = nil -- del connection + else + mem.connections[out_dir] = {pos = peer_pos, in_dir = peer_in_dir} + end + -- To be called delayed, so that all network connections have been established + minetest.after(0.2, network_changed, pos, mem) + if pwr.after_tube_update then + return pwr.after_tube_update(node, pos, out_dir, peer_pos, peer_in_dir) + end + end, + }) + pwr_def.power_network:add_secondary_node_names({name}) + end + end +end + +function techage.power.after_tube_update(node, pos, out_dir, peer_pos, peer_in_dir, power) + local mem = tubelib2.get_mem(pos) + mem.connections = mem.connections or {} + if not peer_pos or not valid_indir(peer_pos, peer_in_dir) + or not valid_outdir(pos, out_dir) + or not matching_nodes(pos, peer_pos) then + mem.connections[out_dir] = nil -- del connection + else + mem.connections[out_dir] = {pos = peer_pos, in_dir = peer_in_dir} + end + -- To be called delayed, so that all network connections have been established + minetest.after(0.2, network_changed, pos, mem) + if power.after_tube_update then + return power.after_tube_update(node, pos, out_dir, peer_pos, peer_in_dir) + end +end + + +function techage.power.percent(max_val, curr_val) + return math.min(math.ceil(((curr_val or 0) * 100.0) / (max_val or 1.0)), 100) +end + +function techage.power.formspec_load_bar(charging, max_val) + local percent + charging = charging or 0 + max_val = max_val or 1 + if charging ~= 0 then + percent = 50 + math.ceil((charging * 50.0) / max_val) + end + + if charging > 0 then + return "techage_form_level_off.png^[lowpart:"..percent..":techage_form_level_charge.png" + elseif charging < 0 then + return "techage_form_level_unload.png^[lowpart:"..percent..":techage_form_level_off.png" + else + return "techage_form_level_off.png" + end +end + +function techage.power.formspec_power_bar(max_power, current_power) + local percent = techage.power.percent(max_power, current_power) + percent = (percent + 5) / 1.22 -- texture correction + return "techage_form_level_bg.png^[lowpart:"..percent..":techage_form_level_fg.png" +end + + +function techage.power.side_to_outdir(pos, side) + local node = minetest.get_node(pos) + return side_to_dir(node.param2, side) +end + +-- Used to turn on/off the power by means of a power switch +function techage.power.power_cut(pos, dir, cable, cut) + local npos = vector.add(pos, tubelib2.Dir6dToVector[dir or 0]) + + local node = minetest.get_node(npos) + if node.name ~= "techage:powerswitch_box" and + M(npos):get_string("techage_hidden_nodename") ~= "techage:powerswitch_box" then + return + end + + local mem = tubelib2.get_mem(npos) + mem.interrupted_dirs = mem.interrupted_dirs or {} + + if cut then + mem.interrupted_dirs = {true, true, true, true, true, true} + for dir,_ in pairs(mem.connections) do + mem.interrupted_dirs[dir] = false + techage.power.network_changed(pos, mem) + mem.interrupted_dirs[dir] = true + end + else + mem.interrupted_dirs = {} + techage.power.network_changed(pos, mem) + end +end + diff --git a/tools/repairkit.lua b/tools/repairkit.lua index a7b5ab8..0ea8264 100644 --- a/tools/repairkit.lua +++ b/tools/repairkit.lua @@ -74,7 +74,8 @@ local function read_state(itemstack, user, pointed_thing) elseif ndef and ndef.description then if ndef.is_power_available then local power = ndef.is_power_available(pos) - minetest.chat_send_player(user:get_player_name(), ndef.description..": power = "..power.." ") + local text = "\nGenerators = "..power.prim_available.."\nAkkus = "..power.sec_available.."\nMachines = "..power.prim_needed.."\n" + minetest.chat_send_player(user:get_player_name(), ndef.description..": power = "..text) end itemstack:add_wear(65636/200) return itemstack