From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:26:57 +0000 (+0200) Subject: Namespace cleanup around Mind Roles (#30965) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=40b9fd4ea3b1e06558d8e510c527169965193ccc;p=space-station-14.git Namespace cleanup around Mind Roles (#30965) * namespaces * Comment does not need a semicolon --------- Co-authored-by: Vasilis --- diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs index 27b2a5dedb..6a1881a227 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -7,67 +7,66 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; -namespace Content.Client.Administration +namespace Content.Client.Administration; + +internal sealed class AdminNameOverlay : Overlay { - internal sealed class AdminNameOverlay : Overlay + private readonly AdminSystem _system; + private readonly IEntityManager _entityManager; + private readonly IEyeManager _eyeManager; + private readonly EntityLookupSystem _entityLookup; + private readonly Font _font; + + public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup) { - private readonly AdminSystem _system; - private readonly IEntityManager _entityManager; - private readonly IEyeManager _eyeManager; - private readonly EntityLookupSystem _entityLookup; - private readonly Font _font; + _system = system; + _entityManager = entityManager; + _eyeManager = eyeManager; + _entityLookup = entityLookup; + ZIndex = 200; + _font = new VectorFont(resourceCache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); + } - public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup) - { - _system = system; - _entityManager = entityManager; - _eyeManager = eyeManager; - _entityLookup = entityLookup; - ZIndex = 200; - _font = new VectorFont(resourceCache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); - } + public override OverlaySpace Space => OverlaySpace.ScreenSpace; - public override OverlaySpace Space => OverlaySpace.ScreenSpace; + protected override void Draw(in OverlayDrawArgs args) + { + var viewport = args.WorldAABB; - protected override void Draw(in OverlayDrawArgs args) + foreach (var playerInfo in _system.PlayerList) { - var viewport = args.WorldAABB; + var entity = _entityManager.GetEntity(playerInfo.NetEntity); - foreach (var playerInfo in _system.PlayerList) + // Otherwise the entity can not exist yet + if (entity == null || !_entityManager.EntityExists(entity)) { - var entity = _entityManager.GetEntity(playerInfo.NetEntity); - - // Otherwise the entity can not exist yet - if (entity == null || !_entityManager.EntityExists(entity)) - { - continue; - } + continue; + } - // if not on the same map, continue - if (_entityManager.GetComponent(entity.Value).MapID != args.MapId) - { - continue; - } + // if not on the same map, continue + if (_entityManager.GetComponent(entity.Value).MapID != args.MapId) + { + continue; + } - var aabb = _entityLookup.GetWorldAABB(entity.Value); + var aabb = _entityLookup.GetWorldAABB(entity.Value); - // if not on screen, continue - if (!aabb.Intersects(in viewport)) - { - continue; - } + // if not on screen, continue + if (!aabb.Intersects(in viewport)) + { + continue; + } - var lineoffset = new Vector2(0f, 11f); - var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center + - new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec( - aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f); - if (playerInfo.Antag) - { - args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed); - } - args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White); - args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White); + var lineoffset = new Vector2(0f, 11f); + var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center + + new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec( + aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f); + if (playerInfo.Antag) + { + args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", Color.OrangeRed); } + args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, playerInfo.Connected ? Color.Yellow : Color.White); + args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, playerInfo.Connected ? Color.Aquamarine : Color.White); } } } diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index 2187c40ce9..043bf343ec 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -10,220 +10,219 @@ using Robust.Client.UserInterface.XAML; using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader; using static Robust.Client.UserInterface.Controls.BaseButton; -namespace Content.Client.Administration.UI.Tabs.PlayerTab +namespace Content.Client.Administration.UI.Tabs.PlayerTab; + +[GenerateTypedNameReferences] +public sealed partial class PlayerTab : Control { - [GenerateTypedNameReferences] - public sealed partial class PlayerTab : Control - { - [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IPlayerManager _playerMan = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IPlayerManager _playerMan = default!; - private const string ArrowUp = "↑"; - private const string ArrowDown = "↓"; - private readonly Color _altColor = Color.FromHex("#292B38"); - private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); - private readonly AdminSystem _adminSystem; - private IReadOnlyList _players = new List(); + private const string ArrowUp = "↑"; + private const string ArrowDown = "↓"; + private readonly Color _altColor = Color.FromHex("#292B38"); + private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); + private readonly AdminSystem _adminSystem; + private IReadOnlyList _players = new List(); - private Header _headerClicked = Header.Username; - private bool _ascending = true; - private bool _showDisconnected; + private Header _headerClicked = Header.Username; + private bool _ascending = true; + private bool _showDisconnected; - public event Action? OnEntryKeyBindDown; + public event Action? OnEntryKeyBindDown; - public PlayerTab() - { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); + public PlayerTab() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); - _adminSystem = _entManager.System(); - _adminSystem.PlayerListChanged += RefreshPlayerList; - _adminSystem.OverlayEnabled += OverlayEnabled; - _adminSystem.OverlayDisabled += OverlayDisabled; + _adminSystem = _entManager.System(); + _adminSystem.PlayerListChanged += RefreshPlayerList; + _adminSystem.OverlayEnabled += OverlayEnabled; + _adminSystem.OverlayDisabled += OverlayDisabled; - OverlayButton.OnPressed += OverlayButtonPressed; - ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed; + OverlayButton.OnPressed += OverlayButtonPressed; + ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed; - ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor); - ListHeader.OnHeaderClicked += HeaderClicked; + ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor); + ListHeader.OnHeaderClicked += HeaderClicked; - SearchList.SearchBar = SearchLineEdit; - SearchList.GenerateItem += GenerateButton; - SearchList.DataFilterCondition += DataFilterCondition; - SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data); + SearchList.SearchBar = SearchLineEdit; + SearchList.GenerateItem += GenerateButton; + SearchList.DataFilterCondition += DataFilterCondition; + SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data); - RefreshPlayerList(_adminSystem.PlayerList); + RefreshPlayerList(_adminSystem.PlayerList); - } + } - #region Antag Overlay + #region Antag Overlay - private void OverlayEnabled() - { - OverlayButton.Pressed = true; - } + private void OverlayEnabled() + { + OverlayButton.Pressed = true; + } - private void OverlayDisabled() + private void OverlayDisabled() + { + OverlayButton.Pressed = false; + } + + private void OverlayButtonPressed(ButtonEventArgs args) + { + if (args.Button.Pressed) { - OverlayButton.Pressed = false; + _adminSystem.AdminOverlayOn(); } - - private void OverlayButtonPressed(ButtonEventArgs args) + else { - if (args.Button.Pressed) - { - _adminSystem.AdminOverlayOn(); - } - else - { - _adminSystem.AdminOverlayOff(); - } + _adminSystem.AdminOverlayOff(); } + } - #endregion + #endregion + + private void ShowDisconnectedPressed(ButtonEventArgs args) + { + _showDisconnected = args.Button.Pressed; + RefreshPlayerList(_players); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - private void ShowDisconnectedPressed(ButtonEventArgs args) + if (disposing) { - _showDisconnected = args.Button.Pressed; - RefreshPlayerList(_players); + _adminSystem.PlayerListChanged -= RefreshPlayerList; + _adminSystem.OverlayEnabled -= OverlayEnabled; + _adminSystem.OverlayDisabled -= OverlayDisabled; + + OverlayButton.OnPressed -= OverlayButtonPressed; + + ListHeader.OnHeaderClicked -= HeaderClicked; } + } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); + #region ListContainer - if (disposing) - { - _adminSystem.PlayerListChanged -= RefreshPlayerList; - _adminSystem.OverlayEnabled -= OverlayEnabled; - _adminSystem.OverlayDisabled -= OverlayDisabled; + private void RefreshPlayerList(IReadOnlyList players) + { + _players = players; + PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount)); - OverlayButton.OnPressed -= OverlayButtonPressed; + var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList(); - ListHeader.OnHeaderClicked -= HeaderClicked; - } - } + var sortedPlayers = new List(filteredPlayers); + sortedPlayers.Sort(Compare); - #region ListContainer + UpdateHeaderSymbols(); - private void RefreshPlayerList(IReadOnlyList players) - { - _players = players; - PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount)); + SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info, + $"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}")) + .ToList()); + } - var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList(); + private void GenerateButton(ListData data, ListContainerButton button) + { + if (data is not PlayerListData { Info: var player}) + return; - var sortedPlayers = new List(filteredPlayers); - sortedPlayers.Sort(Compare); + var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor)); + button.AddChild(entry); + button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}"; + } - UpdateHeaderSymbols(); + /// + /// Determines whether is contained in .FilteringString. + /// If all characters are lowercase, the comparison ignores case. + /// If there is an uppercase character, the comparison is case sensitive. + /// + /// + /// + /// Whether is contained in .FilteringString. + private bool DataFilterCondition(string filter, ListData listData) + { + if (listData is not PlayerListData {Info: var info, FilteringString: var playerString}) + return false; - SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info, - $"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}")) - .ToList()); - } + if (!_showDisconnected && !info.Connected) + return false; - private void GenerateButton(ListData data, ListContainerButton button) + if (IsAllLower(filter)) { - if (data is not PlayerListData { Info: var player}) - return; - - var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor)); - button.AddChild(entry); - button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}"; + if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase)) + return false; } - - /// - /// Determines whether is contained in .FilteringString. - /// If all characters are lowercase, the comparison ignores case. - /// If there is an uppercase character, the comparison is case sensitive. - /// - /// - /// - /// Whether is contained in .FilteringString. - private bool DataFilterCondition(string filter, ListData listData) + else { - if (listData is not PlayerListData {Info: var info, FilteringString: var playerString}) + if (!playerString.Contains(filter)) return false; + } - if (!_showDisconnected && !info.Connected) - return false; + return true; + } - if (IsAllLower(filter)) - { - if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase)) - return false; - } - else - { - if (!playerString.Contains(filter)) - return false; - } - - return true; + private bool IsAllLower(string input) + { + foreach (var c in input) + { + if (char.IsLetter(c) && !char.IsLower(c)) + return false; } - private bool IsAllLower(string input) - { - foreach (var c in input) - { - if (char.IsLetter(c) && !char.IsLower(c)) - return false; - } + return true; + } - return true; - } + #endregion - #endregion + #region Header - #region Header + private void UpdateHeaderSymbols() + { + ListHeader.ResetHeaderText(); + ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}"; + } - private void UpdateHeaderSymbols() + private int Compare(PlayerInfo x, PlayerInfo y) + { + if (!_ascending) { - ListHeader.ResetHeaderText(); - ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}"; + (x, y) = (y, x); } - private int Compare(PlayerInfo x, PlayerInfo y) + return _headerClicked switch { - if (!_ascending) - { - (x, y) = (y, x); - } - - return _headerClicked switch - { - Header.Username => Compare(x.Username, y.Username), - Header.Character => Compare(x.CharacterName, y.CharacterName), - Header.Job => Compare(x.StartingJob, y.StartingJob), - Header.Antagonist => x.Antag.CompareTo(y.Antag), - Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default), - _ => 1 - }; - } + Header.Username => Compare(x.Username, y.Username), + Header.Character => Compare(x.CharacterName, y.CharacterName), + Header.Job => Compare(x.StartingJob, y.StartingJob), + Header.Antagonist => x.Antag.CompareTo(y.Antag), + Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default), + _ => 1 + }; + } + + private int Compare(string x, string y) + { + return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); + } - private int Compare(string x, string y) + private void HeaderClicked(Header header) + { + if (_headerClicked == header) { - return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); + _ascending = !_ascending; } - - private void HeaderClicked(Header header) + else { - if (_headerClicked == header) - { - _ascending = !_ascending; - } - else - { - _headerClicked = header; - _ascending = true; - } - - RefreshPlayerList(_adminSystem.PlayerList); + _headerClicked = header; + _ascending = true; } - #endregion + RefreshPlayerList(_adminSystem.PlayerList); } - public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData; + #endregion } + +public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData; diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 67b5f5202f..e428d30f20 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -5,71 +5,70 @@ using Content.Shared.Chat; using Robust.Client.Console; using Robust.Shared.Utility; -namespace Content.Client.Chat.Managers +namespace Content.Client.Chat.Managers; + +internal sealed class ChatManager : IChatManager { - internal sealed class ChatManager : IChatManager - { - [Dependency] private readonly IClientConsoleHost _consoleHost = default!; - [Dependency] private readonly IClientAdminManager _adminMgr = default!; - [Dependency] private readonly IEntitySystemManager _systems = default!; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; + [Dependency] private readonly IClientAdminManager _adminMgr = default!; + [Dependency] private readonly IEntitySystemManager _systems = default!; - private ISawmill _sawmill = default!; + private ISawmill _sawmill = default!; - public void Initialize() - { - _sawmill = Logger.GetSawmill("chat"); - _sawmill.Level = LogLevel.Info; - } + public void Initialize() + { + _sawmill = Logger.GetSawmill("chat"); + _sawmill.Level = LogLevel.Info; + } - public void SendMessage(string text, ChatSelectChannel channel) + public void SendMessage(string text, ChatSelectChannel channel) + { + var str = text.ToString(); + switch (channel) { - var str = text.ToString(); - switch (channel) - { - case ChatSelectChannel.Console: - // run locally - _consoleHost.ExecuteCommand(text); - break; + case ChatSelectChannel.Console: + // run locally + _consoleHost.ExecuteCommand(text); + break; - case ChatSelectChannel.LOOC: - _consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\""); - break; + case ChatSelectChannel.LOOC: + _consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\""); + break; - case ChatSelectChannel.OOC: - _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\""); - break; + case ChatSelectChannel.OOC: + _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\""); + break; - case ChatSelectChannel.Admin: - _consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\""); - break; + case ChatSelectChannel.Admin: + _consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\""); + break; - case ChatSelectChannel.Emotes: - _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\""); - break; + case ChatSelectChannel.Emotes: + _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\""); + break; - case ChatSelectChannel.Dead: - if (_systems.GetEntitySystemOrNull() is {IsGhost: true}) - goto case ChatSelectChannel.Local; + case ChatSelectChannel.Dead: + if (_systems.GetEntitySystemOrNull() is {IsGhost: true}) + goto case ChatSelectChannel.Local; - if (_adminMgr.HasFlag(AdminFlags.Admin)) - _consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\""); - else - _sawmill.Warning("Tried to speak on deadchat without being ghost or admin."); - break; + if (_adminMgr.HasFlag(AdminFlags.Admin)) + _consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\""); + else + _sawmill.Warning("Tried to speak on deadchat without being ghost or admin."); + break; - // TODO sepearate radio and say into separate commands. - case ChatSelectChannel.Radio: - case ChatSelectChannel.Local: - _consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\""); - break; + // TODO sepearate radio and say into separate commands. + case ChatSelectChannel.Radio: + case ChatSelectChannel.Local: + _consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\""); + break; - case ChatSelectChannel.Whisper: - _consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\""); - break; + case ChatSelectChannel.Whisper: + _consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\""); + break; - default: - throw new ArgumentOutOfRangeException(nameof(channel), channel, null); - } + default: + throw new ArgumentOutOfRangeException(nameof(channel), channel, null); } } } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 16c079e4ba..db22c41520 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -31,426 +31,425 @@ using Robust.Shared.Enums; using Robust.Shared.Network; using Robust.Shared.Player; -namespace Content.Server.Administration.Systems +namespace Content.Server.Administration.Systems; + +public sealed class AdminSystem : EntitySystem { - public sealed class AdminSystem : EntitySystem + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly IPlayerManager _playerManager = 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 SharedAudioSystem _audio = default!; + [Dependency] private readonly StationRecordsSystem _stationRecords = default!; + [Dependency] private readonly TransformSystem _transform = default!; + + private readonly Dictionary _playerList = new(); + + /// + /// Set of players that have participated in this round. + /// + public IReadOnlySet RoundActivePlayers => _roundActivePlayers; + + private readonly HashSet _roundActivePlayers = new(); + public readonly PanicBunkerStatus PanicBunker = new(); + public readonly BabyJailStatus BabyJail = new(); + + public override void Initialize() { - [Dependency] private readonly IAdminManager _adminManager = default!; - [Dependency] private readonly IChatManager _chat = default!; - [Dependency] private readonly IConfigurationManager _config = default!; - [Dependency] private readonly IPlayerManager _playerManager = 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 SharedAudioSystem _audio = default!; - [Dependency] private readonly StationRecordsSystem _stationRecords = default!; - [Dependency] private readonly TransformSystem _transform = default!; - - private readonly Dictionary _playerList = new(); - - /// - /// Set of players that have participated in this round. - /// - public IReadOnlySet RoundActivePlayers => _roundActivePlayers; - - private readonly HashSet _roundActivePlayers = new(); - public readonly PanicBunkerStatus PanicBunker = new(); - public readonly BabyJailStatus BabyJail = new(); - - public override void Initialize() + base.Initialize(); + + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + _adminManager.OnPermsChanged += OnAdminPermsChanged; + _playTime.SessionPlayTimeUpdated += OnSessionPlayTimeUpdated; + + // Panic Bunker Settings + Subs.CVar(_config, CCVars.PanicBunkerEnabled, OnPanicBunkerChanged, true); + Subs.CVar(_config, CCVars.PanicBunkerDisableWithAdmins, OnPanicBunkerDisableWithAdminsChanged, true); + Subs.CVar(_config, CCVars.PanicBunkerEnableWithoutAdmins, OnPanicBunkerEnableWithoutAdminsChanged, true); + Subs.CVar(_config, CCVars.PanicBunkerCountDeadminnedAdmins, OnPanicBunkerCountDeadminnedAdminsChanged, true); + Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnPanicBunkerShowReasonChanged, true); + Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true); + Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true); + + /* + * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future. + */ + + // Baby Jail Settings + Subs.CVar(_config, CCVars.BabyJailEnabled, OnBabyJailChanged, true); + Subs.CVar(_config, CCVars.BabyJailShowReason, OnBabyJailShowReasonChanged, true); + Subs.CVar(_config, CCVars.BabyJailMaxAccountAge, OnBabyJailMaxAccountAgeChanged, true); + Subs.CVar(_config, CCVars.BabyJailMaxOverallMinutes, OnBabyJailMaxOverallMinutesChanged, true); + + SubscribeLocalEvent(OnIdentityChanged); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnRoleEvent); + SubscribeLocalEvent(OnRoleEvent); + SubscribeLocalEvent(OnRoundRestartCleanup); + } + + private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _roundActivePlayers.Clear(); + + foreach (var (id, data) in _playerList) { - base.Initialize(); - - _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; - _adminManager.OnPermsChanged += OnAdminPermsChanged; - _playTime.SessionPlayTimeUpdated += OnSessionPlayTimeUpdated; - - // Panic Bunker Settings - Subs.CVar(_config, CCVars.PanicBunkerEnabled, OnPanicBunkerChanged, true); - Subs.CVar(_config, CCVars.PanicBunkerDisableWithAdmins, OnPanicBunkerDisableWithAdminsChanged, true); - Subs.CVar(_config, CCVars.PanicBunkerEnableWithoutAdmins, OnPanicBunkerEnableWithoutAdminsChanged, true); - Subs.CVar(_config, CCVars.PanicBunkerCountDeadminnedAdmins, OnPanicBunkerCountDeadminnedAdminsChanged, true); - Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnPanicBunkerShowReasonChanged, true); - Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true); - Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true); - - /* - * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future. - */ - - // Baby Jail Settings - Subs.CVar(_config, CCVars.BabyJailEnabled, OnBabyJailChanged, true); - Subs.CVar(_config, CCVars.BabyJailShowReason, OnBabyJailShowReasonChanged, true); - Subs.CVar(_config, CCVars.BabyJailMaxAccountAge, OnBabyJailMaxAccountAgeChanged, true); - Subs.CVar(_config, CCVars.BabyJailMaxOverallMinutes, OnBabyJailMaxOverallMinutesChanged, true); - - SubscribeLocalEvent(OnIdentityChanged); - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnPlayerDetached); - SubscribeLocalEvent(OnRoleEvent); - SubscribeLocalEvent(OnRoleEvent); - SubscribeLocalEvent(OnRoundRestartCleanup); + if (!data.ActiveThisRound) + continue; + + if (!_playerManager.TryGetPlayerData(id, out var playerData)) + return; + + _playerManager.TryGetSessionById(id, out var session); + _playerList[id] = GetPlayerInfo(playerData, session); } - private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) + var updateEv = new FullPlayerListEvent() { PlayersInfo = _playerList.Values.ToList() }; + + foreach (var admin in _adminManager.ActiveAdmins) { - _roundActivePlayers.Clear(); + RaiseNetworkEvent(updateEv, admin.Channel); + } + } - foreach (var (id, data) in _playerList) - { - if (!data.ActiveThisRound) - continue; + public void UpdatePlayerList(ICommonSession player) + { + _playerList[player.UserId] = GetPlayerInfo(player.Data, player); - if (!_playerManager.TryGetPlayerData(id, out var playerData)) - return; + var playerInfoChangedEvent = new PlayerInfoChangedEvent + { + PlayerInfo = _playerList[player.UserId] + }; - _playerManager.TryGetSessionById(id, out var session); - _playerList[id] = GetPlayerInfo(playerData, session); - } + foreach (var admin in _adminManager.ActiveAdmins) + { + RaiseNetworkEvent(playerInfoChangedEvent, admin.Channel); + } + } - var updateEv = new FullPlayerListEvent() { PlayersInfo = _playerList.Values.ToList() }; + public PlayerInfo? GetCachedPlayerInfo(NetUserId? netUserId) + { + if (netUserId == null) + return null; - foreach (var admin in _adminManager.ActiveAdmins) - { - RaiseNetworkEvent(updateEv, admin.Channel); - } - } + _playerList.TryGetValue(netUserId.Value, out var value); + return value ?? null; + } - public void UpdatePlayerList(ICommonSession player) - { - _playerList[player.UserId] = GetPlayerInfo(player.Data, player); + private void OnIdentityChanged(ref IdentityChangedEvent ev) + { + if (!TryComp(ev.CharacterEntity, out var actor)) + return; - var playerInfoChangedEvent = new PlayerInfoChangedEvent - { - PlayerInfo = _playerList[player.UserId] - }; + UpdatePlayerList(actor.PlayerSession); + } - foreach (var admin in _adminManager.ActiveAdmins) - { - RaiseNetworkEvent(playerInfoChangedEvent, admin.Channel); - } - } + private void OnRoleEvent(RoleEvent ev) + { + var session = _minds.GetSession(ev.Mind); + if (!ev.Antagonist || session == null) + return; - public PlayerInfo? GetCachedPlayerInfo(NetUserId? netUserId) - { - if (netUserId == null) - return null; + UpdatePlayerList(session); + } - _playerList.TryGetValue(netUserId.Value, out var value); - return value ?? null; - } + private void OnAdminPermsChanged(AdminPermsChangedEventArgs obj) + { + UpdatePanicBunker(); - private void OnIdentityChanged(ref IdentityChangedEvent ev) + if (!obj.IsAdmin) { - if (!TryComp(ev.CharacterEntity, out var actor)) - return; - - UpdatePlayerList(actor.PlayerSession); + RaiseNetworkEvent(new FullPlayerListEvent(), obj.Player.Channel); + return; } - private void OnRoleEvent(RoleEvent ev) - { - var session = _minds.GetSession(ev.Mind); - if (!ev.Antagonist || session == null) - return; + SendFullPlayerList(obj.Player); + } - UpdatePlayerList(session); - } + private void OnPlayerDetached(PlayerDetachedEvent ev) + { + // If disconnected then the player won't have a connected entity to get character name from. + // The disconnected state gets sent by OnPlayerStatusChanged. + if (ev.Player.Status == SessionStatus.Disconnected) + return; - private void OnAdminPermsChanged(AdminPermsChangedEventArgs obj) - { - UpdatePanicBunker(); + UpdatePlayerList(ev.Player); + } - if (!obj.IsAdmin) - { - RaiseNetworkEvent(new FullPlayerListEvent(), obj.Player.Channel); - return; - } + private void OnPlayerAttached(PlayerAttachedEvent ev) + { + if (ev.Player.Status == SessionStatus.Disconnected) + return; - SendFullPlayerList(obj.Player); - } + _roundActivePlayers.Add(ev.Player.UserId); + UpdatePlayerList(ev.Player); + } - private void OnPlayerDetached(PlayerDetachedEvent ev) - { - // If disconnected then the player won't have a connected entity to get character name from. - // The disconnected state gets sent by OnPlayerStatusChanged. - if (ev.Player.Status == SessionStatus.Disconnected) - return; + public override void Shutdown() + { + base.Shutdown(); + _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged; + _adminManager.OnPermsChanged -= OnAdminPermsChanged; + _playTime.SessionPlayTimeUpdated -= OnSessionPlayTimeUpdated; + } - UpdatePlayerList(ev.Player); - } + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + UpdatePlayerList(e.Session); + UpdatePanicBunker(); + } - private void OnPlayerAttached(PlayerAttachedEvent ev) - { - if (ev.Player.Status == SessionStatus.Disconnected) - return; + private void SendFullPlayerList(ICommonSession playerSession) + { + var ev = new FullPlayerListEvent(); - _roundActivePlayers.Add(ev.Player.UserId); - UpdatePlayerList(ev.Player); - } + ev.PlayersInfo = _playerList.Values.ToList(); + + RaiseNetworkEvent(ev, playerSession.Channel); + } - public override void Shutdown() + private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession? session) + { + var name = data.UserName; + var entityName = string.Empty; + var identityName = string.Empty; + + if (session?.AttachedEntity != null) { - base.Shutdown(); - _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged; - _adminManager.OnPermsChanged -= OnAdminPermsChanged; - _playTime.SessionPlayTimeUpdated -= OnSessionPlayTimeUpdated; + entityName = EntityManager.GetComponent(session.AttachedEntity.Value).EntityName; + identityName = Identity.Name(session.AttachedEntity.Value, EntityManager); } - private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + var antag = false; + var startingRole = string.Empty; + if (_minds.TryGetMind(session, out var mindId, out _)) { - UpdatePlayerList(e.Session); - UpdatePanicBunker(); + antag = _role.MindIsAntagonist(mindId); + startingRole = _jobs.MindTryGetJobName(mindId); } - private void SendFullPlayerList(ICommonSession playerSession) + var connected = session != null && session.Status is SessionStatus.Connected or SessionStatus.InGame; + TimeSpan? overallPlaytime = null; + if (session != null && + _playTime.TryGetTrackerTimes(session, out var playTimes) && + playTimes.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out var playTime)) { - var ev = new FullPlayerListEvent(); - - ev.PlayersInfo = _playerList.Values.ToList(); - - RaiseNetworkEvent(ev, playerSession.Channel); + overallPlaytime = playTime; } - private PlayerInfo GetPlayerInfo(SessionData data, ICommonSession? session) - { - var name = data.UserName; - var entityName = string.Empty; - var identityName = string.Empty; + return new PlayerInfo(name, entityName, identityName, startingRole, antag, GetNetEntity(session?.AttachedEntity), data.UserId, + connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime); + } - if (session?.AttachedEntity != null) - { - entityName = EntityManager.GetComponent(session.AttachedEntity.Value).EntityName; - identityName = Identity.Name(session.AttachedEntity.Value, EntityManager); - } + private void OnPanicBunkerChanged(bool enabled) + { + PanicBunker.Enabled = enabled; + _chat.SendAdminAlert(Loc.GetString(enabled + ? "admin-ui-panic-bunker-enabled-admin-alert" + : "admin-ui-panic-bunker-disabled-admin-alert" + )); - var antag = false; - var startingRole = string.Empty; - if (_minds.TryGetMind(session, out var mindId, out _)) - { - antag = _role.MindIsAntagonist(mindId); - startingRole = _jobs.MindTryGetJobName(mindId); - } + SendPanicBunkerStatusAll(); + } - var connected = session != null && session.Status is SessionStatus.Connected or SessionStatus.InGame; - TimeSpan? overallPlaytime = null; - if (session != null && - _playTime.TryGetTrackerTimes(session, out var playTimes) && - playTimes.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out var playTime)) - { - overallPlaytime = playTime; - } + private void OnBabyJailChanged(bool enabled) + { + BabyJail.Enabled = enabled; + _chat.SendAdminAlert(Loc.GetString(enabled + ? "admin-ui-baby-jail-enabled-admin-alert" + : "admin-ui-baby-jail-disabled-admin-alert" + )); - return new PlayerInfo(name, entityName, identityName, startingRole, antag, GetNetEntity(session?.AttachedEntity), data.UserId, - connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime); - } + SendBabyJailStatusAll(); + } - private void OnPanicBunkerChanged(bool enabled) - { - PanicBunker.Enabled = enabled; - _chat.SendAdminAlert(Loc.GetString(enabled - ? "admin-ui-panic-bunker-enabled-admin-alert" - : "admin-ui-panic-bunker-disabled-admin-alert" - )); + private void OnPanicBunkerDisableWithAdminsChanged(bool enabled) + { + PanicBunker.DisableWithAdmins = enabled; + UpdatePanicBunker(); + } - SendPanicBunkerStatusAll(); - } + private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled) + { + PanicBunker.EnableWithoutAdmins = enabled; + UpdatePanicBunker(); + } - private void OnBabyJailChanged(bool enabled) - { - BabyJail.Enabled = enabled; - _chat.SendAdminAlert(Loc.GetString(enabled - ? "admin-ui-baby-jail-enabled-admin-alert" - : "admin-ui-baby-jail-disabled-admin-alert" - )); + private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled) + { + PanicBunker.CountDeadminnedAdmins = enabled; + UpdatePanicBunker(); + } - SendBabyJailStatusAll(); - } + private void OnPanicBunkerShowReasonChanged(bool enabled) + { + PanicBunker.ShowReason = enabled; + SendPanicBunkerStatusAll(); + } - private void OnPanicBunkerDisableWithAdminsChanged(bool enabled) - { - PanicBunker.DisableWithAdmins = enabled; - UpdatePanicBunker(); - } + private void OnBabyJailShowReasonChanged(bool enabled) + { + BabyJail.ShowReason = enabled; + SendBabyJailStatusAll(); + } - private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled) - { - PanicBunker.EnableWithoutAdmins = enabled; - UpdatePanicBunker(); - } + private void OnPanicBunkerMinAccountAgeChanged(int minutes) + { + PanicBunker.MinAccountAgeMinutes = minutes; + SendPanicBunkerStatusAll(); + } - private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled) - { - PanicBunker.CountDeadminnedAdmins = enabled; - UpdatePanicBunker(); - } + private void OnBabyJailMaxAccountAgeChanged(int minutes) + { + BabyJail.MaxAccountAgeMinutes = minutes; + SendBabyJailStatusAll(); + } - private void OnPanicBunkerShowReasonChanged(bool enabled) - { - PanicBunker.ShowReason = enabled; - SendPanicBunkerStatusAll(); - } + private void OnPanicBunkerMinOverallMinutesChanged(int minutes) + { + PanicBunker.MinOverallMinutes = minutes; + SendPanicBunkerStatusAll(); + } - private void OnBabyJailShowReasonChanged(bool enabled) - { - BabyJail.ShowReason = enabled; - SendBabyJailStatusAll(); - } + private void OnBabyJailMaxOverallMinutesChanged(int minutes) + { + BabyJail.MaxOverallMinutes = minutes; + SendBabyJailStatusAll(); + } - private void OnPanicBunkerMinAccountAgeChanged(int minutes) + private void UpdatePanicBunker() + { + var admins = PanicBunker.CountDeadminnedAdmins + ? _adminManager.AllAdmins + : _adminManager.ActiveAdmins; + var hasAdmins = admins.Any(); + + // TODO Fix order dependent Cvars + // Please for the sake of my sanity don't make cvars & order dependent. + // Just make a bool field on the system instead of having some cvars automatically modify other cvars. + // + // I.e., this: + // /sudo cvar game.panic_bunker.enabled true + // /sudo cvar game.panic_bunker.disable_with_admins true + // and this: + // /sudo cvar game.panic_bunker.disable_with_admins true + // /sudo cvar game.panic_bunker.enabled true + // + // should have the same effect, but currently setting the disable_with_admins can modify enabled. + + if (hasAdmins && PanicBunker.DisableWithAdmins) { - PanicBunker.MinAccountAgeMinutes = minutes; - SendPanicBunkerStatusAll(); + _config.SetCVar(CCVars.PanicBunkerEnabled, false); } - - private void OnBabyJailMaxAccountAgeChanged(int minutes) + else if (!hasAdmins && PanicBunker.EnableWithoutAdmins) { - BabyJail.MaxAccountAgeMinutes = minutes; - SendBabyJailStatusAll(); + _config.SetCVar(CCVars.PanicBunkerEnabled, true); } - private void OnPanicBunkerMinOverallMinutesChanged(int minutes) - { - PanicBunker.MinOverallMinutes = minutes; - SendPanicBunkerStatusAll(); - } + SendPanicBunkerStatusAll(); + } - private void OnBabyJailMaxOverallMinutesChanged(int minutes) + private void SendPanicBunkerStatusAll() + { + var ev = new PanicBunkerChangedEvent(PanicBunker); + foreach (var admin in _adminManager.AllAdmins) { - BabyJail.MaxOverallMinutes = minutes; - SendBabyJailStatusAll(); + RaiseNetworkEvent(ev, admin); } + } - private void UpdatePanicBunker() + private void SendBabyJailStatusAll() + { + var ev = new BabyJailChangedEvent(BabyJail); + foreach (var admin in _adminManager.AllAdmins) { - var admins = PanicBunker.CountDeadminnedAdmins - ? _adminManager.AllAdmins - : _adminManager.ActiveAdmins; - var hasAdmins = admins.Any(); - - // TODO Fix order dependent Cvars - // Please for the sake of my sanity don't make cvars & order dependent. - // Just make a bool field on the system instead of having some cvars automatically modify other cvars. - // - // I.e., this: - // /sudo cvar game.panic_bunker.enabled true - // /sudo cvar game.panic_bunker.disable_with_admins true - // and this: - // /sudo cvar game.panic_bunker.disable_with_admins true - // /sudo cvar game.panic_bunker.enabled true - // - // should have the same effect, but currently setting the disable_with_admins can modify enabled. - - if (hasAdmins && PanicBunker.DisableWithAdmins) - { - _config.SetCVar(CCVars.PanicBunkerEnabled, false); - } - else if (!hasAdmins && PanicBunker.EnableWithoutAdmins) - { - _config.SetCVar(CCVars.PanicBunkerEnabled, true); - } - - SendPanicBunkerStatusAll(); + RaiseNetworkEvent(ev, admin); } + } - private void SendPanicBunkerStatusAll() - { - var ev = new PanicBunkerChangedEvent(PanicBunker); - foreach (var admin in _adminManager.AllAdmins) - { - 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(ICommonSession player) + { + var entity = player.AttachedEntity; + _chat.DeleteMessagesBy(player); - private void SendBabyJailStatusAll() + if (entity != null && !TerminatingOrDeleted(entity.Value)) { - var ev = new BabyJailChangedEvent(BabyJail); - foreach (var admin in _adminManager.AllAdmins) + if (TryComp(entity.Value, out TransformComponent? transform)) { - RaiseNetworkEvent(ev, admin); + 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); + var filter = Filter.Pvs(coordinates, 1, EntityManager, _playerManager); + var audioParams = new AudioParams().WithVolume(3); + _audio.PlayStatic("/Audio/Effects/pop_high.ogg", filter, coordinates, true, audioParams); } - } - - /// - /// 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(ICommonSession player) - { - var entity = player.AttachedEntity; - _chat.DeleteMessagesBy(player); - if (entity != null && !TerminatingOrDeleted(entity.Value)) + foreach (var item in _inventory.GetHandOrInventoryEntities(entity.Value)) { - if (TryComp(entity.Value, out TransformComponent? transform)) + if (TryComp(item, out PdaComponent? pda) && + TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) && + keyStorage.Key is { } key && + _stationRecords.TryGetRecord(key, out GeneralStationRecord? record)) { - 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); - var filter = Filter.Pvs(coordinates, 1, EntityManager, _playerManager); - var audioParams = new AudioParams().WithVolume(3); - _audio.PlayStatic("/Audio/Effects/pop_high.ogg", filter, coordinates, true, audioParams); - } + if (TryComp(entity, out DnaComponent? dna) && + dna.DNA != record.DNA) + { + continue; + } - 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, out GeneralStationRecord? record)) + if (TryComp(entity, out FingerprintComponent? fingerPrint) && + fingerPrint.Fingerprint != record.Fingerprint) { - 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); - Del(item); + continue; } + + _stationRecords.RemoveRecord(key); + Del(item); } + } - if (_inventory.TryGetContainerSlotEnumerator(entity.Value, out var enumerator)) + if (_inventory.TryGetContainerSlotEnumerator(entity.Value, out var enumerator)) + { + while (enumerator.NextItem(out var item, out var slot)) { - while (enumerator.NextItem(out var item, out var slot)) - { - if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, true, true)) - _physics.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse); - } + if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, true, true)) + _physics.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse); } + } - if (TryComp(entity.Value, out HandsComponent? hands)) + if (TryComp(entity.Value, out HandsComponent? hands)) + { + foreach (var hand in _hands.EnumerateHands(entity.Value, hands)) { - foreach (var hand in _hands.EnumerateHands(entity.Value, hands)) - { - _hands.TryDrop(entity.Value, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands); - } + _hands.TryDrop(entity.Value, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands); } } + } - _minds.WipeMind(player); - QueueDel(entity); + _minds.WipeMind(player); + QueueDel(entity); - _gameTicker.SpawnObserver(player); - } + _gameTicker.SpawnObserver(player); + } - private void OnSessionPlayTimeUpdated(ICommonSession session) - { - UpdatePlayerList(session); - } + private void OnSessionPlayTimeUpdated(ICommonSession session) + { + UpdatePlayerList(session); } } diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index b1b083f86b..6fb7bbba8e 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -18,385 +18,384 @@ using Robust.Shared.Player; using Robust.Shared.Replays; using Robust.Shared.Utility; -namespace Content.Server.Chat.Managers +namespace Content.Server.Chat.Managers; + +/// +/// Dispatches chat messages to clients. +/// +internal sealed partial class ChatManager : IChatManager { + private static readonly Dictionary PatronOocColors = new() + { + // I had plans for multiple colors and those went nowhere so... + { "nuclear_operative", "#aa00ff" }, + { "syndicate_agent", "#aa00ff" }, + { "revolutionary", "#aa00ff" } + }; + + [Dependency] private readonly IReplayRecordingManager _replay = default!; + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IMoMMILink _mommiLink = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly INetConfigurationManager _netConfigManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly PlayerRateLimitManager _rateLimitManager = default!; + /// - /// Dispatches chat messages to clients. + /// The maximum length a player-sent message can be sent /// - internal sealed partial class ChatManager : IChatManager + public int MaxMessageLength => _configurationManager.GetCVar(CCVars.ChatMaxMessageLength); + + private bool _oocEnabled = true; + private bool _adminOocEnabled = true; + + private readonly Dictionary _players = new(); + + public void Initialize() { - private static readonly Dictionary PatronOocColors = new() - { - // I had plans for multiple colors and those went nowhere so... - { "nuclear_operative", "#aa00ff" }, - { "syndicate_agent", "#aa00ff" }, - { "revolutionary", "#aa00ff" } - }; - - [Dependency] private readonly IReplayRecordingManager _replay = default!; - [Dependency] private readonly IServerNetManager _netManager = default!; - [Dependency] private readonly IMoMMILink _mommiLink = default!; - [Dependency] private readonly IAdminManager _adminManager = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly INetConfigurationManager _netConfigManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly PlayerRateLimitManager _rateLimitManager = default!; - - /// - /// The maximum length a player-sent message can be sent - /// - public int MaxMessageLength => _configurationManager.GetCVar(CCVars.ChatMaxMessageLength); - - private bool _oocEnabled = true; - private bool _adminOocEnabled = true; - - private readonly Dictionary _players = new(); - - public void Initialize() - { - _netManager.RegisterNetMessage(); - _netManager.RegisterNetMessage(); + _netManager.RegisterNetMessage(); + _netManager.RegisterNetMessage(); - _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); - _configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true); + _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); + _configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true); - RegisterRateLimits(); - } + RegisterRateLimits(); + } - private void OnOocEnabledChanged(bool val) - { - if (_oocEnabled == val) return; + private void OnOocEnabledChanged(bool val) + { + if (_oocEnabled == val) return; - _oocEnabled = val; - DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-ooc-chat-enabled-message" : "chat-manager-ooc-chat-disabled-message")); - } + _oocEnabled = val; + DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-ooc-chat-enabled-message" : "chat-manager-ooc-chat-disabled-message")); + } - private void OnAdminOocEnabledChanged(bool val) - { - if (_adminOocEnabled == val) return; + private void OnAdminOocEnabledChanged(bool val) + { + if (_adminOocEnabled == val) return; - _adminOocEnabled = val; - DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message")); - } + _adminOocEnabled = val; + DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message")); + } - public void DeleteMessagesBy(ICommonSession player) - { - if (!_players.TryGetValue(player.UserId, out var user)) - return; + public void DeleteMessagesBy(ICommonSession player) + { + if (!_players.TryGetValue(player.UserId, out var user)) + return; - var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities }; - _netManager.ServerSendToAll(msg); - } + var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities }; + _netManager.ServerSendToAll(msg); + } - [return: NotNullIfNotNull(nameof(author))] - public ChatUser? EnsurePlayer(NetUserId? author) - { - if (author == null) - return null; + [return: NotNullIfNotNull(nameof(author))] + public ChatUser? EnsurePlayer(NetUserId? author) + { + if (author == null) + return null; - ref var user = ref CollectionsMarshal.GetValueRefOrAddDefault(_players, author.Value, out var exists); - if (!exists || user == null) - user = new ChatUser(_players.Count); + ref var user = ref CollectionsMarshal.GetValueRefOrAddDefault(_players, author.Value, out var exists); + if (!exists || user == null) + user = new ChatUser(_players.Count); - return user; - } + return user; + } - #region Server Announcements + #region Server Announcements - public void DispatchServerAnnouncement(string message, Color? colorOverride = null) - { - var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", FormattedMessage.EscapeText(message))); - ChatMessageToAll(ChatChannel.Server, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride); - Logger.InfoS("SERVER", message); + public void DispatchServerAnnouncement(string message, Color? colorOverride = null) + { + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", FormattedMessage.EscapeText(message))); + ChatMessageToAll(ChatChannel.Server, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride); + Logger.InfoS("SERVER", message); - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server announcement: {message}"); - } + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server announcement: {message}"); + } - public void DispatchServerMessage(ICommonSession player, string message, bool suppressLog = false) - { - var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", FormattedMessage.EscapeText(message))); - ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, player.Channel); + public void DispatchServerMessage(ICommonSession player, string message, bool suppressLog = false) + { + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", FormattedMessage.EscapeText(message))); + ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, player.Channel); - if (!suppressLog) - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server message to {player:Player}: {message}"); - } + if (!suppressLog) + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server message to {player:Player}: {message}"); + } - public void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist, AdminFlags? flagWhitelist) + public void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist, AdminFlags? flagWhitelist) + { + var clients = _adminManager.ActiveAdmins.Where(p => { - var clients = _adminManager.ActiveAdmins.Where(p => - { - var adminData = _adminManager.GetAdminData(p); + var adminData = _adminManager.GetAdminData(p); - DebugTools.AssertNotNull(adminData); + DebugTools.AssertNotNull(adminData); - if (adminData == null) - return false; + if (adminData == null) + return false; - if (flagBlacklist != null && adminData.HasFlag(flagBlacklist.Value)) - return false; + if (flagBlacklist != null && adminData.HasFlag(flagBlacklist.Value)) + return false; - return flagWhitelist == null || adminData.HasFlag(flagWhitelist.Value); + return flagWhitelist == null || adminData.HasFlag(flagWhitelist.Value); - }).Select(p => p.Channel); + }).Select(p => p.Channel); - var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", - ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message))); + var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", + ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message))); - ChatMessageToMany(ChatChannel.Admin, message, wrappedMessage, default, false, true, clients); - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Admin announcement: {message}"); - } + ChatMessageToMany(ChatChannel.Admin, message, wrappedMessage, default, false, true, clients); + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Admin announcement: {message}"); + } - public void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true) - { - var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", - ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), - ("message", FormattedMessage.EscapeText(message))); - ChatMessageToOne(ChatChannel.Admin, message, wrappedMessage, default, false, player.Channel); - } + public void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true) + { + var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", + ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), + ("message", FormattedMessage.EscapeText(message))); + ChatMessageToOne(ChatChannel.Admin, message, wrappedMessage, default, false, player.Channel); + } - public void SendAdminAlert(string message) - { - var clients = _adminManager.ActiveAdmins.Select(p => p.Channel); + public void SendAdminAlert(string message) + { + var clients = _adminManager.ActiveAdmins.Select(p => p.Channel); - var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", - ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message))); + var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", + ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message))); - ChatMessageToMany(ChatChannel.AdminAlert, message, wrappedMessage, default, false, true, clients); - } + ChatMessageToMany(ChatChannel.AdminAlert, message, wrappedMessage, default, false, true, clients); + } - public void SendAdminAlert(EntityUid player, string message) + public void SendAdminAlert(EntityUid player, string message) + { + var mindSystem = _entityManager.System(); + if (!mindSystem.TryGetMind(player, out var mindId, out var mind)) { - var mindSystem = _entityManager.System(); - if (!mindSystem.TryGetMind(player, out var mindId, out var mind)) - { - SendAdminAlert(message); - return; - } + SendAdminAlert(message); + return; + } - var adminSystem = _entityManager.System(); - var antag = mind.UserId != null && (adminSystem.GetCachedPlayerInfo(mind.UserId.Value)?.Antag ?? false); + var adminSystem = _entityManager.System(); + var antag = mind.UserId != null && (adminSystem.GetCachedPlayerInfo(mind.UserId.Value)?.Antag ?? false); - SendAdminAlert($"{mind.Session?.Name}{(antag ? " (ANTAG)" : "")} {message}"); - } + SendAdminAlert($"{mind.Session?.Name}{(antag ? " (ANTAG)" : "")} {message}"); + } - public void SendHookOOC(string sender, string message) + public void SendHookOOC(string sender, string message) + { + if (!_oocEnabled && _configurationManager.GetCVar(CCVars.DisablingOOCDisablesRelay)) { - if (!_oocEnabled && _configurationManager.GetCVar(CCVars.DisablingOOCDisablesRelay)) - { - return; - } - var wrappedMessage = Loc.GetString("chat-manager-send-hook-ooc-wrap-message", ("senderName", sender), ("message", FormattedMessage.EscapeText(message))); - ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, source: EntityUid.Invalid, hideChat: false, recordReplay: true); - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Hook OOC from {sender}: {message}"); + return; } + var wrappedMessage = Loc.GetString("chat-manager-send-hook-ooc-wrap-message", ("senderName", sender), ("message", FormattedMessage.EscapeText(message))); + ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, source: EntityUid.Invalid, hideChat: false, recordReplay: true); + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Hook OOC from {sender}: {message}"); + } - #endregion + #endregion - #region Public OOC Chat API + #region Public OOC Chat API - /// - /// Called for a player to attempt sending an OOC, out-of-game. message. - /// - /// The player sending the message. - /// The message. - /// The type of message. - public void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type) - { - if (HandleRateLimit(player) != RateLimitStatus.Allowed) - return; + /// + /// Called for a player to attempt sending an OOC, out-of-game. message. + /// + /// The player sending the message. + /// The message. + /// The type of message. + public void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type) + { + if (HandleRateLimit(player) != RateLimitStatus.Allowed) + return; - // Check if message exceeds the character limit - if (message.Length > MaxMessageLength) - { - DispatchServerMessage(player, Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength))); - return; - } + // Check if message exceeds the character limit + if (message.Length > MaxMessageLength) + { + DispatchServerMessage(player, Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength))); + return; + } - switch (type) - { - case OOCChatType.OOC: - SendOOC(player, message); - break; - case OOCChatType.Admin: - SendAdminChat(player, message); - break; - } + switch (type) + { + case OOCChatType.OOC: + SendOOC(player, message); + break; + case OOCChatType.Admin: + SendAdminChat(player, message); + break; } + } - #endregion + #endregion - #region Private API + #region Private API - private void SendOOC(ICommonSession player, string message) + private void SendOOC(ICommonSession player, string message) + { + if (_adminManager.IsAdmin(player)) { - if (_adminManager.IsAdmin(player)) - { - if (!_adminOocEnabled) - { - return; - } - } - else if (!_oocEnabled) + if (!_adminOocEnabled) { return; } - - Color? colorOverride = null; - var wrappedMessage = Loc.GetString("chat-manager-send-ooc-wrap-message", ("playerName",player.Name), ("message", FormattedMessage.EscapeText(message))); - if (_adminManager.HasAdminFlag(player, AdminFlags.Admin)) - { - var prefs = _preferencesManager.GetPreferences(player.UserId); - colorOverride = prefs.AdminOOCColor; - } - if ( _netConfigManager.GetClientCVar(player.Channel, CCVars.ShowOocPatronColor) && player.Channel.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor)) - { - wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message))); - } - - //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: colorOverride, author: player.UserId); - _mommiLink.SendOOCMessage(player.Name, message.Replace("@", "\\@").Replace("<", "\\<").Replace("/", "\\/")); // @ and < are both problematic for discord due to pinging. / is sanitized solely to kneecap links to murder embeds via blunt force - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}"); } - - private void SendAdminChat(ICommonSession player, string message) + else if (!_oocEnabled) { - if (!_adminManager.IsAdmin(player)) - { - _adminLogger.Add(LogType.Chat, LogImpact.Extreme, $"{player:Player} attempted to send admin message but was not admin"); - return; - } + return; + } - var clients = _adminManager.ActiveAdmins.Select(p => p.Channel); - 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))); + Color? colorOverride = null; + var wrappedMessage = Loc.GetString("chat-manager-send-ooc-wrap-message", ("playerName",player.Name), ("message", FormattedMessage.EscapeText(message))); + if (_adminManager.HasAdminFlag(player, AdminFlags.Admin)) + { + var prefs = _preferencesManager.GetPreferences(player.UserId); + colorOverride = prefs.AdminOOCColor; + } + if ( _netConfigManager.GetClientCVar(player.Channel, CCVars.ShowOocPatronColor) && player.Channel.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor)) + { + wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message))); + } - foreach (var client in clients) - { - var isSource = client != player.Channel; - ChatMessageToOne(ChatChannel.AdminChat, - message, - wrappedMessage, - default, - false, - client, - audioPath: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundPath) : default, - audioVolume: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundVolume) : default, - author: player.UserId); - } + //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: colorOverride, author: player.UserId); + _mommiLink.SendOOCMessage(player.Name, message.Replace("@", "\\@").Replace("<", "\\<").Replace("/", "\\/")); // @ and < are both problematic for discord due to pinging. / is sanitized solely to kneecap links to murder embeds via blunt force + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}"); + } - _adminLogger.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}"); + private void SendAdminChat(ICommonSession player, string message) + { + if (!_adminManager.IsAdmin(player)) + { + _adminLogger.Add(LogType.Chat, LogImpact.Extreme, $"{player:Player} attempted to send admin message but was not admin"); + return; } - #endregion + var clients = _adminManager.ActiveAdmins.Select(p => p.Channel); + 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))); - #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, NetUserId? author = null) + foreach (var client in clients) { - var user = author == null ? null : EnsurePlayer(author); - var netSource = _entityManager.GetNetEntity(source); - user?.AddEntity(netSource); + var isSource = client != player.Channel; + ChatMessageToOne(ChatChannel.AdminChat, + message, + wrappedMessage, + default, + false, + client, + audioPath: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundPath) : default, + audioVolume: isSource ? _netConfigManager.GetClientCVar(client, CCVars.AdminChatSoundVolume) : default, + author: player.UserId); + } - var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume); - _netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client); + _adminLogger.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}"); + } - if (!recordReplay) - return; + #endregion - if ((channel & ChatChannel.AdminRelated) == 0 || - _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) - { - _replay.RecordServerMessage(msg); - } - } + #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, NetUserId? author = null) + { + var user = author == null ? null : EnsurePlayer(author); + var netSource = _entityManager.GetNetEntity(source); + user?.AddEntity(netSource); - public 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, NetUserId? author = null) - => ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients.ToList(), colorOverride, audioPath, audioVolume, author); + var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume); + _netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client); - 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, NetUserId? author = null) + if (!recordReplay) + return; + + if ((channel & ChatChannel.AdminRelated) == 0 || + _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) { - var user = author == null ? null : EnsurePlayer(author); - var netSource = _entityManager.GetNetEntity(source); - user?.AddEntity(netSource); + _replay.RecordServerMessage(msg); + } + } - var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume); - _netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients); + public 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, NetUserId? author = null) + => ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients.ToList(), colorOverride, audioPath, audioVolume, author); - if (!recordReplay) - return; + 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, NetUserId? author = null) + { + var user = author == null ? null : EnsurePlayer(author); + var netSource = _entityManager.GetNetEntity(source); + user?.AddEntity(netSource); - if ((channel & ChatChannel.AdminRelated) == 0 || - _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) - { - _replay.RecordServerMessage(msg); - } - } + var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume); + _netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients); + + if (!recordReplay) + return; - public void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string wrappedMessage, EntityUid source, - bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) + if ((channel & ChatChannel.AdminRelated) == 0 || + _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) { - if (!recordReplay && !filter.Recipients.Any()) - return; + _replay.RecordServerMessage(msg); + } + } - var clients = new List(); - foreach (var recipient in filter.Recipients) - { - clients.Add(recipient.Channel); - } + public void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string wrappedMessage, EntityUid source, + bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) + { + if (!recordReplay && !filter.Recipients.Any()) + return; - ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients, colorOverride, audioPath, audioVolume); + var clients = new List(); + foreach (var recipient in filter.Recipients) + { + clients.Add(recipient.Channel); } - public void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null) - { - var user = author == null ? null : EnsurePlayer(author); - var netSource = _entityManager.GetNetEntity(source); - user?.AddEntity(netSource); + ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients, colorOverride, audioPath, audioVolume); + } - var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume); - _netManager.ServerSendToAll(new MsgChatMessage() { Message = msg }); + public void ChatMessageToAll(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null) + { + var user = author == null ? null : EnsurePlayer(author); + var netSource = _entityManager.GetNetEntity(source); + user?.AddEntity(netSource); - if (!recordReplay) - return; + var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume); + _netManager.ServerSendToAll(new MsgChatMessage() { Message = msg }); - if ((channel & ChatChannel.AdminRelated) == 0 || - _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) - { - _replay.RecordServerMessage(msg); - } - } + if (!recordReplay) + return; - public bool MessageCharacterLimit(ICommonSession? player, string message) + if ((channel & ChatChannel.AdminRelated) == 0 || + _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) { - var isOverLength = false; + _replay.RecordServerMessage(msg); + } + } - // Non-players don't need to be checked. - if (player == null) - return false; + public bool MessageCharacterLimit(ICommonSession? player, string message) + { + var isOverLength = false; - // Check if message exceeds the character limit if the sender is a player - if (message.Length > MaxMessageLength) - { - var feedback = Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength)); + // Non-players don't need to be checked. + if (player == null) + return false; - DispatchServerMessage(player, feedback); + // Check if message exceeds the character limit if the sender is a player + if (message.Length > MaxMessageLength) + { + var feedback = Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength)); - isOverLength = true; - } + DispatchServerMessage(player, feedback); - return isOverLength; + isOverLength = true; } - #endregion + return isOverLength; } - public enum OOCChatType : byte - { - OOC, - Admin - } + #endregion +} + +public enum OOCChatType : byte +{ + OOC, + Admin } diff --git a/Content.Server/Ghost/ObserverRoleComponent.cs b/Content.Server/Ghost/ObserverRoleComponent.cs index 9952da72a1..24e592eb94 100644 --- a/Content.Server/Ghost/ObserverRoleComponent.cs +++ b/Content.Server/Ghost/ObserverRoleComponent.cs @@ -1,11 +1,10 @@ -namespace Content.Server.Ghost +namespace Content.Server.Ghost; + +/// +/// This is used to mark Observers properly, as they get Minds +/// +[RegisterComponent] +public sealed partial class ObserverRoleComponent : Component { - /// - /// This is used to mark Observers properly, as they get Minds - /// - [RegisterComponent] - public sealed partial class ObserverRoleComponent : Component - { - public string Name => Loc.GetString("observer-role-name"); - } + public string Name => Loc.GetString("observer-role-name"); } diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index 14007edcbf..8089941444 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -2,102 +2,101 @@ using Content.Server.Mind.Commands; using Content.Shared.Roles; -namespace Content.Server.Ghost.Roles.Components +namespace Content.Server.Ghost.Roles.Components; + +[RegisterComponent] +[Access(typeof(GhostRoleSystem))] +public sealed partial class GhostRoleComponent : Component { - [RegisterComponent] - [Access(typeof(GhostRoleSystem))] - public sealed partial class GhostRoleComponent : Component - { - [DataField("name")] private string _roleName = "Unknown"; + [DataField("name")] private string _roleName = "Unknown"; - [DataField("description")] private string _roleDescription = "Unknown"; + [DataField("description")] private string _roleDescription = "Unknown"; - [DataField("rules")] private string _roleRules = "ghost-role-component-default-rules"; + [DataField("rules")] private string _roleRules = "ghost-role-component-default-rules"; - // TODO ROLE TIMERS - // Actually make use of / enforce this requirement? - // Why is this even here. - // Move to ghost role prototype & respect CCvars.GameRoleTimerOverride - [DataField("requirements")] - public HashSet? Requirements; + // TODO ROLE TIMERS + // Actually make use of / enforce this requirement? + // Why is this even here. + // Move to ghost role prototype & respect CCvars.GameRoleTimerOverride + [DataField("requirements")] + public HashSet? Requirements; - /// - /// Whether the should run on the mob. - /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")] - public bool MakeSentient = true; + /// + /// Whether the should run on the mob. + /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")] + public bool MakeSentient = true; - /// - /// The probability that this ghost role will be available after init. - /// Used mostly for takeover roles that want some probability of being takeover, but not 100%. - /// - [DataField("prob")] - public float Probability = 1f; + /// + /// The probability that this ghost role will be available after init. + /// Used mostly for takeover roles that want some probability of being takeover, but not 100%. + /// + [DataField("prob")] + public float Probability = 1f; - // We do this so updating RoleName and RoleDescription in VV updates the open EUIs. + // We do this so updating RoleName and RoleDescription in VV updates the open EUIs. - [ViewVariables(VVAccess.ReadWrite)] - [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public string RoleName + [ViewVariables(VVAccess.ReadWrite)] + [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public string RoleName + { + get => Loc.GetString(_roleName); + set { - get => Loc.GetString(_roleName); - set - { - _roleName = value; - IoCManager.Resolve().System().UpdateAllEui(); - } + _roleName = value; + IoCManager.Resolve().System().UpdateAllEui(); } + } - [ViewVariables(VVAccess.ReadWrite)] - [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public string RoleDescription + [ViewVariables(VVAccess.ReadWrite)] + [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public string RoleDescription + { + get => Loc.GetString(_roleDescription); + set { - get => Loc.GetString(_roleDescription); - set - { - _roleDescription = value; - IoCManager.Resolve().System().UpdateAllEui(); - } + _roleDescription = value; + IoCManager.Resolve().System().UpdateAllEui(); } + } - [ViewVariables(VVAccess.ReadWrite)] - [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public string RoleRules + [ViewVariables(VVAccess.ReadWrite)] + [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public string RoleRules + { + get => Loc.GetString(_roleRules); + set { - get => Loc.GetString(_roleRules); - set - { - _roleRules = value; - IoCManager.Resolve().System().UpdateAllEui(); - } + _roleRules = value; + IoCManager.Resolve().System().UpdateAllEui(); } - - [DataField("allowSpeech")] - [ViewVariables(VVAccess.ReadWrite)] - public bool AllowSpeech { get; set; } = true; - - [DataField("allowMovement")] - [ViewVariables(VVAccess.ReadWrite)] - public bool AllowMovement { get; set; } - - [ViewVariables(VVAccess.ReadOnly)] - public bool Taken { get; set; } - - [ViewVariables] - public uint Identifier { get; set; } - - /// - /// Reregisters the ghost role when the current player ghosts. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("reregister")] - public bool ReregisterOnGhost { get; set; } = true; - - /// - /// If set, ghost role is raffled, otherwise it is first-come-first-serve. - /// - [DataField("raffle")] - [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public GhostRoleRaffleConfig? RaffleConfig { get; set; } } + + [DataField("allowSpeech")] + [ViewVariables(VVAccess.ReadWrite)] + public bool AllowSpeech { get; set; } = true; + + [DataField("allowMovement")] + [ViewVariables(VVAccess.ReadWrite)] + public bool AllowMovement { get; set; } + + [ViewVariables(VVAccess.ReadOnly)] + public bool Taken { get; set; } + + [ViewVariables] + public uint Identifier { get; set; } + + /// + /// Reregisters the ghost role when the current player ghosts. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("reregister")] + public bool ReregisterOnGhost { get; set; } = true; + + /// + /// If set, ghost role is raffled, otherwise it is first-come-first-serve. + /// + [DataField("raffle")] + [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public GhostRoleRaffleConfig? RaffleConfig { get; set; } } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index d95fc92267..4580e2ef36 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -34,794 +34,793 @@ using Content.Shared.Verbs; using Robust.Shared.Collections; using Content.Shared.Ghost.Roles.Components; -namespace Content.Server.Ghost.Roles +namespace Content.Server.Ghost.Roles; + +[UsedImplicitly] +public sealed class GhostRoleSystem : EntitySystem { - [UsedImplicitly] - public sealed class GhostRoleSystem : EntitySystem - { - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly EuiManager _euiManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly FollowerSystem _followerSystem = default!; - [Dependency] private readonly TransformSystem _transform = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; - - private uint _nextRoleIdentifier; - private bool _needsUpdateGhostRoleCount = true; - - private readonly Dictionary> _ghostRoles = new(); - private readonly Dictionary> _ghostRoleRaffles = new(); - - private readonly Dictionary _openUis = new(); - private readonly Dictionary _openMakeGhostRoleUis = new(); - - [ViewVariables] - public IReadOnlyCollection> GhostRoles => _ghostRoles.Values; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(Reset); - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnMindAdded); - SubscribeLocalEvent(OnMindRemoved); - SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnRoleStartup); - SubscribeLocalEvent(OnRoleShutdown); - SubscribeLocalEvent(OnPaused); - SubscribeLocalEvent(OnUnpaused); - SubscribeLocalEvent(OnRaffleInit); - SubscribeLocalEvent(OnRaffleShutdown); - SubscribeLocalEvent(OnSpawnerTakeRole); - SubscribeLocalEvent(OnTakeoverTakeRole); - SubscribeLocalEvent>(OnVerb); - SubscribeLocalEvent(OnGhostRoleRadioMessage); - _playerManager.PlayerStatusChanged += PlayerStatusChanged; - } + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly FollowerSystem _followerSystem = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + + private uint _nextRoleIdentifier; + private bool _needsUpdateGhostRoleCount = true; + + private readonly Dictionary> _ghostRoles = new(); + private readonly Dictionary> _ghostRoleRaffles = new(); + + private readonly Dictionary _openUis = new(); + private readonly Dictionary _openMakeGhostRoleUis = new(); + + [ViewVariables] + public IReadOnlyCollection> GhostRoles => _ghostRoles.Values; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(Reset); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnMindAdded); + SubscribeLocalEvent(OnMindRemoved); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnRoleStartup); + SubscribeLocalEvent(OnRoleShutdown); + SubscribeLocalEvent(OnPaused); + SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnRaffleInit); + SubscribeLocalEvent(OnRaffleShutdown); + SubscribeLocalEvent(OnSpawnerTakeRole); + SubscribeLocalEvent(OnTakeoverTakeRole); + SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnGhostRoleRadioMessage); + _playerManager.PlayerStatusChanged += PlayerStatusChanged; + } - private void OnMobStateChanged(Entity component, ref MobStateChangedEvent args) - { - if (!TryComp(component, out GhostRoleComponent? ghostRole)) - return; + private void OnMobStateChanged(Entity component, ref MobStateChangedEvent args) + { + if (!TryComp(component, out GhostRoleComponent? ghostRole)) + return; - switch (args.NewMobState) - { - case MobState.Alive: - { - if (!ghostRole.Taken) - RegisterGhostRole((component, ghostRole)); - break; - } - case MobState.Critical: - case MobState.Dead: - UnregisterGhostRole((component, ghostRole)); + switch (args.NewMobState) + { + case MobState.Alive: + { + if (!ghostRole.Taken) + RegisterGhostRole((component, ghostRole)); break; - } + } + case MobState.Critical: + case MobState.Dead: + UnregisterGhostRole((component, ghostRole)); + break; } + } - public override void Shutdown() - { - base.Shutdown(); + public override void Shutdown() + { + base.Shutdown(); - _playerManager.PlayerStatusChanged -= PlayerStatusChanged; - } + _playerManager.PlayerStatusChanged -= PlayerStatusChanged; + } - private uint GetNextRoleIdentifier() - { - return unchecked(_nextRoleIdentifier++); - } + private uint GetNextRoleIdentifier() + { + return unchecked(_nextRoleIdentifier++); + } - public void OpenEui(ICommonSession session) - { - if (session.AttachedEntity is not { Valid: true } attached || - !EntityManager.HasComponent(attached)) - return; + public void OpenEui(ICommonSession session) + { + if (session.AttachedEntity is not { Valid: true } attached || + !EntityManager.HasComponent(attached)) + return; - if (_openUis.ContainsKey(session)) - CloseEui(session); + if (_openUis.ContainsKey(session)) + CloseEui(session); - var eui = _openUis[session] = new GhostRolesEui(); - _euiManager.OpenEui(eui, session); - eui.StateDirty(); - } + var eui = _openUis[session] = new GhostRolesEui(); + _euiManager.OpenEui(eui, session); + eui.StateDirty(); + } - public void OpenMakeGhostRoleEui(ICommonSession session, EntityUid uid) - { - if (session.AttachedEntity == null) - return; + public void OpenMakeGhostRoleEui(ICommonSession session, EntityUid uid) + { + if (session.AttachedEntity == null) + return; - if (_openMakeGhostRoleUis.ContainsKey(session)) - CloseEui(session); + if (_openMakeGhostRoleUis.ContainsKey(session)) + CloseEui(session); - var eui = _openMakeGhostRoleUis[session] = new MakeGhostRoleEui(EntityManager, GetNetEntity(uid)); - _euiManager.OpenEui(eui, session); - eui.StateDirty(); - } + var eui = _openMakeGhostRoleUis[session] = new MakeGhostRoleEui(EntityManager, GetNetEntity(uid)); + _euiManager.OpenEui(eui, session); + eui.StateDirty(); + } - public void CloseEui(ICommonSession session) - { - if (!_openUis.ContainsKey(session)) - return; + public void CloseEui(ICommonSession session) + { + if (!_openUis.ContainsKey(session)) + return; - _openUis.Remove(session, out var eui); + _openUis.Remove(session, out var eui); - eui?.Close(); - } + eui?.Close(); + } - public void CloseMakeGhostRoleEui(ICommonSession session) + public void CloseMakeGhostRoleEui(ICommonSession session) + { + if (_openMakeGhostRoleUis.Remove(session, out var eui)) { - if (_openMakeGhostRoleUis.Remove(session, out var eui)) - { - eui.Close(); - } + eui.Close(); } + } - public void UpdateAllEui() + public void UpdateAllEui() + { + foreach (var eui in _openUis.Values) { - foreach (var eui in _openUis.Values) - { - eui.StateDirty(); - } - // Note that this, like the EUIs, is deferred. - // This is for roughly the same reasons, too: - // Someone might spawn a ton of ghost roles at once. - _needsUpdateGhostRoleCount = true; + eui.StateDirty(); } + // Note that this, like the EUIs, is deferred. + // This is for roughly the same reasons, too: + // Someone might spawn a ton of ghost roles at once. + _needsUpdateGhostRoleCount = true; + } - public override void Update(float frameTime) - { - base.Update(frameTime); + public override void Update(float frameTime) + { + base.Update(frameTime); - UpdateGhostRoleCount(); - UpdateRaffles(frameTime); - } + UpdateGhostRoleCount(); + UpdateRaffles(frameTime); + } - /// - /// Handles sending count update for the ghost role button in ghost UI, if ghost role count changed. - /// - private void UpdateGhostRoleCount() - { - if (!_needsUpdateGhostRoleCount) - return; + /// + /// Handles sending count update for the ghost role button in ghost UI, if ghost role count changed. + /// + private void UpdateGhostRoleCount() + { + if (!_needsUpdateGhostRoleCount) + return; - _needsUpdateGhostRoleCount = false; - var response = new GhostUpdateGhostRoleCountEvent(GetGhostRoleCount()); - foreach (var player in _playerManager.Sessions) - { - RaiseNetworkEvent(response, player.Channel); - } + _needsUpdateGhostRoleCount = false; + var response = new GhostUpdateGhostRoleCountEvent(GetGhostRoleCount()); + foreach (var player in _playerManager.Sessions) + { + RaiseNetworkEvent(response, player.Channel); } + } - /// - /// Handles ghost role raffle logic. - /// - private void UpdateRaffles(float frameTime) + /// + /// Handles ghost role raffle logic. + /// + private void UpdateRaffles(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var entityUid, out var raffle, out var meta)) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var entityUid, out var raffle, out var meta)) + if (meta.EntityPaused) + continue; + + // if all participants leave/were removed from the raffle, the raffle is canceled. + if (raffle.CurrentMembers.Count == 0) { - if (meta.EntityPaused) - continue; + RemoveRaffleAndUpdateEui(entityUid, raffle); + continue; + } - // if all participants leave/were removed from the raffle, the raffle is canceled. - if (raffle.CurrentMembers.Count == 0) - { - RemoveRaffleAndUpdateEui(entityUid, raffle); - continue; - } + raffle.Countdown = raffle.Countdown.Subtract(TimeSpan.FromSeconds(frameTime)); + if (raffle.Countdown.Ticks > 0) + continue; - raffle.Countdown = raffle.Countdown.Subtract(TimeSpan.FromSeconds(frameTime)); - if (raffle.Countdown.Ticks > 0) - continue; + // the raffle is over! find someone to take over the ghost role + if (!TryComp(entityUid, out GhostRoleComponent? ghostRole)) + { + Log.Warning($"Ghost role raffle finished on {entityUid} but {nameof(GhostRoleComponent)} is missing"); + RemoveRaffleAndUpdateEui(entityUid, raffle); + continue; + } - // the raffle is over! find someone to take over the ghost role - if (!TryComp(entityUid, out GhostRoleComponent? ghostRole)) - { - Log.Warning($"Ghost role raffle finished on {entityUid} but {nameof(GhostRoleComponent)} is missing"); - RemoveRaffleAndUpdateEui(entityUid, raffle); - continue; - } + if (ghostRole.RaffleConfig is null) + { + Log.Warning($"Ghost role raffle finished on {entityUid} but RaffleConfig became null"); + RemoveRaffleAndUpdateEui(entityUid, raffle); + continue; + } - if (ghostRole.RaffleConfig is null) - { - Log.Warning($"Ghost role raffle finished on {entityUid} but RaffleConfig became null"); - RemoveRaffleAndUpdateEui(entityUid, raffle); - continue; - } + var foundWinner = false; + var deciderPrototype = _prototype.Index(ghostRole.RaffleConfig.Decider); - var foundWinner = false; - var deciderPrototype = _prototype.Index(ghostRole.RaffleConfig.Decider); - - // use the ghost role's chosen winner picker to find a winner - deciderPrototype.Decider.PickWinner( - raffle.CurrentMembers.AsEnumerable(), - session => - { - var success = TryTakeover(session, raffle.Identifier); - foundWinner |= success; - return success; - } - ); - - if (!foundWinner) + // use the ghost role's chosen winner picker to find a winner + deciderPrototype.Decider.PickWinner( + raffle.CurrentMembers.AsEnumerable(), + session => { - Log.Warning($"Ghost role raffle for {entityUid} ({ghostRole.RoleName}) finished without " + - $"{ghostRole.RaffleConfig?.Decider} finding a winner"); + var success = TryTakeover(session, raffle.Identifier); + foundWinner |= success; + return success; } + ); - // raffle over - RemoveRaffleAndUpdateEui(entityUid, raffle); + if (!foundWinner) + { + Log.Warning($"Ghost role raffle for {entityUid} ({ghostRole.RoleName}) finished without " + + $"{ghostRole.RaffleConfig?.Decider} finding a winner"); } + + // raffle over + RemoveRaffleAndUpdateEui(entityUid, raffle); } + } + + private bool TryTakeover(ICommonSession player, uint identifier) + { + // TODO: the following two checks are kind of redundant since they should already be removed + // from the raffle + // can't win if you are disconnected (although you shouldn't be a candidate anyway) + if (player.Status != SessionStatus.InGame) + return false; + + // can't win if you are no longer a ghost (e.g. if you returned to your body) + if (player.AttachedEntity == null || !HasComp(player.AttachedEntity)) + return false; - private bool TryTakeover(ICommonSession player, uint identifier) + if (Takeover(player, identifier)) { - // TODO: the following two checks are kind of redundant since they should already be removed - // from the raffle - // can't win if you are disconnected (although you shouldn't be a candidate anyway) - if (player.Status != SessionStatus.InGame) - return false; + // takeover successful, we have a winner! remove the winner from other raffles they might be in + LeaveAllRaffles(player); + return true; + } - // can't win if you are no longer a ghost (e.g. if you returned to your body) - if (player.AttachedEntity == null || !HasComp(player.AttachedEntity)) - return false; + return false; + } - if (Takeover(player, identifier)) - { - // takeover successful, we have a winner! remove the winner from other raffles they might be in - LeaveAllRaffles(player); - return true; - } + private void RemoveRaffleAndUpdateEui(EntityUid entityUid, GhostRoleRaffleComponent raffle) + { + _ghostRoleRaffles.Remove(raffle.Identifier); + RemComp(entityUid, raffle); + UpdateAllEui(); + } - return false; + private void PlayerStatusChanged(object? blah, SessionStatusEventArgs args) + { + if (args.NewStatus == SessionStatus.InGame) + { + var response = new GhostUpdateGhostRoleCountEvent(_ghostRoles.Count); + RaiseNetworkEvent(response, args.Session.Channel); } - - private void RemoveRaffleAndUpdateEui(EntityUid entityUid, GhostRoleRaffleComponent raffle) + else { - _ghostRoleRaffles.Remove(raffle.Identifier); - RemComp(entityUid, raffle); - UpdateAllEui(); + // people who disconnect are removed from ghost role raffles + LeaveAllRaffles(args.Session); } + } - private void PlayerStatusChanged(object? blah, SessionStatusEventArgs args) + public void RegisterGhostRole(Entity role) + { + if (_ghostRoles.ContainsValue(role)) + return; + + _ghostRoles[role.Comp.Identifier = GetNextRoleIdentifier()] = role; + UpdateAllEui(); + } + + public void UnregisterGhostRole(Entity role) + { + var comp = role.Comp; + if (!_ghostRoles.ContainsKey(comp.Identifier) || _ghostRoles[comp.Identifier] != role) + return; + + _ghostRoles.Remove(comp.Identifier); + if (TryComp(role.Owner, out GhostRoleRaffleComponent? raffle)) { - if (args.NewStatus == SessionStatus.InGame) - { - var response = new GhostUpdateGhostRoleCountEvent(_ghostRoles.Count); - RaiseNetworkEvent(response, args.Session.Channel); - } - else - { - // people who disconnect are removed from ghost role raffles - LeaveAllRaffles(args.Session); - } + // if a raffle is still running, get rid of it + RemoveRaffleAndUpdateEui(role.Owner, raffle); } - - public void RegisterGhostRole(Entity role) + else { - if (_ghostRoles.ContainsValue(role)) - return; - - _ghostRoles[role.Comp.Identifier = GetNextRoleIdentifier()] = role; UpdateAllEui(); } + } - public void UnregisterGhostRole(Entity role) + // probably fine to be init because it's never added during entity initialization, but much later + private void OnRaffleInit(Entity ent, ref ComponentInit args) + { + if (!TryComp(ent, out GhostRoleComponent? ghostRole)) { - var comp = role.Comp; - if (!_ghostRoles.ContainsKey(comp.Identifier) || _ghostRoles[comp.Identifier] != role) - return; - - _ghostRoles.Remove(comp.Identifier); - if (TryComp(role.Owner, out GhostRoleRaffleComponent? raffle)) - { - // if a raffle is still running, get rid of it - RemoveRaffleAndUpdateEui(role.Owner, raffle); - } - else - { - UpdateAllEui(); - } + // can't have a raffle for a ghost role that doesn't exist + RemComp(ent); + return; } - // probably fine to be init because it's never added during entity initialization, but much later - private void OnRaffleInit(Entity ent, ref ComponentInit args) + var config = ghostRole.RaffleConfig; + if (config is null) + return; // should, realistically, never be reached but you never know + + var settings = config.SettingsOverride + ?? _prototype.Index(config.Settings).Settings; + + if (settings.MaxDuration < settings.InitialDuration) { - if (!TryComp(ent, out GhostRoleComponent? ghostRole)) - { - // can't have a raffle for a ghost role that doesn't exist - RemComp(ent); - return; - } + Log.Error($"Ghost role on {ent} has invalid raffle settings (max duration shorter than initial)"); + ghostRole.RaffleConfig = null; // make it a non-raffle role so stuff isn't entirely broken + RemComp(ent); + return; + } - var config = ghostRole.RaffleConfig; - if (config is null) - return; // should, realistically, never be reached but you never know + var raffle = ent.Comp; + raffle.Identifier = ghostRole.Identifier; + var countdown = _cfg.GetCVar(CCVars.GhostQuickLottery)? 1 : settings.InitialDuration; + raffle.Countdown = TimeSpan.FromSeconds(countdown); + raffle.CumulativeTime = TimeSpan.FromSeconds(settings.InitialDuration); + // we copy these settings into the component because they would be cumbersome to access otherwise + raffle.JoinExtendsDurationBy = TimeSpan.FromSeconds(settings.JoinExtendsDurationBy); + raffle.MaxDuration = TimeSpan.FromSeconds(settings.MaxDuration); + } + + private void OnRaffleShutdown(Entity ent, ref ComponentShutdown args) + { + _ghostRoleRaffles.Remove(ent.Comp.Identifier); + } - var settings = config.SettingsOverride - ?? _prototype.Index(config.Settings).Settings; + /// + /// Joins the given player onto a ghost role raffle, or creates it if it doesn't exist. + /// + /// The player. + /// The ID that represents the ghost role or ghost role raffle. + /// (A raffle will have the same ID as the ghost role it's for.) + private void JoinRaffle(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var roleEnt)) + return; - if (settings.MaxDuration < settings.InitialDuration) - { - Log.Error($"Ghost role on {ent} has invalid raffle settings (max duration shorter than initial)"); - ghostRole.RaffleConfig = null; // make it a non-raffle role so stuff isn't entirely broken - RemComp(ent); - return; - } + // get raffle or create a new one if it doesn't exist + var raffle = _ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt) + ? raffleEnt.Comp + : EnsureComp(roleEnt.Owner); - var raffle = ent.Comp; - raffle.Identifier = ghostRole.Identifier; - var countdown = _cfg.GetCVar(CCVars.GhostQuickLottery)? 1 : settings.InitialDuration; - raffle.Countdown = TimeSpan.FromSeconds(countdown); - raffle.CumulativeTime = TimeSpan.FromSeconds(settings.InitialDuration); - // we copy these settings into the component because they would be cumbersome to access otherwise - raffle.JoinExtendsDurationBy = TimeSpan.FromSeconds(settings.JoinExtendsDurationBy); - raffle.MaxDuration = TimeSpan.FromSeconds(settings.MaxDuration); - } + _ghostRoleRaffles.TryAdd(identifier, (roleEnt.Owner, raffle)); - private void OnRaffleShutdown(Entity ent, ref ComponentShutdown args) + if (!raffle.CurrentMembers.Add(player)) { - _ghostRoleRaffles.Remove(ent.Comp.Identifier); + Log.Warning($"{player.Name} tried to join raffle for ghost role {identifier} but they are already in the raffle"); + return; } - /// - /// Joins the given player onto a ghost role raffle, or creates it if it doesn't exist. - /// - /// The player. - /// The ID that represents the ghost role or ghost role raffle. - /// (A raffle will have the same ID as the ghost role it's for.) - private void JoinRaffle(ICommonSession player, uint identifier) + // if this is the first time the player joins this raffle, and the player wasn't the starter of the raffle: + // extend the countdown, but only if doing so will not make the raffle take longer than the maximum + // duration + if (raffle.AllMembers.Add(player) && raffle.AllMembers.Count > 1 + && raffle.CumulativeTime.Add(raffle.JoinExtendsDurationBy) <= raffle.MaxDuration) { - if (!_ghostRoles.TryGetValue(identifier, out var roleEnt)) - return; - - // get raffle or create a new one if it doesn't exist - var raffle = _ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt) - ? raffleEnt.Comp - : EnsureComp(roleEnt.Owner); - - _ghostRoleRaffles.TryAdd(identifier, (roleEnt.Owner, raffle)); + raffle.Countdown += raffle.JoinExtendsDurationBy; + raffle.CumulativeTime += raffle.JoinExtendsDurationBy; + } - if (!raffle.CurrentMembers.Add(player)) - { - Log.Warning($"{player.Name} tried to join raffle for ghost role {identifier} but they are already in the raffle"); - return; - } + UpdateAllEui(); + } - // if this is the first time the player joins this raffle, and the player wasn't the starter of the raffle: - // extend the countdown, but only if doing so will not make the raffle take longer than the maximum - // duration - if (raffle.AllMembers.Add(player) && raffle.AllMembers.Count > 1 - && raffle.CumulativeTime.Add(raffle.JoinExtendsDurationBy) <= raffle.MaxDuration) - { - raffle.Countdown += raffle.JoinExtendsDurationBy; - raffle.CumulativeTime += raffle.JoinExtendsDurationBy; - } + /// + /// Makes the given player leave the raffle corresponding to the given ID. + /// + public void LeaveRaffle(ICommonSession player, uint identifier) + { + if (!_ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt)) + return; + if (raffleEnt.Comp.CurrentMembers.Remove(player)) + { UpdateAllEui(); } - - /// - /// Makes the given player leave the raffle corresponding to the given ID. - /// - public void LeaveRaffle(ICommonSession player, uint identifier) + else { - if (!_ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt)) - return; + Log.Warning($"{player.Name} tried to leave raffle for ghost role {identifier} but they are not in the raffle"); + } - if (raffleEnt.Comp.CurrentMembers.Remove(player)) - { - UpdateAllEui(); - } - else - { - Log.Warning($"{player.Name} tried to leave raffle for ghost role {identifier} but they are not in the raffle"); - } + // (raffle ending because all players left is handled in update()) + } - // (raffle ending because all players left is handled in update()) - } + /// + /// Makes the given player leave all ghost role raffles. + /// + public void LeaveAllRaffles(ICommonSession player) + { + var shouldUpdateEui = false; - /// - /// Makes the given player leave all ghost role raffles. - /// - public void LeaveAllRaffles(ICommonSession player) + foreach (var raffleEnt in _ghostRoleRaffles.Values) { - var shouldUpdateEui = false; + shouldUpdateEui |= raffleEnt.Comp.CurrentMembers.Remove(player); + } - foreach (var raffleEnt in _ghostRoleRaffles.Values) - { - shouldUpdateEui |= raffleEnt.Comp.CurrentMembers.Remove(player); - } + if (shouldUpdateEui) + UpdateAllEui(); + } - if (shouldUpdateEui) - UpdateAllEui(); - } + /// + /// Request a ghost role. If it's a raffled role starts or joins a raffle, otherwise the player immediately + /// takes over the ghost role if possible. + /// + /// The player. + /// ID of the ghost role. + public void Request(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var roleEnt)) + return; - /// - /// Request a ghost role. If it's a raffled role starts or joins a raffle, otherwise the player immediately - /// takes over the ghost role if possible. - /// - /// The player. - /// ID of the ghost role. - public void Request(ICommonSession player, uint identifier) + if (roleEnt.Comp.RaffleConfig is not null) { - if (!_ghostRoles.TryGetValue(identifier, out var roleEnt)) - return; - - if (roleEnt.Comp.RaffleConfig is not null) - { - JoinRaffle(player, identifier); - } - else - { - Takeover(player, identifier); - } + JoinRaffle(player, identifier); } - - /// - /// Attempts having the player take over the ghost role with the corresponding ID. Does not start a raffle. - /// - /// True if takeover was successful, otherwise false. - public bool Takeover(ICommonSession player, uint identifier) + else { - if (!_ghostRoles.TryGetValue(identifier, out var role)) - return false; + Takeover(player, identifier); + } + } - var ev = new TakeGhostRoleEvent(player); - RaiseLocalEvent(role, ref ev); + /// + /// Attempts having the player take over the ghost role with the corresponding ID. Does not start a raffle. + /// + /// True if takeover was successful, otherwise false. + public bool Takeover(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var role)) + return false; - if (!ev.TookRole) - return false; + var ev = new TakeGhostRoleEvent(player); + RaiseLocalEvent(role, ref ev); - if (player.AttachedEntity != null) - _adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}"); + if (!ev.TookRole) + return false; - CloseEui(player); - return true; - } + if (player.AttachedEntity != null) + _adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}"); - public void Follow(ICommonSession player, uint identifier) - { - if (!_ghostRoles.TryGetValue(identifier, out var role)) - return; + CloseEui(player); + return true; + } - if (player.AttachedEntity == null) - return; + public void Follow(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var role)) + return; - _followerSystem.StartFollowingEntity(player.AttachedEntity.Value, role); - } + if (player.AttachedEntity == null) + return; - public void GhostRoleInternalCreateMindAndTransfer(ICommonSession player, EntityUid roleUid, EntityUid mob, GhostRoleComponent? role = null) - { - if (!Resolve(roleUid, ref role)) - return; + _followerSystem.StartFollowingEntity(player.AttachedEntity.Value, role); + } - DebugTools.AssertNotNull(player.ContentData()); + public void GhostRoleInternalCreateMindAndTransfer(ICommonSession player, EntityUid roleUid, EntityUid mob, GhostRoleComponent? role = null) + { + if (!Resolve(roleUid, ref role)) + return; - var newMind = _mindSystem.CreateMind(player.UserId, - EntityManager.GetComponent(mob).EntityName); - _roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName }); + DebugTools.AssertNotNull(player.ContentData()); - _mindSystem.SetUserId(newMind, player.UserId); - _mindSystem.TransferTo(newMind, mob); - } + var newMind = _mindSystem.CreateMind(player.UserId, + EntityManager.GetComponent(mob).EntityName); + _roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName }); - /// - /// Returns the number of available ghost roles. - /// - public int GetGhostRoleCount() - { - var metaQuery = GetEntityQuery(); - return _ghostRoles.Count(pair => metaQuery.GetComponent(pair.Value.Owner).EntityPaused == false); - } + _mindSystem.SetUserId(newMind, player.UserId); + _mindSystem.TransferTo(newMind, mob); + } + + /// + /// Returns the number of available ghost roles. + /// + public int GetGhostRoleCount() + { + var metaQuery = GetEntityQuery(); + return _ghostRoles.Count(pair => metaQuery.GetComponent(pair.Value.Owner).EntityPaused == false); + } + + /// + /// Returns information about all available ghost roles. + /// + /// + /// If not null, the s will show if the given player is in a raffle. + /// + public GhostRoleInfo[] GetGhostRolesInfo(ICommonSession? player) + { + var roles = new List(); + var metaQuery = GetEntityQuery(); - /// - /// Returns information about all available ghost roles. - /// - /// - /// If not null, the s will show if the given player is in a raffle. - /// - public GhostRoleInfo[] GetGhostRolesInfo(ICommonSession? player) + foreach (var (id, (uid, role)) in _ghostRoles) { - var roles = new List(); - var metaQuery = GetEntityQuery(); + if (metaQuery.GetComponent(uid).EntityPaused) + continue; - foreach (var (id, (uid, role)) in _ghostRoles) - { - if (metaQuery.GetComponent(uid).EntityPaused) - continue; + var kind = GhostRoleKind.FirstComeFirstServe; + GhostRoleRaffleComponent? raffle = null; - var kind = GhostRoleKind.FirstComeFirstServe; - GhostRoleRaffleComponent? raffle = null; + if (role.RaffleConfig is not null) + { + kind = GhostRoleKind.RaffleReady; - if (role.RaffleConfig is not null) + if (_ghostRoleRaffles.TryGetValue(id, out var raffleEnt)) { - kind = GhostRoleKind.RaffleReady; - - if (_ghostRoleRaffles.TryGetValue(id, out var raffleEnt)) - { - kind = GhostRoleKind.RaffleInProgress; - raffle = raffleEnt.Comp; + kind = GhostRoleKind.RaffleInProgress; + raffle = raffleEnt.Comp; - if (player is not null && raffle.CurrentMembers.Contains(player)) - kind = GhostRoleKind.RaffleJoined; - } + if (player is not null && raffle.CurrentMembers.Contains(player)) + kind = GhostRoleKind.RaffleJoined; } - - var rafflePlayerCount = (uint?) raffle?.CurrentMembers.Count ?? 0; - var raffleEndTime = raffle is not null - ? _timing.CurTime.Add(raffle.Countdown) - : TimeSpan.MinValue; - - roles.Add(new GhostRoleInfo - { - Identifier = id, - Name = role.RoleName, - Description = role.RoleDescription, - Rules = role.RoleRules, - Requirements = role.Requirements, - Kind = kind, - RafflePlayerCount = rafflePlayerCount, - RaffleEndTime = raffleEndTime - }); } - return roles.ToArray(); - } + var rafflePlayerCount = (uint?) raffle?.CurrentMembers.Count ?? 0; + var raffleEndTime = raffle is not null + ? _timing.CurTime.Add(raffle.Countdown) + : TimeSpan.MinValue; - private void OnPlayerAttached(PlayerAttachedEvent message) - { - // Close the session of any player that has a ghost roles window open and isn't a ghost anymore. - if (!_openUis.ContainsKey(message.Player)) - return; + roles.Add(new GhostRoleInfo + { + Identifier = id, + Name = role.RoleName, + Description = role.RoleDescription, + Rules = role.RoleRules, + Requirements = role.Requirements, + Kind = kind, + RafflePlayerCount = rafflePlayerCount, + RaffleEndTime = raffleEndTime + }); + } + + return roles.ToArray(); + } - if (HasComp(message.Entity)) - return; + private void OnPlayerAttached(PlayerAttachedEvent message) + { + // Close the session of any player that has a ghost roles window open and isn't a ghost anymore. + if (!_openUis.ContainsKey(message.Player)) + return; + + if (HasComp(message.Entity)) + return; + + // The player is not a ghost (anymore), so they should not be in any raffles. Remove them. + // This ensures player doesn't win a raffle after returning to their (revived) body and ends up being + // forced into a ghost role. + LeaveAllRaffles(message.Player); + CloseEui(message.Player); + } - // The player is not a ghost (anymore), so they should not be in any raffles. Remove them. - // This ensures player doesn't win a raffle after returning to their (revived) body and ends up being - // forced into a ghost role. - LeaveAllRaffles(message.Player); - CloseEui(message.Player); - } + private void OnMindAdded(EntityUid uid, GhostTakeoverAvailableComponent component, MindAddedMessage args) + { + if (!TryComp(uid, out GhostRoleComponent? ghostRole)) + return; - private void OnMindAdded(EntityUid uid, GhostTakeoverAvailableComponent component, MindAddedMessage args) - { - if (!TryComp(uid, out GhostRoleComponent? ghostRole)) - return; + ghostRole.Taken = true; + UnregisterGhostRole((uid, ghostRole)); + } - ghostRole.Taken = true; - UnregisterGhostRole((uid, ghostRole)); - } + private void OnMindRemoved(EntityUid uid, GhostTakeoverAvailableComponent component, MindRemovedMessage args) + { + if (!TryComp(uid, out GhostRoleComponent? ghostRole)) + return; - private void OnMindRemoved(EntityUid uid, GhostTakeoverAvailableComponent component, MindRemovedMessage args) - { - if (!TryComp(uid, out GhostRoleComponent? ghostRole)) - return; + // Avoid re-registering it for duplicate entries and potential exceptions. + if (!ghostRole.ReregisterOnGhost || component.LifeStage > ComponentLifeStage.Running) + return; - // Avoid re-registering it for duplicate entries and potential exceptions. - if (!ghostRole.ReregisterOnGhost || component.LifeStage > ComponentLifeStage.Running) - return; + ghostRole.Taken = false; + RegisterGhostRole((uid, ghostRole)); + } - ghostRole.Taken = false; - RegisterGhostRole((uid, ghostRole)); + public void Reset(RoundRestartCleanupEvent ev) + { + foreach (var session in _openUis.Keys) + { + CloseEui(session); } - public void Reset(RoundRestartCleanupEvent ev) - { - foreach (var session in _openUis.Keys) - { - CloseEui(session); - } + _openUis.Clear(); + _ghostRoles.Clear(); + _ghostRoleRaffles.Clear(); + _nextRoleIdentifier = 0; + } - _openUis.Clear(); - _ghostRoles.Clear(); - _ghostRoleRaffles.Clear(); - _nextRoleIdentifier = 0; - } + private void OnPaused(EntityUid uid, GhostRoleComponent component, ref EntityPausedEvent args) + { + if (HasComp(uid)) + return; - private void OnPaused(EntityUid uid, GhostRoleComponent component, ref EntityPausedEvent args) - { - if (HasComp(uid)) - return; + UpdateAllEui(); + } - UpdateAllEui(); - } + private void OnUnpaused(EntityUid uid, GhostRoleComponent component, ref EntityUnpausedEvent args) + { + if (HasComp(uid)) + return; - private void OnUnpaused(EntityUid uid, GhostRoleComponent component, ref EntityUnpausedEvent args) - { - if (HasComp(uid)) - return; + UpdateAllEui(); + } - UpdateAllEui(); - } + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (ent.Comp.Probability < 1f && !_random.Prob(ent.Comp.Probability)) + RemCompDeferred(ent); + } - private void OnMapInit(Entity ent, ref MapInitEvent args) - { - if (ent.Comp.Probability < 1f && !_random.Prob(ent.Comp.Probability)) - RemCompDeferred(ent); - } + private void OnRoleStartup(Entity ent, ref ComponentStartup args) + { + RegisterGhostRole(ent); + } - private void OnRoleStartup(Entity ent, ref ComponentStartup args) - { - RegisterGhostRole(ent); - } + private void OnRoleShutdown(Entity role, ref ComponentShutdown args) + { + UnregisterGhostRole(role); + } - private void OnRoleShutdown(Entity role, ref ComponentShutdown args) + private void OnSpawnerTakeRole(EntityUid uid, GhostRoleMobSpawnerComponent component, ref TakeGhostRoleEvent args) + { + if (!TryComp(uid, out GhostRoleComponent? ghostRole) || + !CanTakeGhost(uid, ghostRole)) { - UnregisterGhostRole(role); + args.TookRole = false; + return; } - private void OnSpawnerTakeRole(EntityUid uid, GhostRoleMobSpawnerComponent component, ref TakeGhostRoleEvent args) - { - if (!TryComp(uid, out GhostRoleComponent? ghostRole) || - !CanTakeGhost(uid, ghostRole)) - { - args.TookRole = false; - return; - } + if (string.IsNullOrEmpty(component.Prototype)) + throw new NullReferenceException("Prototype string cannot be null or empty!"); - if (string.IsNullOrEmpty(component.Prototype)) - throw new NullReferenceException("Prototype string cannot be null or empty!"); + var mob = Spawn(component.Prototype, Transform(uid).Coordinates); + _transform.AttachToGridOrMap(mob); - var mob = Spawn(component.Prototype, Transform(uid).Coordinates); - _transform.AttachToGridOrMap(mob); + var spawnedEvent = new GhostRoleSpawnerUsedEvent(uid, mob); + RaiseLocalEvent(mob, spawnedEvent); - var spawnedEvent = new GhostRoleSpawnerUsedEvent(uid, mob); - RaiseLocalEvent(mob, spawnedEvent); + if (ghostRole.MakeSentient) + MakeSentientCommand.MakeSentient(mob, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech); - if (ghostRole.MakeSentient) - MakeSentientCommand.MakeSentient(mob, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech); + EnsureComp(mob); - EnsureComp(mob); + GhostRoleInternalCreateMindAndTransfer(args.Player, uid, mob, ghostRole); - GhostRoleInternalCreateMindAndTransfer(args.Player, uid, mob, ghostRole); + if (++component.CurrentTakeovers < component.AvailableTakeovers) + { + args.TookRole = true; + return; + } - if (++component.CurrentTakeovers < component.AvailableTakeovers) - { - args.TookRole = true; - return; - } + ghostRole.Taken = true; - ghostRole.Taken = true; + if (component.DeleteOnSpawn) + QueueDel(uid); - if (component.DeleteOnSpawn) - QueueDel(uid); + args.TookRole = true; + } - args.TookRole = true; - } + private bool CanTakeGhost(EntityUid uid, GhostRoleComponent? component = null) + { + return Resolve(uid, ref component, false) && + !component.Taken && + !MetaData(uid).EntityPaused; + } - private bool CanTakeGhost(EntityUid uid, GhostRoleComponent? component = null) + private void OnTakeoverTakeRole(EntityUid uid, GhostTakeoverAvailableComponent component, ref TakeGhostRoleEvent args) + { + if (!TryComp(uid, out GhostRoleComponent? ghostRole) || + !CanTakeGhost(uid, ghostRole)) { - return Resolve(uid, ref component, false) && - !component.Taken && - !MetaData(uid).EntityPaused; + args.TookRole = false; + return; } - private void OnTakeoverTakeRole(EntityUid uid, GhostTakeoverAvailableComponent component, ref TakeGhostRoleEvent args) - { - if (!TryComp(uid, out GhostRoleComponent? ghostRole) || - !CanTakeGhost(uid, ghostRole)) - { - args.TookRole = false; - return; - } - - ghostRole.Taken = true; + ghostRole.Taken = true; - var mind = EnsureComp(uid); + var mind = EnsureComp(uid); - if (mind.HasMind) - { - args.TookRole = false; - return; - } + if (mind.HasMind) + { + args.TookRole = false; + return; + } - if (ghostRole.MakeSentient) - MakeSentientCommand.MakeSentient(uid, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech); + if (ghostRole.MakeSentient) + MakeSentientCommand.MakeSentient(uid, EntityManager, ghostRole.AllowMovement, ghostRole.AllowSpeech); - GhostRoleInternalCreateMindAndTransfer(args.Player, uid, uid, ghostRole); - UnregisterGhostRole((uid, ghostRole)); + GhostRoleInternalCreateMindAndTransfer(args.Player, uid, uid, ghostRole); + UnregisterGhostRole((uid, ghostRole)); - args.TookRole = true; - } + args.TookRole = true; + } - private void OnVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, GetVerbsEvent args) - { - var prototypes = component.SelectablePrototypes; - if (prototypes.Count < 1) - return; + private void OnVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, GetVerbsEvent args) + { + var prototypes = component.SelectablePrototypes; + if (prototypes.Count < 1) + return; - if (!args.CanAccess || !args.CanInteract || args.Hands == null) - return; + if (!args.CanAccess || !args.CanInteract || args.Hands == null) + return; - var verbs = new ValueList(); + var verbs = new ValueList(); - foreach (var prototypeID in prototypes) + foreach (var prototypeID in prototypes) + { + if (_prototype.TryIndex(prototypeID, out var prototype)) { - if (_prototype.TryIndex(prototypeID, out var prototype)) - { - var verb = CreateVerb(uid, component, args.User, prototype); - verbs.Add(verb); - } + var verb = CreateVerb(uid, component, args.User, prototype); + verbs.Add(verb); } - - args.Verbs.UnionWith(verbs); } - private Verb CreateVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, EntityUid userUid, GhostRolePrototype prototype) - { - var verbText = Loc.GetString(prototype.Name); + args.Verbs.UnionWith(verbs); + } - return new Verb() - { - Text = verbText, - Disabled = component.Prototype == prototype.EntityPrototype, - Category = VerbCategory.SelectType, - Act = () => SetMode(uid, prototype, verbText, component, userUid) - }; - } + private Verb CreateVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, EntityUid userUid, GhostRolePrototype prototype) + { + var verbText = Loc.GetString(prototype.Name); - public void SetMode(EntityUid uid, GhostRolePrototype prototype, string verbText, GhostRoleMobSpawnerComponent? component, EntityUid? userUid = null) + return new Verb() { - if (!Resolve(uid, ref component)) - return; + Text = verbText, + Disabled = component.Prototype == prototype.EntityPrototype, + Category = VerbCategory.SelectType, + Act = () => SetMode(uid, prototype, verbText, component, userUid) + }; + } - var ghostrolecomp = EnsureComp(uid); + public void SetMode(EntityUid uid, GhostRolePrototype prototype, string verbText, GhostRoleMobSpawnerComponent? component, EntityUid? userUid = null) + { + if (!Resolve(uid, ref component)) + return; - component.Prototype = prototype.EntityPrototype; - ghostrolecomp.RoleName = verbText; - ghostrolecomp.RoleDescription = prototype.Description; - ghostrolecomp.RoleRules = prototype.Rules; + var ghostrolecomp = EnsureComp(uid); - // Dirty(ghostrolecomp); + component.Prototype = prototype.EntityPrototype; + ghostrolecomp.RoleName = verbText; + ghostrolecomp.RoleDescription = prototype.Description; + ghostrolecomp.RoleRules = prototype.Rules; - if (userUid != null) - { - var msg = Loc.GetString("ghostrole-spawner-select", ("mode", verbText)); - _popupSystem.PopupEntity(msg, uid, userUid.Value); - } - } + // Dirty(ghostrolecomp); - public void OnGhostRoleRadioMessage(Entity entity, ref GhostRoleRadioMessage args) + if (userUid != null) { - if (!_prototype.TryIndex(args.ProtoId, out var ghostRoleProto)) - return; - - // if the prototype chosen isn't actually part of the selectable options, ignore it - foreach (var selectableProto in entity.Comp.SelectablePrototypes) - { - if (selectableProto == ghostRoleProto.EntityPrototype.Id) - return; - } - - SetMode(entity.Owner, ghostRoleProto, ghostRoleProto.Name, entity.Comp); + var msg = Loc.GetString("ghostrole-spawner-select", ("mode", verbText)); + _popupSystem.PopupEntity(msg, uid, userUid.Value); } } - [AnyCommand] - public sealed class GhostRoles : IConsoleCommand + public void OnGhostRoleRadioMessage(Entity entity, ref GhostRoleRadioMessage args) { - [Dependency] private readonly IEntityManager _e = default!; + if (!_prototype.TryIndex(args.ProtoId, out var ghostRoleProto)) + return; - public string Command => "ghostroles"; - public string Description => "Opens the ghost role request window."; - public string Help => $"{Command}"; - public void Execute(IConsoleShell shell, string argStr, string[] args) + // if the prototype chosen isn't actually part of the selectable options, ignore it + foreach (var selectableProto in entity.Comp.SelectablePrototypes) { - if (shell.Player != null) - _e.System().OpenEui(shell.Player); - else - shell.WriteLine("You can only open the ghost roles UI on a client."); + if (selectableProto == ghostRoleProto.EntityPrototype.Id) + return; } + + SetMode(entity.Owner, ghostRoleProto, ghostRoleProto.Name, entity.Comp); + } +} + +[AnyCommand] +public sealed class GhostRoles : IConsoleCommand +{ + [Dependency] private readonly IEntityManager _e = default!; + + public string Command => "ghostroles"; + public string Description => "Opens the ghost role request window."; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player != null) + _e.System().OpenEui(shell.Player); + else + shell.WriteLine("You can only open the ghost roles UI on a client."); } } diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 74adea6cd6..84ab7e2e4a 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -37,245 +37,244 @@ using Content.Shared.Traits.Assorted; using Robust.Shared.Audio.Systems; using Content.Shared.Ghost.Roles.Components; -namespace Content.Server.Zombies +namespace Content.Server.Zombies; + +/// +/// Handles zombie propagation and inherent zombie traits +/// +/// +/// Don't Shitcode Open Inside +/// +public sealed partial class ZombieSystem { + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly ServerInventorySystem _inventory = default!; + [Dependency] private readonly NpcFactionSystem _faction = default!; + [Dependency] private readonly NPCSystem _npc = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!; + [Dependency] private readonly IdentitySystem _identity = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly SharedCombatModeSystem _combat = default!; + [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + /// + /// Handles an entity turning into a zombie when they die or go into crit + /// + private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + { + ZombifyEntity(uid, args.Component); + } + } + /// - /// Handles zombie propagation and inherent zombie traits + /// This is the general purpose function to call if you want to zombify an entity. + /// It handles both humanoid and nonhumanoid transformation and everything should be called through it. /// + /// the entity being zombified + /// /// - /// Don't Shitcode Open Inside + /// ALRIGHT BIG BOYS, GIRLS AND ANYONE ELSE. YOU'VE COME TO THE LAYER OF THE BEAST. THIS IS YOUR WARNING. + /// This function is the god function for zombie stuff, and it is cursed. I have + /// attempted to label everything thouroughly for your sanity. I have attempted to + /// rewrite this, but this is how it shall lie eternal. Turn back now. + /// -emo /// - public sealed partial class ZombieSystem + public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) { - [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly ServerInventorySystem _inventory = default!; - [Dependency] private readonly NpcFactionSystem _faction = default!; - [Dependency] private readonly NPCSystem _npc = default!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!; - [Dependency] private readonly IdentitySystem _identity = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; - [Dependency] private readonly SharedCombatModeSystem _combat = default!; - [Dependency] private readonly IChatManager _chatMan = default!; - [Dependency] private readonly MindSystem _mind = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - - /// - /// Handles an entity turning into a zombie when they die or go into crit - /// - private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args) + //Don't zombfiy zombies + if (HasComp(target) || HasComp(target)) + return; + + if (!Resolve(target, ref mobState, logMissing: false)) + return; + + //you're a real zombie now, son. + var zombiecomp = AddComp(target); + + //we need to basically remove all of these because zombies shouldn't + //get diseases, breath, be thirst, be hungry, die in space, have offspring or be paraplegic. + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + RemComp(target); + + //funny voice + var accentType = "zombie"; + if (TryComp(target, out var accent)) + accentType = accent.Accent; + + EnsureComp(target).Accent = accentType; + + //This is needed for stupid entities that fuck up combat mode component + //in an attempt to make an entity not attack. This is the easiest way to do it. + var combat = EnsureComp(target); + RemComp(target); + _combat.SetCanDisarm(target, false, combat); + _combat.SetInCombatMode(target, true, combat); + + //This is the actual damage of the zombie. We assign the visual appearance + //and range here because of stuff we'll find out later + var melee = EnsureComp(target); + melee.Animation = zombiecomp.AttackAnimation; + melee.WideAnimation = zombiecomp.AttackAnimation; + melee.AltDisarm = false; + melee.Range = 1.2f; + melee.Angle = 0.0f; + melee.HitSound = zombiecomp.BiteSound; + + if (mobState.CurrentState == MobState.Alive) { - if (args.NewMobState == MobState.Dead) - { - ZombifyEntity(uid, args.Component); - } + // Groaning when damaged + EnsureComp(target); + _emoteOnDamage.AddEmote(target, "Scream"); + + // Random groaning + EnsureComp(target); + _autoEmote.AddEmote(target, "ZombieGroan"); } - /// - /// This is the general purpose function to call if you want to zombify an entity. - /// It handles both humanoid and nonhumanoid transformation and everything should be called through it. - /// - /// the entity being zombified - /// - /// - /// ALRIGHT BIG BOYS, GIRLS AND ANYONE ELSE. YOU'VE COME TO THE LAYER OF THE BEAST. THIS IS YOUR WARNING. - /// This function is the god function for zombie stuff, and it is cursed. I have - /// attempted to label everything thouroughly for your sanity. I have attempted to - /// rewrite this, but this is how it shall lie eternal. Turn back now. - /// -emo - /// - public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) + //We have specific stuff for humanoid zombies because they matter more + if (TryComp(target, out var huApComp)) //huapcomp { - //Don't zombfiy zombies - if (HasComp(target) || HasComp(target)) - return; - - if (!Resolve(target, ref mobState, logMissing: false)) - return; - - //you're a real zombie now, son. - var zombiecomp = AddComp(target); - - //we need to basically remove all of these because zombies shouldn't - //get diseases, breath, be thirst, be hungry, die in space, have offspring or be paraplegic. - RemComp(target); - RemComp(target); - RemComp(target); - RemComp(target); - RemComp(target); - RemComp(target); - RemComp(target); - RemComp(target); - - //funny voice - var accentType = "zombie"; - if (TryComp(target, out var accent)) - accentType = accent.Accent; - - EnsureComp(target).Accent = accentType; - - //This is needed for stupid entities that fuck up combat mode component - //in an attempt to make an entity not attack. This is the easiest way to do it. - var combat = EnsureComp(target); - RemComp(target); - _combat.SetCanDisarm(target, false, combat); - _combat.SetInCombatMode(target, true, combat); - - //This is the actual damage of the zombie. We assign the visual appearance - //and range here because of stuff we'll find out later - var melee = EnsureComp(target); - melee.Animation = zombiecomp.AttackAnimation; - melee.WideAnimation = zombiecomp.AttackAnimation; - melee.AltDisarm = false; - melee.Range = 1.2f; - melee.Angle = 0.0f; - melee.HitSound = zombiecomp.BiteSound; - - if (mobState.CurrentState == MobState.Alive) + //store some values before changing them in case the humanoid get cloned later + zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor; + zombiecomp.BeforeZombifiedEyeColor = huApComp.EyeColor; + zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers); + if (TryComp(target, out var stream)) + zombiecomp.BeforeZombifiedBloodReagent = stream.BloodReagent; + + _humanoidAppearance.SetSkinColor(target, zombiecomp.SkinColor, verify: false, humanoid: huApComp); + + // Messing with the eye layer made it vanish upon cloning, and also it didn't even appear right + huApComp.EyeColor = zombiecomp.EyeColor; + + // this might not resync on clone? + _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.Tail, zombiecomp.BaseLayerExternal, humanoid: huApComp); + _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.HeadSide, zombiecomp.BaseLayerExternal, humanoid: huApComp); + _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.HeadTop, zombiecomp.BaseLayerExternal, humanoid: huApComp); + _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.Snout, zombiecomp.BaseLayerExternal, humanoid: huApComp); + + //This is done here because non-humanoids shouldn't get baller damage + //lord forgive me for the hardcoded damage + DamageSpecifier dspec = new() { - // Groaning when damaged - EnsureComp(target); - _emoteOnDamage.AddEmote(target, "Scream"); + DamageDict = new() + { + { "Slash", 13 }, + { "Piercing", 7 }, + { "Structural", 10 } + } + }; + melee.Damage = dspec; + + // humanoid zombies get to pry open doors and shit + var pryComp = EnsureComp(target); + pryComp.SpeedModifier = 0.75f; + pryComp.PryPowered = true; + pryComp.Force = true; + + Dirty(target, pryComp); + } - // Random groaning - EnsureComp(target); - _autoEmote.AddEmote(target, "ZombieGroan"); - } + Dirty(target, melee); - //We have specific stuff for humanoid zombies because they matter more - if (TryComp(target, out var huApComp)) //huapcomp - { - //store some values before changing them in case the humanoid get cloned later - zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor; - zombiecomp.BeforeZombifiedEyeColor = huApComp.EyeColor; - zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers); - if (TryComp(target, out var stream)) - zombiecomp.BeforeZombifiedBloodReagent = stream.BloodReagent; - - _humanoidAppearance.SetSkinColor(target, zombiecomp.SkinColor, verify: false, humanoid: huApComp); - - // Messing with the eye layer made it vanish upon cloning, and also it didn't even appear right - huApComp.EyeColor = zombiecomp.EyeColor; - - // this might not resync on clone? - _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.Tail, zombiecomp.BaseLayerExternal, humanoid: huApComp); - _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.HeadSide, zombiecomp.BaseLayerExternal, humanoid: huApComp); - _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.HeadTop, zombiecomp.BaseLayerExternal, humanoid: huApComp); - _humanoidAppearance.SetBaseLayerId(target, HumanoidVisualLayers.Snout, zombiecomp.BaseLayerExternal, humanoid: huApComp); - - //This is done here because non-humanoids shouldn't get baller damage - //lord forgive me for the hardcoded damage - DamageSpecifier dspec = new() - { - DamageDict = new() - { - { "Slash", 13 }, - { "Piercing", 7 }, - { "Structural", 10 } - } - }; - melee.Damage = dspec; - - // humanoid zombies get to pry open doors and shit - var pryComp = EnsureComp(target); - pryComp.SpeedModifier = 0.75f; - pryComp.PryPowered = true; - pryComp.Force = true; - - Dirty(target, pryComp); - } - - Dirty(target, melee); - - //The zombie gets the assigned damage weaknesses and strengths - _damageable.SetDamageModifierSetId(target, "Zombie"); - - //This makes it so the zombie doesn't take bloodloss damage. - //NOTE: they are supposed to bleed, just not take damage - _bloodstream.SetBloodLossThreshold(target, 0f); - //Give them zombie blood - _bloodstream.ChangeBloodReagent(target, zombiecomp.NewBloodReagent); - - //This is specifically here to combat insuls, because frying zombies on grilles is funny as shit. - _inventory.TryUnequip(target, "gloves", true, true); - //Should prevent instances of zombies using comms for information they shouldnt be able to have. - _inventory.TryUnequip(target, "ears", true, true); - - //popup - _popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution); - - //Make it sentient if it's an animal or something - MakeSentientCommand.MakeSentient(target, EntityManager); - - //Make the zombie not die in the cold. Good for space zombies - if (TryComp(target, out var tempComp)) - tempComp.ColdDamage.ClampMax(0); - - //Heals the zombie from all the damage it took while human - if (TryComp(target, out var damageablecomp)) - _damageable.SetAllDamage(target, damageablecomp, 0); - _mobState.ChangeMobState(target, MobState.Alive); - - _faction.ClearFactions(target, dirty: false); - _faction.AddFaction(target, "Zombie"); - - //gives it the funny "Zombie ___" name. - _nameMod.RefreshNameModifiers(target); - - _identity.QueueIdentityUpdate(target); - - var htn = EnsureComp(target); - htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; - htn.Blackboard.SetValue(NPCBlackboard.Owner, target); - _npc.SleepNPC(target, htn); - - //He's gotta have a mind - var hasMind = _mind.TryGetMind(target, out var mindId, out _); - if (hasMind && _mind.TryGetSession(mindId, out var session)) - { - //Zombie role for player manifest - _roles.MindAddRole(mindId, new ZombieRoleComponent { PrototypeId = zombiecomp.ZombieRoleId }); + //The zombie gets the assigned damage weaknesses and strengths + _damageable.SetDamageModifierSetId(target, "Zombie"); - //Greeting message for new bebe zombers - _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); + //This makes it so the zombie doesn't take bloodloss damage. + //NOTE: they are supposed to bleed, just not take damage + _bloodstream.SetBloodLossThreshold(target, 0f); + //Give them zombie blood + _bloodstream.ChangeBloodReagent(target, zombiecomp.NewBloodReagent); - // Notificate player about new role assignment - _audio.PlayGlobal(zombiecomp.GreetSoundNotification, session); - } - else - { - _npc.WakeNPC(target, htn); - } + //This is specifically here to combat insuls, because frying zombies on grilles is funny as shit. + _inventory.TryUnequip(target, "gloves", true, true); + //Should prevent instances of zombies using comms for information they shouldnt be able to have. + _inventory.TryUnequip(target, "ears", true, true); - if (!HasComp(target) && !hasMind) //this specific component gives build test trouble so pop off, ig - { - //yet more hardcoding. Visit zombie.ftl for more information. - var ghostRole = EnsureComp(target); - EnsureComp(target); - ghostRole.RoleName = Loc.GetString("zombie-generic"); - ghostRole.RoleDescription = Loc.GetString("zombie-role-desc"); - ghostRole.RoleRules = Loc.GetString("zombie-role-rules"); - } - - if (TryComp(target, out var handsComp)) - { - _hands.RemoveHands(target); - RemComp(target, handsComp); - } - - // Sloth: What the fuck? - // How long until compregistry lmao. - RemComp(target); - - // No longer waiting to become a zombie: - // Requires deferral because this is (probably) the event which called ZombifyEntity in the first place. - RemCompDeferred(target); - - //zombie gamemode stuff - var ev = new EntityZombifiedEvent(target); - RaiseLocalEvent(target, ref ev, true); - //zombies get slowdown once they convert - _movementSpeedModifier.RefreshMovementSpeedModifiers(target); + //popup + _popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution); + + //Make it sentient if it's an animal or something + MakeSentientCommand.MakeSentient(target, EntityManager); + + //Make the zombie not die in the cold. Good for space zombies + if (TryComp(target, out var tempComp)) + tempComp.ColdDamage.ClampMax(0); + + //Heals the zombie from all the damage it took while human + if (TryComp(target, out var damageablecomp)) + _damageable.SetAllDamage(target, damageablecomp, 0); + _mobState.ChangeMobState(target, MobState.Alive); + + _faction.ClearFactions(target, dirty: false); + _faction.AddFaction(target, "Zombie"); + + //gives it the funny "Zombie ___" name. + _nameMod.RefreshNameModifiers(target); + + _identity.QueueIdentityUpdate(target); + + var htn = EnsureComp(target); + htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; + htn.Blackboard.SetValue(NPCBlackboard.Owner, target); + _npc.SleepNPC(target, htn); + + //He's gotta have a mind + var hasMind = _mind.TryGetMind(target, out var mindId, out _); + if (hasMind && _mind.TryGetSession(mindId, out var session)) + { + //Zombie role for player manifest + _roles.MindAddRole(mindId, new ZombieRoleComponent { PrototypeId = zombiecomp.ZombieRoleId }); + + //Greeting message for new bebe zombers + _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); + + // Notificate player about new role assignment + _audio.PlayGlobal(zombiecomp.GreetSoundNotification, session); + } + else + { + _npc.WakeNPC(target, htn); + } + + if (!HasComp(target) && !hasMind) //this specific component gives build test trouble so pop off, ig + { + //yet more hardcoding. Visit zombie.ftl for more information. + var ghostRole = EnsureComp(target); + EnsureComp(target); + ghostRole.RoleName = Loc.GetString("zombie-generic"); + ghostRole.RoleDescription = Loc.GetString("zombie-role-desc"); + ghostRole.RoleRules = Loc.GetString("zombie-role-rules"); + } + + if (TryComp(target, out var handsComp)) + { + _hands.RemoveHands(target); + RemComp(target, handsComp); } + + // Sloth: What the fuck? + // How long until compregistry lmao. + RemComp(target); + + // No longer waiting to become a zombie: + // Requires deferral because this is (probably) the event which called ZombifyEntity in the first place. + RemCompDeferred(target); + + //zombie gamemode stuff + var ev = new EntityZombifiedEvent(target); + RaiseLocalEvent(target, ref ev, true); + //zombies get slowdown once they convert + _movementSpeedModifier.RefreshMovementSpeedModifiers(target); } } diff --git a/Content.Shared/Administration/Events/PlayerInfoChangedEvent.cs b/Content.Shared/Administration/Events/PlayerInfoChangedEvent.cs index f82ca55542..e22ebc0b28 100644 --- a/Content.Shared/Administration/Events/PlayerInfoChangedEvent.cs +++ b/Content.Shared/Administration/Events/PlayerInfoChangedEvent.cs @@ -1,10 +1,9 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Administration.Events +namespace Content.Shared.Administration.Events; + +[NetSerializable, Serializable] +public sealed class PlayerInfoChangedEvent : EntityEventArgs { - [NetSerializable, Serializable] - public sealed class PlayerInfoChangedEvent : EntityEventArgs - { - public PlayerInfo? PlayerInfo; - } + public PlayerInfo? PlayerInfo; } diff --git a/Content.Shared/Administration/PlayerInfo.cs b/Content.Shared/Administration/PlayerInfo.cs index ed54d57bbe..89d1477d6b 100644 --- a/Content.Shared/Administration/PlayerInfo.cs +++ b/Content.Shared/Administration/PlayerInfo.cs @@ -1,36 +1,35 @@ using Robust.Shared.Network; using Robust.Shared.Serialization; -namespace Content.Shared.Administration +namespace Content.Shared.Administration; + +[Serializable, NetSerializable] +public sealed record PlayerInfo( + string Username, + string CharacterName, + string IdentityName, + string StartingJob, + bool Antag, + NetEntity? NetEntity, + NetUserId SessionId, + bool Connected, + bool ActiveThisRound, + TimeSpan? OverallPlaytime) { - [Serializable, NetSerializable] - public sealed record PlayerInfo( - string Username, - string CharacterName, - string IdentityName, - string StartingJob, - bool Antag, - NetEntity? NetEntity, - NetUserId SessionId, - bool Connected, - bool ActiveThisRound, - TimeSpan? OverallPlaytime) - { - private string? _playtimeString; + private string? _playtimeString; - public bool IsPinned { get; set; } + public bool IsPinned { get; set; } - public string PlaytimeString => _playtimeString ??= - OverallPlaytime?.ToString("%d':'hh':'mm") ?? Loc.GetString("generic-unknown-title"); + public string PlaytimeString => _playtimeString ??= + OverallPlaytime?.ToString("%d':'hh':'mm") ?? Loc.GetString("generic-unknown-title"); - public bool Equals(PlayerInfo? other) - { - return other?.SessionId == SessionId; - } + public bool Equals(PlayerInfo? other) + { + return other?.SessionId == SessionId; + } - public override int GetHashCode() - { - return SessionId.GetHashCode(); - } + public override int GetHashCode() + { + return SessionId.GetHashCode(); } } diff --git a/Content.Shared/Ghost/Roles/SharedGhostRoleSystem.cs b/Content.Shared/Ghost/Roles/SharedGhostRoleSystem.cs index b64844e9a0..f8c4e5559e 100644 --- a/Content.Shared/Ghost/Roles/SharedGhostRoleSystem.cs +++ b/Content.Shared/Ghost/Roles/SharedGhostRoleSystem.cs @@ -1,12 +1,11 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Ghost.Roles +namespace Content.Shared.Ghost.Roles; + +[Serializable, NetSerializable] +public sealed class GhostRole { - [Serializable, NetSerializable] - public sealed class GhostRole - { - public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public NetEntity Id; - } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public NetEntity Id; } diff --git a/Content.Shared/Mind/Components/MindContainerComponent.cs b/Content.Shared/Mind/Components/MindContainerComponent.cs index 62b26cbd35..380d30ca14 100644 --- a/Content.Shared/Mind/Components/MindContainerComponent.cs +++ b/Content.Shared/Mind/Components/MindContainerComponent.cs @@ -1,97 +1,96 @@ using System.Diagnostics.CodeAnalysis; using Robust.Shared.GameStates; -namespace Content.Shared.Mind.Components +namespace Content.Shared.Mind.Components; + +/// +/// This component indicates that this entity may have mind, which is simply an entity with a . +/// The mind entity is not actually stored in a "container", but is simply stored in nullspace. +/// +[RegisterComponent, Access(typeof(SharedMindSystem)), NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MindContainerComponent : Component { /// - /// This component indicates that this entity may have mind, which is simply an entity with a . - /// The mind entity is not actually stored in a "container", but is simply stored in nullspace. + /// The mind controlling this mob. Can be null. /// - [RegisterComponent, Access(typeof(SharedMindSystem)), NetworkedComponent, AutoGenerateComponentState] - public sealed partial class MindContainerComponent : Component - { - /// - /// The mind controlling this mob. Can be null. - /// - [DataField, AutoNetworkedField] - [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public EntityUid? Mind { get; set; } + [DataField, AutoNetworkedField] + [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public EntityUid? Mind { get; set; } - /// - /// True if we have a mind, false otherwise. - /// - [MemberNotNullWhen(true, nameof(Mind))] - public bool HasMind => Mind != null; + /// + /// True if we have a mind, false otherwise. + /// + [MemberNotNullWhen(true, nameof(Mind))] + public bool HasMind => Mind != null; - /// - /// Whether examining should show information about the mind or not. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("showExamineInfo"), AutoNetworkedField] - public bool ShowExamineInfo { get; set; } + /// + /// Whether examining should show information about the mind or not. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("showExamineInfo"), AutoNetworkedField] + public bool ShowExamineInfo { get; set; } - /// - /// Whether the mind will be put on a ghost after this component is shutdown. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("ghostOnShutdown")] - [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends - public bool GhostOnShutdown { get; set; } = true; - } + /// + /// Whether the mind will be put on a ghost after this component is shutdown. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("ghostOnShutdown")] + [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public bool GhostOnShutdown { get; set; } = true; +} - public abstract class MindEvent : EntityEventArgs - { - public readonly Entity Mind; - public readonly Entity Container; +public abstract class MindEvent : EntityEventArgs +{ + public readonly Entity Mind; + public readonly Entity Container; - public MindEvent(Entity mind, Entity container) - { - Mind = mind; - Container = container; - } + public MindEvent(Entity mind, Entity container) + { + Mind = mind; + Container = container; } +} - /// - /// Event raised directed at a mind-container when a mind gets removed. - /// - public sealed class MindRemovedMessage : MindEvent +/// +/// Event raised directed at a mind-container when a mind gets removed. +/// +public sealed class MindRemovedMessage : MindEvent +{ + public MindRemovedMessage(Entity mind, Entity container) + : base(mind, container) { - public MindRemovedMessage(Entity mind, Entity container) - : base(mind, container) - { - } } +} - /// - /// Event raised directed at a mind when it gets removed from a mind-container. - /// - public sealed class MindGotRemovedEvent : MindEvent +/// +/// Event raised directed at a mind when it gets removed from a mind-container. +/// +public sealed class MindGotRemovedEvent : MindEvent +{ + public MindGotRemovedEvent(Entity mind, Entity container) + : base(mind, container) { - public MindGotRemovedEvent(Entity mind, Entity container) - : base(mind, container) - { - } } +} - /// - /// Event raised directed at a mind-container when a mind gets added. - /// - public sealed class MindAddedMessage : MindEvent +/// +/// Event raised directed at a mind-container when a mind gets added. +/// +public sealed class MindAddedMessage : MindEvent +{ + public MindAddedMessage(Entity mind, Entity container) + : base(mind, container) { - public MindAddedMessage(Entity mind, Entity container) - : base(mind, container) - { - } } +} - /// - /// Event raised directed at a mind when it gets added to a mind-container. - /// - public sealed class MindGotAddedEvent : MindEvent +/// +/// Event raised directed at a mind when it gets added to a mind-container. +/// +public sealed class MindGotAddedEvent : MindEvent +{ + public MindGotAddedEvent(Entity mind, Entity container) + : base(mind, container) { - public MindGotAddedEvent(Entity mind, Entity container) - : base(mind, container) - { - } } } diff --git a/Content.Shared/Mind/MindComponent.cs b/Content.Shared/Mind/MindComponent.cs index 51a174c846..d603102682 100644 --- a/Content.Shared/Mind/MindComponent.cs +++ b/Content.Shared/Mind/MindComponent.cs @@ -5,105 +5,104 @@ using Robust.Shared.GameStates; using Robust.Shared.Network; using Robust.Shared.Player; -namespace Content.Shared.Mind +namespace Content.Shared.Mind; + +/// +/// This component stores information about a player/mob mind. The component will be attached to a mind-entity +/// which is stored in null-space. The entity that is currently "possessed" by the mind will have a +/// . +/// +/// +/// Roles are attached as components on the mind-entity entity. +/// Think of it like this: if a player is supposed to have their memories, +/// their mind follows along. +/// +/// Things such as respawning do not follow, because you're a new character. +/// Getting borged, cloned, turned into a catbeast, etc... will keep it following you. +/// +/// Minds are stored in null-space, and are thus generally not set to players unless that player is the owner +/// of the mind. As a result it should be safe to network "secret" information like roles & objectives +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class MindComponent : Component { + [DataField, AutoNetworkedField] + public List Objectives = new(); + /// - /// This component stores information about a player/mob mind. The component will be attached to a mind-entity - /// which is stored in null-space. The entity that is currently "possessed" by the mind will have a - /// . + /// The session ID of the player owning this mind. /// - /// - /// Roles are attached as components on the mind-entity entity. - /// Think of it like this: if a player is supposed to have their memories, - /// their mind follows along. - /// - /// Things such as respawning do not follow, because you're a new character. - /// Getting borged, cloned, turned into a catbeast, etc... will keep it following you. - /// - /// Minds are stored in null-space, and are thus generally not set to players unless that player is the owner - /// of the mind. As a result it should be safe to network "secret" information like roles & objectives - /// - [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] - public sealed partial class MindComponent : Component - { - [DataField, AutoNetworkedField] - public List Objectives = new(); - - /// - /// The session ID of the player owning this mind. - /// - [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] - public NetUserId? UserId { get; set; } + [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] + public NetUserId? UserId { get; set; } - /// - /// The session ID of the original owner, if any. - /// May end up used for round-end information (as the owner may have abandoned Mind since) - /// - [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] - public NetUserId? OriginalOwnerUserId { get; set; } + /// + /// The session ID of the original owner, if any. + /// May end up used for round-end information (as the owner may have abandoned Mind since) + /// + [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] + public NetUserId? OriginalOwnerUserId { get; set; } - /// - /// The first entity that this mind controlled. Used for round end information. - /// Might be relevant if the player has ghosted since. - /// - [DataField, AutoNetworkedField] - public NetEntity? OriginalOwnedEntity; - // This is a net entity, because this field currently ddoes not get set to null when this entity is deleted. - // This is a lazy way to ensure that people check that the entity still exists. - // TODO MIND Fix this properly by adding an OriginalMindContainerComponent or something like that. + /// + /// The first entity that this mind controlled. Used for round end information. + /// Might be relevant if the player has ghosted since. + /// + [DataField, AutoNetworkedField] + public NetEntity? OriginalOwnedEntity; + // This is a net entity, because this field currently ddoes not get set to null when this entity is deleted. + // This is a lazy way to ensure that people check that the entity still exists. + // TODO MIND Fix this properly by adding an OriginalMindContainerComponent or something like that. - [ViewVariables] - public bool IsVisitingEntity => VisitingEntity != null; + [ViewVariables] + public bool IsVisitingEntity => VisitingEntity != null; - [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] - public EntityUid? VisitingEntity { get; set; } + [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] + public EntityUid? VisitingEntity { get; set; } - [ViewVariables] - public EntityUid? CurrentEntity => VisitingEntity ?? OwnedEntity; + [ViewVariables] + public EntityUid? CurrentEntity => VisitingEntity ?? OwnedEntity; - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] - public string? CharacterName { get; set; } + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public string? CharacterName { get; set; } - /// - /// The time of death for this Mind. - /// Can be null - will be null if the Mind is not considered "dead". - /// - [DataField] - public TimeSpan? TimeOfDeath { get; set; } + /// + /// The time of death for this Mind. + /// Can be null - will be null if the Mind is not considered "dead". + /// + [DataField] + public TimeSpan? TimeOfDeath { get; set; } - /// - /// The entity currently owned by this mind. - /// Can be null. - /// - [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] - public EntityUid? OwnedEntity { get; set; } + /// + /// The entity currently owned by this mind. + /// Can be null. + /// + [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))] + public EntityUid? OwnedEntity { get; set; } - /// - /// An enumerable over all the objective entities this mind has. - /// - [ViewVariables, Obsolete("Use Objectives field")] - public IEnumerable AllObjectives => Objectives; + /// + /// An enumerable over all the objective entities this mind has. + /// + [ViewVariables, Obsolete("Use Objectives field")] + public IEnumerable AllObjectives => Objectives; - /// - /// Prevents user from ghosting out - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("preventGhosting")] - public bool PreventGhosting { get; set; } + /// + /// Prevents user from ghosting out + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("preventGhosting")] + public bool PreventGhosting { get; set; } - /// - /// Prevents user from suiciding - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("preventSuicide")] - public bool PreventSuicide { get; set; } + /// + /// Prevents user from suiciding + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("preventSuicide")] + public bool PreventSuicide { get; set; } - /// - /// The session of the player owning this mind. - /// Can be null, in which case the player is currently not logged in. - /// - [ViewVariables, Access(typeof(SharedMindSystem), typeof(SharedGameTicker))] - // TODO remove this after moving IPlayerManager functions to shared - public ICommonSession? Session { get; set; } - } + /// + /// The session of the player owning this mind. + /// Can be null, in which case the player is currently not logged in. + /// + [ViewVariables, Access(typeof(SharedMindSystem), typeof(SharedGameTicker))] + // TODO remove this after moving IPlayerManager functions to shared + public ICommonSession? Session { get; set; } }