Files
factorio-expcluster/exp_scenario/module/control/death_markers.lua
T
bbassieandClaude Opus 5.5 4bc1773c82 Address review: page layout, five more features, framework changes
Framework:
- Intervals are gone. Update periods are hardcoded on_nth_tick values
  again, since nobody has needed to change them.
- apply is a single loop over the declared fields and logs a warning
  when an override is invalid. receive_update also warns about
  unknown keys.
- Nested tables are addressed as "section.key". Lists of strings can be
  overridden. Features.optional declares a value with no default.
- Features.on_apply rebuilds values derived from the config, such as
  lookup sets. It also runs during on_load.

exp_roles is assumed to be present:
- AFK Kick picks a role (active_role_id) instead of a custom check
  function.
- Chat Auto Reply, Protection, and Nuke Protection no longer take a
  permission as a setting.

New features, each chosen for an edge case:
- Discord Alerts: conditional handlers, and a command map that becomes
  a list.
- Entity Protection: lists turned into sets at load, and bookkeeping
  that must keep running while disabled.
- Nuke Protection: config keyed by defines, flattened to a banned items
  list.
- Popups: one config shared by two control files.
- Spawn Area: nested sections, and settings that only apply when the
  map is created.

Page: now laid out like the role page. It has a search across every
setting, one long list with a sticky sidebar to jump between sections,
and the enabled switch separated at the front of each header with
Reset to defaults on the right. Changed rows are highlighted, and a
bottom bar offers Revert and Apply. Role settings use a role picker and
lists use a tag select.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:28:23 +00:00

161 lines
5.6 KiB
Lua

--[[-- Control - Death Markers
Makes markers on the map where places have died and reclaims items if not recovered
]]
local ExpUtil = require("modules/exp_util")
local Features = require("modules/exp_scenario/features")
local Storage = require("modules/exp_util/storage")
local config = Features.config("death_markers", {
collect_corpses = true, --- @setting collect_corpses enables items being returned to the spawn point in chests upon corpse expiring
show_map_markers = true, --- @setting show_map_markers shows markers on the map where bodies are
clean_map_markers = false, --- @setting clean_map_markers removes the map marker once the body is gone
include_time_of_death = true, --- @setting include_time_of_death weather to include the time of death on the map marker
map_icon = nil, --- @setting map_icon the icon that the map marker shows; nil means no icon; format as a SingleID
show_light_at_corpse = true, --- @setting show_light_at_corpse if a light should be rendered at the corpse
show_line_to_corpse = true, --- @setting show_line_to_corpse if a line should be rendered from you to your corpse
})
local map_tag_time_format = ExpUtil.format_time_factory{ format = "short", hours = true, minutes = true }
--- @class CorpseData
--- @field player LuaPlayer
--- @field corpse LuaEntity
--- @field tag LuaCustomChartTag?
--- @field created_at number
--- @type table<number, CorpseData>
local character_corpses = {}
Storage.register(character_corpses, function(tbl)
character_corpses = tbl
end)
--- Creates a new death marker and saves it to the given death
--- @param corpse_data CorpseData
local function create_map_tag(corpse_data)
local player = corpse_data.player
local message = player.name .. " died"
if config.include_time_of_death then
local time = map_tag_time_format(corpse_data.created_at)
message = message .. " at " .. time
end
corpse_data.tag = (player.force --[[@as LuaForce]]).add_chart_tag(corpse_data.corpse.surface, {
position = corpse_data.corpse.position,
icon = config.map_icon,
text = message,
})
end
--- Checks that all map tags are present and valid, creating any that are missing
local function check_map_tags()
if not config.show_map_markers then return end
for _, corpse_data in pairs(character_corpses) do
if not corpse_data.tag or not corpse_data.tag.valid then
create_map_tag(corpse_data)
end
end
end
-- when a player dies a new death is added to the records and a map marker is made
--- @param event EventData.on_player_died
local function on_player_died(event)
local player = assert(game.get_player(event.player_index))
local corpse = player.surface.find_entity("character-corpse", player.physical_position)
if not corpse or not corpse.valid then return end
local corpse_data = {
player = player,
corpse = corpse,
created_at = event.tick,
}
local registration_number = script.register_on_object_destroyed(corpse)
character_corpses[registration_number] = corpse_data
-- Create a map marker
if config.show_map_markers then
create_map_tag(corpse_data)
end
-- Draw a light attached to the corpse with the player color
if config.show_light_at_corpse then
rendering.draw_light{
sprite = "utility/light_medium",
surface = player.surface,
color = player.color,
force = player.force,
target = corpse,
}
end
end
--- Called to remove stale corpse data
--- @param event EventData.on_object_destroyed
local function on_object_destroyed(event)
local corpse_data = character_corpses[event.registration_number]
character_corpses[event.registration_number] = nil
if not corpse_data then
return
end
local tag = corpse_data.tag
if tag and config.clean_map_markers then
tag.destroy()
end
end
--- Draw lines to the player corpse
--- @param event EventData.on_player_respawned
local function on_player_respawned(event)
if not config.show_line_to_corpse then return end
local index = event.player_index
local player = assert(game.get_player(index))
for _, corpse_data in pairs(character_corpses) do
if corpse_data.player.index == index then
local line_color = player.color
line_color.a = .3
rendering.draw_line{
color = line_color,
from = player.character,
to = corpse_data.corpse,
surface = player.surface,
players = { index },
draw_on_ground = true,
dash_length = 1,
gap_length = 1,
width = 2,
}
end
end
end
--- Collect all items from expired character corpses
--- @param event EventData.on_character_corpse_expired
local function on_character_corpse_expired(event)
if not config.collect_corpses then return end
local corpse = event.corpse
local inventory = assert(corpse.get_inventory(defines.inventory.character_corpse))
ExpUtil.transfer_inventory_to_surface{
inventory = inventory,
surface = corpse.surface,
name = "iron-chest",
allow_creation = true,
}
end
local e = defines.events
return Features.guard(config, {
on_nth_tick = {
[60 * 60 * 5] = check_map_tags,
},
events = {
[e.on_player_died] = on_player_died,
[e.on_object_destroyed] = on_object_destroyed,
[e.on_player_respawned] = on_player_respawned,
[e.on_character_corpse_expired] = on_character_corpse_expired,
},
})