est31
2015-06-18 a793747d92d9b1d93153c7fb4e0c82fe90624c78
technic/machines/HV/nuclear_reactor.lua
@@ -32,7 +32,8 @@
   "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;]"
   "list[current_player;main;0,5;8,4;]"..
   "listring[]"
-- "Boxy sphere"
local nodebox = {
@@ -97,64 +98,74 @@
   end
end
-- The standard reactor structure consists of a 9x9x9 cube.  A cross
-- section through the middle:
--
--     CCCC CCCC
--     CBBB BBBC
--     CBSS SSBC
--     CBSWWWSBC
--     CBSW#WSBC
--     CBSW|WSBC
--     CBSS|SSBC
--     CBBB|BBBC
--     CCCC|CCCC
--     C = Concrete, B = Blast-resistant concrete, S = Stainless Steel,
--     W = water node, # = reactor core, | = HV cable
--
-- The man-hole and the HV cable are only in the middle, and the man-hole
-- is optional.
--
-- For the reactor to operate and not melt down, it insists on the inner
-- 7x7x7 portion (from the core out to the blast-resistant concrete)
-- being intact.  Intactness only depends on the number of nodes of the
-- right type in each layer.  The water layer must have water in all but
-- at most one node; the steel and blast-resistant concrete layers must
-- have the right material in all but at most two nodes.  The permitted
-- gaps are meant for the cable and man-hole, but can actually be anywhere
-- and contain anything.  For the reactor to be useful, a cable must
-- connect to the core, but it can go in any direction.
--
-- The outer concrete layer of the standard structure is not required
-- for the reactor to operate.  It is noted here because it used to
-- be mandatory, and for historical reasons (that it predates the
-- implementation of radiation) it needs to continue being adequate
-- shielding of legacy reactors.  If it ever ceases to be adequate
-- shielding for new reactors, legacy ones should be grandfathered.
local reactor_structure_badness = function(pos)
   -- The reactor consists of a 9x9x9 cube structure
   -- A cross section through the middle:
   --  CCCC CCCC
   --  CBBB BBBC
   --  CBSS SSBC
   --  CBSWWWSBC
   --  CBSW#WSBC
   --  CBSW|WSBC
   --  CBSS|SSBC
   --  CBBB|BBBC
   --  CCCC|CCCC
   --  C = Concrete, B = Blast resistant concrete, S = Stainless Steel,
   --  W = water node, # = reactor core, | = HV cable
   --  The man-hole and the HV cable is only in the middle
   --  The man-hole is optional
   local vm = VoxelManip()
   local pos1 = vector.subtract(pos, 4)
   local pos2 = vector.add(pos, 4)
   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})
   local c_concrete = minetest.get_content_id("technic:concrete")
   local c_blast_concrete = minetest.get_content_id("technic:blast_resistant_concrete")
   local c_stainless_steel = minetest.get_content_id("technic:stainless_steel_block")
   local c_water_source = minetest.get_content_id("default:water_source")
   local c_water_flowing = minetest.get_content_id("default:water_flowing")
   local concretelayer, blastlayer, steellayer, waterlayer = 0, 0, 0, 0
   local blastlayer, steellayer, waterlayer = 0, 0, 0
   for z = pos1.z, pos2.z do
   for y = pos1.y, pos2.y do
   for x = pos1.x, pos2.x do
      -- If the position is in the outer layer
      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
         if data[area:index(x, y, z)] == c_concrete then
            concretelayer = concretelayer + 1
         if cid == c_blast_concrete then
            blastlayer = blastlayer + 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
         if data[area:index(x, y, z)] == c_blast_concrete then
            blastlayer = blastlayer + 1
         if cid == c_stainless_steel then
            steellayer = steellayer + 1
         end
      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
         if data[area:index(x, y, z)] == c_stainless_steel then
            steellayer = steellayer + 1
         end
      elseif x == pos1.x+3 or x == pos2.x-3 or
         y == pos1.y+3 or y == pos2.y-3 or
         z == pos1.z+3 or z == pos2.z-3 then
            local cid = data[area:index(x, y, z)]
         if cid == c_water_source or cid == c_water_flowing then
            waterlayer = waterlayer + 1
         end
