Vanessa Ezekowitz
2014-07-23 29c7ff5228864bcf5456f391b122c9eb477c754b
commit | author | age
ee5c6c 1 -- SWITCHING STATION
K 2 -- The switching station is the center of all power distribution on an electric network.
3 -- The station will collect all produced power from producers (PR) and batteries (BA)
4 -- and distribute it to receivers (RE) and depleted batteries (BA).
5 --
6 -- It works like this:
7 --  All PR,BA,RE nodes are indexed and tagged with the switching station.
8 -- The tagging is to allow more stations to be built without allowing a cheat
9 -- with duplicating power.
10 --  All the RE nodes are queried for their current EU demand. Those which are off
11 -- would require no or a small standby EU demand, while those which are on would
12 -- require more.
13 -- If the total demand is less than the available power they are all updated with the
14 -- demand number.
15 -- If any surplus exists from the PR nodes the batteries will be charged evenly with this.
16 -- If the total demand requires draw on the batteries they will be discharged evenly.
17 --
18 -- If the total demand is more than the available power all RE nodes will be shut down.
19 -- We have a brown-out situation.
20 --
21 -- Hence all the power distribution logic resides in this single node.
22 --
23 --  Nodes connected to the network will have one or more of these parameters as meta data:
24 --   <LV|MV|HV>_EU_supply : Exists for PR and BA node types. This is the EU value supplied by the node. Output
25 --   <LV|MV|HV>_EU_demand : Exists for RE and BA node types. This is the EU value the node requires to run. Output
26 --   <LV|MV|HV>_EU_input  : Exists for RE and BA node types. This is the actual EU value the network can give the node. Input
27 --
28 --  The reason the LV|MV|HV type is prepended toe meta data is because some machine could require several supplies to work.
29 --  This way the supplies are separated per network.
30
468d79 31 technic.networks = {}
be2f30 32
S 33 local S = technic.getter
468d79 34
ee0765 35 minetest.register_craft({
S 36     output = "technic:switching_station",
37     recipe = {
e8a5a6 38         {"",                        "technic:lv_transformer", ""},
Z 39         {"default:copper_ingot",    "technic:machine_casing", "default:copper_ingot"},
40         {"technic:lv_cable0",       "technic:lv_cable0",      "technic:lv_cable0"}
ee0765 41     }
S 42 })
ee5c6c 43
ee0765 44 minetest.register_node("technic:switching_station",{
be2f30 45     description = S("Switching Station"),
ee0765 46     tiles  = {"technic_water_mill_top_active.png", "technic_water_mill_top_active.png",
S 47                   "technic_water_mill_top_active.png", "technic_water_mill_top_active.png",
48               "technic_water_mill_top_active.png", "technic_water_mill_top_active.png"},
49     groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2},
50     sounds = default.node_sound_wood_defaults(),
51     drawtype = "nodebox",
52     paramtype = "light",
53     node_box = {
54         type = "fixed",
55         fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
56     },
57     on_construct = function(pos)
58         local meta = minetest.get_meta(pos)
be2f30 59         meta:set_string("infotext", S("Switching Station"))
563a4c 60         meta:set_string("active", 1)
ee0765 61     end,
S 62 })
ee5c6c 63
K 64 --------------------------------------------------
65 -- Functions to traverse the electrical network
66 --------------------------------------------------
67
68 -- Add a wire node to the LV/MV/HV network
ee0765 69 local add_new_cable_node = function(nodes, pos)
S 70     -- Ignore if the node has already been added
71     for i = 1, #nodes do
72         if pos.x == nodes[i].x and
73            pos.y == nodes[i].y and
74            pos.z == nodes[i].z then
75             return false
76         end
77     end
78     table.insert(nodes, {x=pos.x, y=pos.y, z=pos.z, visited=1})
79     return true
80 end
ee5c6c 81
563a4c 82 local load_position = function(pos)
29c7ff 83     if minetest.get_node_or_nil(pos) then return end
563a4c 84     local vm = VoxelManip()
N 85     local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
86 end
87
ee5c6c 88 -- Generic function to add found connected nodes to the right classification array
563a4c 89 local check_node_subp = function(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, pos, machines, tier, sw_pos)
N 90     load_position(pos)
ee0765 91     local meta = minetest.get_meta(pos)
S 92     local name = minetest.get_node(pos).name
93
94     if technic.is_tier_cable(name, tier) then
95         add_new_cable_node(all_nodes, pos)
96     elseif machines[name] then
97         --dprint(name.." is a "..machines[name])
98         if     machines[name] == technic.producer then
99             add_new_cable_node(PR_nodes, pos)
100         elseif machines[name] == technic.receiver then
101             add_new_cable_node(RE_nodes, pos)
623fca 102         elseif machines[name] == technic.producer_receiver then
Z 103             add_new_cable_node(PR_nodes, pos)
104             add_new_cable_node(RE_nodes, pos)
563a4c 105         elseif machines[name] == "SPECIAL" and
N 106                 (pos.x ~= sw_pos.x or pos.y ~= sw_pos.y or pos.z ~= sw_pos.z) then
107             -- Another switching station -> disable it
108             add_new_cable_node(SP_nodes, pos)
109             meta:set_int("active", 0)
110             meta:set_string("active_pos", minetest.serialize(sw_pos))
ee0765 111         elseif machines[name] == technic.battery then
S 112             add_new_cable_node(BA_nodes, pos)
113         end
114
115         meta:set_int(tier.."_EU_timeout", 2) -- Touch node
116     end
117 end
ee5c6c 118
K 119 -- Traverse a network given a list of machines and a cable type name
563a4c 120 local traverse_network = function(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, i, machines, tier, sw_pos)
ee0765 121     local pos = all_nodes[i]
S 122     local positions = {
123         {x=pos.x+1, y=pos.y,   z=pos.z},
124         {x=pos.x-1, y=pos.y,   z=pos.z},
125         {x=pos.x,   y=pos.y+1, z=pos.z},
126         {x=pos.x,   y=pos.y-1, z=pos.z},
127         {x=pos.x,   y=pos.y,   z=pos.z+1},
128         {x=pos.x,   y=pos.y,   z=pos.z-1}}
129     --print("ON")
130     for i, cur_pos in pairs(positions) do
563a4c 131         check_node_subp(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes, cur_pos, machines, tier, sw_pos)
ee0765 132     end
S 133 end
ee5c6c 134
f4ac2b 135 local touch_nodes = function(list, tier)
N 136     for _, pos in ipairs(list) do
137         local meta = minetest.get_meta(pos)
138         meta:set_int(tier.."_EU_timeout", 2) -- Touch node
139     end
140 end
141
563a4c 142 local get_network = function(sw_pos, pos1, tier)
12d29c 143     local cached = technic.networks[minetest.hash_node_position(pos1)]
f4ac2b 144     if cached and cached.tier == tier then
N 145         touch_nodes(cached.PR_nodes, tier)
146         touch_nodes(cached.BA_nodes, tier)
147         touch_nodes(cached.RE_nodes, tier)
563a4c 148         for _, pos in ipairs(cached.SP_nodes) do
N 149             local meta = minetest.get_meta(pos)
150             meta:set_int("active", 0)
151             meta:set_string("active_pos", minetest.serialize(sw_pos))
152         end
f4ac2b 153         return cached.PR_nodes, cached.BA_nodes, cached.RE_nodes
N 154     end
155     local i = 1
156     local PR_nodes = {}
157     local BA_nodes = {}
158     local RE_nodes = {}
563a4c 159     local SP_nodes = {}
f4ac2b 160     local all_nodes = {pos1}
N 161     repeat
563a4c 162         traverse_network(PR_nodes, RE_nodes, BA_nodes, SP_nodes, all_nodes,
N 163                 i, technic.machines[tier], tier, sw_pos)
f4ac2b 164         i = i + 1
N 165     until all_nodes[i] == nil
563a4c 166     technic.networks[minetest.hash_node_position(pos1)] = {tier = tier, PR_nodes = PR_nodes,
N 167             RE_nodes = RE_nodes, BA_nodes = BA_nodes, SP_nodes = SP_nodes}
f4ac2b 168     return PR_nodes, BA_nodes, RE_nodes
N 169 end
170
ee0765 171 -----------------------------------------------
S 172 -- The action code for the switching station --
173 -----------------------------------------------
174 minetest.register_abm({
175     nodenames = {"technic:switching_station"},
ee5c6c 176     interval   = 1,
K 177     chance     = 1,
178     action = function(pos, node, active_object_count, active_object_count_wider)
ee0765 179         local meta             = minetest.get_meta(pos)
S 180         local meta1            = nil
181         local pos1             = {}
182         local PR_EU            = 0 -- EUs from PR nodes
183         local BA_PR_EU         = 0 -- EUs from BA nodes (discharching)
184         local BA_RE_EU         = 0 -- EUs to BA nodes (charging)
185         local RE_EU            = 0 -- EUs to RE nodes
ee5c6c 186
ee0765 187         local tier      = ""
f4ac2b 188         local PR_nodes
N 189         local BA_nodes
190         local RE_nodes
be2f30 191         local machine_name = S("Switching Station")
ee5c6c 192
563a4c 193         if meta:get_int("active") ~= 1 then
N 194             meta:set_int("active", 1)
195             local active_pos = minetest.deserialize(meta:get_string("active_pos"))
196             if active_pos then
197                 local meta1 = minetest.get_meta(active_pos)
198                 meta:set_string("infotext", S("%s (Slave)"):format(meta1:get_string("infotext")))
199             end
200             return
201         end
202         
ee0765 203         -- Which kind of network are we on:
S 204         pos1 = {x=pos.x, y=pos.y-1, z=pos.z}
ee5c6c 205
ee0765 206         local name = minetest.get_node(pos1).name
S 207         local tier = technic.get_cable_tier(name)
208         if tier then
563a4c 209             PR_nodes, BA_nodes, RE_nodes = get_network(pos, pos1, tier)
ee0765 210         else
S 211             --dprint("Not connected to a network")
be2f30 212             meta:set_string("infotext", S("%s Has No Network"):format(machine_name))
ee0765 213             return
S 214         end
563a4c 215         
N 216         -- Run all the nodes
217         local function run_nodes(list)
218             for _, pos2 in ipairs(list) do
219                 load_position(pos2)
220                 local node2 = minetest.get_node(pos2)
221                 local nodedef
222                 if node2 and node2.name then
223                     nodedef = minetest.registered_nodes[node2.name]
224                 end
225                 if nodedef and nodedef.technic_run then
226                     nodedef.technic_run(pos2, node2)
227                 end
228             end
229         end
230         
231         run_nodes(PR_nodes)
232         run_nodes(RE_nodes)
233         run_nodes(BA_nodes)
ee5c6c 234
ee0765 235         -- Strings for the meta data
S 236         local eu_demand_str    = tier.."_EU_demand"
237         local eu_input_str     = tier.."_EU_input"
238         local eu_supply_str    = tier.."_EU_supply"
ee5c6c 239
ee0765 240         -- Get all the power from the PR nodes
S 241         local PR_eu_supply = 0 -- Total power
242         for _, pos1 in pairs(PR_nodes) do
243             meta1 = minetest.get_meta(pos1)
244             PR_eu_supply = PR_eu_supply + meta1:get_int(eu_supply_str)
245         end
246         --dprint("Total PR supply:"..PR_eu_supply)
ee5c6c 247
ee0765 248         -- Get all the demand from the RE nodes
S 249         local RE_eu_demand = 0
250         for _, pos1 in pairs(RE_nodes) do
251             meta1 = minetest.get_meta(pos1)
252             RE_eu_demand = RE_eu_demand + meta1:get_int(eu_demand_str)
253         end
254         --dprint("Total RE demand:"..RE_eu_demand)
ee5c6c 255
ee0765 256         -- Get all the power from the BA nodes
S 257         local BA_eu_supply = 0
258         for _, pos1 in pairs(BA_nodes) do
259             meta1 = minetest.get_meta(pos1)
260             BA_eu_supply = BA_eu_supply + meta1:get_int(eu_supply_str)
261         end
262         --dprint("Total BA supply:"..BA_eu_supply)
ee5c6c 263
ee0765 264         -- Get all the demand from the BA nodes
S 265         local BA_eu_demand = 0
266         for _, pos1 in pairs(BA_nodes) do
267             meta1 = minetest.get_meta(pos1)
268             BA_eu_demand = BA_eu_demand + meta1:get_int(eu_demand_str)
269         end
270         --dprint("Total BA demand:"..BA_eu_demand)
ee5c6c 271
ee0765 272         meta:set_string("infotext",
be2f30 273                 S("%s. Supply: %d Demand: %d"):format(
S 274                 machine_name, PR_eu_supply, RE_eu_demand))
ee5c6c 275
ee0765 276         -- If the PR supply is enough for the RE demand supply them all
S 277         if PR_eu_supply >= RE_eu_demand then
278         --dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
279             for _, pos1 in pairs(RE_nodes) do
280                 meta1 = minetest.get_meta(pos1)
281                 local eu_demand = meta1:get_int(eu_demand_str)
282                 meta1:set_int(eu_input_str, eu_demand)
283             end
284             -- We have a surplus, so distribute the rest equally to the BA nodes
285             -- Let's calculate the factor of the demand
286             PR_eu_supply = PR_eu_supply - RE_eu_demand
287             local charge_factor = 0 -- Assume all batteries fully charged
288             if BA_eu_demand > 0 then
289                 charge_factor = PR_eu_supply / BA_eu_demand
290             end
291             for n, pos1 in pairs(BA_nodes) do
292                 meta1 = minetest.get_meta(pos1)
293                 local eu_demand = meta1:get_int(eu_demand_str)
294                 meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor))
295                 --dprint("Charging battery:"..math.floor(eu_demand*charge_factor))
296             end
297             return
298         end
ee5c6c 299
ee0765 300         -- If the PR supply is not enough for the RE demand we will discharge the batteries too
S 301         if PR_eu_supply + BA_eu_supply >= RE_eu_demand then
302             --dprint("PR_eu_supply "..PR_eu_supply.."+BA_eu_supply "..BA_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
303             for _, pos1 in pairs(RE_nodes) do
304                 meta1  = minetest.get_meta(pos1)
305                 local eu_demand = meta1:get_int(eu_demand_str)
306                 meta1:set_int(eu_input_str, eu_demand)
307             end
308             -- We have a deficit, so distribute to the BA nodes
309             -- Let's calculate the factor of the supply
310             local charge_factor = 0 -- Assume all batteries depleted
311             if BA_eu_supply > 0 then
312                 charge_factor = (PR_eu_supply - RE_eu_demand) / BA_eu_supply
313             end
314             for n,pos1 in pairs(BA_nodes) do
315                 meta1 = minetest.get_meta(pos1)
ee5c6c 316                 local eu_supply = meta1:get_int(eu_supply_str)
ee0765 317                 meta1:set_int(eu_input_str, math.floor(eu_supply * charge_factor))
S 318                 --dprint("Discharging battery:"..math.floor(eu_supply*charge_factor))
319             end
320             return
321         end
ee5c6c 322
ee0765 323         -- If the PR+BA supply is not enough for the RE demand: Power only the batteries
S 324         local charge_factor = 0 -- Assume all batteries fully charged
325         if BA_eu_demand > 0 then
326             charge_factor = PR_eu_supply / BA_eu_demand
327         end
328         for n, pos1 in pairs(BA_nodes) do
329             meta1 = minetest.get_meta(pos1)
330             local eu_demand = meta1:get_int(eu_demand_str)
331             meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor))
332         end
333         for n, pos1 in pairs(RE_nodes) do
334             meta1 = minetest.get_meta(pos1)
335             meta1:set_int(eu_input_str, 0)
336         end
ee5c6c 337     end,
K 338 })
ee0765 339
563a4c 340 -- Timeout ABM
N 341 -- Timeout for a node in case it was disconnected from the network
342 -- A node must be touched by the station continuously in order to function
343 local function switching_station_timeout_count(pos, tier)
344     local meta = minetest.get_meta(pos)
345     local timeout = meta:get_int(tier.."_EU_timeout")
346     if timeout <= 0 then
347         --meta:set_int(tier.."_EU_input", 0) -- Not needed anymore
348         return true
349     else
350         meta:set_int(tier.."_EU_timeout", timeout - 1)
351         return false
352     end
353 end
354 minetest.register_abm({
355     nodenames = {"group:technic_machine"},
356     interval   = 1,
357     chance     = 1,
358     action = function(pos, node, active_object_count, active_object_count_wider)
359         for tier, machines in pairs(technic.machines) do
360             if machines[node.name] and switching_station_timeout_count(pos, tier) then
361                 local nodedef = minetest.registered_nodes[node.name]
362                 if nodedef and nodedef.technic_disabled_machine_name then
363                     node.name = nodedef.technic_disabled_machine_name
364                     minetest.swap_node(pos, node)
1c617f 365                 elseif nodedef and nodedef.technic_on_disable then
N 366                     nodedef.technic_on_disable(pos, node)
563a4c 367                 end
N 368                 if nodedef then
369                     local meta = minetest.get_meta(pos)
370                     meta:set_string("infotext", S("%s Has No Network"):format(nodedef.description))
371                 end
372             end
373         end
374     end,
375 })
376
ee0765 377 for tier, machines in pairs(technic.machines) do
S 378     -- SPECIAL will not be traversed
379     technic.register_machine(tier, "technic:switching_station", "SPECIAL")
380 end
381