cx384
2024-01-22 a08ba2bb93d7683b619a0e6b0bf00e3afd614ae4
technic/machines/HV/nuclear_reactor.lua
@@ -12,6 +12,8 @@
local burn_ticks = 7 * 24 * 60 * 60  -- Seconds
local power_supply = 100000  -- EUs
local fuel_type = "technic:uranium_fuel"  -- The reactor burns this
local digiline_meltdown = technic.config:get_bool("enable_nuclear_reactor_digiline_selfdestruct")
local digiline_remote_path = minetest.get_modpath("digiline_remote")
local S = technic.getter
@@ -28,12 +30,28 @@
   }
})
local reactor_formspec =
   "invsize[8,9;]"..
   "label[0,0;"..S("Nuclear Reactor Rod Compartment").."]"..
   "list[current_name;src;2,1;3,2;]"..
   "list[current_player;main;0,5;8,4;]"..
   "listring[]"
local function make_reactor_formspec(meta)
   local f =
      "formspec_version[4]"..
      "size[10.75,10.75]"..
      "label[0.2,0.4;"..S("Nuclear Reactor Rod Compartment").."]"..
      "list[current_name;src;1.5,1;3,2;]"..
      "list[current_player;main;0.5,5.5;8,4;]"..
      "listring[]"..
      "button[5.7,1;2,1;start;Start]"..
      "checkbox[5.7,2.75;autostart;automatic Start;"..meta:get_string("autostart").."]"
   if not digiline_remote_path then
      return f
   end
   local digiline_enabled = meta:get_string("enable_digiline")
   f = f.."checkbox[1.5,3.75;enable_digiline;Enable Digiline channel;"..digiline_enabled.."]"
   if digiline_enabled ~= "true" then
      return f
   end
   return f..
      "field[2,4.2;4.25,1;remote_channel;;${remote_channel}]" ..
      "button_exit[6.5,4.2;2,1;save;Save]"
end
local SS_OFF = 0
local SS_DANGER = 1
@@ -124,8 +142,11 @@
--]]
local function reactor_structure_badness(pos)
   local vm = VoxelManip()
   -- Blast-resistant Concrete Block layer outer positions
   local pos1 = vector.subtract(pos, 3)
   local pos2 = vector.add(pos, 3)
   local MinEdge, MaxEdge = vm:read_from_map(pos1, pos2)
   local data = vm:get_data()
   local area = VoxelArea:new({MinEdge=MinEdge, MaxEdge=MaxEdge})
@@ -141,16 +162,19 @@
   for z = pos1.z, pos2.z do
   for y = pos1.y, pos2.y do
   for x = pos1.x, pos2.x do
      -- In the entire volume, make sure there is:
      local cid = data[area:index(x, y, z)]
      if x == pos1.x or x == pos2.x or
         y == pos1.y or y == pos2.y or
         z == pos1.z or z == pos2.z then
         -- r=3 : Blast-resistant Concrete Block shell
         if cid == c_blast_concrete then
            blast_layer = blast_layer + 1
         end
      elseif x == pos1.x+1 or x == pos2.x-1 or
             y == pos1.y+1 or y == pos2.y-1 or
             z == pos1.z+1 or z == pos2.z-1 then
         -- r=2 : Lead Block shell
         if cid == c_lead then
            lead_layer = lead_layer + 1
         elseif cid == c_steel then
@@ -159,6 +183,7 @@
      elseif x == pos1.x+2 or x == pos2.x-2 or
             y == pos1.y+2 or y == pos2.y-2 or
             z == pos1.z+2 or z == pos2.z-2 then
         -- r=1 : Water cooling
         if cid == c_water_source or cid == c_water_flowing then
            water_layer = water_layer + 1
         end
@@ -168,6 +193,8 @@
   end
   if steel_layer >= 96 then
      -- Legacy: convert stainless steel to lead
      -- Why don't we accept both without conversion?
      for z = pos1.z+1, pos2.z-1 do
      for y = pos1.y+1, pos2.y-1 do
      for x = pos1.x+1, pos2.x-1 do
@@ -190,13 +217,54 @@
   if water_layer > 25 then water_layer = 25 end
   if lead_layer > 96 then lead_layer = 96 end
   if blast_layer > 216 then blast_layer = 216 end
   -- Amount of missing blocks
   return (25 - water_layer) + (96 - lead_layer) + (216 - blast_layer)
end
local function melt_down_reactor(pos)
   minetest.log("action", "A reactor melted down at "..minetest.pos_to_string(pos))
   minetest.set_node(pos, {name="technic:corium_source"})
   minetest.set_node(pos, {name = "technic:corium_source"})
