Files
factorio-expcluster/exp_scenario/module/control/death_markers.lua
T
bbassieandClaude Opus 5.5 628e4df514 Rework features into a Feature class with per field events
Review points from Cooldude:

- Feature.register(name[, config]) is required again and returns a
  feature object. Each feature has its config, its fields, and its
  overrides, so one registry holds all the data. Only the overrides are
  stored. On load each feature points at its own stored table again.
- The methods are feature:guard(lib), feature:update_config(enabled,
  values), and feature:is_enabled(). Feature.get(name) looks up a
  feature, and commands flag the feature object.
- update_config raises on_config_updated once per changed value, with
  the feature name, path, old value, and new value. The gui refresh only
  reacts to the "enabled" path.
- apply only sets values. on_apply is gone. Lookup sets built from
  lists use Feature.to_set, which is cached by the list itself. A
  replaced list gets a new set, so the sets are also correct after a
  load. Protection exposes getters for its protected names and types.

Web: settings use small inputs, the same size as the config pages.
Fields are shaped like clusterio config field definitions. A field
with an inputComponent renders the component registered with the web
interface, so the role setting now uses core's role input, and other
plugin inputs can be used later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:03:11 +00:00

162 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 Feature = require("modules/exp_scenario/features")
local Storage = require("modules/exp_util/storage")
local feature = Feature.register("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 config = feature.config
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 feature:guard{
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,
},
}