TechDudie
2021-02-09 43acec290067f9aca534647d46ba1f13cfeb377a
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,
d1b54a 57     ["default:tinblock"] = 37,
df15a5 58     ["pbj_pup:pbj_pup"] = 10000,
VE 59     ["pbj_pup:pbj_pup_candies"] = 10000,
383f7d 60     ["gloopblocks:rainbow_block_diagonal"] = 5000,
VE 61     ["gloopblocks:rainbow_block_horizontal"] = 10000,
cb5e3e 62     ["default:nyancat"] = 10000,
VE 63     ["default:nyancat_rainbow"] = 10000,
64     ["nyancat:nyancat"] = 10000,
65     ["nyancat:nyancat_rainbow"] = 10000,
1da213 66     ["default:obsidian"] = 18,
S 67     ["default:obsidian_glass"] = 18,
68     ["default:sand"] = 10,
69     ["default:sandstone"] = 15,
70     ["default:sandstonebrick"] = 15,
71     ["default:snowblock"] = 1.7,
72     ["default:steelblock"] = 40,
73     ["default:stone"] = 17,
74     ["default:stone_with_coal"] = 16,
75     ["default:stone_with_copper"] = 20,
76     ["default:stone_with_diamond"] = 18,
77     ["default:stone_with_gold"] = 34,
78     ["default:stone_with_iron"] = 20,
79     ["default:stone_with_mese"] = 17,
d1b54a 80     ["default:stone_with_tin"] = 19,
1da213 81     ["default:stonebrick"] = 17,
S 82     ["default:water_flowing"] = 2.8,
83     ["default:water_source"] = 5.6,
84     ["farming:desert_sand_soil"] = 10,
85     ["farming:desert_sand_soil_wet"] = 10,
86     ["farming:soil"] = 8.2,
87     ["farming:soil_wet"] = 8.2,
88     ["glooptest:akalin_crystal_glass"] = 21,
89     ["glooptest:akalinblock"] = 40,
90     ["glooptest:alatro_crystal_glass"] = 21,
91     ["glooptest:alatroblock"] = 40,
92     ["glooptest:amethystblock"] = 18,
93     ["glooptest:arol_crystal_glass"] = 21,
94     ["glooptest:crystal_glass"] = 21,
95     ["glooptest:emeraldblock"] = 19,
96     ["glooptest:heavy_crystal_glass"] = 21,
97     ["glooptest:mineral_akalin"] = 20,
98     ["glooptest:mineral_alatro"] = 20,
99     ["glooptest:mineral_amethyst"] = 17,
100     ["glooptest:mineral_arol"] = 20,
101     ["glooptest:mineral_desert_coal"] = 16,
102     ["glooptest:mineral_desert_iron"] = 20,
103     ["glooptest:mineral_emerald"] = 17,
104     ["glooptest:mineral_kalite"] = 20,
105     ["glooptest:mineral_ruby"] = 18,
106     ["glooptest:mineral_sapphire"] = 18,
107     ["glooptest:mineral_talinite"] = 20,
108     ["glooptest:mineral_topaz"] = 18,
109     ["glooptest:reinforced_crystal_glass"] = 21,
110     ["glooptest:rubyblock"] = 27,
111     ["glooptest:sapphireblock"] = 27,
112     ["glooptest:talinite_crystal_glass"] = 21,
113     ["glooptest:taliniteblock"] = 40,
114     ["glooptest:topazblock"] = 24,
115     ["mesecons_extrawires:mese_powered"] = 21,
116     ["moreblocks:cactus_brick"] = 13,
117     ["moreblocks:cactus_checker"] = 8.5,
118     ["moreblocks:circle_stone_bricks"] = 17,
119     ["moreblocks:clean_glass"] = 17,
120     ["moreblocks:coal_checker"] = 9.0,
121     ["moreblocks:coal_glass"] = 17,
122     ["moreblocks:coal_stone"] = 17,
123     ["moreblocks:coal_stone_bricks"] = 17,
124     ["moreblocks:glow_glass"] = 17,
125     ["moreblocks:grey_bricks"] = 15,
126     ["moreblocks:iron_checker"] = 11,
127     ["moreblocks:iron_glass"] = 17,
128     ["moreblocks:iron_stone"] = 17,
129     ["moreblocks:iron_stone_bricks"] = 17,
130     ["moreblocks:plankstone"] = 9.3,
131     ["moreblocks:split_stone_tile"] = 15,
132     ["moreblocks:split_stone_tile_alt"] = 15,
133     ["moreblocks:stone_tile"] = 15,
134     ["moreblocks:super_glow_glass"] = 17,
135     ["moreblocks:tar"] = 7.0,
136     ["moreblocks:wood_tile"] = 1.7,
137     ["moreblocks:wood_tile_center"] = 1.7,
138     ["moreblocks:wood_tile_down"] = 1.7,
139     ["moreblocks:wood_tile_flipped"] = 1.7,
140     ["moreblocks:wood_tile_full"] = 1.7,
141     ["moreblocks:wood_tile_left"] = 1.7,
142     ["moreblocks:wood_tile_right"] = 1.7,
143     ["moreblocks:wood_tile_up"] = 1.7,
144     ["moreores:mineral_mithril"] = 18,
145     ["moreores:mineral_silver"] = 21,
146     ["moreores:mithril_block"] = 26,
147     ["moreores:silver_block"] = 53,
148     ["snow:snow_brick"] = 2.8,
44cb8d 149     ["basic_materials:brass_block"] = 43,
1da213 150     ["technic:carbon_steel_block"] = 40,
S 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
06dec2 247 local rad_dmg_cutoff = 0.2
S 248 local radiated_players = {}
1da213 249
cbe974 250 local armor_enabled = technic.config:get_bool("enable_radiation_protection")
b739ed 251 local entity_damage = technic.config:get_bool("enable_entity_radiation_damage")
NZ 252 local longterm_damage = technic.config:get_bool("enable_longterm_radiation_damage")
253
06dec2 254 local function apply_fractional_damage(o, dmg)
1da213 255     local dmg_int = math.floor(dmg)
S 256     -- The closer you are to getting one more damage point,
257     -- the more likely it will be added.
258     if math.random() < dmg - dmg_int then
259         dmg_int = dmg_int + 1
260     end
261     if dmg_int > 0 then
06dec2 262         local new_hp = math.max(o:get_hp() - dmg_int, 0)
S 263         o:set_hp(new_hp)
264         return new_hp == 0
1da213 265     end
06dec2 266     return false
S 267 end
268
1810f4 269 local function calculate_base_damage(node_pos, object_pos, strength)
06dec2 270     local shielding = 0
1810f4 271     local dist = vector.distance(node_pos, object_pos)
06dec2 272
1810f4 273     for ray_pos in technic.trace_node_ray(node_pos,
NZ 274             vector.direction(node_pos, object_pos), dist) do
06dec2 275         local shield_name = minetest.get_node(ray_pos).name
51be33 276         shielding = shielding + node_radiation_resistance(shield_name) * 0.025
06dec2 277     end
S 278
279     local dmg = (strength * strength) /
280         (math.max(0.75, dist * dist) * math.exp(shielding))
281
282     if dmg < rad_dmg_cutoff then return end
1810f4 283     return dmg
NZ 284 end
06dec2 285
1810f4 286 local function calculate_damage_multiplier(object)
NZ 287     local ag = object.get_armor_groups and object:get_armor_groups()
288     if not ag then
289         return 0
290     end
73afc4 291     if ag.immortal then
NZ 292         return 0
293     end
1810f4 294     if ag.radiation then
NZ 295         return 0.01 * ag.radiation
da95be 296     elseif armor_enabled then
C 297         return 0
1810f4 298     end
NZ 299     if ag.fleshy then
300         return math.sqrt(0.01 * ag.fleshy)
301     end
302     return 0
303 end
304
305 local function calculate_object_center(object)
306     if object:is_player() then
307         return {x=0, y=abdomen_offset, z=0}
308     end
309     return {x=0, y=0, z=0}
310 end
311
312 local function dmg_object(pos, object, strength)
d5df30 313     local obj_pos = vector.add(object:get_pos(), calculate_object_center(object))
cbe974 314     local mul
NZ 315     if armor_enabled or entity_damage then
316         -- we need to check may the object be damaged even if armor is disabled
317         mul = calculate_damage_multiplier(object)
318         if mul == 0 then
319             return
320         end
1810f4 321     end
b739ed 322     local dmg = calculate_base_damage(pos, obj_pos, strength)
NZ 323     if not dmg then
324         return
325     end
cbe974 326     if armor_enabled then
NZ 327         dmg = dmg * mul
328     end
1810f4 329     apply_fractional_damage(object, dmg)
b739ed 330     if longterm_damage and object:is_player() then
1810f4 331         local pn = object:get_player_name()
NZ 332         radiated_players[pn] = (radiated_players[pn] or 0) + dmg
333     end
1da213 334 end
S 335
336 local rad_dmg_mult_sqrt = math.sqrt(1 / rad_dmg_cutoff)
337 local function dmg_abm(pos, node)
338     local strength = minetest.get_item_group(node.name, "radioactive")
339     local max_dist = strength * rad_dmg_mult_sqrt
340     for _, o in pairs(minetest.get_objects_inside_radius(pos,
341             max_dist + abdomen_offset)) do
dc4f4f 342         if (entity_damage or o:is_player()) and o:get_hp() > 0 then
b739ed 343             dmg_object(pos, o, strength)
NZ 344         end
1da213 345     end
S 346 end
347
97e1c8 348 if minetest.settings:get_bool("enable_damage") then
1da213 349     minetest.register_abm({
78f16c 350         label = "Radiation damage",
1da213 351         nodenames = {"group:radioactive"},
S 352         interval = 1,
353         chance = 1,
354         action = dmg_abm,
355     })
06dec2 356
b739ed 357     if longterm_damage then
NZ 358         minetest.register_globalstep(function(dtime)
359             for pn, dmg in pairs(radiated_players) do
360                 dmg = dmg - (dtime / 8)
361                 local player = minetest.get_player_by_name(pn)
362                 local killed
363                 if player and dmg > rad_dmg_cutoff then
364                     killed = apply_fractional_damage(player, (dmg * dtime) / 8)
365                 else
366                     dmg = nil
367                 end
368                 -- on_dieplayer will have already set this if the player died
369                 if not killed then
370                     radiated_players[pn] = dmg
371                 end
06dec2 372             end
b739ed 373         end)
06dec2 374
b739ed 375         minetest.register_on_dieplayer(function(player)
NZ 376             radiated_players[player:get_player_name()] = nil
377         end)
378     end
1da213 379 end
S 380
381 -- Radioactive materials that can result from destroying a reactor
382 local griefing = technic.config:get_bool("enable_corium_griefing")
383
384 for _, state in pairs({"flowing", "source"}) do
385     minetest.register_node("technic:corium_"..state, {
386         description = S(state == "source" and "Corium Source" or "Flowing Corium"),
387         drawtype = (state == "source" and "liquid" or "flowingliquid"),
83a4bb 388         tiles = {{
1da213 389             name = "technic_corium_"..state.."_animated.png",
S 390             animation = {
391                 type = "vertical_frames",
392                 aspect_w = 16,
393                 aspect_h = 16,
394                 length = 3.0,
395             },
396         }},
83a4bb 397         special_tiles = {
N 398             {
399                 name = "technic_corium_"..state.."_animated.png",
400                 backface_culling = false,
401                 animation = {
402                     type = "vertical_frames",
403                     aspect_w = 16,
404                     aspect_h = 16,
405                     length = 3.0,
406                 },
407             },
408             {
409                 name = "technic_corium_"..state.."_animated.png",
410                 backface_culling = true,
411                 animation = {
412                     type = "vertical_frames",
413                     aspect_w = 16,
414                     aspect_h = 16,
415                     length = 3.0,
416                 },
417             },
418         },
1da213 419         paramtype = "light",
S 420         paramtype2 = (state == "flowing" and "flowingliquid" or nil),
421         light_source = (state == "source" and 8 or 5),
422         walkable = false,
423         pointable = false,
424         diggable = false,
425         buildable_to = true,
426         drop = "",
427         drowning = 1,
428         liquidtype = state,
429         liquid_alternative_flowing = "technic:corium_flowing",
430         liquid_alternative_source = "technic:corium_source",
431         liquid_viscosity = LAVA_VISC,
432         liquid_renewable = false,
433         damage_per_second = 6,
434         post_effect_color = {a=192, r=80, g=160, b=80},
435         groups = {
436             liquid = 2,
437             hot = 3,
438             igniter = (griefing and 1 or 0),
06dec2 439             radioactive = (state == "source" and 12 or 6),
1da213 440             not_in_creative_inventory = (state == "flowing" and 1 or nil),
S 441         },
442     })
443 end
444
445 if rawget(_G, "bucket") and bucket.register_liquid then
446     bucket.register_liquid(
447         "technic:corium_source",
448         "technic:corium_flowing",
449         "technic:bucket_corium",
450         "technic_bucket_corium.png",
451         "Corium Bucket"
452     )
453 end
454
455 minetest.register_node("technic:chernobylite_block", {
456         description = S("Chernobylite Block"),
457     tiles = {"technic_chernobylite_block.png"},
458     is_ground_content = true,
06dec2 459     groups = {cracky=1, radioactive=4, level=2},
1da213 460     sounds = default.node_sound_stone_defaults(),
S 461     light_source = 2,
462 })
463
464 minetest.register_abm({
78f16c 465     label = "Corium: boil-off water (sources)",
1da213 466     nodenames = {"group:water"},
S 467     neighbors = {"technic:corium_source"},
468     interval = 1,
469     chance = 1,
470     action = function(pos, node)
471         minetest.remove_node(pos)
472     end,
473 })
474
475 minetest.register_abm({
78f16c 476     label = "Corium: boil-off water (flowing)",
1da213 477     nodenames = {"technic:corium_flowing"},
S 478     neighbors = {"group:water"},
479     interval = 1,
480     chance = 1,
481     action = function(pos, node)
482         minetest.set_node(pos, {name="technic:chernobylite_block"})
483     end,
484 })
485
486 minetest.register_abm({
78f16c 487     label = "Corium: become chernobylite",
1da213 488     nodenames = {"technic:corium_flowing"},
S 489     interval = 5,
490     chance = (griefing and 10 or 1),
491     action = function(pos, node)
492         minetest.set_node(pos, {name="technic:chernobylite_block"})
493     end,
494 })
495
496 if griefing then
497     minetest.register_abm({
78f16c 498         label = "Corium: griefing",
1da213 499         nodenames = {"technic:corium_source", "technic:corium_flowing"},
S 500         interval = 4,
501         chance = 4,
502         action = function(pos, node)
503             for _, offset in ipairs({
504                 vector.new(1,0,0),
505                 vector.new(-1,0,0),
506                 vector.new(0,0,1),
507                 vector.new(0,0,-1),
508                 vector.new(0,-1,0),
509             }) do
510                 if math.random(8) == 1 then
511                     minetest.dig_node(vector.add(pos, offset))
512                 end
513             end
514         end,
515     })
516 end