Move protection into exp_scenario

The entity protection module moves to exp_scenario/module/control/protection.lua
in the event_handler shape the other control modules use, and the copy in
exp_legacy is removed. The config lookups are built as local sets instead of
rewriting the config tables in place, the two mined handlers become one, and
the entity and area key helpers are exposed so the protection command no
longer keeps its own copies. Event ids are exposed as
Protection.on_player_mined_protected and Protection.on_repeat_violation.
This commit is contained in:
bbassie
2026-09-05 19:19:45 +00:00
parent 810269971b
commit 0c52532c05
6 changed files with 247 additions and 228 deletions
@@ -12,10 +12,9 @@ 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_legacy.modules.control.protection") --- @dep modules.control.protection
local format_string = string.format
local floor = math.floor
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")
@@ -28,21 +27,6 @@ Storage.register({
renders = tbl.renders
end)
--- Get the key used in protected_entities
--- @param entity LuaEntity
--- @return string
local function get_entity_key(entity)
return format_string("%i,%i", floor(entity.position.x), floor(entity.position.y))
end
--- Get the key used in protected_areas
--- TODO expose this from EntityProtection
--- @param area BoundingBox.struct
--- @return string
local function get_area_key(area)
return format_string("%i,%i", floor(area.left_top.x), floor(area.left_top.y))
end
--- Show a protected entity to a player
--- @param player LuaPlayer
--- @param entity LuaEntity
@@ -232,6 +216,6 @@ end
return {
events = {
[EntityProtection.events.on_repeat_violation] = on_repeat_violation,
[EntityProtection.on_repeat_violation] = on_repeat_violation,
}
}
+1
View File
@@ -60,6 +60,7 @@ add(require("modules/exp_scenario/control/inventory_clear"))
add(require("modules/exp_scenario/control/mine_depletion"))
add(require("modules/exp_scenario/control/nuke_protection"))
add(require("modules/exp_scenario/control/pollution_grading"))
add(require("modules/exp_scenario/control/protection"))
add(require("modules/exp_scenario/control/protection_jail"))
add(require("modules/exp_scenario/control/report_jail"))
add(require("modules/exp_scenario/control/research"))
@@ -81,8 +81,8 @@ end
--- Repeated protected entity mining
if config.entity_protection then
local EntityProtection = require("modules.exp_legacy.modules.control.protection")
events[EntityProtection.events.on_repeat_violation] = function(event)
local EntityProtection = require("modules/exp_scenario/control/protection")
events[EntityProtection.on_repeat_violation] = function(event)
local player_name = get_player_name(event)
emit_event{
title = "Entity Protection",
+238
View File
@@ -0,0 +1,238 @@
--[[-- Control - Protection
Protects entities and areas from being mined by players other than the one who placed them
]]
local Storage = require("modules/exp_util/storage")
local Roles = require("modules/exp_roles")
local config = require("modules.exp_legacy.config.protection")
local format_string = string.format
local floor = math.floor
--- @class ExpScenario_Protection
local Protection = {
--- Raised when a player mines a protected entity
--- @type EventData.ExpScenario_Protection.on_player_mined_protected
on_player_mined_protected = script.generate_event_name(),
--- Raised when a player mines protected entities repeatedly, or one which always counts as repeated
--- @type EventData.ExpScenario_Protection.on_repeat_violation
on_repeat_violation = script.generate_event_name(),
--- Names of entities which are always protected
--- @type string[]
protected_entity_names = config.always_protected_names,
--- Types of entities which are always protected
--- @type string[]
protected_entity_types = config.always_protected_types,
--- @package
events = {},
--- @package
on_nth_tick = {},
}
--- @class EventData.ExpScenario_Protection.on_player_mined_protected : EventData.on_pre_player_mined_item
--- @class EventData.ExpScenario_Protection.on_repeat_violation : EventData.on_pre_player_mined_item
--- @class ExpScenario_Protection.Repeat
--- @field last uint Tick of the last protected removal
--- @field count number Protected removals since the last repeat violation
--- @param values string[]
--- @return table<string, true>
local function to_set(values)
local set = {}
for _, value in ipairs(values) do
set[value] = true
end
return set
end
local always_protected_names = to_set(config.always_protected_names)
local always_protected_types = to_set(config.always_protected_types)
local always_trigger_repeat_names = to_set(config.always_trigger_repeat_names)
local always_trigger_repeat_types = to_set(config.always_trigger_repeat_types)
local protected_entities = {} --- @type table<uint, table<string, LuaEntity>> Keyed by surface index then entity key
local protected_areas = {} --- @type table<uint, table<string, BoundingBox>> Keyed by surface index then area key
local repeats = {} --- @type table<string, ExpScenario_Protection.Repeat> Keyed by player name
Storage.register({
protected_entities = protected_entities,
protected_areas = protected_areas,
repeats = repeats,
}, function(tbl)
protected_entities = tbl.protected_entities
protected_areas = tbl.protected_areas
repeats = tbl.repeats
end)
--- Get the key an entity is stored under
--- @param entity LuaEntity
--- @return string
function Protection.get_entity_key(entity)
return format_string("%i,%i", floor(entity.position.x), floor(entity.position.y))
end
--- Get the key an area is stored under
--- @param area BoundingBox
--- @return string
function Protection.get_area_key(area)
return format_string("%i,%i", floor(area.left_top.x), floor(area.left_top.y))
end
--- Protect an entity
--- @param entity LuaEntity
function Protection.add_entity(entity)
local entities = protected_entities[entity.surface.index]
if not entities then
entities = {}
protected_entities[entity.surface.index] = entities
end
entities[Protection.get_entity_key(entity)] = entity
end
--- Remove the protection from an entity
--- @param entity LuaEntity
function Protection.remove_entity(entity)
local entities = protected_entities[entity.surface.index]
if not entities then return end
entities[Protection.get_entity_key(entity)] = nil
end
--- Get the protected entities on a surface, always protected entities are not included
--- @param surface LuaSurface
--- @return table<string, LuaEntity>
function Protection.get_entities(surface)
return protected_entities[surface.index] or {}
end
--- Check if an entity is protected, either directly or by its name or type
--- @param entity LuaEntity
--- @return boolean
function Protection.is_entity_protected(entity)
if always_protected_names[entity.name] or always_protected_types[entity.type] then return true end
local entities = protected_entities[entity.surface.index]
if not entities then return false end
return entities[Protection.get_entity_key(entity)] == entity
end
--- Protect every position within an area
--- @param surface LuaSurface
--- @param area BoundingBox
function Protection.add_area(surface, area)
local areas = protected_areas[surface.index]
if not areas then
areas = {}
protected_areas[surface.index] = areas
end
areas[Protection.get_area_key(area)] = area
end
--- Remove the protection from an area
--- @param surface LuaSurface
--- @param area BoundingBox
function Protection.remove_area(surface, area)
local areas = protected_areas[surface.index]
if not areas then return end
areas[Protection.get_area_key(area)] = nil
end
--- Get the protected areas on a surface
--- @param surface LuaSurface
--- @return table<string, BoundingBox>
function Protection.get_areas(surface)
return protected_areas[surface.index] or {}
end
--- Check if a position is within a protected area
--- @param surface LuaSurface
--- @param position MapPosition
--- @return boolean
function Protection.is_position_protected(surface, position)
local areas = protected_areas[surface.index]
if not areas then return false end
for _, area in pairs(areas) do
if area.left_top.x <= position.x and area.left_top.y <= position.y
and area.right_bottom.x >= position.x and area.right_bottom.y >= position.y
then
return true
end
end
return false
end
--- Players are never checked against their own entities, and can be excluded by permission or admin status
--- @param player LuaPlayer
--- @param entity LuaEntity
--- @return boolean
local function is_ignored(player, entity)
if config.ignore_admins and player.admin then return true end
if entity.last_user == nil or entity.last_user.index == player.index then return true end
if config.ignore_permission and Roles.player_has_permission(player, config.ignore_permission) then return true end
return false
end
--- Raise the protection events, the event data is reused with the name replaced
--- @param event EventData.on_pre_player_mined_item
--- @param player LuaPlayer
local function raise_violation(event, player)
local player_repeats = repeats[player.name]
if not player_repeats then
player_repeats = { last = game.tick, count = 0 }
repeats[player.name] = player_repeats
end
player_repeats.last = game.tick
player_repeats.count = player_repeats.count + 1
event.name = Protection.on_player_mined_protected
script.raise_event(Protection.on_player_mined_protected, event)
local entity = event.entity
local always_repeat = always_trigger_repeat_names[entity.name] or always_trigger_repeat_types[entity.type]
if always_repeat or player_repeats.count >= config.repeat_count then
player_repeats.count = 0
event.name = Protection.on_repeat_violation
script.raise_event(Protection.on_repeat_violation, event)
end
end
--- Raise the protection events when a protected entity is mined, then forget the entity
--- @param event EventData.on_pre_player_mined_item
local function on_pre_player_mined_item(event)
local entity = event.entity
local player = game.players[event.player_index]
if not is_ignored(player, entity)
and (Protection.is_entity_protected(entity) or Protection.is_position_protected(entity.surface, entity.position))
then
raise_violation(event, player)
end
Protection.remove_entity(entity)
end
--- Forget an entity once it no longer exists
--- @param event { entity: LuaEntity }
local function on_entity_removed(event)
Protection.remove_entity(event.entity)
end
--- Forget protected removals older than the repeat lifetime
local function clear_old_repeats()
local old = game.tick - config.repeat_lifetime
for player_name, player_repeats in pairs(repeats) do
if player_repeats.last <= old then
repeats[player_name] = nil
end
end
end
local e = defines.events
Protection.events[e.on_pre_player_mined_item] = on_pre_player_mined_item
Protection.events[e.on_space_platform_pre_mined] = on_entity_removed
Protection.events[e.on_robot_pre_mined] = on_entity_removed
Protection.events[e.on_entity_died] = on_entity_removed
Protection.events[e.script_raised_destroy] = on_entity_removed
Protection.on_nth_tick[config.refresh_rate] = clear_old_repeats
return Protection
@@ -5,7 +5,7 @@ When a player triggers protection multiple times they are automatically jailed
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
local Jail = require("modules.exp_legacy.modules.control.jail")
local Protection = require("modules.exp_legacy.modules.control.protection")
local Protection = require("modules/exp_scenario/control/protection")
local format_player_name = ExpUtil.format_player_name_locale
@@ -41,7 +41,7 @@ local e = defines.events
return {
events = {
[Protection.events.on_repeat_violation] = on_repeat_violation,
[Protection.on_repeat_violation] = on_repeat_violation,
[e.on_player_left_game] = on_player_left_game,
}
}