-- This mod deals radiation damage to players that are too close too
-- the world border.

local S = minetest.get_translator("hades_bounds")

-- World boundaries
local END_MIN, END_MAX = minetest.get_mapgen_edges()

-- If any of the absolute world boundaries are smaller than this
-- value, radiation damage is disabled entirely and this mod is
-- essentially a no-op.
local SAFE_ZONE = 2000

-- For small worlds, disable radiation damage entirely
if math.abs(END_MIN.x) < SAFE_ZONE or math.abs(END_MAX.x) < SAFE_ZONE or
		math.abs(END_MIN.y) < SAFE_ZONE or math.abs(END_MAX.y) < SAFE_ZONE or
		math.abs(END_MIN.z) < SAFE_ZONE or math.abs(END_MAX.z) < SAFE_ZONE then
	minetest.log("action", "[hades_bounds] Small world size detected. Radiation damage is disabled")

	-- There is nothing else to do, so just return from this mod
	return
end

-- Boilerplate for compatibiliity with pre-5.9.0
-- versions of Luanti/Minetest
local hud_def_type_field
if minetest.features.hud_def_type_field then
	hud_def_type_field = "type"
else
	hud_def_type_field = "hud_elem_type"
end

local players_out_of_bounds = {}
local players_out_of_bounds_blinding = {}

local has_death_messages_mod = minetest.get_modpath("hades_death_messages") ~= nil

-- Where the player starts to take damage
local BOUNDARY_DIFF = 500.5
-- Where the textual out-of-bounds warning appears
local BOUNDARY_WARNING_DIFF = BOUNDARY_DIFF + 50
-- Where the out-of-bounds "green radiation warning screen" appears
local BOUNDARY_WARNING_DIFF_2 = BOUNDARY_DIFF + 20
-- At which time interval (in seconds) the player takes radiation damage
local BOUNDARY_TIME_CHECK = 1
-- Every how many blocks the radiation damage increases
local BOUNDARY_DAMAGE_INCREASE = 50
-- Maximum boundary damage to deal
local BOUNDARY_MAX_DAMAGE = 4

local timer = 0

local function out_of_bounds(pos, diff)
	local boundary_min_x = END_MIN.x + diff
	local boundary_max_x = END_MAX.x - diff
	local boundary_min_z = END_MIN.z + diff
	local boundary_max_z = END_MAX.z - diff
	local boundary_max_y = END_MAX.y - diff
	return pos.x > boundary_max_x or pos.x < boundary_min_x or pos.z > boundary_max_z or pos.z < boundary_min_z or pos.y > boundary_max_y
end
local function out_of_bounds_farthest(pos, diff)
	local boundary_min_x = math.abs(END_MIN.x + diff)
	local boundary_max_x = math.abs(END_MAX.x - diff)
	local boundary_min_z = math.abs(END_MIN.z + diff)
	local boundary_max_z = math.abs(END_MAX.z - diff)
	local boundary_max_y = math.abs(END_MAX.y - diff)
	local dists = {
		math.abs(pos.x) - boundary_max_x,
		math.abs(pos.x) - boundary_min_x,
		math.abs(pos.z) - boundary_max_z,
		math.abs(pos.z) - boundary_min_z,
		math.abs(pos.y) - boundary_max_y,
	}
	local far = 0
	for d=1, #dists do
		if dists[d] > far then
			far = dists[d]
		end
	end
	return far
end

minetest.register_globalstep(function(dtime)
	timer = timer + dtime
	if timer < BOUNDARY_TIME_CHECK then
		return
	end
	timer = 0
	local players = minetest.get_connected_players()
	for p=1, #players do
		local player = players[p]
		local name = player:get_player_name()
		local pos = player:get_pos()
		if out_of_bounds(pos, BOUNDARY_WARNING_DIFF) then
			local oob = out_of_bounds(pos, BOUNDARY_DIFF)
			local text, img
			if oob then
				text = S("WARNING: Critically high radiation!")
				img = "hades_bounds_screen_radiation_full.png"
			else
				text = S("WARNING: High radiation!")
				if out_of_bounds(pos, BOUNDARY_WARNING_DIFF_2) then
					img = "hades_bounds_screen_radiation_warn.png"
				end
			end
			if not players_out_of_bounds[name] then
				local hudid = player:hud_add({
					[hud_def_type_field]= "text",
					text = text,
					position = { x = 0.5, y = 0.2 },
					number = 0xFFFF00,
					z_index = 100,
					scale = { x = 100, y = 100 },
					size = { x = 2, y = 2 },
				})
				players_out_of_bounds[name] = hudid
			else
				player:hud_change(players_out_of_bounds[name], "text", text)
			end
			if img and not players_out_of_bounds_blinding[name] then
				local hudid = player:hud_add({
					[hud_def_type_field] = "image",
					text = img,
					position = { x = 0.5, y = 0.5 },
					number = 0xFFFF00,
					z_index = -400,
					scale = { x = -100, y = -100 },
				})
				players_out_of_bounds_blinding[name] = hudid
			elseif img then
				player:hud_change(players_out_of_bounds_blinding[name], "text", img)
			end
			local hp = player:get_hp()
			if oob and hp > 0 then
				local how_deep_in = out_of_bounds_farthest(pos, BOUNDARY_DIFF)
				local dmg = math.min(BOUNDARY_MAX_DAMAGE, math.max(1, math.floor(how_deep_in / BOUNDARY_DAMAGE_INCREASE)))
				if has_death_messages_mod then
					hades_death_messages.player_damage(player, S("Radiation poisoning"))
				end
				player:set_hp(hp - dmg, {type="set_hp", from="mod", _custom_reason="radiation"})
			end
		else
			if players_out_of_bounds[name] then
				player:hud_remove(players_out_of_bounds[name])
				players_out_of_bounds[name] = nil
			end
		end
		if not out_of_bounds(pos, BOUNDARY_WARNING_DIFF_2) then
			if players_out_of_bounds_blinding[name] then
				player:hud_remove(players_out_of_bounds_blinding[name])
				players_out_of_bounds_blinding[name] = nil
			end
		end
	end
end)

minetest.register_on_leaveplayer(function(player)
	local name = player:get_player_name()
	players_out_of_bounds[name] = nil
	players_out_of_bounds_blinding[name] = nil
end)
