diff --git a/death.lua b/death.lua
index eb057e393baef974841222c1743d0d1bd5cfbc3a..6d36664510982986f2494609afc718d637cab6d6 100644
--- a/death.lua
+++ b/death.lua
@@ -1,9 +1,225 @@
 
--- adopted from https://github.com/pandorabox-io/pandorabox_custom/blob/master/death.lua
-local BONES_WAYPOINT_EXPIRES_SECONDS = 42 * 60
+-- adapted from https://github.com/pandorabox-io/pandorabox_custom/blob/master/death.lua
+local WAYPOINT_EXPIRES_SECONDS = tonumber(minetest.settings:get("kif_custom.death.waypoint_expires_seconds")) or 60 * 60
+local WAYPOINT_EXPIRES_SECONDS_CREATIVE = tonumber(minetest.settings:get("kif_custom.death.waypoint_expires_seconds_creative")) or 5 * 60
+
+local UPDATE_INTERVAL = tonumber(minetest.settings:get("kif_custom.death.update_interval")) or 5
+local WAYPOINT_SATURATION = tonumber(minetest.settings:get("kif_custom.death.waypoint_saturation")) or 1
+
+local MARKERS_FILE = minetest.get_worldpath().."/death_markers.json"
+
+
+-- table for deaths indexed by player name and pos
+local death_markers = {}
+local dirty = false
+local WRITEBACK_INTERVAL_SEC = 10
+
+-- table to remember which hud elements we know and manage (per player and pos)
+local managed_hud_ids = {}
+
+-- color calculation got a bit crazy, so let's cache these...
+local color_cache = {}
+
+
+local read_json_file = function(path)
+	local file = io.open(path, "r")
+	local content = {}
+	if file then
+		local json = file:read("*a")
+		content = minetest.parse_json(json or "[]") or {}
+		file:close()
+	end
+	return content
+end
+
+local write_json_file = function(path, content)
+	local file = io.open(path,"w")
+	local json = minetest.write_json(content)
+	if file and file:write(json) and file:close() then
+		return true
+	else
+		return false
+	end
+end
+
+death_markers = read_json_file(MARKERS_FILE)
+
+local mark_dirty_and_schedule_writeback = function()
+	if not dirty then
+		dirty = true
+		minetest.after(WRITEBACK_INTERVAL_SEC, function()
+			if dirty then
+				write_json_file(MARKERS_FILE, death_markers)
+				dirty = false
+			end
+		end)
+	end
+end
+
+
+-- add something to the bones on_punch to remove markers when the bones are picked up
+
+local bones_def = minetest.registered_items["bones:bones"]
+assert(bones_def)
+
+local bones_on_punch = bones_def.on_punch
+assert(bones_on_punch)
+assert(type(bones_on_punch) == "function")
+
+minetest.override_item("bones:bones", {
+	on_punch = function(pos, node, player)
+		-- call original function
+		bones_on_punch(pos, node, player)
+
+		local player_name = player:get_player_name()
+		local pos_string = minetest.pos_to_string(pos)
+
+		if death_markers[player_name] ~= nil and death_markers[player_name][pos_string] ~= nil then
+			minetest.after(1, function()
+				if minetest.get_node(pos).name ~= "bones:bones" then
+					death_markers[player_name][pos_string] = nil
+					update_hud_markers(player_name)
+				end
+			end)
+		end
+	end
+})
+
+
+minetest.register_on_shutdown(function()
+	if dirty then
+		write_json_file(MARKERS_FILE, death_markers)
+		dirty = false
+	end
+
+	-- clean up markers (not sure if this makes sense, but I got weird results before, so...)
+	for player_name, hud_ids in pairs(managed_hud_ids) do
+		local player = minetest.get_player_by_name(player_name)
+
+		if player ~= nil then
+			for pos_string, marker in pairs(hud_ids) do
+				player:hud_remove(hud_id)
+			end
+		end
+		managed_hud_ids[player_name] = nil
+	end
+end)
+
+
+local interpolate = function(a, b, t)
+	return t*(b-a) + a
+end
+
+local calculate_color = function(t)
+	-- clamp t between 0 and 1, in steps of 0.01
+	t = math.max(0, math.min(1, math.floor(t * 100 + 0.5) / 100))
+
+	if color_cache[t] == nil then
+		-- helper variables
+		local t2 = t^2	-- t squared
+		local it = 1 - t	-- inverse t
+		local it2 = it^2	-- inverse t squared
+
+		local r = t2
+		local g = 2*t * it
+		local b = it2
+		local avg = math.sqrt(r^2 + g^2 + b^2)
+
+		r = interpolate(avg, r, WAYPOINT_SATURATION)
+		g = interpolate(avg, g, WAYPOINT_SATURATION)
+		b = interpolate(avg, b, WAYPOINT_SATURATION)
+
+		local white_start = it2^2
+		local dimming = 1 - t^8
+
+		r = (r * (1-white_start) + white_start) * dimming
+		g = (g * (1-white_start) + white_start) * dimming
+		b = (b * (1-white_start) + white_start) * dimming
+
+		-- we have 255 steps per subpixel
+		local base = 0xFF
+
+		-- clamp values, discard fractions
+		r = math.floor(base * math.max(0, math.min(1, r)))
+		g = math.floor(base * math.max(0, math.min(1, g)))
+		b = math.floor(base * math.max(0, math.min(1, b)))
+
+		-- pack it into one number representing the RGB values
+		color_cache[t] = r*2^16 + g*2^8 + b
+	end
+
+	return color_cache[t]
+end
+
+local update_hud_markers = function(player_name)
+	if death_markers[player_name] ~= nil then
+		local player = minetest.get_player_by_name(player_name)
+		local now = os.time()
+		local valid_hud_ids = {}
+
+		for pos_string, marker in pairs(death_markers[player_name]) do
+			local t = now - marker.timestamp
+			if marker.is_creative then
+				t = t / WAYPOINT_EXPIRES_SECONDS_CREATIVE
+			else
+				t = t / WAYPOINT_EXPIRES_SECONDS
+			end
+
+			if managed_hud_ids[player_name] ~= nil and managed_hud_ids[player_name][pos_string] ~= nil then
+				local hud_id = managed_hud_ids[player_name][pos_string]
+				valid_hud_ids[hud_id] = hud_id
+
+				if t > 1 then
+					death_markers[player_name][pos_string] = nil
+					mark_dirty_and_schedule_writeback()
+
+					if player ~= nil then
+						player:hud_remove(hud_id)
+						managed_hud_ids[player_name][pos_string] = nil
+					end
+				else
+					if player ~= nil then
+						player:hud_change(hud_id, "name", marker.text)--.." (t = "..tostring(math.floor(t * 100 + 0.5) / 100)..")")
+						player:hud_change(hud_id, "number", calculate_color(t))
+					end
+				end
+			else
+				if player ~= nil and t < 1 then
+					if managed_hud_ids[player_name] == nil then
+						managed_hud_ids[player_name] = {}
+					end
+
+					local pos = minetest.string_to_pos(pos_string)
+
+					local hud_id = player:hud_add({
+						hud_elem_type = "waypoint",
+						name = marker.text,--.." (add | t = "..tostring(math.floor(t * 100 + 0.5) / 100)..")",
+						text = "m",
+						number = calculate_color(t),
+						world_pos = pos
+					})
+					managed_hud_ids[player_name][pos_string] = hud_id
+					valid_hud_ids[hud_id] = hud_id
+				end
+			end
+		end
+
+		if player ~= nil and managed_hud_ids[player_name] ~= nil then
+			-- everything that remains should be removed
+			for pos_string, hud_id in pairs(managed_hud_ids[player_name]) do
+				if valid_hud_ids[hud_id] == nil then
+					player:hud_remove(hud_id)
+					managed_hud_ids[player_name][pos_string] = nil
+				end
+			end
+		end
+	end
+end
 
 minetest.register_on_dieplayer(function(player)
 	local player_name = player:get_player_name()
+	local is_creative = creative and creative.is_enabled_for(player_name)
+
 	local pos = player:get_pos()
 
 	pos.x = math.floor(pos.x + 0.5)
@@ -12,31 +228,72 @@ minetest.register_on_dieplayer(function(player)
 
 	local pos_string = minetest.pos_to_string(pos)
 
-	minetest.log("action", "[death] player '" .. player_name .. "' died at " .. pos_string)
+	minetest.log("action", "[death] player '" .. player_name .. "' died at " .. pos_string .. (is_creative and " (creative)" or ""))
 	minetest.chat_send_player(player_name, "You died at " .. pos_string)
 
-	local bone_string = "Bones"
-	if player.get_attribute then
-		-- [xp_redo] keeps track of deathcount, let's see if it is there
-		local count = player:get_attribute("died")
-		if count then
-			bone_string = "Bone #" .. tostring(count)
-		end
-	end -- if not fake player
-	local hud_id = player:hud_add({
-		hud_elem_type = "waypoint",
-		name = bone_string .. " " .. pos_string,
-		text = "m",
-		number = 0xFFFFFF,
-		world_pos = pos
-	})
-
-	minetest.after(BONES_WAYPOINT_EXPIRES_SECONDS, function()
-		-- retrieve player by name, the "player" object should not be carried across server-steps
-		player = minetest.get_player_by_name(player_name)
-		if player then
-			player:hud_remove(hud_id)
+	local msg_string = ""
+
+	if not is_creative then
+		msg_string = "Bones"
+
+		if player.get_attribute then
+			-- [xp_redo] keeps track of deathcount, let's see if it is there
+			local count = player:get_attribute("died")
+			if count then
+				msg_string = "Bone #" .. tostring(count)
+			end
 		end
-	end)
+	else
+		msg_string = "Death"
+	end
+
+	if death_markers[player_name] == nil then
+		death_markers[player_name] = {}
+	end
+	death_markers[player_name][pos_string] = {
+		text = msg_string,
+		timestamp = os.time(),
+		is_creative = is_creative,
+	}
+	mark_dirty_and_schedule_writeback()
+
+	update_hud_markers(player_name)
+end)
+
+minetest.register_on_joinplayer(function(player)
+	local player_name = player:get_player_name()
+	update_hud_markers(player_name)
 
+	if death_markers[player_name] ~= nil then
+		minetest.after(2, function()
+			local markers = {}
+			for pos_string, marker in pairs(death_markers[player_name]) do
+				table.insert(markers, pos_string.." "..marker.text..(marker.is_creative and " (creative)" or ""))
+			end
+			--minetest.chat_send_player(player_name, "Your death markers ("..tostring(#markers).."): "..table.concat(markers, ", "))
+		end)
+	end
 end)
+
+minetest.register_on_leaveplayer(function(player)
+	local player_name = player:get_player_name()
+
+	--[[
+	for pos_string, marker in pairs(managed_hud_ids[player_name]) do
+		player:hud_remove(hud_id)
+	end
+	managed_hud_ids[player_name] = nil
+	]]--
+end)
+
+
+local update_loop
+update_loop = function()
+	for player_name, markers in pairs(managed_hud_ids) do
+		update_hud_markers(player_name)
+	end
+
+	minetest.after(UPDATE_INTERVAL, update_loop)
+end
+
+update_loop()
diff --git a/settingtypes.txt b/settingtypes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..84ca5f47fa1b95e20fa5822ad8508659297ae462
--- /dev/null
+++ b/settingtypes.txt
@@ -0,0 +1,15 @@
+[Death Markers]
+
+# Interval at which death markers are updated in seconds
+kif_custom.death.update_interval (Update interval) int 5
+
+# The amount of seconds in which waypoints to bones expire.
+kif_custom.death.waypoint_expires_seconds (Seconds in which waypoints expire) int 3600
+
+# The amount of seconds in which waypoints for deaths in creative mode expire.
+kif_custom.death.waypoint_expires_seconds_creative (Seconds in which waypoints expire, creative) int 600
+
+# The saturation of the waypoint text color. The text fades not only from white to black
+# until it disappears, but also changes to a blue, green and finally red tint before disappearing.
+# The saturation interpolates between the color and the grey shade of the same value.
+kif_custom.death.waypoint_saturation (Color saturation) float 1 0 2