Material-dependent radiation shielding

Radiation is attenuated exponentially by passing through shielding
material.  Radiation resistance values are assigned to all bulk-material
nodes, and the radiation damage ABM traces the path of each radiation ray
to count up the shielding.  The relative radiation resistance values are
essentially real, but the effectiveness of all shielding is scaled down
by a factor of about 70 for game purposes.  Strength of the existing
radiation sources is increased by varying amounts to compensate for
shielding.  Uranium block and ore, both usable as shielding, are made
slightly radioactive, the latter only very slightly.
This commit is contained in:
Zefram 2014-07-25 03:26:43 +01:00
parent 17c5b66524
commit ec008d7045
2 changed files with 246 additions and 35 deletions

@ -12,12 +12,9 @@ local fuel_type = "technic:uranium_fuel" -- The reactor burns this stuff
local S = technic.getter local S = technic.getter
if not vector.distance_square then if not vector.length_square then
vector.distance_square = function (u, v) vector.length_square = function (v)
local dx = v.x - u.x return v.x*v.x + v.y*v.y + v.z*v.z
local dy = v.y - u.y
local dz = v.z - u.z
return dx*dx + dy*dy + dz*dz
end end
end end
@ -220,7 +217,7 @@ minetest.register_node("technic:hv_nuclear_reactor_core_active", {
tiles = {"technic_hv_nuclear_reactor_core.png", "technic_hv_nuclear_reactor_core.png", 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",
"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=3, not_in_creative_inventory=1}, groups = {snappy=2, choppy=2, oddly_breakable_by_hand=2, technic_machine=1, radioactive=7, not_in_creative_inventory=1},
legacy_facedir_simple = true, legacy_facedir_simple = true,
sounds = default.node_sound_wood_defaults(), sounds = default.node_sound_wood_defaults(),
drop="technic:hv_nuclear_reactor_core", drop="technic:hv_nuclear_reactor_core",
@ -264,40 +261,252 @@ minetest.register_node("technic:hv_nuclear_reactor_core_active", {
technic.register_machine("HV", "technic:hv_nuclear_reactor_core", technic.producer) technic.register_machine("HV", "technic:hv_nuclear_reactor_core", technic.producer)
technic.register_machine("HV", "technic:hv_nuclear_reactor_core_active", technic.producer) technic.register_machine("HV", "technic:hv_nuclear_reactor_core_active", technic.producer)
-- radioactive materials that can result from destroying a reactor -- radioactivity
-- Radiation resistance represents the extent to which a material
-- attenuates radiation passing through it; i.e., how good a radiation
-- shield it is. This is identified per node type. For materials that
-- exist in real life, the radiation resistance value that this system
-- uses for a node type consisting of a solid cube of that material is the
-- (approximate) number of halvings of ionising radiation that is achieved
-- by a metre of the material in real life. This is approximately
-- proportional to density, which provides a good way to estimate it.
-- Homogeneous mixtures of materials have radiation resistance computed
-- by a simple weighted mean. Note that the amount of attenuation that
-- a material achieves in-game is not required to be (and is not) the
-- same as the attenuation achieved in real life.
--
-- Radiation resistance for a node type may be specified in the node
-- definition, under the key "radiation_resistance". As an interim
-- measure, until node definitions widely include this, this code
-- knows a bunch of values for particular node types in several mods,
-- and values for groups of node types. The node definition takes
-- precedence if it specifies a value. Nodes for which no value at
-- all is known are taken to provide no radiation resistance at all;
-- this is appropriate for the majority of node types. Only node types
-- consisting of a fairly homogeneous mass of material should report
-- non-zero radiation resistance; anything with non-uniform geometry
-- or complex internal structure should show no radiation resistance.
-- Fractional resistance values are permitted; two significant figures
-- is the recommended precision.
local default_radiation_resistance_per_node = {
["default:brick"] = 13,
["default:bronzeblock"] = 45,
["default:clay"] = 15,
["default:coalblock"] = 9.6,
["default:cobble"] = 15,
["default:copperblock"] = 46,
["default:desert_cobble"] = 15,
["default:desert_sand"] = 10,
["default:desert_stone"] = 17,
["default:desert_stonebrick"] = 17,
["default:diamondblock"] = 24,
["default:dirt"] = 8.2,
["default:dirt_with_grass"] = 8.2,
["default:dirt_with_grass_footsteps"] = 8.2,
["default:dirt_with_snow"] = 8.2,
["default:glass"] = 17,
["default:goldblock"] = 170,
["default:gravel"] = 10,
["default:ice"] = 5.6,
["default:lava_flowing"] = 8.5,
["default:lava_source"] = 17,
["default:mese"] = 21,
["default:mossycobble"] = 15,
["default:nyancat"] = 1000,
["default:nyancat_rainbow"] = 1000,
["default:obsidian"] = 18,
["default:obsidian_glass"] = 18,
["default:sand"] = 10,
["default:sandstone"] = 15,
["default:sandstonebrick"] = 15,
["default:snowblock"] = 1.7,
["default:steelblock"] = 40,
["default:stone"] = 17,
["default:stone_with_coal"] = 16,
["default:stone_with_copper"] = 20,
["default:stone_with_diamond"] = 18,
["default:stone_with_gold"] = 34,
["default:stone_with_iron"] = 20,
["default:stone_with_mese"] = 17,
["default:stonebrick"] = 17,
["default:water_flowing"] = 2.8,
["default:water_source"] = 5.6,
["farming:desert_sand_soil"] = 10,
["farming:desert_sand_soil_wet"] = 10,
["farming:soil"] = 8.2,
["farming:soil_wet"] = 8.2,
["glooptest:akalin_crystal_glass"] = 21,
["glooptest:akalinblock"] = 40,
["glooptest:alatro_crystal_glass"] = 21,
["glooptest:alatroblock"] = 40,
["glooptest:amethystblock"] = 18,
["glooptest:arol_crystal_glass"] = 21,
["glooptest:crystal_glass"] = 21,
["glooptest:emeraldblock"] = 19,
["glooptest:heavy_crystal_glass"] = 21,
["glooptest:mineral_akalin"] = 20,
["glooptest:mineral_alatro"] = 20,
["glooptest:mineral_amethyst"] = 17,
["glooptest:mineral_arol"] = 20,
["glooptest:mineral_desert_coal"] = 16,
["glooptest:mineral_desert_iron"] = 20,
["glooptest:mineral_emerald"] = 17,
["glooptest:mineral_kalite"] = 20,
["glooptest:mineral_ruby"] = 18,
["glooptest:mineral_sapphire"] = 18,
["glooptest:mineral_talinite"] = 20,
["glooptest:mineral_topaz"] = 18,
["glooptest:reinforced_crystal_glass"] = 21,
["glooptest:rubyblock"] = 27,
["glooptest:sapphireblock"] = 27,
["glooptest:talinite_crystal_glass"] = 21,
["glooptest:taliniteblock"] = 40,
["glooptest:topazblock"] = 24,
["mesecons_extrawires:mese_powered"] = 21,
["moreblocks:cactus_brick"] = 13,
["moreblocks:cactus_checker"] = 8.5,
["moreblocks:circle_stone_bricks"] = 17,
["moreblocks:clean_glass"] = 17,
["moreblocks:coal_checker"] = 9.0,
["moreblocks:coal_glass"] = 17,
["moreblocks:coal_stone"] = 17,
["moreblocks:coal_stone_bricks"] = 17,
["moreblocks:glow_glass"] = 17,
["moreblocks:grey_bricks"] = 15,
["moreblocks:iron_checker"] = 11,
["moreblocks:iron_glass"] = 17,
["moreblocks:iron_stone"] = 17,
["moreblocks:iron_stone_bricks"] = 17,
["moreblocks:plankstone"] = 9.3,
["moreblocks:split_stone_tile"] = 15,
["moreblocks:split_stone_tile_alt"] = 15,
["moreblocks:stone_tile"] = 15,
["moreblocks:super_glow_glass"] = 17,
["moreblocks:tar"] = 7.0,
["moreblocks:wood_tile"] = 1.7,
["moreblocks:wood_tile_center"] = 1.7,
["moreblocks:wood_tile_down"] = 1.7,
["moreblocks:wood_tile_flipped"] = 1.7,
["moreblocks:wood_tile_full"] = 1.7,
["moreblocks:wood_tile_left"] = 1.7,
["moreblocks:wood_tile_right"] = 1.7,
["moreblocks:wood_tile_up"] = 1.7,
["moreores:mineral_mithril"] = 18,
["moreores:mineral_silver"] = 21,
["moreores:mineral_tin"] = 19,
["moreores:mithril_block"] = 26,
["moreores:silver_block"] = 53,
["moreores:tin_block"] = 37,
["snow:snow_brick"] = 2.8,
["technic:brass_block"] = 43,
["technic:carbon_steel_block"] = 40,
["technic:cast_iron_block"] = 40,
["technic:chernobylite_block"] = 40,
["technic:chromium_block"] = 37,
["technic:corium_flowing"] = 40,
["technic:corium_source"] = 80,
["technic:granite"] = 18,
["technic:marble"] = 18,
["technic:marble_bricks"] = 18,
["technic:mineral_chromium"] = 19,
["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,
}
local default_radiation_resistance_per_group = {
concrete = 16,
tree = 3.4,
wood = 1.7,
}
local cache_radiation_resistance = {}
local function node_radiation_resistance(nodename)
local eff = cache_radiation_resistance[nodename]
if eff then return eff end
local def = minetest.registered_nodes[nodename] or {groups={}}
eff = def.radiation_resistance or default_radiation_resistance_per_node[nodename]
if not eff then
for g, v in pairs(def.groups) do
if v > 0 and default_radiation_resistance_per_group[g] then
eff = default_radiation_resistance_per_group[g]
break
end
end
end
if not eff then eff = 0 end
cache_radiation_resistance[nodename] = eff
return eff
end
-- Radioactive nodes cause damage to nearby players. The damage
-- effect depends on the intrinsic strength of the radiation source,
-- the distance between the source and the player, and the shielding
-- effect of the intervening material. These determine a rate of damage;
-- total damage caused is the integral of this over time.
--
-- In the absence of effective shielding, for a specific source the
-- damage rate varies realistically in inverse proportion to the square
-- of the distance. (Distance is measured to the player's abdomen,
-- not to the nominal player position which corresponds to the foot.)
-- However, if the player is inside a non-walkable (liquid or gaseous)
-- radioactive node, the nominal distance could go to zero, yielding
-- infinite damage. In that case, the player's body is displacing the
-- radioactive material, so the effective distance should remain non-zero.
-- We therefore apply a lower distance bound of sqrt(0.75) m, which is
-- the maximum distance one can get from the node centre within the node.
--
-- 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.
--
-- 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.
--
-- 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"
-- group value. Computed damage rates below 0.25 HP/s result in no
-- damage at all to the player. This gives the player an opportunity
-- to be safe, and limits the range at which source/player interactions
-- need to be considered.
local assumed_abdomen_offset = vector.new(0, 1, 0) local assumed_abdomen_offset = vector.new(0, 1, 0)
local assumed_abdomen_offset_length = vector.length(assumed_abdomen_offset) local assumed_abdomen_offset_length = vector.length(assumed_abdomen_offset)
minetest.register_abm({ minetest.register_abm({
nodenames = {"group:radioactive"}, nodenames = {"group:radioactive"},
interval = 1, interval = 1,
chance = 1, chance = 1,
action = function (pos, node) action = function (pos, node)
-- Damage depends on distance between the radiation source
-- and the player, with an inverse square relationship.
-- The "radioactive" group value is the distance in
-- metres from a node at which a player will be damaged by
-- 1 HP/s. Or, equivalently, it is the square root of the
-- damage rate in HP/s that a player 1 m away will take.
local strength = minetest.registered_nodes[node.name].groups.radioactive local strength = minetest.registered_nodes[node.name].groups.radioactive
-- Damage is processed at rates down to 0.25 HP/s, for _, o in ipairs(minetest.get_objects_inside_radius(pos, strength + assumed_abdomen_offset_length)) do
-- which is attained at twice the 1 HP/s distance.
for _, o in ipairs(minetest.get_objects_inside_radius(pos, strength*2 + assumed_abdomen_offset_length)) do
if o:is_player() then if o:is_player() then
-- If the player is very close, swimming local rel = vector.subtract(vector.add(o:getpos(), assumed_abdomen_offset), pos)
-- in radioactive liquid, then the nominal local dist_sq = vector.length_square(rel)
-- distance could go to zero, but in local dist = math.sqrt(dist_sq)
-- that case we model the player's body local dirstep = dist == 0 and vector.new(0,0,0) or vector.divide(rel, dist*4)
-- displacing the liquid and increasing local intpos = pos
-- the effective distance to non-zero. local resistance = 0
-- The minimum effective distance is set for intdist = 0.25, dist, 0.25 do
-- to the maximum distance one can get intpos = vector.add(intpos, dirstep)
-- from the node centre within the node, local intnodepos = vector.round(intpos)
-- so that swimming in radioactive liquid if not vector.equals(intnodepos, pos) then
-- gives a uniform effective distance. resistance = resistance + node_radiation_resistance(minetest.get_node(intnodepos).name)
local dist_sq = math.max(0.75, vector.distance_square(pos, vector.add(o:getpos(), assumed_abdomen_offset))) end
local dmg_rate = strength*strength/dist_sq 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 if dmg_rate >= 0.25 then
local dmg_int = math.floor(dmg_rate) local dmg_int = math.floor(dmg_rate)
if math.random() < dmg_rate-dmg_int then if math.random() < dmg_rate-dmg_int then
@ -312,6 +521,8 @@ minetest.register_abm({
end, end,
}) })
-- radioactive materials that can result from destroying a reactor
for _, state in ipairs({ "flowing", "source" }) do for _, state in ipairs({ "flowing", "source" }) do
minetest.register_node("technic:corium_"..state, { minetest.register_node("technic:corium_"..state, {
description = S(state == "source" and "Corium Source" or "Flowing Corium"), description = S(state == "source" and "Corium Source" or "Flowing Corium"),
@ -345,7 +556,7 @@ for _, state in ipairs({ "flowing", "source" }) do
liquid = 2, liquid = 2,
hot = 3, hot = 3,
igniter = 1, igniter = 1,
radioactive = (state == "source" and 4 or 3), radioactive = (state == "source" and 32 or 16),
not_in_creative_inventory = (state == "flowing" and 1 or nil), not_in_creative_inventory = (state == "flowing" and 1 or nil),
}, },
}) })
@ -365,7 +576,7 @@ minetest.register_node("technic:chernobylite_block", {
description = S("Chernobylite Block"), description = S("Chernobylite Block"),
tiles = { "technic_chernobylite_block.png" }, tiles = { "technic_chernobylite_block.png" },
is_ground_content = true, is_ground_content = true,
groups = { cracky=1, radioactive=2, level=2 }, groups = { cracky=1, radioactive=5, level=2 },
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
light_source = 2, light_source = 2,

@ -5,7 +5,7 @@ minetest.register_node( ":technic:mineral_uranium", {
description = S("Uranium Ore"), description = S("Uranium Ore"),
tiles = { "default_stone.png^technic_mineral_uranium.png" }, tiles = { "default_stone.png^technic_mineral_uranium.png" },
is_ground_content = true, is_ground_content = true,
groups = {cracky=3}, groups = {cracky=3, radioactive=1},
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
drop = 'craft "technic:uranium" 1', drop = 'craft "technic:uranium" 1',
}) })
@ -56,7 +56,7 @@ minetest.register_node(":technic:uranium_block", {
description = S("Uranium Block"), description = S("Uranium Block"),
tiles = { "technic_uranium_block.png" }, tiles = { "technic_uranium_block.png" },
is_ground_content = true, is_ground_content = true,
groups = {cracky=1, level=2}, groups = {cracky=1, level=2, radioactive=3},
sounds = default.node_sound_stone_defaults() sounds = default.node_sound_stone_defaults()
}) })