Files
factorio-expcluster/exp_scenario/module/commands/protected_entities.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

224 lines
7.9 KiB
Lua

--[[-- Commands - Protection
Adds commands that can add and remove protection
]]
local Storage = require("modules/exp_util/storage")
local AABB = require("modules/exp_util/aabb")
local contains_area = AABB.contains_area
local expand_area = AABB.expand
local Commands = require("modules/exp_commands")
local format_player_name = Commands.format_player_name_locale
local Roles = require("modules/exp_roles")
local EntityProtection = require("modules/exp_scenario/control/protection")
local get_entity_key = EntityProtection.get_entity_key
local get_area_key = EntityProtection.get_area_key
local Selection = require("modules/exp_util/selection")
local SelectEntities = Selection.connect("ExpCommand_ProtectEntity")
local SelectArea = Selection.connect("ExpCommand_ProtectArea")
local renders = {} --- @type table<number, table<string, LuaRenderObject>> Stores all renders for a player
Storage.register({
renders = renders,
}, function(tbl)
renders = tbl.renders
end)
--- Show a protected entity to a player
--- @param player LuaPlayer
--- @param entity LuaEntity
local function show_protected_entity(player, entity)
local key = get_entity_key(entity)
if renders[player.index][key] then return end
local selection_box = entity.selection_box
local rb = selection_box.right_bottom
local position = entity.position
renders[player.index][key] = rendering.draw_sprite{
sprite = "utility/notification",
target = entity,
target_offset = {
(rb.x - position.x) * 0.75,
(rb.y - position.y) * 0.75,
},
x_scale = 2,
y_scale = 2,
surface = entity.surface,
players = { player },
}
end
--- Show a protected area to a player
--- @param player LuaPlayer
--- @param surface LuaSurface
--- @param area BoundingBox.struct
local function show_protected_area(player, surface, area)
local key = get_area_key(area)
if renders[player.index][key] then return end
renders[player.index][key] = rendering.draw_rectangle{
color = { 1, 1, 0, 0.5 },
filled = false,
width = 3,
left_top = area.left_top,
right_bottom = area.right_bottom,
surface = surface,
players = { player },
}
end
--- Remove a render object for a player
--- @param player LuaPlayer
--- @param key string
local function remove_render(player, key)
local render = renders[player.index][key]
if render and render.valid then render.destroy() end
renders[player.index][key] = nil
end
--- Toggles entity protection selection
Commands.new("protect-entity", { "exp-commands_entity-protection.description-entity" })
:add_aliases{ "pe" }
:register(function(player)
if SelectEntities:stop(player) then
return Commands.status.success{ "exp-commands_entity-protection.exit-entity" }
end
SelectEntities:start(player)
return Commands.status.success{ "exp-commands_entity-protection.enter-entity" }
end)
--- Toggles area protection selection
Commands.new("protect-area", { "exp-commands_entity-protection.description-area" })
:add_aliases{ "pa" }
:register(function(player)
if SelectArea:stop(player) then
return Commands.status.success{ "exp-commands_entity-protection.exit-area" }
end
SelectArea:start(player)
return Commands.status.success{ "exp-commands_entity-protection.enter-area" }
end)
--- When an area is selected to add protection to entities
SelectEntities:on_selection(function(event)
local player = game.players[event.player_index]
for _, entity in ipairs(event.entities) do
EntityProtection.add_entity(entity)
show_protected_entity(player, entity)
end
player.print({ "exp-commands_entity-protection.protected-entities", #event.entities }, Commands.print_settings.default)
end)
--- When an area is selected to remove protection from entities
SelectEntities:on_alt_selection(function(event)
local player = game.players[event.player_index]
for _, entity in ipairs(event.entities) do
EntityProtection.remove_entity(entity)
remove_render(player, get_entity_key(entity))
end
player.print({ "exp-commands_entity-protection.unprotected-entities", #event.entities }, Commands.print_settings.default)
end)
--- When an area is selected to add protection to the area
SelectArea:on_selection(function(event)
local surface = event.surface
local area = expand_area(event.area)
local areas = EntityProtection.get_areas(event.surface)
local player = game.players[event.player_index]
for _, next_area in pairs(areas) do
if contains_area(next_area, area) then
return player.print({ "exp-commands_entity-protection.already-protected" }, Commands.print_settings.error)
end
end
EntityProtection.add_area(surface, area)
show_protected_area(player, surface, area)
player.print({ "exp-commands_entity-protection.protected-area" }, Commands.print_settings.default)
end)
--- When an area is selected to remove protection from the area
SelectArea:on_alt_selection(function(event)
local surface = event.surface
local area = expand_area(event.area)
local areas = EntityProtection.get_areas(surface)
local player = game.players[event.player_index]
for _, next_area in pairs(areas) do
if contains_area(area, next_area) then
EntityProtection.remove_area(surface, next_area)
player.print({ "exp-commands_entity-protection.unprotected-area" }, Commands.print_settings.default)
remove_render(player, get_area_key(next_area))
end
end
end)
--- When selection starts show all protected entities and protected areas
local function on_player_selection_start(event)
local player = game.players[event.player_index]
local surface = player.surface -- Allow remote view
renders[player.index] = {}
-- Show protected entities
local entities = EntityProtection.get_entities(surface)
for _, entity in pairs(entities) do
show_protected_entity(player, entity)
end
-- Show always protected entities by name
local protected_entity_names = EntityProtection.get_protected_entity_names()
if #protected_entity_names > 0 then
for _, entity in pairs(surface.find_entities_filtered{ name = protected_entity_names, force = player.force }) do
show_protected_entity(player, entity)
end
end
-- Show always protected entities by type
local protected_entity_types = EntityProtection.get_protected_entity_types()
if #protected_entity_types > 0 then
for _, entity in pairs(surface.find_entities_filtered{ type = protected_entity_types, force = player.force }) do
show_protected_entity(player, entity)
end
end
-- Show protected areas
local areas = EntityProtection.get_areas(surface)
for _, area in pairs(areas) do
show_protected_area(player, surface, area)
end
end
--- When selection ends hide protected entities and protected areas
local function on_player_selection_stop(event)
for _, render in pairs(renders[event.player_index]) do
if render.valid then render.destroy() end
end
renders[event.player_index] = nil
end
SelectArea:on_start(on_player_selection_start)
SelectEntities:on_start(on_player_selection_start)
SelectArea:on_stop(on_player_selection_stop)
SelectEntities:on_stop(on_player_selection_stop)
--- When there is a repeat offence print it in chat
local function on_repeat_violation(event)
local regular = Roles.get_role_by_name("Regular")
for _, role in ipairs(regular and Roles.get_higher_roles(regular) or {}) do
role:print{
"exp-commands_entity-protection.repeat-offence",
format_player_name(event.player_index),
event.entity.localised_name,
event.entity.position.x,
event.entity.position.y
}
end
end
return {
events = {
[EntityProtection.on_repeat_violation] = on_repeat_violation,
}
}