@@ -165,8 +176,7 @@
   if waterlayer > 25 then waterlayer = 25 end
   if steellayer > 96 then steellayer = 96 end
   if blastlayer > 216 then blastlayer = 216 end
   if concretelayer > 384 then concretelayer = 384 end
   return (25 - waterlayer) + (96 - steellayer) + (216 - blastlayer) + (384 - concretelayer)
   return (25 - waterlayer) + (96 - steellayer) + (216 - blastlayer)
end
local function meltdown_reactor(pos)
@@ -250,7 +260,7 @@
   tiles = {"technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png",
            "technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png",
            "technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png"},
   groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_machine=1},
   groups = {cracky=1, technic_machine=1},
   legacy_facedir_simple = true,
   sounds = default.node_sound_wood_defaults(),
   drawtype="nodebox",
@@ -284,7 +294,7 @@
   tiles = {"technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png",
            "technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png",
       "technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png"},
   groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_machine=1, radioactive=7, not_in_creative_inventory=1},
   groups = {cracky=1, technic_machine=1, radioactive=11000, not_in_creative_inventory=1},
   legacy_facedir_simple = true,
   sounds = default.node_sound_wood_defaults(),
   drop="technic:hv_nuclear_reactor_core",
@@ -310,7 +320,7 @@
      local meta = minetest.get_meta(pos)
      
      -- Connected back?
      if meta:get_int("HV_EU_timeout") > 0 then return end
      if meta:get_int("HV_EU_timeout") > 0 then return false end
      
      local burn_time = meta:get_int("burn_time") or 0
@@ -320,12 +330,11 @@
         technic.swap_node(pos, "technic:hv_nuclear_reactor_core")
         meta:set_int("structure_accumulated_badness", 0)
         siren_clear(pos, meta)
         return
         return false
      end
      
      meta:set_int("burn_time", burn_time + 1)
      local timer = minetest.get_node_timer(pos)
           timer:start(1)
      return true
   end,
})
@@ -484,7 +493,6 @@
   ["technic:mineral_uranium"] = 71,
   ["technic:mineral_zinc"] = 19,
   ["technic:stainless_steel_block"] = 40,
   ["technic:uranium_block"] = 500,
   ["technic:zinc_block"] = 36,
   ["tnt:tnt"] = 11,
   ["tnt:tnt_burning"] = 11,
