mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-13 16:14:52 +09:00
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>
161 lines
5.6 KiB
Lua
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,
|
|
},
|
|
})
|