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