end
local function start_reactor(pos, meta)
   local correct_fuel_count = 6
   local msg_fuel_missing = "Error: You need to insert " .. correct_fuel_count .. " pieces of Uranium Fuel."
   if minetest.get_node(pos).name ~= "technic:hv_nuclear_reactor_core" then
      return msg_fuel_missing
   end
   local inv = meta:get_inventory()
   if inv:is_empty("src") then
      return msg_fuel_missing
   end
   local src_list = inv:get_list("src")
   local fuel_count = 0
   for _, src_stack in pairs(src_list) do
      if src_stack and src_stack:get_name() == fuel_type then
         fuel_count = fuel_count + 1
      end
   end
   -- Check that the has the correct fuel
   if fuel_count ~= correct_fuel_count then
      return msg_fuel_missing
   end
   -- Check that the reactor is complete
   if reactor_structure_badness(pos) ~= 0 then
      return "Error: The power plant seems to be built incorrectly."
   end
   meta:set_int("burn_time", 1)
   technic.swap_node(pos, "technic:hv_nuclear_reactor_core_active")
   meta:set_int("HV_EU_supply", power_supply)
   for idx, src_stack in pairs(src_list) do
      src_stack:take_item()
      inv:set_stack("src", idx, src_stack)
   end
   return nil
end
@@ -211,7 +279,7 @@
      local accum_badness = meta:get_int("structure_accumulated_badness")
      if badness == 0 then
         if accum_badness ~= 0 then
            meta:set_int("structure_accumulated_badness", accum_badness - 4)
            meta:set_int("structure_accumulated_badness", math.max(accum_badness - 4, 0))
            siren_clear(pos, meta)
         end
      else
@@ -229,33 +297,19 @@
local function run(pos, node)
   local meta = minetest.get_meta(pos)
   local burn_time = meta:get_int("burn_time") or 0
   if burn_time >= burn_ticks or burn_time == 0 then
      local inv = meta:get_inventory()
      if not inv:is_empty("src") then
         local src_list = inv:get_list("src")
         local correct_fuel_count = 0
         for _, src_stack in pairs(src_list) do
            if src_stack and src_stack:get_name() == fuel_type then
               correct_fuel_count = correct_fuel_count + 1
            end
         end
         -- Check that the reactor is complete and has the correct fuel
         if correct_fuel_count == 6 and
               reactor_structure_badness(pos) == 0 then
            meta:set_int("burn_time", 1)
            technic.swap_node(pos, "technic:hv_nuclear_reactor_core_active")
            meta:set_int("HV_EU_supply", power_supply)
            for idx, src_stack in pairs(src_list) do
               src_stack:take_item()
               inv:set_stack("src", idx, src_stack)
            end
      if digiline_remote_path and meta:get_int("HV_EU_supply") == power_supply then
         digiline_remote.send_to_node(pos, meta:get_string("remote_channel"),
               "fuel used", 6, true)
      end
      if meta:get_string("autostart") == "true" then
         if not start_reactor(pos, meta) then
            return
         end
      end
      meta:set_int("HV_EU_supply", 0)
      meta:set_int("burn_time", 0)
      meta:set_string("infotext", S("%s Idle"):format(reactor_desc))
      meta:set_string("infotext", S("@1 Idle", reactor_desc))
      technic.swap_node(pos, "technic:hv_nuclear_reactor_core")
      meta:set_int("structure_accumulated_badness", 0)
      siren_clear(pos, meta)
@@ -268,6 +322,100 @@
   end
end
local nuclear_reactor_receive_fields = function(pos, formname, fields, sender)
   local player_name = sender:get_player_name()
   if minetest.is_protected(pos, player_name) then
      minetest.chat_send_player(player_name, "You are not allowed to edit this!")
      minetest.record_protection_violation(pos, player_name)
      return
   end
   local meta = minetest.get_meta(pos)
   local update_formspec = false
   if fields.remote_channel then
      meta:set_string("remote_channel", fields.remote_channel)
   end
   if fields.start then
      local start_error_msg = start_reactor(pos, meta)
      if not start_error_msg then
         minetest.chat_send_player(player_name, "Start successful")
      else
         minetest.chat_send_player(player_name, start_error_msg)
      end
   end
   if fields.autostart then
      meta:set_string("autostart", fields.autostart)
      update_formspec = true
   end
   if fields.enable_digiline then
      meta:set_string("enable_digiline", fields.enable_digiline)
      update_formspec = true
   end
   if update_formspec then
      meta:set_string("formspec", make_reactor_formspec(meta))
   end
