From da95be53ec5703e808233d7b45fc42fb500863dd Mon Sep 17 00:00:00 2001
From: coil <51716565+coil0@users.noreply.github.com>
Date: Mon, 30 Dec 2019 21:02:01 +0100
Subject: [PATCH] Fix radiation protection when armor group is not set (#509)

---
 technic/radiation.lua |  177 +++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 147 insertions(+), 30 deletions(-)

diff --git a/technic/radiation.lua b/technic/radiation.lua
index 2dec38b..e4ed5c5 100644
--- a/technic/radiation.lua
+++ b/technic/radiation.lua
@@ -54,8 +54,15 @@
 	["default:lava_source"] = 17,
 	["default:mese"] = 21,
 	["default:mossycobble"] = 15,
-	["default:nyancat"] = 1000,
-	["default:nyancat_rainbow"] = 1000,
+	["default:tinblock"] = 37,
+	["pbj_pup:pbj_pup"] = 10000,
+	["pbj_pup:pbj_pup_candies"] = 10000,
+	["gloopblocks:rainbow_block_diagonal"] = 5000,
+	["gloopblocks:rainbow_block_horizontal"] = 10000,
+	["default:nyancat"] = 10000,
+	["default:nyancat_rainbow"] = 10000,
+	["nyancat:nyancat"] = 10000,
+	["nyancat:nyancat_rainbow"] = 10000,
 	["default:obsidian"] = 18,
 	["default:obsidian_glass"] = 18,
 	["default:sand"] = 10,
@@ -70,6 +77,7 @@
 	["default:stone_with_gold"] = 34,
 	["default:stone_with_iron"] = 20,
 	["default:stone_with_mese"] = 17,
+	["default:stone_with_tin"] = 19,
 	["default:stonebrick"] = 17,
 	["default:water_flowing"] = 2.8,
 	["default:water_source"] = 5.6,
@@ -135,12 +143,10 @@
 	["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,
+	["basic_materials:brass_block"] = 43,
 	["technic:carbon_steel_block"] = 40,
 	["technic:cast_iron_block"] = 40,
 	["technic:chernobylite_block"] = 40,
@@ -230,30 +236,23 @@
 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
+Damage is processed at rates down to 0.2 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
+group value.  Computed damage rates below 0.2 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 abdomen_offset = 1
 local cache_scaled_shielding = {}
-local rad_dmg_cutoff = 0.25
+local rad_dmg_cutoff = 0.2
+local radiated_players = {}
 
-local function dmg_player(pos, o, strength)
-	local pl_pos = o:getpos()
-	pl_pos.y = pl_pos.y + abdomen_offset
-	local shielding = 0
-	local dist = vector.distance(pos, pl_pos)
-	for ray_pos in technic.trace_node_ray(pos,
-			vector.direction(pos, pl_pos), dist) do
-		local shield_name = minetest.get_node(ray_pos).name
-		shielding = shielding + node_radiation_resistance(shield_name) * 0.1
-	end
-	local dmg = (strength * strength) /
-		(math.max(0.75, dist * dist) * math.exp(shielding))
-	if dmg < rad_dmg_cutoff then return end
+local armor_enabled = technic.config:get_bool("enable_radiation_protection")
+local entity_damage = technic.config:get_bool("enable_entity_radiation_damage")
+local longterm_damage = technic.config:get_bool("enable_longterm_radiation_damage")
+
+local function apply_fractional_damage(o, dmg)
 	local dmg_int = math.floor(dmg)
 	-- The closer you are to getting one more damage point,
 	-- the more likely it will be added.
@@ -261,7 +260,77 @@
 		dmg_int = dmg_int + 1
 	end
 	if dmg_int > 0 then
-		o:set_hp(math.max(o:get_hp() - dmg_int, 0))
+		local new_hp = math.max(o:get_hp() - dmg_int, 0)
+		o:set_hp(new_hp)
+		return new_hp == 0
+	end
+	return false
+end
+
+local function calculate_base_damage(node_pos, object_pos, strength)
+	local shielding = 0
+	local dist = vector.distance(node_pos, object_pos)
+
+	for ray_pos in technic.trace_node_ray(node_pos,
+			vector.direction(node_pos, object_pos), dist) do
+		local shield_name = minetest.get_node(ray_pos).name
+		shielding = shielding + node_radiation_resistance(shield_name) * 0.025
+	end
+
+	local dmg = (strength * strength) /
+		(math.max(0.75, dist * dist) * math.exp(shielding))
+
+	if dmg < rad_dmg_cutoff then return end
+	return dmg
+end
+
+local function calculate_damage_multiplier(object)
+	local ag = object.get_armor_groups and object:get_armor_groups()
+	if not ag then
+		return 0
+	end
+	if ag.immortal then
+		return 0
+	end
+	if ag.radiation then
+		return 0.01 * ag.radiation
+	elseif armor_enabled then
+		return 0
+	end
+	if ag.fleshy then
+		return math.sqrt(0.01 * ag.fleshy)
+	end
+	return 0
+end
+
+local function calculate_object_center(object)
+	if object:is_player() then
+		return {x=0, y=abdomen_offset, z=0}
+	end
+	return {x=0, y=0, z=0}
+end
+
+local function dmg_object(pos, object, strength)
+	local obj_pos = vector.add(object:get_pos(), calculate_object_center(object))
+	local mul
+	if armor_enabled or entity_damage then
+		-- we need to check may the object be damaged even if armor is disabled
+		mul = calculate_damage_multiplier(object)
+		if mul == 0 then
+			return
+		end
+	end
+	local dmg = calculate_base_damage(pos, obj_pos, strength)
+	if not dmg then
+		return
+	end
+	if armor_enabled then
+		dmg = dmg * mul
+	end
+	apply_fractional_damage(object, dmg)
+	if longterm_damage and object:is_player() then
+		local pn = object:get_player_name()
+		radiated_players[pn] = (radiated_players[pn] or 0) + dmg
 	end
 end
 
@@ -271,20 +340,43 @@
 	local max_dist = strength * rad_dmg_mult_sqrt
 	for _, o in pairs(minetest.get_objects_inside_radius(pos,
 			max_dist + abdomen_offset)) do
-		if o:is_player() then
-			dmg_player(pos, o, strength)
+		if (entity_damage or o:is_player()) and o:get_hp() > 0 then
+			dmg_object(pos, o, strength)
 		end
 	end
 end
 
-
-if minetest.setting_getbool("enable_damage") then
+if minetest.settings:get_bool("enable_damage") then
 	minetest.register_abm({
+		label = "Radiation damage",
 		nodenames = {"group:radioactive"},
 		interval = 1,
 		chance = 1,
 		action = dmg_abm,
 	})
+
+	if longterm_damage then
+		minetest.register_globalstep(function(dtime)
+			for pn, dmg in pairs(radiated_players) do
+				dmg = dmg - (dtime / 8)
+				local player = minetest.get_player_by_name(pn)
+				local killed
+				if player and dmg > rad_dmg_cutoff then
+					killed = apply_fractional_damage(player, (dmg * dtime) / 8)
+				else
+					dmg = nil
+				end
+				-- on_dieplayer will have already set this if the player died
+				if not killed then
+					radiated_players[pn] = dmg
+				end
+			end
+		end)
+
+		minetest.register_on_dieplayer(function(player)
+			radiated_players[player:get_player_name()] = nil
+		end)
+	end
 end
 
 -- Radioactive materials that can result from destroying a reactor
@@ -294,7 +386,7 @@
 	minetest.register_node("technic:corium_"..state, {
 		description = S(state == "source" and "Corium Source" or "Flowing Corium"),
 		drawtype = (state == "source" and "liquid" or "flowingliquid"),
-		[state == "source" and "tiles" or "special_tiles"] = {{
+		tiles = {{
 			name = "technic_corium_"..state.."_animated.png",
 			animation = {
 				type = "vertical_frames",
@@ -303,6 +395,28 @@
 				length = 3.0,
 			},
 		}},
+		special_tiles = {
+			{
+				name = "technic_corium_"..state.."_animated.png",
+				backface_culling = false,
+				animation = {
+					type = "vertical_frames",
+					aspect_w = 16,
+					aspect_h = 16,
+					length = 3.0,
+				},
+			},
+			{
+				name = "technic_corium_"..state.."_animated.png",
+				backface_culling = true,
+				animation = {
+					type = "vertical_frames",
+					aspect_w = 16,
+					aspect_h = 16,
+					length = 3.0,
+				},
+			},
+		},
 		paramtype = "light",
 		paramtype2 = (state == "flowing" and "flowingliquid" or nil),
 		light_source = (state == "source" and 8 or 5),
@@ -323,7 +437,7 @@
 			liquid = 2,
 			hot = 3,
 			igniter = (griefing and 1 or 0),
-			radioactive = (state == "source" and 16 or 8),
+			radioactive = (state == "source" and 12 or 6),
 			not_in_creative_inventory = (state == "flowing" and 1 or nil),
 		},
 	})
@@ -343,12 +457,13 @@
         description = S("Chernobylite Block"),
 	tiles = {"technic_chernobylite_block.png"},
 	is_ground_content = true,
-	groups = {cracky=1, radioactive=6, level=2},
+	groups = {cracky=1, radioactive=4, level=2},
 	sounds = default.node_sound_stone_defaults(),
 	light_source = 2,
 })
 
 minetest.register_abm({
+	label = "Corium: boil-off water (sources)",
 	nodenames = {"group:water"},
 	neighbors = {"technic:corium_source"},
 	interval = 1,
@@ -359,6 +474,7 @@
 })
 
 minetest.register_abm({
+	label = "Corium: boil-off water (flowing)",
 	nodenames = {"technic:corium_flowing"},
 	neighbors = {"group:water"},
 	interval = 1,
@@ -369,6 +485,7 @@
 })
 
 minetest.register_abm({
+	label = "Corium: become chernobylite",
 	nodenames = {"technic:corium_flowing"},
 	interval = 5,
 	chance = (griefing and 10 or 1),
@@ -379,6 +496,7 @@
 
 if griefing then
 	minetest.register_abm({
+		label = "Corium: griefing",
 		nodenames = {"technic:corium_source", "technic:corium_flowing"},
 		interval = 4,
 		chance = 4,
@@ -397,4 +515,3 @@
 		end,
 	})
 end
-

--
Gitblit v1.8.0