Vanessa Ezekowitz
2017-03-26 383f7df22d5e81fb59b315673a68c4d01c5a82d3
commit | author | age
1da213 1 --[[
S 2 Radioactivity
3
4 Radiation resistance represents the extent to which a material
5 attenuates radiation passing through it; i.e., how good a radiation
6 shield it is.  This is identified per node type.  For materials that
7 exist in real life, the radiation resistance value that this system
8 uses for a node type consisting of a solid cube of that material is the
9 (approximate) number of halvings of ionising radiation that is achieved
10 by a meter of the material in real life.  This is approximately
11 proportional to density, which provides a good way to estimate it.
12 Homogeneous mixtures of materials have radiation resistance computed
13 by a simple weighted mean.  Note that the amount of attenuation that
14 a material achieves in-game is not required to be (and is not) the
15 same as the attenuation achieved in real life.
16
17 Radiation resistance for a node type may be specified in the node
18 definition, under the key "radiation_resistance".  As an interim
19 measure, until node definitions widely include this, this code
20 knows a bunch of values for particular node types in several mods,
21 and values for groups of node types.  The node definition takes
22 precedence if it specifies a value.  Nodes for which no value at
23 all is known are taken to provide no radiation resistance at all;
24 this is appropriate for the majority of node types.  Only node types
25 consisting of a fairly homogeneous mass of material should report
26 non-zero radiation resistance; anything with non-uniform geometry
27 or complex internal structure should show no radiation resistance.
28 Fractional resistance values are permitted.
29 --]]
30
31 local S = technic.getter
32
33 local rad_resistance_node = {
34     ["default:brick"] = 13,
35     ["default:bronzeblock"] = 45,
36     ["default:clay"] = 15,
37     ["default:coalblock"] = 9.6,
38     ["default:cobble"] = 15,
39     ["default:copperblock"] = 46,
40     ["default:desert_cobble"] = 15,
41     ["default:desert_sand"] = 10,
42     ["default:desert_stone"] = 17,
43     ["default:desert_stonebrick"] = 17,
44     ["default:diamondblock"] = 24,
45     ["default:dirt"] = 8.2,
46     ["default:dirt_with_grass"] = 8.2,
47     ["default:dirt_with_grass_footsteps"] = 8.2,
48     ["default:dirt_with_snow"] = 8.2,
49     ["default:glass"] = 17,
50     ["default:goldblock"] = 170,
51     ["default:gravel"] = 10,
52     ["default:ice"] = 5.6,
53     ["default:lava_flowing"] = 8.5,
54     ["default:lava_source"] = 17,
55     ["default:mese"] = 21,
56     ["default:mossycobble"] = 15,
df15a5 57     ["pbj_pup:pbj_pup"] = 10000,
VE 58     ["pbj_pup:pbj_pup_candies"] = 10000,
383f7d 59     ["gloopblocks:rainbow_block_diagonal"] = 5000,
VE 60     ["gloopblocks:rainbow_block_horizontal"] = 10000,
cb5e3e 61     ["default:nyancat"] = 10000,
VE 62     ["default:nyancat_rainbow"] = 10000,
63     ["nyancat:nyancat"] = 10000,
64     ["nyancat:nyancat_rainbow"] = 10000,
1da213 65     ["default:obsidian"] = 18,
S 66     ["default:obsidian_glass"] = 18,
67     ["default:sand"] = 10,
68     ["default:sandstone"] = 15,
69     ["default:sandstonebrick"] = 15,
70     ["default:snowblock"] = 1.7,
71     ["default:steelblock"] = 40,
72     ["default:stone"] = 17,
73     ["default:stone_with_coal"] = 16,
74     ["default:stone_with_copper"] = 20,
75     ["default:stone_with_diamond"] = 18,
76     ["default:stone_with_gold"] = 34,
77     ["default:stone_with_iron"] = 20,
78     ["default:stone_with_mese"] = 17,
79     ["default:stonebrick"] = 17,
80     ["default:water_flowing"] = 2.8,
81     ["default:water_source"] = 5.6,
82     ["farming:desert_sand_soil"] = 10,
83     ["farming:desert_sand_soil_wet"] = 10,
84     ["farming:soil"] = 8.2,
85     ["farming:soil_wet"] = 8.2,
86     ["glooptest:akalin_crystal_glass"] = 21,
87     ["glooptest:akalinblock"] = 40,
88     ["glooptest:alatro_crystal_glass"] = 21,
89     ["glooptest:alatroblock"] = 40,
90     ["glooptest:amethystblock"] = 18,
91     ["glooptest:arol_crystal_glass"] = 21,
92     ["glooptest:crystal_glass"] = 21,
93     ["glooptest:emeraldblock"] = 19,
94     ["glooptest:heavy_crystal_glass"] = 21,
95     ["glooptest:mineral_akalin"] = 20,
96     ["glooptest:mineral_alatro"] = 20,
97     ["glooptest:mineral_amethyst"] = 17,
98     ["glooptest:mineral_arol"] = 20,
99     ["glooptest:mineral_desert_coal"] = 16,
100     ["glooptest:mineral_desert_iron"] = 20,
101     ["glooptest:mineral_emerald"] = 17,
102     ["glooptest:mineral_kalite"] = 20,
103     ["glooptest:mineral_ruby"] = 18,
104     ["glooptest:mineral_sapphire"] = 18,
105     ["glooptest:mineral_talinite"] = 20,
106     ["glooptest:mineral_topaz"] = 18,
107     ["glooptest:reinforced_crystal_glass"] = 21,
108     ["glooptest:rubyblock"] = 27,
109     ["glooptest:sapphireblock"] = 27,
110     ["glooptest:talinite_crystal_glass"] = 21,
111     ["glooptest:taliniteblock"] = 40,
112     ["glooptest:topazblock"] = 24,
113     ["mesecons_extrawires:mese_powered"] = 21,
114     ["moreblocks:cactus_brick"] = 13,
115     ["moreblocks:cactus_checker"] = 8.5,
116     ["moreblocks:circle_stone_bricks"] = 17,
117     ["moreblocks:clean_glass"] = 17,
118     ["moreblocks:coal_checker"] = 9.0,
119     ["moreblocks:coal_glass"] = 17,
120     ["moreblocks:coal_stone"] = 17,
121     ["moreblocks:coal_stone_bricks"] = 17,
122     ["moreblocks:glow_glass"] = 17,
123     ["moreblocks:grey_bricks"] = 15,
124     ["moreblocks:iron_checker"] = 11,
125     ["moreblocks:iron_glass"] = 17,
126     ["moreblocks:iron_stone"] = 17,
127     ["moreblocks:iron_stone_bricks"] = 17,
128     ["moreblocks:plankstone"] = 9.3,
129     ["moreblocks:split_stone_tile"] = 15,
130     ["moreblocks:split_stone_tile_alt"] = 15,
131     ["moreblocks:stone_tile"] = 15,
132     ["moreblocks:super_glow_glass"] = 17,
133     ["moreblocks:tar"] = 7.0,
134     ["moreblocks:wood_tile"] = 1.7,
135     ["moreblocks:wood_tile_center"] = 1.7,
136     ["moreblocks:wood_tile_down"] = 1.7,
137     ["moreblocks:wood_tile_flipped"] = 1.7,
138     ["moreblocks:wood_tile_full"] = 1.7,
139     ["moreblocks:wood_tile_left"] = 1.7,
140     ["moreblocks:wood_tile_right"] = 1.7,
141     ["moreblocks:wood_tile_up"] = 1.7,
142     ["moreores:mineral_mithril"] = 18,
143     ["moreores:mineral_silver"] = 21,
144     ["moreores:mineral_tin"] = 19,
145     ["moreores:mithril_block"] = 26,
146     ["moreores:silver_block"] = 53,
147     ["moreores:tin_block"] = 37,
148     ["snow:snow_brick"] = 2.8,
149     ["technic:brass_block"] = 43,
150     ["technic:carbon_steel_block"] = 40,
151     ["technic:cast_iron_block"] = 40,
152     ["technic:chernobylite_block"] = 40,
153     ["technic:chromium_block"] = 37,
154     ["technic:corium_flowing"] = 40,
155     ["technic:corium_source"] = 80,
156     ["technic:granite"] = 18,
157     ["technic:lead_block"] = 80,
158     ["technic:marble"] = 18,
159     ["technic:marble_bricks"] = 18,
160     ["technic:mineral_chromium"] = 19,
161     ["technic:mineral_uranium"] = 71,
162     ["technic:mineral_zinc"] = 19,
163     ["technic:stainless_steel_block"] = 40,
164     ["technic:zinc_block"] = 36,
165     ["tnt:tnt"] = 11,
166     ["tnt:tnt_burning"] = 11,
167 }
168 local rad_resistance_group = {
169     concrete = 16,
170     tree = 3.4,
171     uranium_block = 500,
172     wood = 1.7,
173 }
174 local cache_radiation_resistance = {}
175 local function node_radiation_resistance(node_name)
176     local resistance = cache_radiation_resistance[node_name]
177     if resistance then
178         return resistance
179     end
180     local def = minetest.registered_nodes[node_name]
181     if not def then
182         cache_radiation_resistance[node_name] = 0
183         return 0
184     end
185     resistance = def.radiation_resistance or
186             rad_resistance_node[node_name]
187     if not resistance then
188         resistance = 0
189         for g, v in pairs(def.groups) do
190             if v > 0 and rad_resistance_group[g] then
191                 resistance = resistance + rad_resistance_group[g]
192             end
193         end
194     end
195     resistance = math.sqrt(resistance)
196     cache_radiation_resistance[node_name] = resistance
197     return resistance
198 end
199
200
201 --[[
202 Radioactive nodes cause damage to nearby players.  The damage
203 effect depends on the intrinsic strength of the radiation source,
204 the distance between the source and the player, and the shielding
205 effect of the intervening material.  These determine a rate of damage;
206 total damage caused is the integral of this over time.
207
208 In the absence of effective shielding, for a specific source the
209 damage rate varies realistically in inverse proportion to the square
210 of the distance.  (Distance is measured to the player's abdomen,
211 not to the nominal player position which corresponds to the foot.)
212 However, if the player is inside a non-walkable (liquid or gaseous)
213 radioactive node, the nominal distance could go to zero, yielding
214 infinite damage.  In that case, the player's body is displacing the
215 radioactive material, so the effective distance should remain non-zero.
216 We therefore apply a lower distance bound of sqrt(0.75), which is
217 the maximum distance one can get from the node center within the node.
218
219 A radioactive node is identified by being in the "radioactive" group,
220 and the group value signifies the strength of the radiation source.
221 The group value is the distance from a node at which an unshielded
222 player will be damaged by 1 HP/s.  Or, equivalently, it is the square
223 root of the damage rate in HP/s that an unshielded player one node
224 away will take.
225
226 Shielding is assessed by adding the shielding values of all nodes
227 between the source node and the player, ignoring the source node itself.
228 As in reality, shielding causes exponential attenuation of radiation.
229 However, the effect is scaled down relative to real life.  A node with
230 radiation resistance value R yields attenuation of sqrt(R) * 0.1 nepers.
231 (In real life it would be about R * 0.69 nepers, by the definition
232 of the radiation resistance values.)  The sqrt part of this formula
233 scales down the differences between shielding types, reflecting the
234 game's simplification of making expensive materials such as gold
235 readily available in cubes.  The multiplicative factor in the
236 formula scales down the difference between shielded and unshielded
237 safe distances, avoiding the latter becoming impractically large.
238
06dec2 239 Damage is processed at rates down to 0.2 HP/s, which in the absence of
1da213 240 shielding is attained at the distance specified by the "radioactive"
06dec2 241 group value.  Computed damage rates below 0.2 HP/s result in no
1da213 242 damage at all to the player.  This gives the player an opportunity
S 243 to be safe, and limits the range at which source/player interactions
244 need to be considered.
245 --]]
246 local abdomen_offset = 1
247 local cache_scaled_shielding = {}
06dec2 248 local rad_dmg_cutoff = 0.2
S 249 local radiated_players = {}
1da213 250
cbe974 251 local armor_enabled = technic.config:get_bool("enable_radiation_protection")
b739ed 252 local entity_damage = technic.config:get_bool("enable_entity_radiation_damage")
NZ 253 local longterm_damage = technic.config:get_bool("enable_longterm_radiation_damage")
254
06dec2 255 local function apply_fractional_damage(o, dmg)
1da213 256     local dmg_int = math.floor(dmg)
S 257     -- The closer you are to getting one more damage point,
258     -- the more likely it will be added.
259     if math.random() < dmg - dmg_int then
260         dmg_int = dmg_int + 1
261     end
262     if dmg_int > 0 then
06dec2 263         local new_hp = math.max(o:get_hp() - dmg_int, 0)
S 264         o:set_hp(new_hp)
265         return new_hp == 0
1da213 266     end
06dec2 267     return false
S 268 end
269
1810f4 270 local function calculate_base_damage(node_pos, object_pos, strength)
06dec2 271     local shielding = 0
1810f4 272     local dist = vector.distance(node_pos, object_pos)
06dec2 273
1810f4 274     for ray_pos in technic.trace_node_ray(node_pos,
NZ 275             vector.direction(node_pos, object_pos), dist) do
06dec2 276         local shield_name = minetest.get_node(ray_pos).name
51be33 277         shielding = shielding + node_radiation_resistance(shield_name) * 0.025
06dec2 278     end
S 279
280     local dmg = (strength * strength) /
281         (math.max(0.75, dist * dist) * math.exp(shielding))
282
283     if dmg < rad_dmg_cutoff then return end
1810f4 284     return dmg
NZ 285 end
06dec2 286
1810f4 287 local function calculate_damage_multiplier(object)
NZ 288     local ag = object.get_armor_groups and object:get_armor_groups()
289     if not ag then
290         return 0
291     end
73afc4 292     if ag.immortal then
NZ 293         return 0
294     end
1810f4 295     if ag.radiation then
NZ 296         return 0.01 * ag.radiation
297     end
298     if ag.fleshy then
299         return math.sqrt(0.01 * ag.fleshy)
300     end
301     return 0
302 end
303
304 local function calculate_object_center(object)
305     if object:is_player() then
306         return {x=0, y=abdomen_offset, z=0}
307     end
308     return {x=0, y=0, z=0}
309 end
310
311 local function dmg_object(pos, object, strength)
312     local obj_pos = vector.add(object:getpos(), calculate_object_center(object))
cbe974 313     local mul
NZ 314     if armor_enabled or entity_damage then
315         -- we need to check may the object be damaged even if armor is disabled
316         mul = calculate_damage_multiplier(object)
317         if mul == 0 then
318             return
319         end
1810f4 320     end
b739ed 321     local dmg = calculate_base_damage(pos, obj_pos, strength)
NZ 322     if not dmg then
323         return
324     end
cbe974 325     if armor_enabled then
NZ 326         dmg = dmg * mul
327     end
1810f4 328     apply_fractional_damage(object, dmg)
b739ed 329     if longterm_damage and object:is_player() then
1810f4 330         local pn = object:get_player_name()
NZ 331         radiated_players[pn] = (radiated_players[pn] or 0) + dmg
332     end
1da213 333 end
S 334
335 local rad_dmg_mult_sqrt = math.sqrt(1 / rad_dmg_cutoff)
336 local function dmg_abm(pos, node)
337     local strength = minetest.get_item_group(node.name, "radioactive")
338     local max_dist = strength * rad_dmg_mult_sqrt
339     for _, o in pairs(minetest.get_objects_inside_radius(pos,
340             max_dist + abdomen_offset)) do
b739ed 341         if entity_damage or o:is_player() then
NZ 342             dmg_object(pos, o, strength)
343         end
1da213 344     end
S 345 end
346
347 if minetest.setting_getbool("enable_damage") then
348     minetest.register_abm({
349         nodenames = {"group:radioactive"},
350         interval = 1,
351         chance = 1,
352         action = dmg_abm,
353     })
06dec2 354
b739ed 355     if longterm_damage then
NZ 356         minetest.register_globalstep(function(dtime)
357             for pn, dmg in pairs(radiated_players) do
358                 dmg = dmg - (dtime / 8)
359                 local player = minetest.get_player_by_name(pn)
360                 local killed
361                 if player and dmg > rad_dmg_cutoff then
362                     killed = apply_fractional_damage(player, (dmg * dtime) / 8)
363                 else
364                     dmg = nil
365                 end
366                 -- on_dieplayer will have already set this if the player died
367                 if not killed then
368                     radiated_players[pn] = dmg
369                 end
06dec2 370             end
b739ed 371         end)
06dec2 372
b739ed 373         minetest.register_on_dieplayer(function(player)
NZ 374             radiated_players[player:get_player_name()] = nil
375         end)
376     end
1da213 377 end
S 378
379 -- Radioactive materials that can result from destroying a reactor
380 local griefing = technic.config:get_bool("enable_corium_griefing")
381
382 for _, state in pairs({"flowing", "source"}) do
383     minetest.register_node("technic:corium_"..state, {
384         description = S(state == "source" and "Corium Source" or "Flowing Corium"),
385         drawtype = (state == "source" and "liquid" or "flowingliquid"),
386         [state == "source" and "tiles" or "special_tiles"] = {{
387             name = "technic_corium_"..state.."_animated.png",
388             animation = {
389                 type = "vertical_frames",
390                 aspect_w = 16,
391                 aspect_h = 16,
392                 length = 3.0,
393             },
394         }},
395         paramtype = "light",
396         paramtype2 = (state == "flowing" and "flowingliquid" or nil),
397         light_source = (state == "source" and 8 or 5),
398         walkable = false,
399         pointable = false,
400         diggable = false,
401         buildable_to = true,
402         drop = "",
403         drowning = 1,
404         liquidtype = state,
405         liquid_alternative_flowing = "technic:corium_flowing",
406         liquid_alternative_source = "technic:corium_source",
407         liquid_viscosity = LAVA_VISC,
408         liquid_renewable = false,
409         damage_per_second = 6,
410         post_effect_color = {a=192, r=80, g=160, b=80},
411         groups = {
412             liquid = 2,
413             hot = 3,
414             igniter = (griefing and 1 or 0),
06dec2 415             radioactive = (state == "source" and 12 or 6),
1da213 416             not_in_creative_inventory = (state == "flowing" and 1 or nil),
S 417         },
418     })
419 end
420
421 if rawget(_G, "bucket") and bucket.register_liquid then
422     bucket.register_liquid(
423         "technic:corium_source",
424         "technic:corium_flowing",
425         "technic:bucket_corium",
426         "technic_bucket_corium.png",
427         "Corium Bucket"
428     )
429 end
430
431 minetest.register_node("technic:chernobylite_block", {
432         description = S("Chernobylite Block"),
433     tiles = {"technic_chernobylite_block.png"},
434     is_ground_content = true,
06dec2 435     groups = {cracky=1, radioactive=4, level=2},
1da213 436     sounds = default.node_sound_stone_defaults(),
S 437     light_source = 2,
438 })
439
440 minetest.register_abm({
441     nodenames = {"group:water"},
442     neighbors = {"technic:corium_source"},
443     interval = 1,
444     chance = 1,
445     action = function(pos, node)
446         minetest.remove_node(pos)
447     end,
448 })
449
450 minetest.register_abm({
451     nodenames = {"technic:corium_flowing"},
452     neighbors = {"group:water"},
453     interval = 1,
454     chance = 1,
455     action = function(pos, node)
456         minetest.set_node(pos, {name="technic:chernobylite_block"})
457     end,
458 })
459
460 minetest.register_abm({
461     nodenames = {"technic:corium_flowing"},
462     interval = 5,
463     chance = (griefing and 10 or 1),
464     action = function(pos, node)
465         minetest.set_node(pos, {name="technic:chernobylite_block"})
466     end,
467 })
468
469 if griefing then
470     minetest.register_abm({
471         nodenames = {"technic:corium_source", "technic:corium_flowing"},
472         interval = 4,
473         chance = 4,
474         action = function(pos, node)
475             for _, offset in ipairs({
476                 vector.new(1,0,0),
477                 vector.new(-1,0,0),
478                 vector.new(0,0,1),
479                 vector.new(0,0,-1),
480                 vector.new(0,-1,0),
481             }) do
482                 if math.random(8) == 1 then
483                     minetest.dig_node(vector.add(pos, offset))
484                 end
485             end
486         end,
487     })
488 end
489