@@ -492,6 +500,7 @@
local default_radiation_resistance_per_group = {
   concrete = 16,
   tree = 3.4,
   uranium_block = 500,
   wood = 1.7,
}
local cache_radiation_resistance = {}
@@ -532,21 +541,25 @@
--
-- A radioactive node is identified by being in the "radioactive" group,
-- and the group value signifies the strength of the radiation source.
-- The group value is the distance in metres from a node at which an
-- unshielded player will be damaged by 0.25 HP/s.  Or, equivalently, it
-- is half the square root of the damage rate in HP/s that an unshielded
-- player 1 m away will take.
-- The group value is the distance in millimetres from a node at which
-- an unshielded player will be damaged by 0.25 HP/s.  Or, equivalently,
-- it is 2000 times the square root of the damage rate in HP/s that an
-- unshielded player 1 m away will take.
--
-- Shielding is assessed by sampling every 0.25 m along the path
-- from the source to the player, ignoring the source node itself.
-- The summed radiation resistance values from the sampled nodes yield
-- a measure of the total amount of radiation resistance on the path.
-- As in reality, shielding causes exponential attenuation of radiation.
-- However, the effect is scaled down relative to real life: each
-- metre-point of shielding, corresponding to a real-life halving of
-- radiation, reduces radiation by 0.01 nepers (a factor of about 1.01).
-- This scales down the difference between shielded and unshielded safe
-- distances, avoiding the latter becoming impractically large.
-- The summed shielding values from the sampled nodes yield a measure
-- of the total amount of shielding on the path.  As in reality,
-- shielding causes exponential attenuation of radiation.  However, the
-- effect is scaled down relative to real life.  A metre of a node with
-- radiation resistance value R yields attenuation of sqrt(R)*0.1 nepers.
-- (In real life it would be about R*0.69 nepers, by the definition
-- of the radiation resistance values.)  The sqrt part of this formula
-- scales down the differences between shielding types, reflecting the
-- game's simplification of making expensive materials such as gold
-- readily available in cubic metres.  The multiplicative factor in the
-- formula scales down the difference between shielded and unshielded
-- safe distances, avoiding the latter becoming impractically large.
--
-- Damage is processed at rates down to 0.25 HP/s, which in the absence of
-- shielding is attained at the distance specified by the "radioactive"
@@ -556,41 +569,53 @@
-- need to be considered.
local assumed_abdomen_offset = vector.new(0, 1, 0)
local assumed_abdomen_offset_length = vector.length(assumed_abdomen_offset)
minetest.register_abm({
   nodenames = {"group:radioactive"},
   interval = 1,
   chance = 1,
   action = function (pos, node)
      local strength = minetest.registered_nodes[node.name].groups.radioactive
      for _, o in ipairs(minetest.get_objects_inside_radius(pos, strength + assumed_abdomen_offset_length)) do
         if o:is_player() then
            local rel = vector.subtract(vector.add(o:getpos(), assumed_abdomen_offset), pos)
            local dist_sq = vector.length_square(rel)
            local dist = math.sqrt(dist_sq)
            local dirstep = dist == 0 and vector.new(0,0,0) or vector.divide(rel, dist*4)
            local intpos = pos
            local resistance = 0
            for intdist = 0.25, dist, 0.25 do
               intpos = vector.add(intpos, dirstep)
               local intnodepos = vector.round(intpos)
               if not vector.equals(intnodepos, pos) then
                  resistance = resistance + node_radiation_resistance(minetest.get_node(intnodepos).name)
local cache_scaled_shielding = {}
local damage_enabled = minetest.setting_getbool("enable_damage")
if damage_enabled then
   minetest.register_abm({
      nodenames = {"group:radioactive"},
      interval = 1,
      chance = 1,
      action = function (pos, node)
         local strength = minetest.registered_nodes[node.name].groups.radioactive
         for _, o in ipairs(minetest.get_objects_inside_radius(pos, strength*0.001 + assumed_abdomen_offset_length)) do
            if o:is_player() then
               local rel = vector.subtract(vector.add(o:getpos(), assumed_abdomen_offset), pos)
               local dist_sq = vector.length_square(rel)
               local dist = math.sqrt(dist_sq)
               local dirstep = dist == 0 and vector.new(0,0,0) or vector.divide(rel, dist*4)
               local intpos = pos
               local shielding = 0
               for intdist = 0.25, dist, 0.25 do
                  intpos = vector.add(intpos, dirstep)
                  local intnodepos = vector.round(intpos)
                  if not vector.equals(intnodepos, pos) then
                     local sname = minetest.get_node(intnodepos).name
                     local sval = cache_scaled_shielding[sname]
                     if not sval then
                        sval = math.sqrt(node_radiation_resistance(sname)) * -0.025
                        cache_scaled_shielding[sname] = sval
                     end
                     shielding = shielding + sval
                  end
               end
            end
            local dmg_rate = 0.25 * strength*strength * math.exp(-0.0025*resistance) / math.max(0.75, dist_sq)
            if dmg_rate >= 0.25 then
               local dmg_int = math.floor(dmg_rate)
               if math.random() < dmg_rate-dmg_int then
                  dmg_int = dmg_int + 1
               end
               if dmg_int > 0 then
                  o:set_hp(math.max(o:get_hp() - dmg_int, 0))
               local dmg_rate = 0.25e-6 * strength*strength * math.exp(shielding) / math.max(0.75, dist_sq)
               if dmg_rate >= 0.25 then
                  local dmg_int = math.floor(dmg_rate)
                  if math.random() < dmg_rate-dmg_int then
                     dmg_int = dmg_int + 1
                  end
                  if dmg_int > 0 then
                     o:set_hp(math.max(o:get_hp() - dmg_int, 0))
                  end
               end
            end
         end
      end
   end,
})
      end,
   })
end
-- radioactive materials that can result from destroying a reactor
@@ -627,7 +652,7 @@
         liquid = 2,
         hot = 3,
         igniter = 1,
         radioactive = (state == "source" and 32 or 16),
         radioactive = (state == "source" and 32000 or 16000),
         not_in_creative_inventory = (state == "flowing" and 1 or nil),
      },
   })
@@ -647,7 +672,7 @@
        description = S("Chernobylite Block"),
   tiles = { "technic_chernobylite_block.png" },
   is_ground_content = true,
   groups = { cracky=1, radioactive=5, level=2 },
   groups = { cracky=1, radioactive=5000, level=2 },
   sounds = default.node_sound_stone_defaults(),
   light_source = 2,