From 5be0df32ad2b3a5c91f89eefe5c8f19463a58579 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 14 Oct 2023 02:02:56 -0700 Subject: [PATCH] Add admin Erase verb, add checkbox to erase from the ban panel (#20985) --- .../Administration/UI/BanPanel/BanPanel.xaml | 3 +- .../UI/BanPanel/BanPanel.xaml.cs | 8 +- .../Administration/UI/BanPanel/BanPanelEui.cs | 9 +- .../Systems/Chat/ChatUIController.cs | 16 +++- .../Systems/Chat/Widgets/ChatBox.xaml.cs | 8 +- Content.Server/Administration/BanPanelEui.cs | 36 +++++++- .../Administration/Systems/AdminSystem.cs | 91 ++++++++++++++++++- .../Administration/Systems/AdminVerbSystem.cs | 15 +++ Content.Server/Chat/Managers/ChatManager.cs | 37 ++++++-- Content.Server/Chat/Managers/IChatManager.cs | 17 +++- Content.Server/Chat/Systems/ChatSystem.cs | 15 ++- .../CrewManifest/CrewManifestSystem.cs | 7 ++ .../Radio/EntitySystems/RadioSystem.cs | 13 ++- .../GeneralStationRecordConsoleSystem.cs | 3 +- .../Systems/StationRecordsSystem.cs | 11 ++- .../Administration/BanPanelEuiState.cs | 7 +- Content.Shared/Chat/MsgChatMessage.cs | 13 ++- .../Chat/MsgDeleteChatMessagesBy.cs | 37 ++++++++ Resources/Changelog/Admin.yml | 5 + .../en-US/administration/admin-verbs.ftl | 1 + .../en-US/administration/ui/admin-erase.ftl | 1 + Resources/Locale/en-US/info/ban.ftl | 1 + 22 files changed, 297 insertions(+), 57 deletions(-) create mode 100644 Content.Shared/Chat/MsgDeleteChatMessagesBy.cs create mode 100644 Resources/Locale/en-US/administration/ui/admin-erase.ftl diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml index b8f91e050e..333184f1c0 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml @@ -22,6 +22,7 @@ + @@ -30,7 +31,7 @@ - + diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index e6d122766e..1f32640f7d 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -1,10 +1,7 @@ -using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; -using System.Text.RegularExpressions; using Content.Client.Administration.UI.CustomControls; -using Content.Client.Stylesheets; using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Roles; @@ -23,7 +20,7 @@ namespace Content.Client.Administration.UI.BanPanel; [GenerateTypedNameReferences] public sealed partial class BanPanel : DefaultWindow { - public event Action? BanSubmitted; + public event Action? BanSubmitted; public event Action? PlayerChanged; private string? PlayerUsername { get; set; } private (IPAddress, int)? IpAddress { get; set; } @@ -441,7 +438,8 @@ public sealed partial class BanPanel : DefaultWindow var useLastIp = IpCheckbox.Pressed && LastConnCheckbox.Pressed && IpAddress is null; var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null; var severity = (NoteSeverity) SeverityOption.SelectedId; - BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles); + var erase = EraseCheckbox.Pressed; + BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles, erase); } protected override void FrameUpdate(FrameEventArgs args) diff --git a/Content.Client/Administration/UI/BanPanel/BanPanelEui.cs b/Content.Client/Administration/UI/BanPanel/BanPanelEui.cs index 0a7d88f65d..940a55e010 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanelEui.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanelEui.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Content.Client.Eui; using Content.Shared.Administration; using Content.Shared.Eui; @@ -19,8 +14,8 @@ public sealed class BanPanelEui : BaseEui { BanPanel = new BanPanel(); BanPanel.OnClose += () => SendMessage(new CloseEuiMessage()); - BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles) - => SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles)); + BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase) + => SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles, erase)); BanPanel.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player)); } diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 78193b5aec..6d86e458c5 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -9,7 +9,6 @@ using Content.Client.Chat.UI; using Content.Client.Examine; using Content.Client.Gameplay; using Content.Client.Ghost; -using Content.Client.Lobby.UI; using Content.Client.UserInterface.Screens; using Content.Client.UserInterface.Systems.Chat.Widgets; using Content.Client.UserInterface.Systems.Gameplay; @@ -31,7 +30,6 @@ using Robust.Shared.Configuration; using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Network; -using Robust.Shared.Random; using Robust.Shared.Replays; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -135,7 +133,8 @@ public sealed class ChatUIController : UIController /// private readonly Dictionary _unreadMessages = new(); - public readonly List<(GameTick, ChatMessage)> History = new(); + // TODO add a cap for this for non-replays + public readonly List<(GameTick Tick, ChatMessage Msg)> History = new(); // Maintains which channels a client should be able to filter (for showing in the chatbox) // and select (for attempting to send on). @@ -166,6 +165,7 @@ public sealed class ChatUIController : UIController _player.LocalPlayerChanged += OnLocalPlayerChanged; _state.OnStateChanged += StateChanged; _net.RegisterNetMessage(OnChatMessage); + _net.RegisterNetMessage(OnDeleteChatMessagesBy); SubscribeNetworkEvent(OnDamageForceSay); _speechBubbleRoot = new LayoutContainer(); @@ -867,6 +867,16 @@ public sealed class ChatUIController : UIController } } + public void OnDeleteChatMessagesBy(MsgDeleteChatMessagesBy msg) + { + // This will delete messages from an entity even if different players were the author. + // Usages of the erase admin verb should be rare enough that this does not matter. + // Otherwise the client would need to know that one entity has multiple author players, + // or the server would need to track when and which entities a player sent messages as. + History.RemoveAll(h => h.Msg.SenderKey == msg.Key || msg.Entities.Contains(h.Msg.SenderEntity)); + Repopulate(); + } + public void RegisterChat(ChatBox chat) { _chats.Add(chat); diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs index 09647bb583..56c0c28634 100644 --- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs @@ -1,5 +1,3 @@ -using Content.Client.Chat; -using Content.Client.Chat.TypingIndicator; using Content.Client.UserInterface.Systems.Chat.Controls; using Content.Shared.Chat; using Content.Shared.Input; @@ -54,14 +52,12 @@ public partial class ChatBox : UIWidget return; } - if (msg is { Read: false, AudioPath: { } }) + if (msg is { Read: false, AudioPath: not null }) SoundSystem.Play(msg.AudioPath, Filter.Local(), new AudioParams().WithVolume(msg.AudioVolume)); msg.Read = true; - var color = msg.MessageColorOverride != null - ? msg.MessageColorOverride.Value - : msg.Channel.TextColor(); + var color = msg.MessageColorOverride ?? msg.Channel.TextColor(); AddLine(msg.WrappedMessage, color); } diff --git a/Content.Server/Administration/BanPanelEui.cs b/Content.Server/Administration/BanPanelEui.cs index f4a1a308d0..2e6dfab18a 100644 --- a/Content.Server/Administration/BanPanelEui.cs +++ b/Content.Server/Administration/BanPanelEui.cs @@ -2,22 +2,29 @@ using System.Collections.Immutable; using System.Net; using System.Net.Sockets; using Content.Server.Administration.Managers; +using Content.Server.Administration.Systems; using Content.Server.Chat.Managers; using Content.Server.EUI; using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Eui; +using Robust.Server.Player; using Robust.Shared.Network; namespace Content.Server.Administration; -public sealed class BanPanelEui : BaseEui +public sealed class BanPanelEui : BaseEui, IPostInjectInit { [Dependency] private readonly IBanManager _banManager = default!; + [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly ILogManager _log = default!; [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly IAdminManager _admins = default!; + private ISawmill _sawmill = default!; + private NetUserId? PlayerId { get; set; } private string PlayerName { get; set; } = string.Empty; private IPAddress? LastAddress { get; set; } @@ -41,7 +48,7 @@ public sealed class BanPanelEui : BaseEui switch (msg) { case BanPanelEuiStateMsg.CreateBanRequest r: - BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles); + BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles, r.Erase); break; case BanPanelEuiStateMsg.GetPlayerInfoRequest r: ChangePlayer(r.PlayerUsername); @@ -49,11 +56,11 @@ public sealed class BanPanelEui : BaseEui } } - private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection? roles) + private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection? roles, bool erase) { if (!_admins.HasAdminFlag(Player, AdminFlags.Ban)) { - Logger.WarningS("admin.bans_eui", $"{Player.Name} ({Player.UserId}) tried to create a ban with no ban flag"); + _sawmill.Warning($"{Player.Name} ({Player.UserId}) tried to create a ban with no ban flag"); return; } if (target == null && string.IsNullOrWhiteSpace(ipAddressString) && hwid == null) @@ -120,7 +127,23 @@ public sealed class BanPanelEui : BaseEui return; } + if (erase && + targetUid != null && + _playerManager.TryGetSessionById(targetUid.Value, out var targetPlayer)) + { + try + { + if (_entities.TrySystem(out AdminSystem? adminSystem)) + adminSystem.Erase(targetPlayer); + } + catch (Exception e) + { + _sawmill.Error($"Error while erasing banned player:\n{e}"); + } + } + _banManager.CreateServerBan(targetUid, target, Player.UserId, addressRange, targetHWid, minutes, severity, reason); + Close(); } @@ -160,4 +183,9 @@ public sealed class BanPanelEui : BaseEui StateDirty(); } + + public void PostInject() + { + _sawmill = _log.GetSawmill("admin.bans_eui"); + } } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index e1d769b1b2..8a2b6d5119 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -1,17 +1,28 @@ using System.Linq; using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; +using Content.Server.Forensics; +using Content.Server.GameTicking; +using Content.Server.Hands.Systems; using Content.Server.IdentityManagement; using Content.Server.Mind; using Content.Server.Players.PlayTimeTracking; +using Content.Server.Popups; +using Content.Server.StationRecords.Systems; using Content.Shared.Administration; using Content.Shared.Administration.Events; using Content.Shared.CCVar; using Content.Shared.GameTicking; +using Content.Shared.Hands.Components; using Content.Shared.IdentityManagement; +using Content.Shared.Inventory; +using Content.Shared.PDA; using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Popups; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; +using Content.Shared.StationRecords; +using Content.Shared.Throwing; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; @@ -27,10 +38,17 @@ namespace Content.Server.Administration.Systems [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly PlayTimeTrackingManager _playTime = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly MindSystem _minds = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + [Dependency] private readonly PlayTimeTrackingManager _playTime = default!; [Dependency] private readonly SharedRoleSystem _role = default!; + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly StationRecordsSystem _stationRecords = default!; + [Dependency] private readonly TransformSystem _transform = default!; private readonly Dictionary _playerList = new(); @@ -299,5 +317,76 @@ namespace Content.Server.Administration.Systems RaiseNetworkEvent(ev, admin); } } + + /// + /// Erases a player from the round. + /// This removes them and any trace of them from the round, deleting their + /// chat messages and showing a popup to other players. + /// Their items are dropped on the ground. + /// + public void Erase(IPlayerSession player) + { + var entity = player.AttachedEntity; + _chat.DeleteMessagesBy(player); + + if (entity != null && !TerminatingOrDeleted(entity.Value)) + { + if (TryComp(entity.Value, out TransformComponent? transform)) + { + var coordinates = _transform.GetMoverCoordinates(entity.Value, transform); + var name = Identity.Entity(entity.Value, EntityManager); + _popup.PopupCoordinates(Loc.GetString("admin-erase-popup", ("user", name)), coordinates, PopupType.LargeCaution); + } + + foreach (var item in _inventory.GetHandOrInventoryEntities(entity.Value)) + { + if (TryComp(item, out PdaComponent? pda) && + TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) && + keyStorage.Key is { } key && + _stationRecords.TryGetRecord(key.OriginStation, key, out GeneralStationRecord? record)) + { + if (TryComp(entity, out DnaComponent? dna) && + dna.DNA != record.DNA) + { + continue; + } + + if (TryComp(entity, out FingerprintComponent? fingerPrint) && + fingerPrint.Fingerprint != record.Fingerprint) + { + continue; + } + + _stationRecords.RemoveRecord(key.OriginStation, key); + Del(item); + } + } + + if (TryComp(entity.Value, out InventoryComponent? inventory) && + _inventory.TryGetSlots(entity.Value, out var slots, inventory)) + { + foreach (var slot in slots) + { + if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, out var item, true, true)) + { + _physics.ApplyAngularImpulse(item.Value, ThrowingSystem.ThrowAngularImpulse); + } + } + } + + if (TryComp(entity.Value, out HandsComponent? hands)) + { + foreach (var hand in _hands.EnumerateHands(entity.Value, hands)) + { + _hands.TryDrop(entity.Value, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands); + } + } + } + + _minds.WipeMind(player); + QueueDel(entity); + + _gameTicker.SpawnObserver(player); + } } } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 8b792b0892..e493cea6a7 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -48,6 +48,7 @@ namespace Content.Server.Administration.Systems [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly AdminSystem _adminSystem = default!; [Dependency] private readonly DisposalTubeSystem _disposalTubes = default!; [Dependency] private readonly EuiManager _euiManager = default!; [Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!; @@ -140,6 +141,20 @@ namespace Content.Server.Administration.Systems }, Impact = LogImpact.Medium, }); + + // Erase + args.Verbs.Add(new Verb + { + Text = Loc.GetString("admin-verbs-erase"), + Category = VerbCategory.Admin, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")), + Act = () => + { + _adminSystem.Erase(targetActor.PlayerSession); + }, + Impact = LogImpact.Extreme, + ConfirmationPopup = true + }); } // Admin Logs diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index aa055e4491..88e143d24e 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Runtime.InteropServices; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Administration.Systems; @@ -50,9 +51,13 @@ namespace Content.Server.Chat.Managers private bool _oocEnabled = true; private bool _adminOocEnabled = true; + public Dictionary SenderKeys { get; } = new(); + public Dictionary> SenderEntities { get; } = new(); + public void Initialize() { _netManager.RegisterNetMessage(); + _netManager.RegisterNetMessage(); _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); _configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true); @@ -74,6 +79,15 @@ namespace Content.Server.Chat.Managers DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message")); } + public void DeleteMessagesBy(IPlayerSession player) + { + var key = SenderKeys.GetValueOrDefault(player); + var entities = SenderEntities.GetValueOrDefault(player) ?? new HashSet(); + var msg = new MsgDeleteChatMessagesBy { Key = key, Entities = entities }; + + _netManager.ServerSendToAll(msg); + } + #region Server Announcements public void DispatchServerAnnouncement(string message, Color? colorOverride = null) @@ -202,8 +216,12 @@ namespace Content.Server.Chat.Managers wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message))); } + ref var key = ref CollectionsMarshal.GetValueRefOrAddDefault(SenderKeys, player, out var exists); + if (!exists) + key = SenderKeys.Count; + //TODO: player.Name color, this will need to change the structure of the MsgChatMessage - ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride); + ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride, senderKey: key); _mommiLink.SendOOCMessage(player.Name, message); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}"); } @@ -220,6 +238,11 @@ namespace Content.Server.Chat.Managers var wrappedMessage = Loc.GetString("chat-manager-send-admin-chat-wrap-message", ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("playerName", player.Name), ("message", FormattedMessage.EscapeText(message))); + + ref var key = ref CollectionsMarshal.GetValueRefOrAddDefault(SenderKeys, player, out var exists); + if (!exists) + key = SenderKeys.Count; + foreach (var client in clients) { var isSource = client != player.ConnectedClient; @@ -230,7 +253,7 @@ namespace Content.Server.Chat.Managers false, client, audioPath: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundPath) : default, - audioVolume: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundVolume) : default); + audioVolume: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundVolume) : default, senderKey: key); } _adminLogger.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}"); @@ -240,9 +263,9 @@ namespace Content.Server.Chat.Managers #region Utility - public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0) + public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, int? senderKey = null) { - var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), hideChat, colorOverride, audioPath, audioVolume); + var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), senderKey, hideChat, colorOverride, audioPath, audioVolume); _netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client); if (!recordReplay) @@ -260,7 +283,7 @@ namespace Content.Server.Chat.Managers public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, List clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) { - var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), hideChat, colorOverride, audioPath, audioVolume); + var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), null, hideChat, colorOverride, audioPath, audioVolume); _netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients); if (!recordReplay) @@ -288,9 +311,9 @@ namespace Content.Server.Chat.Managers ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients, colorOverride, audioPath, audioVolume); } - public void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) + public void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, int? senderKey = null) { - var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), hideChat, colorOverride, audioPath, audioVolume); + var msg = new ChatMessage(channel, message, wrappedMessage, _entityManager.GetNetEntity(source), senderKey, hideChat, colorOverride, audioPath, audioVolume); _netManager.ServerSendToAll(new MsgChatMessage() { Message = msg }); if (!recordReplay) diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index a398be74fd..10103f011d 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -8,6 +8,17 @@ namespace Content.Server.Chat.Managers { public interface IChatManager { + /// + /// Keys identifying messages sent by a specific player, used when sending + /// + /// + Dictionary SenderKeys { get; } + + /// + /// Tracks which entities a player was attached to while sending messages. + /// + Dictionary> SenderEntities { get; } + void Initialize(); /// @@ -27,15 +38,17 @@ namespace Content.Server.Chat.Managers void SendAdminAlert(EntityUid player, string message); void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, - INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0); + INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, int? senderKey = null); void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, IEnumerable clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0); void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride, string? audioPath = null, float audioVolume = 0); - void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0); + void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, int? senderKey = null); bool MessageCharacterLimit(IPlayerSession player, string message); + + void DeleteMessagesBy(IPlayerSession player); } } diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index b8f4e116a4..24e13bcde2 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -15,7 +15,6 @@ using Content.Shared.Database; using Content.Shared.Ghost; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; -using Content.Shared.Inventory; using Content.Shared.Mobs.Systems; using Content.Shared.Radio; using Robust.Server.GameObjects; @@ -33,6 +32,7 @@ using Robust.Shared.Utility; namespace Content.Server.Chat.Systems; +// TODO refactor whatever active warzone this class and chatmanager have become /// /// ChatSystem is responsible for in-simulation chat handling, such as whispering, speaking, emoting, etc. /// ChatSystem depends on ChatManager to actually send the messages. @@ -191,6 +191,9 @@ public sealed partial class ChatSystem : SharedChatSystem if (!CanSendInGame(message, shell, player)) return; + if (player != null) + _chatManager.SenderEntities.GetOrNew(player).Add(GetNetEntity(source)); + if (desiredType == InGameICChatType.Speak && message.StartsWith(LocalPrefix)) { // prevent radios and remove prefix. @@ -484,7 +487,7 @@ public sealed partial class ChatSystem : SharedChatSystem _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedUnknownMessage, source, false, session.ConnectedClient); } - _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), MessageRangeHideChatForReplay(range))); + _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); var ev = new EntitySpokeEvent(source, message, channel, obfuscatedMessage); RaiseLocalEvent(source, ev, true); @@ -559,6 +562,8 @@ public sealed partial class ChatSystem : SharedChatSystem ("entityName", name), ("message", FormattedMessage.EscapeText(message))); + _chatManager.SenderEntities.GetOrNew(player).Add(GetNetEntity(source)); + SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); } @@ -585,8 +590,9 @@ public sealed partial class ChatSystem : SharedChatSystem _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Dead chat from {player:Player}: {message}"); } - _chatManager.ChatMessageToMany(ChatChannel.Dead, message, wrappedMessage, source, hideChat, true, clients.ToList()); + _chatManager.SenderEntities.GetOrNew(player).Add(GetNetEntity(source)); + _chatManager.ChatMessageToMany(ChatChannel.Dead, message, wrappedMessage, source, hideChat, true, clients.ToList()); } #endregion @@ -651,7 +657,7 @@ public sealed partial class ChatSystem : SharedChatSystem _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.ConnectedClient); } - _replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), MessageRangeHideChatForReplay(range))); + _replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); } /// @@ -893,4 +899,3 @@ public enum ChatTransmitRange : byte /// Ghosts can't hear or see it at all. Regular players can if in-range. NoGhosts } - diff --git a/Content.Server/CrewManifest/CrewManifestSystem.cs b/Content.Server/CrewManifest/CrewManifestSystem.cs index 9471c88c9e..fa00648c55 100644 --- a/Content.Server/CrewManifest/CrewManifestSystem.cs +++ b/Content.Server/CrewManifest/CrewManifestSystem.cs @@ -37,6 +37,7 @@ public sealed class CrewManifestSystem : EntitySystem { SubscribeLocalEvent(AfterGeneralRecordCreated); SubscribeLocalEvent(OnRecordModified); + SubscribeLocalEvent(OnRecordRemoved); SubscribeLocalEvent(OnBoundUiClose); SubscribeLocalEvent(OpenEuiFromBui); SubscribeLocalEvent(OnRoundRestart); @@ -83,6 +84,12 @@ public sealed class CrewManifestSystem : EntitySystem UpdateEuis(ev.Key.OriginStation); } + private void OnRecordRemoved(RecordRemovedEvent ev) + { + BuildCrewManifest(ev.Key.OriginStation); + UpdateEuis(ev.Key.OriginStation); + } + private void OnBoundUiClose(EntityUid uid, CrewManifestViewerComponent component, BoundUIClosedEvent ev) { var owningStation = _stationSystem.GetOwningStation(uid); diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index c4f66a0cd9..4f9099d0af 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -1,20 +1,18 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; +using Content.Server.Power.Components; using Content.Server.Radio.Components; using Content.Server.VoiceMask; -using Content.Server.Popups; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Radio; +using Content.Shared.Radio.Components; using Robust.Server.GameObjects; +using Robust.Shared.Map; using Robust.Shared.Network; +using Robust.Shared.Random; using Robust.Shared.Replays; using Robust.Shared.Utility; -using Content.Shared.Popups; -using Robust.Shared.Map; -using Content.Shared.Radio.Components; -using Content.Server.Power.Components; -using Robust.Shared.Random; namespace Content.Server.Radio.EntitySystems; @@ -87,7 +85,8 @@ public sealed class RadioSystem : EntitySystem ChatChannel.Radio, message, wrappedMessage, - NetEntity.Invalid); + NetEntity.Invalid, + null); var chatMsg = new MsgChatMessage { Message = chat }; var ev = new RadioReceiveEvent(message, messageSource, channel, chatMsg); diff --git a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs index ea8eed8445..f69caaa9a7 100644 --- a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs +++ b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs @@ -1,7 +1,7 @@ +using System.Linq; using Content.Server.Station.Systems; using Content.Shared.StationRecords; using Robust.Server.GameObjects; -using System.Linq; namespace Content.Server.StationRecords.Systems; @@ -18,6 +18,7 @@ public sealed class GeneralStationRecordConsoleSystem : EntitySystem SubscribeLocalEvent(OnFiltersChanged); SubscribeLocalEvent(UpdateUserInterface); SubscribeLocalEvent(UpdateUserInterface); + SubscribeLocalEvent(UpdateUserInterface); } private void UpdateUserInterface(EntityUid uid, GeneralStationRecordConsoleComponent component, T ev) diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs index c70e1d0d9a..fd5094d533 100644 --- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs +++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using Content.Server.GameTicking; using Content.Server.Forensics; +using Content.Server.GameTicking; using Content.Shared.Inventory; using Content.Shared.PDA; using Content.Shared.Preferences; @@ -160,8 +160,13 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem if (!Resolve(station, ref records)) return false; - RaiseLocalEvent(new RecordRemovedEvent(station, key)); - return records.Records.RemoveAllRecords(key); + if (records.Records.RemoveAllRecords(key)) + { + RaiseLocalEvent(new RecordRemovedEvent(station, key)); + return true; + } + + return false; } /// diff --git a/Content.Shared/Administration/BanPanelEuiState.cs b/Content.Shared/Administration/BanPanelEuiState.cs index 545c9c0071..dd10068e5d 100644 --- a/Content.Shared/Administration/BanPanelEuiState.cs +++ b/Content.Shared/Administration/BanPanelEuiState.cs @@ -1,8 +1,7 @@ -using System.Collections.Immutable; +using System.Net; using Content.Shared.Database; using Content.Shared.Eui; using Robust.Shared.Serialization; -using System.Net; namespace Content.Shared.Administration; @@ -33,8 +32,9 @@ public static class BanPanelEuiStateMsg public string[]? Roles { get; set; } public bool UseLastIp { get; set; } public bool UseLastHwid { get; set; } + public bool Erase { get; set; } - public CreateBanRequest(string? player, (IPAddress, int)? ipAddress, bool useLastIp, byte[]? hwid, bool useLastHwid, uint minutes, string reason, NoteSeverity severity, string[]? roles) + public CreateBanRequest(string? player, (IPAddress, int)? ipAddress, bool useLastIp, byte[]? hwid, bool useLastHwid, uint minutes, string reason, NoteSeverity severity, string[]? roles, bool erase) { Player = player; IpAddress = ipAddress == null ? null : $"{ipAddress.Value.Item1}/{ipAddress.Value.Item2}"; @@ -45,6 +45,7 @@ public static class BanPanelEuiStateMsg Reason = reason; Severity = severity; Roles = roles; + Erase = erase; } } diff --git a/Content.Shared/Chat/MsgChatMessage.cs b/Content.Shared/Chat/MsgChatMessage.cs index 27ab203d6e..55a3a7342b 100644 --- a/Content.Shared/Chat/MsgChatMessage.cs +++ b/Content.Shared/Chat/MsgChatMessage.cs @@ -1,9 +1,9 @@ +using System.IO; using JetBrains.Annotations; using Lidgren.Network; using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.Utility; -using System.IO; namespace Content.Shared.Chat { @@ -14,6 +14,14 @@ namespace Content.Shared.Chat public string Message; public string WrappedMessage; public NetEntity SenderEntity; + + /// + /// Identifier sent when is + /// if this was sent by a player to assign a key to the sender of this message. + /// This is unique per sender. + /// + public int? SenderKey; + public bool HideChat; public Color? MessageColorOverride; public string? AudioPath; @@ -22,12 +30,13 @@ namespace Content.Shared.Chat [NonSerialized] public bool Read; - public ChatMessage(ChatChannel channel, string message, string wrappedMessage, NetEntity source, bool hideChat = false, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) + public ChatMessage(ChatChannel channel, string message, string wrappedMessage, NetEntity source, int? senderKey, bool hideChat = false, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) { Channel = channel; Message = message; WrappedMessage = wrappedMessage; SenderEntity = source; + SenderKey = senderKey; HideChat = hideChat; MessageColorOverride = colorOverride; AudioPath = audioPath; diff --git a/Content.Shared/Chat/MsgDeleteChatMessagesBy.cs b/Content.Shared/Chat/MsgDeleteChatMessagesBy.cs new file mode 100644 index 0000000000..55d27518d8 --- /dev/null +++ b/Content.Shared/Chat/MsgDeleteChatMessagesBy.cs @@ -0,0 +1,37 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Chat; + +public sealed class MsgDeleteChatMessagesBy : NetMessage +{ + public override MsgGroups MsgGroup => MsgGroups.Command; + + public int Key; + public HashSet Entities = default!; + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + Key = buffer.ReadInt32(); + + var entities = buffer.ReadInt32(); + Entities = new HashSet(entities); + + for (var i = 0; i < entities; i++) + { + Entities.Add(buffer.ReadNetEntity()); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.Write(Key); + + buffer.Write(Entities.Count); + foreach (var ent in Entities) + { + buffer.Write(ent); + } + } +} diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 4a1905d110..93db39b973 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -19,3 +19,8 @@ Entries: - {message: 'Added total playtime to the F7 player list and the AHelp window title.', type: Add} id: 3 time: '2023-10-14T08:55:00.0000000+00:00' +- author: DrSmugleaf + changes: + - {message: 'Added admin Erase verb, add checkbox to erase from the ban panel.', type: Add} + id: 4 + time: '2023-10-14T09:00:00.0000000+00:00' diff --git a/Resources/Locale/en-US/administration/admin-verbs.ftl b/Resources/Locale/en-US/administration/admin-verbs.ftl index 6804171f7d..224ada4b63 100644 --- a/Resources/Locale/en-US/administration/admin-verbs.ftl +++ b/Resources/Locale/en-US/administration/admin-verbs.ftl @@ -7,5 +7,6 @@ admin-verbs-teleport-to = Teleport To admin-verbs-teleport-here = Teleport Here admin-verbs-freeze = Freeze admin-verbs-unfreeze = Unfreeze +admin-verbs-erase = Erase toolshed-verb-mark = Mark toolshed-verb-mark-description = Places this entity into the $marked variable, a list of entities, replacing it's prior value. diff --git a/Resources/Locale/en-US/administration/ui/admin-erase.ftl b/Resources/Locale/en-US/administration/ui/admin-erase.ftl new file mode 100644 index 0000000000..86b75196ac --- /dev/null +++ b/Resources/Locale/en-US/administration/ui/admin-erase.ftl @@ -0,0 +1 @@ +admin-erase-popup = {$user} disappears without a trace. You should keep playing as if they never existed. diff --git a/Resources/Locale/en-US/info/ban.ftl b/Resources/Locale/en-US/info/ban.ftl index f1e67c66cd..2804690fc5 100644 --- a/Resources/Locale/en-US/info/ban.ftl +++ b/Resources/Locale/en-US/info/ban.ftl @@ -76,6 +76,7 @@ ban-panel-years = Years ban-panel-permanent = Permanent ban-panel-ip-hwid-tooltip = Leave empty and check the checkbox below to use last connection's details ban-panel-severity = Severity: +ban-panel-erase = Erase chat messages and player from round # Ban string server-ban-string = {$admin} created a {$severity} severity server ban that expires {$expires} for [{$name}, {$ip}, {$hwid}], with reason: {$reason} -- 2.51.2