Thomas Rudin
2018-11-16 3d52b679b014a92dad8e298c97095b11533ca0c1
commit | author | age
85a984 1 --[[
S 2  The enriched uranium rod driven EU generator.
3 A very large and advanced machine providing vast amounts of power.
4 Very efficient but also expensive to run as it needs uranium.
5 Provides 10000 HV EUs for one week (only counted when loaded).
ee0765 6
85a984 7 The nuclear reactor core requires a casing of water and a protective
S 8 shield to work.  This is checked now and then and if the casing is not
9 intact the reactor will melt down!
10 --]]
11
12 local burn_ticks = 7 * 24 * 60 * 60  -- Seconds
13 local power_supply = 100000  -- EUs
14 local fuel_type = "technic:uranium_fuel"  -- The reactor burns this
bf58c7 15 local digiline_meltdown = technic.config:get_bool("enable_nuclear_reactor_digiline_selfdestruct")
D 16 local digiline_remote_path = minetest.get_modpath("digiline_remote")
ee0765 17
be2f30 18 local S = technic.getter
ee0765 19
1d20af 20 local reactor_desc = S("@1 Nuclear Reactor Core", S("HV"))
VE 21 local cable_entry = "^technic_cable_connection_overlay.png"
85a984 22
S 23 -- FIXME: Recipe should make more sense like a rod recepticle, steam chamber, HV generator?
ee0765 24 minetest.register_craft({
S 25     output = 'technic:hv_nuclear_reactor_core',
26     recipe = {
5e4a87 27         {'technic:carbon_plate',          'default:obsidian_glass', 'technic:carbon_plate'},
Z 28         {'technic:composite_plate',       'technic:machine_casing', 'technic:composite_plate'},
83c649 29         {'technic:stainless_steel_ingot', 'technic:hv_cable',       'technic:stainless_steel_ingot'},
ee0765 30     }
S 31 })
32
bf58c7 33 local function make_reactor_formspec(meta)
D 34     local f = "size[8,9]"..
be2f30 35     "label[0,0;"..S("Nuclear Reactor Rod Compartment").."]"..
ee0765 36     "list[current_name;src;2,1;3,2;]"..
d732c8 37     "list[current_player;main;0,5;8,4;]"..
bf58c7 38     "listring[]"..
D 39     "button[5.5,1.5;2,1;start;Start]"..
40     "checkbox[5.5,2.5;autostart;automatic Start;"..meta:get_string("autostart").."]"
41     if not digiline_remote_path then
42         return f
43     end
44     local digiline_enabled = meta:get_string("enable_digiline")
45     f = f.."checkbox[0.5,2.8;enable_digiline;Enable Digiline;"..digiline_enabled.."]"
46     if digiline_enabled ~= "true" then
47         return f
48     end
49     return f..
50         "button_exit[4.6,3.69;2,1;save;Save]"..
51         "field[1,4;4,1;remote_channel;Digiline Remote Channel;${remote_channel}]"
52 end
ee0765 53
85a984 54 local SS_OFF = 0
S 55 local SS_DANGER = 1
56 local SS_CLEAR = 2
57
d59055 58 local reactor_siren = {}
85a984 59 local function siren_set_state(pos, state)
d59055 60     local hpos = minetest.hash_node_position(pos)
Z 61     local siren = reactor_siren[hpos]
62     if not siren then
85a984 63         if state == SS_OFF then return end
S 64         siren = {state=SS_OFF}
d59055 65         reactor_siren[hpos] = siren
Z 66     end
85a984 67     if state == SS_DANGER and siren.state ~= SS_DANGER then
d59055 68         if siren.handle then minetest.sound_stop(siren.handle) end
85a984 69         siren.handle = minetest.sound_play("technic_hv_nuclear_reactor_siren_danger_loop",
S 70                 {pos=pos, gain=1.5, loop=true, max_hear_distance=48})
71         siren.state = SS_DANGER
72     elseif state == SS_CLEAR then
d59055 73         if siren.handle then minetest.sound_stop(siren.handle) end
85a984 74         local clear_handle = minetest.sound_play("technic_hv_nuclear_reactor_siren_clear",
S 75                 {pos=pos, gain=1.5, loop=false, max_hear_distance=48})
d59055 76         siren.handle = clear_handle
85a984 77         siren.state = SS_CLEAR
S 78         minetest.after(10, function()
79             if siren.handle ~= clear_handle then return end
80             minetest.sound_stop(clear_handle)
81             if reactor_siren[hpos] == siren then
82                 reactor_siren[hpos] = nil
d59055 83             end
Z 84         end)
85a984 85     elseif state == SS_OFF and siren.state ~= SS_OFF then
d59055 86         if siren.handle then minetest.sound_stop(siren.handle) end
Z 87         reactor_siren[hpos] = nil
88     end
89 end
85a984 90
d59055 91 local function siren_danger(pos, meta)
Z 92     meta:set_int("siren", 1)
85a984 93     siren_set_state(pos, SS_DANGER)
d59055 94 end
85a984 95
d59055 96 local function siren_clear(pos, meta)
Z 97     if meta:get_int("siren") ~= 0 then
85a984 98         siren_set_state(pos, SS_CLEAR)
d59055 99         meta:set_int("siren", 0)
Z 100     end
101 end
102
85a984 103 --[[
S 104 The standard reactor structure consists of a 9x9x9 cube.  A cross
105 section through the middle:
106
107     CCCC CCCC
108     CBBB BBBC
8ccb6d 109     CBLL LLBC
S 110     CBLWWWLBC
111     CBLW#WLBC
112     CBLW|WLBC
113     CBLL|LLBC
85a984 114     CBBB|BBBC
S 115     CCCC|CCCC
8ccb6d 116     C = Concrete, B = Blast-resistant concrete, L = Lead,
85a984 117     W = water node, # = reactor core, | = HV cable
S 118
8ccb6d 119 The man-hole is optional (but necessary for refueling).
85a984 120
S 121 For the reactor to operate and not melt down, it insists on the inner
122 7x7x7 portion (from the core out to the blast-resistant concrete)
123 being intact.  Intactness only depends on the number of nodes of the
124 right type in each layer.  The water layer must have water in all but
125 at most one node; the steel and blast-resistant concrete layers must
126 have the right material in all but at most two nodes.  The permitted
127 gaps are meant for the cable and man-hole, but can actually be anywhere
128 and contain anything.  For the reactor to be useful, a cable must
129 connect to the core, but it can go in any direction.
130
131 The outer concrete layer of the standard structure is not required
132 for the reactor to operate.  It is noted here because it used to
133 be mandatory, and for historical reasons (that it predates the
134 implementation of radiation) it needs to continue being adequate
135 shielding of legacy reactors.  If it ever ceases to be adequate
136 shielding for new reactors, legacy ones should be grandfathered.
8ccb6d 137
S 138 For legacy reasons, if the reactor has a stainless steel layer instead
139 of a lead layer it will be converted to a lead layer.
85a984 140 --]]
S 141 local function reactor_structure_badness(pos)
ee0765 142     local vm = VoxelManip()
84cf65 143     local pos1 = vector.subtract(pos, 3)
Z 144     local pos2 = vector.add(pos, 3)
ee0765 145     local MinEdge, MaxEdge = vm:read_from_map(pos1, pos2)
S 146     local data = vm:get_data()
147     local area = VoxelArea:new({MinEdge=MinEdge, MaxEdge=MaxEdge})
148
149     local c_blast_concrete = minetest.get_content_id("technic:blast_resistant_concrete")
8ccb6d 150     local c_lead = minetest.get_content_id("technic:lead_block")
S 151     local c_steel = minetest.get_content_id("technic:stainless_steel_block")
ee0765 152     local c_water_source = minetest.get_content_id("default:water_source")
S 153     local c_water_flowing = minetest.get_content_id("default:water_flowing")
154
8ccb6d 155     local blast_layer, steel_layer, lead_layer, water_layer = 0, 0, 0, 0
ee0765 156
S 157     for z = pos1.z, pos2.z do
158     for y = pos1.y, pos2.y do
159     for x = pos1.x, pos2.x do
84cf65 160         local cid = data[area:index(x, y, z)]
ee0765 161         if x == pos1.x or x == pos2.x or
S 162            y == pos1.y or y == pos2.y or
163            z == pos1.z or z == pos2.z then
84cf65 164             if cid == c_blast_concrete then
8ccb6d 165                 blast_layer = blast_layer + 1
ee0765 166             end
S 167         elseif x == pos1.x+1 or x == pos2.x-1 or
8ccb6d 168                y == pos1.y+1 or y == pos2.y-1 or
S 169                z == pos1.z+1 or z == pos2.z-1 then
170             if cid == c_lead then
171                 lead_layer = lead_layer + 1
172             elseif cid == c_steel then
173                 steel_layer = steel_layer + 1
ee0765 174             end
S 175         elseif x == pos1.x+2 or x == pos2.x-2 or
8ccb6d 176                y == pos1.y+2 or y == pos2.y-2 or
S 177                z == pos1.z+2 or z == pos2.z-2 then
ee0765 178             if cid == c_water_source or cid == c_water_flowing then
8ccb6d 179                 water_layer = water_layer + 1
ee0765 180             end
S 181         end
182     end
183     end
184     end
8ccb6d 185
S 186     if steel_layer >= 96 then
187         for z = pos1.z+1, pos2.z-1 do
188         for y = pos1.y+1, pos2.y-1 do
189         for x = pos1.x+1, pos2.x-1 do
190             local vi = area:index(x, y, z)
191             if x == pos1.x+1 or x == pos2.x-1 or
192                y == pos1.y+1 or y == pos2.y-1 or
193                z == pos1.z+1 or z == pos2.z-1 then
194                 if data[vi] == c_steel then
195                     data[vi] = c_lead
196                 end
197             end
198         end
199         end
200         end
201         vm:set_data(data)
202         vm:write_to_map()
203         lead_layer = steel_layer
204     end
205
206     if water_layer > 25 then water_layer = 25 end
207     if lead_layer > 96 then lead_layer = 96 end
208     if blast_layer > 216 then blast_layer = 216 end
209     return (25 - water_layer) + (96 - lead_layer) + (216 - blast_layer)
ee0765 210 end
S 211
85a984 212
S 213 local function melt_down_reactor(pos)
214     minetest.log("action", "A reactor melted down at "..minetest.pos_to_string(pos))
bf58c7 215     minetest.set_node(pos, {name = "technic:corium_source"})
D 216 end
217
218
219 local function start_reactor(pos, meta)
220     if minetest.get_node(pos).name ~= "technic:hv_nuclear_reactor_core" then
221         return false
222     end
223     local inv = meta:get_inventory()
224     if inv:is_empty("src") then
225         return false
226     end
227     local src_list = inv:get_list("src")
228     local correct_fuel_count = 0
229     for _, src_stack in pairs(src_list) do
230         if src_stack and src_stack:get_name() == fuel_type then
231             correct_fuel_count = correct_fuel_count + 1
232         end
233     end
234     -- Check that the reactor is complete and has the correct fuel
235     if correct_fuel_count ~= 6 or reactor_structure_badness(pos) ~= 0 then
236         return false
237     end
238     meta:set_int("burn_time", 1)
239     technic.swap_node(pos, "technic:hv_nuclear_reactor_core_active")
240     meta:set_int("HV_EU_supply", power_supply)
241     for idx, src_stack in pairs(src_list) do
242         src_stack:take_item()
243         inv:set_stack("src", idx, src_stack)
244     end
245     return true
ee0765 246 end
67b90f 247
85a984 248
67b90f 249 minetest.register_abm({
78f16c 250     label = "Machines: reactor melt-down check",
67b90f 251     nodenames = {"technic:hv_nuclear_reactor_core_active"},
85a984 252     interval = 4,
67b90f 253     chance = 1,
Z 254     action = function (pos, node)
255         local meta = minetest.get_meta(pos)
256         local badness = reactor_structure_badness(pos)
257         local accum_badness = meta:get_int("structure_accumulated_badness")
258         if badness == 0 then
259             if accum_badness ~= 0 then
e05680 260                 meta:set_int("structure_accumulated_badness", math.max(accum_badness - 4, 0))
d59055 261                 siren_clear(pos, meta)
67b90f 262             end
Z 263         else
d59055 264             siren_danger(pos, meta)
67b90f 265             accum_badness = accum_badness + badness
85a984 266             if accum_badness >= 25 then
S 267                 melt_down_reactor(pos)
67b90f 268             else
Z 269                 meta:set_int("structure_accumulated_badness", accum_badness)
270             end
271         end
272     end,
273 })
ee0765 274
85a984 275 local function run(pos, node)
563a4c 276     local meta = minetest.get_meta(pos)
N 277     local burn_time = meta:get_int("burn_time") or 0
278     if burn_time >= burn_ticks or burn_time == 0 then
bf58c7 279         if digiline_remote_path and meta:get_int("HV_EU_supply") == power_supply then
D 280             digiline_remote.send_to_node(pos, meta:get_string("remote_channel"),
281                     "fuel used", 6, true)
282         end
283         if meta:get_string("autostart") == "true" then
284             if start_reactor(pos, meta) then
563a4c 285                 return
ee0765 286             end
S 287         end
563a4c 288         meta:set_int("HV_EU_supply", 0)
N 289         meta:set_int("burn_time", 0)
85a984 290         meta:set_string("infotext", S("%s Idle"):format(reactor_desc))
563a4c 291         technic.swap_node(pos, "technic:hv_nuclear_reactor_core")
67b90f 292         meta:set_int("structure_accumulated_badness", 0)
d59055 293         siren_clear(pos, meta)
563a4c 294     elseif burn_time > 0 then
N 295         burn_time = burn_time + 1
296         meta:set_int("burn_time", burn_time)
297         local percent = math.floor(burn_time / burn_ticks * 100)
85a984 298         meta:set_string("infotext", reactor_desc.." ("..percent.."%)")
563a4c 299         meta:set_int("HV_EU_supply", power_supply)
ee0765 300     end
563a4c 301 end
N 302
bf58c7 303 local nuclear_reactor_receive_fields = function(pos, formname, fields, sender)
D 304     local player_name = sender:get_player_name()
305     if minetest.is_protected(pos, player_name) then
306         minetest.chat_send_player(player_name, "You are not allowed to edit this!")
307         minetest.record_protection_violation(pos, player_name)
308         return
309     end
310     local meta = minetest.get_meta(pos)
311     local update_formspec = false
312     if fields.remote_channel then
313         meta:set_string("remote_channel", fields.remote_channel)
314     end
315     if fields.start then
316         local b = start_reactor(pos, meta)
317         if b then
318             minetest.chat_send_player(player_name, "Start successful")
319         else
320             minetest.chat_send_player(player_name, "Error")
321         end
322     end
323     if fields.autostart then
324         meta:set_string("autostart", fields.autostart)
325         update_formspec = true
326     end
327     if fields.enable_digiline then
328         meta:set_string("enable_digiline", fields.enable_digiline)
329         update_formspec = true
330     end
331     if update_formspec then
332         meta:set_string("formspec", make_reactor_formspec(meta))
333     end
334 end
335
336 local digiline_remote_def = function(pos, channel, msg)
337     local meta = minetest.get_meta(pos)
338     if meta:get_string("enable_digiline") ~= "true" or
339             channel ~= meta:get_string("remote_channel") then
340         return
341     end
342     -- Convert string messages to tables:
343     local msgt = type(msg)
344     if msgt == "string" then
345         local smsg = msg:lower()
346         msg = {}
347         if smsg == "get" then
348             msg.command = "get"
349         elseif smsg:sub(1, 13) == "self_destruct" then
350             msg.command = "self_destruct"
351             msg.timer = tonumber(smsg:sub(15)) or 0
352         elseif smsg == "start" then
353             msg.command = "start"
354         end
355     elseif msgt ~= "table" then
356         return
357     end
358
359     if msg.command == "get" then
360         local inv = meta:get_inventory()
361         local invtable = {}
362         for i = 1, 6 do
363             local stack = inv:get_stack("src", i)
364             if stack:is_empty() then
365                 invtable[i] = 0
366             elseif stack:get_name() == fuel_type then
367                 invtable[i] = stack:get_count()
368             else
369                 invtable[i] = -stack:get_count()
370             end
371         end
372         digiline_remote.send_to_node(pos, channel, {
373             burn_time = meta:get_int("burn_time"),
374             enabled   = meta:get_int("HV_EU_supply") == power_supply,
375             siren     = meta:get_int("siren") == 1,
376             structure_accumulated_badness = meta:get_int("structure_accumulated_badness"),
377             rods = invtable
378         }, 6, true)
379     elseif digiline_meltdown and msg.command == "self_destruct" and
380             minetest.get_node(pos).name == "technic:hv_nuclear_reactor_core_active" then
381         if msg.timer ~= 0 and type(msg.timer) == "number" then
382             siren_danger(pos, meta)
383             minetest.after(msg.timer, melt_down_reactor, pos)
384         else
385             melt_down_reactor(pos)
386         end
387     elseif msg.command == "start" then
388         local b = start_reactor(pos, meta)
389         if b then
390             digiline_remote.send_to_node(pos, channel, "Start successful", 6, true)
391         else
392             digiline_remote.send_to_node(pos, channel, "Error", 6, true)
393         end
394     end
395 end
396
563a4c 397 minetest.register_node("technic:hv_nuclear_reactor_core", {
85a984 398     description = reactor_desc,
1d20af 399     tiles = {
VE 400         "technic_hv_nuclear_reactor_core.png",
401         "technic_hv_nuclear_reactor_core.png"..cable_entry
402     },
403     drawtype = "mesh",
404     mesh = "technic_reactor.obj",
bf58c7 405     groups = {cracky = 1, technic_machine = 1, technic_hv = 1, digiline_remote_receive = 1},
563a4c 406     legacy_facedir_simple = true,
N 407     sounds = default.node_sound_wood_defaults(),
408     paramtype = "light",
1d20af 409     paramtype2 = "facedir",
563a4c 410     stack_max = 1,
bf58c7 411     on_receive_fields = nuclear_reactor_receive_fields,
563a4c 412     on_construct = function(pos)
N 413         local meta = minetest.get_meta(pos)
85a984 414         meta:set_string("infotext", reactor_desc)
bf58c7 415         meta:set_string("formspec", make_reactor_formspec(meta))
D 416         if digiline_remote_path then
417             meta:set_string("remote_channel",
418                     "nucelear_reactor"..minetest.pos_to_string(pos))
419         end
563a4c 420         local inv = meta:get_inventory()
N 421         inv:set_size("src", 6)
85a984 422     end,
bf58c7 423     _on_digiline_remote_receive = digiline_remote_def,
563a4c 424     can_dig = technic.machine_can_dig,
85a984 425     on_destruct = function(pos) siren_set_state(pos, SS_OFF) end,
563a4c 426     allow_metadata_inventory_put = technic.machine_inventory_put,
N 427     allow_metadata_inventory_take = technic.machine_inventory_take,
428     allow_metadata_inventory_move = technic.machine_inventory_move,
429     technic_run = run,
430 })
431
432 minetest.register_node("technic:hv_nuclear_reactor_core_active", {
1d20af 433     tiles = {
VE 434         "technic_hv_nuclear_reactor_core.png",
435         "technic_hv_nuclear_reactor_core.png"..cable_entry
436     },
437     drawtype = "mesh",
438     mesh = "technic_reactor.obj",
bf58c7 439     groups = {cracky = 1, technic_machine = 1, technic_hv = 1, radioactive = 4,
D 440         not_in_creative_inventory = 1, digiline_remote_receive = 1},
563a4c 441     legacy_facedir_simple = true,
N 442     sounds = default.node_sound_wood_defaults(),
85a984 443     drop = "technic:hv_nuclear_reactor_core",
S 444     light_source = 14,
563a4c 445     paramtype = "light",
1d20af 446     paramtype2 = "facedir",
bf58c7 447     on_receive_fields = nuclear_reactor_receive_fields,
D 448     _on_digiline_remote_receive = digiline_remote_def,
563a4c 449     can_dig = technic.machine_can_dig,
85a984 450     after_dig_node = melt_down_reactor,
S 451     on_destruct = function(pos) siren_set_state(pos, SS_OFF) end,
563a4c 452     allow_metadata_inventory_put = technic.machine_inventory_put,
N 453     allow_metadata_inventory_take = technic.machine_inventory_take,
454     allow_metadata_inventory_move = technic.machine_inventory_move,
455     technic_run = run,
1c617f 456     technic_on_disable = function(pos, node)
N 457         local timer = minetest.get_node_timer(pos)
458             timer:start(1)
459         end,
460     on_timer = function(pos, node)
461         local meta = minetest.get_meta(pos)
85a984 462
1c617f 463         -- Connected back?
3252da 464         if meta:get_int("HV_EU_timeout") > 0 then return false end
85a984 465
1c617f 466         local burn_time = meta:get_int("burn_time") or 0
N 467
468         if burn_time >= burn_ticks or burn_time == 0 then
469             meta:set_int("HV_EU_supply", 0)
470             meta:set_int("burn_time", 0)
471             technic.swap_node(pos, "technic:hv_nuclear_reactor_core")
67b90f 472             meta:set_int("structure_accumulated_badness", 0)
d59055 473             siren_clear(pos, meta)
3252da 474             return false
1c617f 475         end
85a984 476
1c617f 477         meta:set_int("burn_time", burn_time + 1)
3252da 478         return true
1c617f 479     end,
ee0765 480 })
S 481
482 technic.register_machine("HV", "technic:hv_nuclear_reactor_core",        technic.producer)
483 technic.register_machine("HV", "technic:hv_nuclear_reactor_core_active", technic.producer)
484