end
local digiline_remote_def = function(pos, channel, msg)
   local meta = minetest.get_meta(pos)
   if meta:get_string("enable_digiline") ~= "true" or
         channel ~= meta:get_string("remote_channel") then
      return
   end
   -- Convert string messages to tables:
   local msgt = type(msg)
   if msgt == "string" then
      local smsg = msg:lower()
      msg = {}
      if smsg == "get" then
         msg.command = "get"
      elseif smsg:sub(1, 13) == "self_destruct" then
         msg.command = "self_destruct"
         msg.timer = tonumber(smsg:sub(15)) or 0
      elseif smsg == "start" then
         msg.command = "start"
      end
   elseif msgt ~= "table" then
      return
   end
   if msg.command == "get" then
      local inv = meta:get_inventory()
      local invtable = {}
      for i = 1, 6 do
         local stack = inv:get_stack("src", i)
         if stack:is_empty() then
            invtable[i] = 0
         elseif stack:get_name() == fuel_type then
            invtable[i] = stack:get_count()
         else
            invtable[i] = -stack:get_count()
         end
      end
      digiline_remote.send_to_node(pos, channel, {
         burn_time = meta:get_int("burn_time"),
         enabled   = meta:get_int("HV_EU_supply") == power_supply,
         siren     = meta:get_int("siren") == 1,
         structure_accumulated_badness = meta:get_int("structure_accumulated_badness"),
         rods = invtable
      }, 6, true)
   elseif digiline_meltdown and msg.command == "self_destruct" and
         minetest.get_node(pos).name == "technic:hv_nuclear_reactor_core_active" then
      if msg.timer ~= 0 and type(msg.timer) == "number" then
         siren_danger(pos, meta)
         minetest.after(msg.timer, melt_down_reactor, pos)
      else
         melt_down_reactor(pos)
      end
   elseif msg.command == "start" then
      local start_error_msg = start_reactor(pos, meta)
      if not start_error_msg then
         digiline_remote.send_to_node(pos, channel, "Start successful", 6, true)
      else
         digiline_remote.send_to_node(pos, channel, start_error_msg, 6, true)
      end
   end
end
minetest.register_node("technic:hv_nuclear_reactor_core", {
   description = reactor_desc,
   tiles = {
@@ -276,19 +424,25 @@
   },
   drawtype = "mesh",
   mesh = "technic_reactor.obj",
   groups = {cracky=1, technic_machine=1, technic_hv=1},
   groups = {cracky = 1, technic_machine = 1, technic_hv = 1, digiline_remote_receive = 1},
   legacy_facedir_simple = true,
   sounds = default.node_sound_wood_defaults(),
   paramtype = "light",
   paramtype2 = "facedir",
   stack_max = 1,
   on_receive_fields = nuclear_reactor_receive_fields,
   on_construct = function(pos)
      local meta = minetest.get_meta(pos)
      meta:set_string("infotext", reactor_desc)
      meta:set_string("formspec", reactor_formspec)
      meta:set_string("formspec", make_reactor_formspec(meta))
      if digiline_remote_path then
         meta:set_string("remote_channel",
               "nucelear_reactor"..minetest.pos_to_string(pos))
      end
      local inv = meta:get_inventory()
      inv:set_size("src", 6)
   end,
   _on_digiline_remote_receive = digiline_remote_def,
   can_dig = technic.machine_can_dig,
   on_destruct = function(pos) siren_set_state(pos, SS_OFF) end,
   allow_metadata_inventory_put = technic.machine_inventory_put,
@@ -304,14 +458,16 @@
   },
   drawtype = "mesh",
   mesh = "technic_reactor.obj",
   groups = {cracky=1, technic_machine=1, technic_hv=1,
      radioactive=4, not_in_creative_inventory=1},
   groups = {cracky = 1, technic_machine = 1, technic_hv = 1, radioactive = 4,
      not_in_creative_inventory = 1, digiline_remote_receive = 1},
   legacy_facedir_simple = true,
   sounds = default.node_sound_wood_defaults(),
   drop = "technic:hv_nuclear_reactor_core",
   light_source = 14,
   paramtype = "light",
   paramtype2 = "facedir",
   on_receive_fields = nuclear_reactor_receive_fields,
   _on_digiline_remote_receive = digiline_remote_def,
   can_dig = technic.machine_can_dig,
   after_dig_node = melt_down_reactor,
   on_destruct = function(pos) siren_set_state(pos, SS_OFF) end,
@@ -321,7 +477,7 @@
   technic_run = run,
   technic_on_disable = function(pos, node)
      local timer = minetest.get_node_timer(pos)
           timer:start(1)
      timer:start(1)
        end,
   on_timer = function(pos, node)
      local meta = minetest.get_meta(pos)