Merge branch 'explosivegaming:main' into aperx2

This commit is contained in:
2026-09-06 12:59:32 +09:00
committed by GitHub
36 changed files with 735 additions and 1227 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ Adds a commands that allow admins to jail and unjail
local Commands = require("modules/exp_commands")
local format_player_name = Commands.format_player_name_locale
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
local Jail = require("modules/exp_scenario/control/jail")
--- Puts a player into jail and removes all other roles.
Commands.new("jail", { "exp-commands_jail.description" })
@@ -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 -1
View File
@@ -3,7 +3,7 @@ Adds commands relating to spectate and follow
]]
local Commands = require("modules/exp_commands")
local Spectate = require("modules.exp_legacy.modules.control.spectate") --- @dep modules.control.spectate
local Spectate = require("modules/exp_scenario/control/spectate")
--- Toggles spectator mode for the caller
Commands.new("spectate", { "exp-commands_spectate.description-spectate" })
-96
View File
@@ -1,96 +0,0 @@
--[[-- Commands - Warnings
Adds a commands that allow admins to warn other players
]]
local Commands = require("modules/exp_commands")
local format_player_name = Commands.format_player_name_locale
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
local config = require("modules.exp_legacy.config.warnings") --- @dep config.warnings
--- Gives a warning to a player; may lead to automatic script action.
Commands.new("create-warning", { "exp-commands_warnings.description-create" })
:argument("player", { "exp-commands_warnings.arg-player-create" }, Commands.types.lower_role_player)
:argument("reason", { "exp-commands_warnings.arg-reason" }, Commands.types.string)
:enable_auto_concatenation()
:add_aliases{ "warn" }
:add_flags{ "admin_only" }
:register(function(player, other_player, reason)
--- @cast other_player LuaPlayer
--- @cast reason string
Warnings.add_warning(other_player, player.name, reason)
local player_name = format_player_name(player)
local other_player_name = format_player_name(other_player)
game.print{ "exp-commands_warnings.create", other_player_name, player_name, reason }
end)
--- Gets a list of all warnings that a player has on them. If no player then lists all players and the number of warnings on them.
Commands.new("get-warnings", { "exp-commands_warnings.description-get" })
:optional("player", { "exp-commands_warnings.arg-player-get" }, Commands.types.player)
:add_aliases{ "warnings" }
:add_flags{ "admin_only" }
:register(function(player, other_player)
--- @cast other_player LuaPlayer?
if other_player then
local warnings = Warnings.get_warnings(player)
local script_warnings = Warnings.get_script_warnings(player)
local other_player_name = format_player_name(other_player)
Commands.print{ "exp-commands_warnings.player-title", other_player_name, #warnings, #script_warnings, config.script_warning_limit }
for _, warning in pairs(warnings) do
local by_player_name_formatted = format_player_name(warning.by_player_name)
Commands.print{ "exp-commands_warnings.list-element-player", by_player_name_formatted, warning.reason }
end
else
local warnings = Warnings.user_warnings:get_all()
local script_warnings = Warnings.user_script_warnings
Commands.print{ "exp-commands_warnings.warnings-title" }
for player_name, player_warnings in pairs(warnings) do
local player_name_formatted = format_player_name(player_name)
local script_warning_count = script_warnings[player_name] and #script_warnings[player_name] or 0
Commands.print{ "exp-commands_warnings.list-element", player_name_formatted, #player_warnings, script_warning_count, config.script_warning_limit }
end
for player_name, player_warnings in pairs(script_warnings) do
if not warnings[player_name] then
local player_name_formatted = format_player_name(player_name)
Commands.print{ "exp-commands_warnings.list-element", player_name_formatted, 0, #player_warnings, config.script_warning_limit }
end
end
end
end)
--- Clears all warnings from a player
Commands.new("clear-warnings", { "exp-commands_warnings.description-clear" })
:argument("player", { "exp-commands_warnings.arg-player-clear" }, Commands.types.player)
:add_flags{ "admin_only" }
:register(function(player, other_player)
--- @cast other_player LuaPlayer
Warnings.clear_warnings(other_player, player.name)
Warnings.clear_script_warnings(other_player)
local player_name = format_player_name(player)
local other_player_name = format_player_name(other_player)
game.print{ "exp-commands_warnings.cleared", other_player_name, player_name }
end)
--- Clears all script warnings from a player
Commands.new("clear-script-warnings", { "exp-commands_warnings.description-clear-script" })
:argument("player", { "exp-commands_warnings.arg-player-clear" }, Commands.types.player)
:add_flags{ "admin_only" }
:register(function(player, other_player)
--- @cast other_player LuaPlayer
Warnings.clear_script_warnings(other_player)
local player_name = format_player_name(player)
local other_player_name = format_player_name(other_player)
game.print{ "exp-commands_warnings.cleared-script", other_player_name, player_name }
end)
--- Clears the last warning that was given to a player
Commands.new("clear-last-warnings", { "exp-commands_warnings.description-clear-last" })
:argument("player", { "exp-commands_warnings.arg-player-clear" }, Commands.types.player)
:add_flags{ "admin_only" }
:register(function(player, other_player)
--- @cast other_player LuaPlayer
Warnings.remove_warning(other_player, player.name)
local player_name = format_player_name(player)
local other_player_name = format_player_name(other_player)
game.print{ "exp-commands_warnings.cleared-last", other_player_name, player_name }
end)
+2 -1
View File
@@ -38,7 +38,6 @@ require("modules/exp_scenario/commands/surface")
require("modules/exp_scenario/commands/teleport")
require("modules/exp_scenario/commands/trains")
require("modules/exp_scenario/commands/vlayer")
require("modules/exp_scenario/commands/warnings")
require("modules/exp_scenario/commands/waterfill")
--- Control
@@ -60,11 +59,13 @@ 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"))
add(require("modules/exp_scenario/control/roles"))
add(require("modules/exp_scenario/control/spawn_area"))
add(require("modules/exp_scenario/control/spectate"))
add(require("modules/exp_scenario/control/station_auto_name"))
--- Guis
@@ -3,6 +3,7 @@ Log certain actions into a file when events are triggered
]]
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
local Roles = require("modules/exp_roles")
local config = require("modules.exp_legacy.config.deconlog")
@@ -130,31 +131,79 @@ local function on_player_mined_entity(event)
add_log_line(player, "mined_entity", format_entity(event.entity))
end
--- Log when rocket is fired
--- Ammo which is logged when fired
local logged_ammo = {
["rocket"] = config.fired_rocket,
["explosive-rocket"] = config.fired_explosive_rocket,
["atomic-bomb"] = config.fired_nuke,
}
--- @class ExpScenario_DeconstructionLog.AmmoSlot
--- @field name string
--- @field count number
--- The last seen contents of each ammo slot, keyed by player index then slot index
local ammo_slots = {} --- @type table<uint, table<uint, ExpScenario_DeconstructionLog.AmmoSlot>>
Storage.register(ammo_slots, function(tbl)
ammo_slots = tbl
end)
--- Read the ammo slots of a player, empty while they have no character
--- @param player LuaPlayer
--- @return table<uint, ExpScenario_DeconstructionLog.AmmoSlot>
local function read_ammo_slots(player)
local slots = {}
if not player.character then return slots end
local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo))
for index = 1, #character_ammo do
local stack = character_ammo[index --[[@as uint]]]
if stack.valid_for_read then
slots[index] = { name = stack.name, count = stack.count }
end
end
return slots
end
--- Log a shot, there is no fired event so a slot losing one of the same ammo is taken as a shot
--- @param event EventData.on_player_ammo_inventory_changed
local function on_player_ammo_inventory_changed(event)
local player = get_log_player(event)
if not player or not player.character then return end
if not player then return end
local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo))
local gun_index = player.character.selected_gun_index --[[@as uint]]
local item = character_ammo[gun_index]
if not item or not item.valid or not item.valid_for_read then
return
local previous_slots = ammo_slots[player.index] or {}
local slots = read_ammo_slots(player)
ammo_slots[player.index] = slots
for index, previous in pairs(previous_slots) do
local current = slots[index]
local fired = nil --- @type string?
if current then
if previous.name == current.name and previous.count == current.count + 1 then
fired = current.name
end
elseif previous.count == 1 then
fired = previous.name
end
if fired and logged_ammo[fired] then
add_log_line(player, "shot-" .. fired, format_position(player.physical_position), format_position(player.shooting_state.position))
end
end
local action_name = "shot-" .. item.name
if not config.fired_rocket and action_name == "shot-rocket" then
return
elseif not config.fired_explosive_rocket and action_name == "shot-explosive-rocket" then
return
elseif not config.fired_nuke and action_name == "shot-atomic-bomb" then
return
end
add_log_line(player, action_name, format_position(player.physical_position), format_position(player.shooting_state.position))
end
--- Read the ammo of a player when they join or get a new character, so the first shot afterwards is seen
--- @param event EventData.on_player_joined_game | EventData.on_player_respawned
local function on_player_character_changed(event)
local player = assert(game.get_player(event.player_index))
ammo_slots[player.index] = read_ammo_slots(player)
end
--- Forget the ammo of a player who left
--- @param event EventData.on_player_left_game
local function on_player_left_game(event)
ammo_slots[event.player_index] = nil
end
local e = defines.events
local events = {
@@ -175,6 +224,9 @@ end
if config.fired_rocket or config.fired_explosive_rocket or config.fired_nuke then
events[e.on_player_ammo_inventory_changed] = on_player_ammo_inventory_changed
events[e.on_player_joined_game] = on_player_character_changed
events[e.on_player_respawned] = on_player_character_changed
events[e.on_player_left_game] = on_player_left_game
end
return {
+5 -39
View File
@@ -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",
@@ -131,44 +131,10 @@ if config.player_reports then
end
end
--- Warnings added and removed
if config.player_warnings then
local Warnings = require("modules.exp_legacy.modules.control.warnings")
events[Warnings.events.on_warning_added] = function(event)
local player_name, by_player_name = get_player_name(event)
local player = assert(game.get_player(player_name))
emit_event{
title = "Warning",
description = "A player has been given a warning",
color = Colors.yellow,
fields = {
{ name = "Player", inline = true, value = append_playtime(player_name) },
{ name = "By", inline = true, value = append_playtime(by_player_name) },
{ name = "Report Count", inline = true, value = Warnings.count_warnings(player) },
{ name = "Reason", value = event.reason },
},
}
end
events[Warnings.events.on_warning_removed] = function(event)
if event.batch ~= 1 then return end
local player_name = get_player_name(event)
emit_event{
title = "Warnings Removed",
description = "A player has a warning removed",
color = Colors.green,
fields = {
{ name = "Player", inline = true, value = append_playtime(player_name) },
{ name = "By", inline = true, value = append_playtime(event.removed_by_name) },
{ name = "Report Count", inline = true, value = tostring(event.batch_count) },
},
}
end
end
--- When a player is jailed or unjailed
if config.player_jail then
local Jail = require("modules.exp_legacy.modules.control.jail")
events[Jail.events.on_player_jailed] = function(event)
local Jail = require("modules/exp_scenario/control/jail")
events[Jail.on_player_jailed] = function(event)
local player_name, by_player_name = get_player_name(event)
emit_event{
title = "Jail",
@@ -181,7 +147,7 @@ if config.player_jail then
},
}
end
events[Jail.events.on_player_unjailed] = function(event)
events[Jail.on_player_unjailed] = function(event)
local player_name, by_player_name = get_player_name(event)
emit_event{
title = "Unjail",
+85
View File
@@ -0,0 +1,85 @@
--[[-- Control - Jail
Adds a way to jail players, the jail role suppresses all of their other roles
]]
local Roles = require("modules/exp_roles")
--- @class ExpScenario_Jail
local Jail = {
--- Raised when a player is put into jail
--- @type EventData.ExpScenario_Jail.on_player_jailed
on_player_jailed = script.generate_event_name(),
--- Raised when a player is taken out of jail
--- @type EventData.ExpScenario_Jail.on_player_unjailed
on_player_unjailed = script.generate_event_name(),
}
--- @class EventData.ExpScenario_Jail.on_player_jailed : EventData
--- @field player_index uint
--- @field by_player_name string
--- @field reason string
--- @class EventData.ExpScenario_Jail.on_player_unjailed : EventData
--- @field player_index uint
--- @field by_player_name string
--- The role given to jailed players, it has a higher priority than every other role
--- @return ExpRoles.Role
local function get_jail_role()
return (assert(Roles.get_role_by_name("Jail"), "The Jail role does not exist"))
end
--- Check if a player is in jail
--- @param player LuaPlayer
--- @return boolean
function Jail.is_jailed(player)
return get_jail_role():has_player(player)
end
--- Put a player into jail, which suppresses all of their other roles
--- @param player LuaPlayer
--- @param by_player_name string
--- @param reason string
--- @return boolean # False when the player was already in jail
function Jail.jail_player(player, by_player_name, reason)
local role = get_jail_role()
if role:has_player(player) then return false end
-- Stop whatever the player is doing, the jail permission group stops them from starting again
player.walking_state = { walking = false, direction = player.walking_state.direction }
player.riding_state = { acceleration = defines.riding.acceleration.nothing, direction = player.riding_state.direction }
player.mining_state = { mining = false }
player.shooting_state = { state = defines.shooting.not_shooting, position = player.shooting_state.position }
player.picking_state = false
player.repair_state = { repairing = false, position = player.repair_state.position }
role:assign(player, { by_player_name = by_player_name, silent = true })
script.raise_event(Jail.on_player_jailed, {
player_index = player.index,
by_player_name = by_player_name,
reason = reason,
})
return true
end
--- Take a player out of jail, which restores all of their other roles
--- @param player LuaPlayer
--- @param by_player_name string
--- @return boolean # False when the player was not in jail
function Jail.unjail_player(player, by_player_name)
local role = get_jail_role()
if not role:has_player(player) then return false end
role:unassign(player, { by_player_name = by_player_name, silent = true })
script.raise_event(Jail.on_player_unjailed, {
player_index = player.index,
by_player_name = by_player_name,
})
return true
end
return Jail
+237
View File
@@ -0,0 +1,237 @@
--[[-- 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
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
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
@@ -4,8 +4,8 @@ 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 Jail = require("modules/exp_scenario/control/jail")
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,
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ When a player is reported, the player is automatically jailed if the combined pl
]]
local ExpUtil = require("modules/exp_util")
local Jail = require("modules.exp_legacy.modules.control.jail")
local Jail = require("modules/exp_scenario/control/jail")
local Reports = require("modules.exp_legacy.modules.control.reports")
local max = math.max
+181
View File
@@ -0,0 +1,181 @@
--[[-- Control - Spectate
Lets players spectate without losing their character, and follow other players or entities
]]
local Storage = require("modules/exp_util/storage")
local Gui = require("modules/exp_gui")
--- @class ExpScenario_Spectate
local Spectate = {
--- @package
events = {},
}
--- @class ExpScenario_Spectate.Following
--- @field player LuaPlayer
--- @field target LuaPlayer | LuaEntity
--- @field position MapPosition Where the player was after the last update, moving away stops following
--- @field started_spectate boolean True when start_follow put the player into spectator mode
--- @field stop boolean? Set when following must end on the next tick
local following = {} --- @type table<uint, ExpScenario_Spectate.Following> Keyed by player index
local spectating = {} --- @type table<uint, LuaEntity> The character a player returns to, keyed by player index
Storage.register({
following = following,
spectating = spectating,
}, function(tbl)
following = tbl.following
spectating = tbl.spectating
end)
--- Label shown while following, clicking it or pressing escape stops following
--- @class ExpScenario_Spectate.follow_label: ExpElement
--- @overload fun(parent: LuaGuiElement, target: LuaPlayer | LuaEntity): LuaGuiElement
local follow_label = Gui.define("spectate/follow_label")
:draw(function(_, parent, target)
Gui.destroy_if_valid(parent.follow_label)
local label = parent.add{
type = "label",
name = "follow_label",
style = "frame_title",
caption = { "exp_spectate.follow-label", target.name },
}
local player = Gui.get_player(parent)
local res = player.display_resolution
label.location = { 0, res.height - 150 }
label.style.width = res.width
label.style.horizontal_align = "center"
player.opened = label
return label
end)
:on_click(function(_, player)
Spectate.stop_follow(player)
end)
:on_closed(function(_, player)
-- set_controller can not be called during on_gui_closed, so the next update stops following
local data = following[player.index]
if data then data.stop = true end
end) --[[@as any]]
--- Check if a player is in spectator mode
--- @param player LuaPlayer
--- @return boolean
function Spectate.is_spectating(player)
return player.controller_type == defines.controllers.spectator
end
--- Put a player into spectator mode while keeping their character
--- @param player LuaPlayer
--- @return boolean # False when the player was already spectating or has no character
function Spectate.start_spectate(player)
if spectating[player.index] or not player.character then return false end
local character = player.character
local opened = player.opened
player.set_controller{ type = defines.controllers.spectator }
player.associate_character(character)
spectating[player.index] = character
if opened then player.opened = opened end -- Changing controller closes the opened gui
return true
end
--- Return a player to their character, or respawn them if it was killed
--- @param player LuaPlayer
function Spectate.stop_spectate(player)
local character = spectating[player.index]
spectating[player.index] = nil
if character and character.valid then
local opened = player.opened
player.teleport(character.position, character.surface)
player.set_controller{ type = defines.controllers.character, character = character }
if opened then player.opened = opened end -- Changing controller closes the opened gui
else
player.ticks_to_respawn = 300
end
end
--- Check if a player is following something
--- @param player LuaPlayer
--- @return boolean
function Spectate.is_following(player)
return following[player.index] ~= nil
end
--- Put a player into spectator mode and keep their camera on a target as it moves
--- @param player LuaPlayer
--- @param target LuaPlayer | LuaEntity
function Spectate.start_follow(player, target)
local started_spectate = Spectate.start_spectate(player)
follow_label(player.gui.screen, target)
player.teleport(target.position, target.surface)
following[player.index] = {
player = player,
target = target,
position = player.position,
started_spectate = started_spectate,
}
end
--- Give a player their camera back, returning them to their character if start_follow took it
--- @param player LuaPlayer
function Spectate.stop_follow(player)
local data = following[player.index]
if data and data.started_spectate and Spectate.is_spectating(player) then
Spectate.stop_spectate(player)
end
Gui.destroy_if_valid(player.gui.screen.follow_label)
following[player.index] = nil
end
--- Move a following player onto their target, or stop following when they have moved away or regained a character
--- @param data ExpScenario_Spectate.Following
local function update_following(data)
local player, target = data.player, data.target
local position = player.position
if
data.stop
or player.character
or not target.valid
or position.x ~= data.position.x
or position.y ~= data.position.y
then
Spectate.stop_follow(player)
else
player.teleport(target.position, target.surface)
data.position = player.position
end
end
local function on_tick()
for _, data in pairs(following) do
update_following(data)
end
end
--- Stop following when a player leaves, and stop anyone who was following them
--- @param event EventData.on_pre_player_left_game
local function on_pre_player_left_game(event)
local player = game.players[event.player_index]
Spectate.stop_follow(player)
for _, data in pairs(following) do
if data.target == player then
Spectate.stop_follow(data.player)
end
end
end
local e = defines.events
Spectate.events[e.on_tick] = on_tick
Spectate.events[e.on_pre_player_left_game] = on_pre_player_left_game
return Spectate
+74
View File
@@ -206,6 +206,78 @@ function Elements.camera.refresh_online()
end
end
--- Slot in the header showing what the player a camera follows holds in their cursor
--- @class ExpGui_Surveillance.elements.cursor_slot: ExpElement
--- @field data table<LuaGuiElement, LuaGuiElement> The camera the slot belongs to
--- @overload fun(parent: LuaGuiElement, camera: LuaGuiElement): LuaGuiElement
Elements.cursor_slot = Gui.define("surveillance/cursor_slot")
:track_all_elements()
:draw{
type = "sprite-button",
style = "slot_button",
tooltip = { "exp-gui_surveillance.tooltip-cursor-empty" },
}
:style{
height = 26,
width = 26,
margin = { -1, -1, -1, -2 },
}
:element_data(
Gui.from_argument(1)
) --[[@as any]]
--- @class ExpGui_Surveillance.elements.cursor_slot.display_data
--- @field sprite SpritePath? Nil when the cursor is empty
--- @field number number? Nil for a ghost, which has no count
--- @field tooltip LocalisedString
--- Calculate what a slot shows for the cursor of a player
--- @param player LuaPlayer
--- @return ExpGui_Surveillance.elements.cursor_slot.display_data
function Elements.cursor_slot.calculate_display_data(player)
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid_for_read then
return {
sprite = "item/" .. cursor_stack.name,
number = cursor_stack.count,
tooltip = { "exp-gui_surveillance.tooltip-cursor", cursor_stack.prototype.localised_name, cursor_stack.count },
}
end
local cursor_ghost = player.cursor_ghost --[[@as ItemIDAndQualityIDPair?]]
if cursor_ghost then
local prototype = cursor_ghost.name --[[@as LuaItemPrototype]]
return {
sprite = "item/" .. prototype.name,
tooltip = { "exp-gui_surveillance.tooltip-cursor-ghost", prototype.localised_name },
}
end
return { tooltip = { "exp-gui_surveillance.tooltip-cursor-empty" } }
end
--- Refresh a slot, a ghost is shown greyed out and the slot is hidden when the camera is not following a player
--- @param cursor_slot LuaGuiElement
--- @param target_player LuaPlayer?
function Elements.cursor_slot.refresh(cursor_slot, target_player)
cursor_slot.visible = target_player ~= nil
if not target_player then return end
local display_data = Elements.cursor_slot.calculate_display_data(target_player)
cursor_slot.sprite = display_data.sprite or ""
cursor_slot.number = display_data.number
cursor_slot.tooltip = display_data.tooltip
cursor_slot.enabled = display_data.sprite == nil or display_data.number ~= nil
end
--- Refresh the slots of all online cameras
function Elements.cursor_slot.refresh_online()
for _, cursor_slot in Elements.cursor_slot:online_elements() do
local camera = Elements.cursor_slot.data[cursor_slot]
Elements.cursor_slot.refresh(cursor_slot, Elements.camera.data[camera])
end
end
--- Container added to the screen
Elements.container = Gui.define("surveillance/container")
:draw(function(def, parent)
@@ -214,6 +286,7 @@ Elements.container = Gui.define("surveillance/container")
local target_player = Gui.get_player(parent)
local camera = Elements.camera(screen_frame, target_player)
Elements.cursor_slot(button_flow, camera)
local type_dropdown_data = {
camera = camera,
@@ -248,6 +321,7 @@ return {
[e.on_tick] = Elements.camera.refresh_online,
},
on_nth_tick = {
[10] = Elements.cursor_slot.refresh_online,
[600] = Elements.type_dropdown.refresh_online,
}
}
+6 -20
View File
@@ -249,25 +249,6 @@ description=Print all vlayer information.
title=VLayer Information:
result=__1__: __2__
[exp-commands_warnings]
description-create=Gives a warning to a player; may lead to automatic script action.
description-get=Gets the number of warnings a player has. If no player then lists all players and the number of warnings they have.
description-clear=Clears all warnings (and script warnings) from a player.
description-clear-script=Clears all script warnings from a player.
description-clear-last=Clears the last warning from a player.
arg-player-create=Player to give the warning to.
arg-player-get=Player to get the warning of, if not given all players are returned.
arg-player-clear=Player to clear the warnings of.
arg-reason=Reason the user is receiving a warning.
create=__1__ received a warning from __2__ for __3__.
player-title=__1__ has __2__ warnings and __3__/__4__ script warnings.
list-element-player=__1__: __2__
warnings-title=The following players have warnings aginst them (and script warnings):
list-element=__1__: __2__ (__3__/__4__)
cleared=__1__ had all their warnings cleared by __2__.
cleared-script=__1__ had all their script warnings cleared by __2__.
cleared-last=__1__ had their last warning cleared by __2__.
[exp-commands_waterfill]
description=Replace tiles with shallow water.
requires-explosives=__ITEM__cliff-explosives__ are required to create water.
@@ -326,7 +307,6 @@ reason-entry=Enter Reason
goto-player=Goto player
bring-player=Bring player
report-player=Report player
warn-player=Warn player
jail-player=Jail player
kick-player=Kick player
ban-player=Ban player
@@ -489,6 +469,9 @@ caption-set-location=Set
type-player=Player
type-static=Static
type-loop=Loop
tooltip-cursor=__1__ x__2__
tooltip-cursor-ghost=__1__ (ghost)
tooltip-cursor-empty=Empty cursor
[exp-gui_task-list]
caption-main=Task List [img=info]
@@ -577,3 +560,6 @@ chat-jailed=__1__ was jailed because they removed too many protected entities. P
[exp_report-jail]
chat-jailed=__1__ was jailed because they were reported too many times. Please wait for a moderator.
[exp_spectate]
follow-label=Following __1__.\nClick here or press escape to stop following.
+3 -19
View File
@@ -246,25 +246,6 @@ description=vlayer 資訊
title=vlayer 資訊:
result=__1__: __2__
[exp-commands_warnings]
description-create=給用戶一個警告; 可能會導致系統的自動行動。
description-get=取得用戶收到的警告次數。如果沒有用戶,則列出所有用戶以及他們受到警告的次數。
description-clear=清除用戶的所有警告(和系統警告)。
description-clear-script=清除用戶的系統警告。
description-clear-last=清除用戶的最後警告。
arg-player-create=要警告的用戶。
arg-player-get=要取得的用戶, 若沒有則返回所有用戶。
arg-player-clear=要清除的用戶。
arg-reason=原因。
create=__1__ 被 __2__ 因 __3__ 作出警告。
player-title=__1__ 有 __2__ 個警告和 __3__/__4__ 的系統警告。
list-element-player=__1__: __2__
warnings-title=該用戶警告如下:
list-element=__1__: __2__ (__3__/__4__)
cleared=__1__ 的警告己被 __2__ 清除。
cleared-script=__1__ 的系統警告己被 __2__ 清除。
cleared-last=__1__ 的最後警告己被 __2__ 清除。
[exp-commands_waterfill]
description=把地換為淺水。
requires-explosives=沒有足夠的 __ITEM__cliff-explosives__ 。
@@ -474,6 +455,9 @@ caption-set-location=設
type-player=用戶
type-static=靜態
type-loop=循環
tooltip-cursor=__1__ x__2__
tooltip-cursor-ghost=__1__ (幻影)
tooltip-cursor-empty=游標為空
[exp-gui_task-list]
caption-main=工作流程 [img=info]
+3 -19
View File
@@ -246,25 +246,6 @@ description=vlayer 資訊
title=vlayer 資訊:
result=__1__: __2__
[exp-commands_warnings]
description-create=給用戶一個警告; 可能會導致系統的自動行動。
description-get=取得用戶收到的警告次數。如果沒有用戶,則列出所有用戶以及他們受到警告的次數。
description-clear=清除用戶的所有警告(和系統警告)。
description-clear-script=清除用戶的系統警告。
description-clear-last=清除用戶的最後警告。
arg-player-create=要警告的用戶。
arg-player-get=要取得的用戶, 若沒有則返回所有用戶。
arg-player-clear=要清除的用戶。
arg-reason=原因。
create=__1__ 被 __2__ 因 __3__ 作出警告。
player-title=__1__ 有 __2__ 個警告和 __3__/__4__ 的系統警告。
list-element-player=__1__: __2__
warnings-title=該用戶警告如下:
list-element=__1__: __2__ (__3__/__4__)
cleared=__1__ 的警告己被 __2__ 清除。
cleared-script=__1__ 的系統警告己被 __2__ 清除。
cleared-last=__1__ 的最後警告己被 __2__ 清除。
[exp-commands_waterfill]
description=把地換為淺水。
requires-explosives=沒有足夠的 __ITEM__cliff-explosives__ 。
@@ -474,6 +455,9 @@ caption-set-location=設
type-player=用戶
type-static=靜態
type-loop=循環
tooltip-cursor=__1__ x__2__
tooltip-cursor-ghost=__1__ (幻影)
tooltip-cursor-empty=游標為空
[exp-gui_task-list]
caption-main=工作流程 [img=info]