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<FontResource>("/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<FontResource>("/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<TransformComponent>(entity.Value).MapID != args.MapId)
- {
- continue;
- }
+ // if not on the same map, continue
+ if (_entityManager.GetComponent<TransformComponent>(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);
}
}
}
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<PlayerInfo> _players = new List<PlayerInfo>();
+ 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<PlayerInfo> _players = new List<PlayerInfo>();
- 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<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
+ public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
- public PlayerTab()
- {
- IoCManager.InjectDependencies(this);
- RobustXamlLoader.Load(this);
+ public PlayerTab()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
- _adminSystem = _entManager.System<AdminSystem>();
- _adminSystem.PlayerListChanged += RefreshPlayerList;
- _adminSystem.OverlayEnabled += OverlayEnabled;
- _adminSystem.OverlayDisabled += OverlayDisabled;
+ _adminSystem = _entManager.System<AdminSystem>();
+ _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<PlayerInfo> 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<PlayerInfo>(filteredPlayers);
+ sortedPlayers.Sort(Compare);
- #region ListContainer
+ UpdateHeaderSymbols();
- private void RefreshPlayerList(IReadOnlyList<PlayerInfo> 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<PlayerInfo>(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();
+ /// <summary>
+ /// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
+ /// If all characters are lowercase, the comparison ignores case.
+ /// If there is an uppercase character, the comparison is case sensitive.
+ /// </summary>
+ /// <param name="filter"></param>
+ /// <param name="listData"></param>
+ /// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
+ 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;
}
-
- /// <summary>
- /// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
- /// If all characters are lowercase, the comparison ignores case.
- /// If there is an uppercase character, the comparison is case sensitive.
- /// </summary>
- /// <param name="filter"></param>
- /// <param name="listData"></param>
- /// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
- 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;
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<GhostSystem>() is {IsGhost: true})
- goto case ChatSelectChannel.Local;
+ case ChatSelectChannel.Dead:
+ if (_systems.GetEntitySystemOrNull<GhostSystem>() 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);
}
}
}
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<NetUserId, PlayerInfo> _playerList = new();
+
+ /// <summary>
+ /// Set of players that have participated in this round.
+ /// </summary>
+ public IReadOnlySet<NetUserId> RoundActivePlayers => _roundActivePlayers;
+
+ private readonly HashSet<NetUserId> _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<NetUserId, PlayerInfo> _playerList = new();
-
- /// <summary>
- /// Set of players that have participated in this round.
- /// </summary>
- public IReadOnlySet<NetUserId> RoundActivePlayers => _roundActivePlayers;
-
- private readonly HashSet<NetUserId> _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<IdentityChangedEvent>(OnIdentityChanged);
+ SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
+ SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
+ SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
+ SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
+ SubscribeLocalEvent<RoundRestartCleanupEvent>(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<IdentityChangedEvent>(OnIdentityChanged);
- SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
- SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
- SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
- SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
- SubscribeLocalEvent<RoundRestartCleanupEvent>(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<ActorComponent>(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<ActorComponent>(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<MetaDataComponent>(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<MetaDataComponent>(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);
- }
- }
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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);
}
- }
-
- /// <summary>
- /// 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.
- /// </summary>
- 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);
}
}
using Robust.Shared.Replays;
using Robust.Shared.Utility;
-namespace Content.Server.Chat.Managers
+namespace Content.Server.Chat.Managers;
+
+/// <summary>
+/// Dispatches chat messages to clients.
+/// </summary>
+internal sealed partial class ChatManager : IChatManager
{
+ private static readonly Dictionary<string, string> 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!;
+
/// <summary>
- /// Dispatches chat messages to clients.
+ /// The maximum length a player-sent message can be sent
/// </summary>
- internal sealed partial class ChatManager : IChatManager
+ public int MaxMessageLength => _configurationManager.GetCVar(CCVars.ChatMaxMessageLength);
+
+ private bool _oocEnabled = true;
+ private bool _adminOocEnabled = true;
+
+ private readonly Dictionary<NetUserId, ChatUser> _players = new();
+
+ public void Initialize()
{
- private static readonly Dictionary<string, string> 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!;
-
- /// <summary>
- /// The maximum length a player-sent message can be sent
- /// </summary>
- public int MaxMessageLength => _configurationManager.GetCVar(CCVars.ChatMaxMessageLength);
-
- private bool _oocEnabled = true;
- private bool _adminOocEnabled = true;
-
- private readonly Dictionary<NetUserId, ChatUser> _players = new();
-
- public void Initialize()
- {
- _netManager.RegisterNetMessage<MsgChatMessage>();
- _netManager.RegisterNetMessage<MsgDeleteChatMessagesBy>();
+ _netManager.RegisterNetMessage<MsgChatMessage>();
+ _netManager.RegisterNetMessage<MsgDeleteChatMessagesBy>();
- _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<SharedMindSystem>();
+ if (!mindSystem.TryGetMind(player, out var mindId, out var mind))
{
- var mindSystem = _entityManager.System<SharedMindSystem>();
- if (!mindSystem.TryGetMind(player, out var mindId, out var mind))
- {
- SendAdminAlert(message);
- return;
- }
+ SendAdminAlert(message);
+ return;
+ }
- var adminSystem = _entityManager.System<AdminSystem>();
- var antag = mind.UserId != null && (adminSystem.GetCachedPlayerInfo(mind.UserId.Value)?.Antag ?? false);
+ var adminSystem = _entityManager.System<AdminSystem>();
+ 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
- /// <summary>
- /// Called for a player to attempt sending an OOC, out-of-game. message.
- /// </summary>
- /// <param name="player">The player sending the message.</param>
- /// <param name="message">The message.</param>
- /// <param name="type">The type of message.</param>
- public void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type)
- {
- if (HandleRateLimit(player) != RateLimitStatus.Allowed)
- return;
+ /// <summary>
+ /// Called for a player to attempt sending an OOC, out-of-game. message.
+ /// </summary>
+ /// <param name="player">The player sending the message.</param>
+ /// <param name="message">The message.</param>
+ /// <param name="type">The type of message.</param>
+ 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<INetChannel> 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<INetChannel> 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<INetChannel> 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<INetChannel> 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<INetChannel>();
- 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<INetChannel>();
+ 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
}
-namespace Content.Server.Ghost
+namespace Content.Server.Ghost;
+
+/// <summary>
+/// This is used to mark Observers properly, as they get Minds
+/// </summary>
+[RegisterComponent]
+public sealed partial class ObserverRoleComponent : Component
{
- /// <summary>
- /// This is used to mark Observers properly, as they get Minds
- /// </summary>
- [RegisterComponent]
- public sealed partial class ObserverRoleComponent : Component
- {
- public string Name => Loc.GetString("observer-role-name");
- }
+ public string Name => Loc.GetString("observer-role-name");
}
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<JobRequirement>? 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<JobRequirement>? Requirements;
- /// <summary>
- /// Whether the <see cref="MakeSentientCommand"/> should run on the mob.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")]
- public bool MakeSentient = true;
+ /// <summary>
+ /// Whether the <see cref="MakeSentientCommand"/> should run on the mob.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)] [DataField("makeSentient")]
+ public bool MakeSentient = true;
- /// <summary>
- /// 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%.
- /// </summary>
- [DataField("prob")]
- public float Probability = 1f;
+ /// <summary>
+ /// 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%.
+ /// </summary>
+ [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<IEntityManager>().System<GhostRoleSystem>().UpdateAllEui();
- }
+ _roleName = value;
+ IoCManager.Resolve<IEntityManager>().System<GhostRoleSystem>().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<IEntityManager>().System<GhostRoleSystem>().UpdateAllEui();
- }
+ _roleDescription = value;
+ IoCManager.Resolve<IEntityManager>().System<GhostRoleSystem>().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<IEntityManager>().System<GhostRoleSystem>().UpdateAllEui();
- }
+ _roleRules = value;
+ IoCManager.Resolve<IEntityManager>().System<GhostRoleSystem>().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; }
-
- /// <summary>
- /// Reregisters the ghost role when the current player ghosts.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("reregister")]
- public bool ReregisterOnGhost { get; set; } = true;
-
- /// <summary>
- /// If set, ghost role is raffled, otherwise it is first-come-first-serve.
- /// </summary>
- [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; }
+
+ /// <summary>
+ /// Reregisters the ghost role when the current player ghosts.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("reregister")]
+ public bool ReregisterOnGhost { get; set; } = true;
+
+ /// <summary>
+ /// If set, ghost role is raffled, otherwise it is first-come-first-serve.
+ /// </summary>
+ [DataField("raffle")]
+ [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
+ public GhostRoleRaffleConfig? RaffleConfig { get; set; }
}
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<uint, Entity<GhostRoleComponent>> _ghostRoles = new();
- private readonly Dictionary<uint, Entity<GhostRoleRaffleComponent>> _ghostRoleRaffles = new();
-
- private readonly Dictionary<ICommonSession, GhostRolesEui> _openUis = new();
- private readonly Dictionary<ICommonSession, MakeGhostRoleEui> _openMakeGhostRoleUis = new();
-
- [ViewVariables]
- public IReadOnlyCollection<Entity<GhostRoleComponent>> GhostRoles => _ghostRoles.Values;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
- SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
- SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindAddedMessage>(OnMindAdded);
- SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
- SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
- SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup);
- SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown);
- SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
- SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
- SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit);
- SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown);
- SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
- SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
- SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
- SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(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<uint, Entity<GhostRoleComponent>> _ghostRoles = new();
+ private readonly Dictionary<uint, Entity<GhostRoleRaffleComponent>> _ghostRoleRaffles = new();
+
+ private readonly Dictionary<ICommonSession, GhostRolesEui> _openUis = new();
+ private readonly Dictionary<ICommonSession, MakeGhostRoleEui> _openMakeGhostRoleUis = new();
+
+ [ViewVariables]
+ public IReadOnlyCollection<Entity<GhostRoleComponent>> GhostRoles => _ghostRoles.Values;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
+ SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
+ SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindAddedMessage>(OnMindAdded);
+ SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
+ SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
+ SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
+ SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup);
+ SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown);
+ SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
+ SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
+ SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit);
+ SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown);
+ SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
+ SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
+ SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
+ SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(OnGhostRoleRadioMessage);
+ _playerManager.PlayerStatusChanged += PlayerStatusChanged;
+ }
- private void OnMobStateChanged(Entity<GhostTakeoverAvailableComponent> component, ref MobStateChangedEvent args)
- {
- if (!TryComp(component, out GhostRoleComponent? ghostRole))
- return;
+ private void OnMobStateChanged(Entity<GhostTakeoverAvailableComponent> 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<GhostComponent>(attached))
- return;
+ public void OpenEui(ICommonSession session)
+ {
+ if (session.AttachedEntity is not { Valid: true } attached ||
+ !EntityManager.HasComponent<GhostComponent>(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);
+ }
- /// <summary>
- /// Handles sending count update for the ghost role button in ghost UI, if ghost role count changed.
- /// </summary>
- private void UpdateGhostRoleCount()
- {
- if (!_needsUpdateGhostRoleCount)
- return;
+ /// <summary>
+ /// Handles sending count update for the ghost role button in ghost UI, if ghost role count changed.
+ /// </summary>
+ 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);
}
+ }
- /// <summary>
- /// Handles ghost role raffle logic.
- /// </summary>
- private void UpdateRaffles(float frameTime)
+ /// <summary>
+ /// Handles ghost role raffle logic.
+ /// </summary>
+ private void UpdateRaffles(float frameTime)
+ {
+ var query = EntityQueryEnumerator<GhostRoleRaffleComponent, MetaDataComponent>();
+ while (query.MoveNext(out var entityUid, out var raffle, out var meta))
{
- var query = EntityQueryEnumerator<GhostRoleRaffleComponent, MetaDataComponent>();
- 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<GhostComponent>(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<GhostComponent>(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<GhostRoleComponent> role)
+ {
+ if (_ghostRoles.ContainsValue(role))
+ return;
+
+ _ghostRoles[role.Comp.Identifier = GetNextRoleIdentifier()] = role;
+ UpdateAllEui();
+ }
+
+ public void UnregisterGhostRole(Entity<GhostRoleComponent> 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<GhostRoleComponent> role)
+ else
{
- if (_ghostRoles.ContainsValue(role))
- return;
-
- _ghostRoles[role.Comp.Identifier = GetNextRoleIdentifier()] = role;
UpdateAllEui();
}
+ }
- public void UnregisterGhostRole(Entity<GhostRoleComponent> role)
+ // probably fine to be init because it's never added during entity initialization, but much later
+ private void OnRaffleInit(Entity<GhostRoleRaffleComponent> 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<GhostRoleRaffleComponent>(ent);
+ return;
}
- // probably fine to be init because it's never added during entity initialization, but much later
- private void OnRaffleInit(Entity<GhostRoleRaffleComponent> 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<GhostRoleRaffleSettingsPrototype>(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<GhostRoleRaffleComponent>(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<GhostRoleRaffleComponent>(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<GhostRoleRaffleComponent> ent, ref ComponentShutdown args)
+ {
+ _ghostRoleRaffles.Remove(ent.Comp.Identifier);
+ }
- var settings = config.SettingsOverride
- ?? _prototype.Index<GhostRoleRaffleSettingsPrototype>(config.Settings).Settings;
+ /// <summary>
+ /// Joins the given player onto a ghost role raffle, or creates it if it doesn't exist.
+ /// </summary>
+ /// <param name="player">The player.</param>
+ /// <param name="identifier">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.)</param>
+ 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<GhostRoleRaffleComponent>(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<GhostRoleRaffleComponent>(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<GhostRoleRaffleComponent> 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;
}
- /// <summary>
- /// Joins the given player onto a ghost role raffle, or creates it if it doesn't exist.
- /// </summary>
- /// <param name="player">The player.</param>
- /// <param name="identifier">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.)</param>
- 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<GhostRoleRaffleComponent>(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;
- }
+ /// <summary>
+ /// Makes the given player leave the raffle corresponding to the given ID.
+ /// </summary>
+ public void LeaveRaffle(ICommonSession player, uint identifier)
+ {
+ if (!_ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt))
+ return;
+ if (raffleEnt.Comp.CurrentMembers.Remove(player))
+ {
UpdateAllEui();
}
-
- /// <summary>
- /// Makes the given player leave the raffle corresponding to the given ID.
- /// </summary>
- 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())
- }
+ /// <summary>
+ /// Makes the given player leave all ghost role raffles.
+ /// </summary>
+ public void LeaveAllRaffles(ICommonSession player)
+ {
+ var shouldUpdateEui = false;
- /// <summary>
- /// Makes the given player leave all ghost role raffles.
- /// </summary>
- 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();
- }
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="player">The player.</param>
+ /// <param name="identifier">ID of the ghost role.</param>
+ public void Request(ICommonSession player, uint identifier)
+ {
+ if (!_ghostRoles.TryGetValue(identifier, out var roleEnt))
+ return;
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="player">The player.</param>
- /// <param name="identifier">ID of the ghost role.</param>
- 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);
}
-
- /// <summary>
- /// Attempts having the player take over the ghost role with the corresponding ID. Does not start a raffle.
- /// </summary>
- /// <returns>True if takeover was successful, otherwise false.</returns>
- 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);
+ /// <summary>
+ /// Attempts having the player take over the ghost role with the corresponding ID. Does not start a raffle.
+ /// </summary>
+ /// <returns>True if takeover was successful, otherwise false.</returns>
+ 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<MetaDataComponent>(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<MetaDataComponent>(mob).EntityName);
+ _roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName });
- /// <summary>
- /// Returns the number of available ghost roles.
- /// </summary>
- public int GetGhostRoleCount()
- {
- var metaQuery = GetEntityQuery<MetaDataComponent>();
- return _ghostRoles.Count(pair => metaQuery.GetComponent(pair.Value.Owner).EntityPaused == false);
- }
+ _mindSystem.SetUserId(newMind, player.UserId);
+ _mindSystem.TransferTo(newMind, mob);
+ }
+
+ /// <summary>
+ /// Returns the number of available ghost roles.
+ /// </summary>
+ public int GetGhostRoleCount()
+ {
+ var metaQuery = GetEntityQuery<MetaDataComponent>();
+ return _ghostRoles.Count(pair => metaQuery.GetComponent(pair.Value.Owner).EntityPaused == false);
+ }
+
+ /// <summary>
+ /// Returns information about all available ghost roles.
+ /// </summary>
+ /// <param name="player">
+ /// If not null, the <see cref="GhostRoleInfo"/>s will show if the given player is in a raffle.
+ /// </param>
+ public GhostRoleInfo[] GetGhostRolesInfo(ICommonSession? player)
+ {
+ var roles = new List<GhostRoleInfo>();
+ var metaQuery = GetEntityQuery<MetaDataComponent>();
- /// <summary>
- /// Returns information about all available ghost roles.
- /// </summary>
- /// <param name="player">
- /// If not null, the <see cref="GhostRoleInfo"/>s will show if the given player is in a raffle.
- /// </param>
- public GhostRoleInfo[] GetGhostRolesInfo(ICommonSession? player)
+ foreach (var (id, (uid, role)) in _ghostRoles)
{
- var roles = new List<GhostRoleInfo>();
- var metaQuery = GetEntityQuery<MetaDataComponent>();
+ 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<GhostComponent>(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<GhostComponent>(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<ActorComponent>(uid))
+ return;
- private void OnPaused(EntityUid uid, GhostRoleComponent component, ref EntityPausedEvent args)
- {
- if (HasComp<ActorComponent>(uid))
- return;
+ UpdateAllEui();
+ }
- UpdateAllEui();
- }
+ private void OnUnpaused(EntityUid uid, GhostRoleComponent component, ref EntityUnpausedEvent args)
+ {
+ if (HasComp<ActorComponent>(uid))
+ return;
- private void OnUnpaused(EntityUid uid, GhostRoleComponent component, ref EntityUnpausedEvent args)
- {
- if (HasComp<ActorComponent>(uid))
- return;
+ UpdateAllEui();
+ }
- UpdateAllEui();
- }
+ private void OnMapInit(Entity<GhostRoleComponent> ent, ref MapInitEvent args)
+ {
+ if (ent.Comp.Probability < 1f && !_random.Prob(ent.Comp.Probability))
+ RemCompDeferred<GhostRoleComponent>(ent);
+ }
- private void OnMapInit(Entity<GhostRoleComponent> ent, ref MapInitEvent args)
- {
- if (ent.Comp.Probability < 1f && !_random.Prob(ent.Comp.Probability))
- RemCompDeferred<GhostRoleComponent>(ent);
- }
+ private void OnRoleStartup(Entity<GhostRoleComponent> ent, ref ComponentStartup args)
+ {
+ RegisterGhostRole(ent);
+ }
- private void OnRoleStartup(Entity<GhostRoleComponent> ent, ref ComponentStartup args)
- {
- RegisterGhostRole(ent);
- }
+ private void OnRoleShutdown(Entity<GhostRoleComponent> role, ref ComponentShutdown args)
+ {
+ UnregisterGhostRole(role);
+ }
- private void OnRoleShutdown(Entity<GhostRoleComponent> 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<MindContainerComponent>(mob);
- EnsureComp<MindContainerComponent>(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<MindContainerComponent>(uid);
+ var mind = EnsureComp<MindContainerComponent>(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<Verb> args)
- {
- var prototypes = component.SelectablePrototypes;
- if (prototypes.Count < 1)
- return;
+ private void OnVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, GetVerbsEvent<Verb> 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<Verb>();
+ var verbs = new ValueList<Verb>();
- foreach (var prototypeID in prototypes)
+ foreach (var prototypeID in prototypes)
+ {
+ if (_prototype.TryIndex<GhostRolePrototype>(prototypeID, out var prototype))
{
- if (_prototype.TryIndex<GhostRolePrototype>(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<GhostRoleComponent>(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<GhostRoleComponent>(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<GhostRoleMobSpawnerComponent> 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<GhostRoleMobSpawnerComponent> 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<GhostRoleSystem>().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<GhostRoleSystem>().OpenEui(shell.Player);
+ else
+ shell.WriteLine("You can only open the ghost roles UI on a client.");
}
}
using Robust.Shared.Audio.Systems;
using Content.Shared.Ghost.Roles.Components;
-namespace Content.Server.Zombies
+namespace Content.Server.Zombies;
+
+/// <summary>
+/// Handles zombie propagation and inherent zombie traits
+/// </summary>
+/// <remarks>
+/// Don't Shitcode Open Inside
+/// </remarks>
+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!;
+
+ /// <summary>
+ /// Handles an entity turning into a zombie when they die or go into crit
+ /// </summary>
+ private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState == MobState.Dead)
+ {
+ ZombifyEntity(uid, args.Component);
+ }
+ }
+
/// <summary>
- /// 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.
/// </summary>
+ /// <param name="target">the entity being zombified</param>
+ /// <param name="mobState"></param>
/// <remarks>
- /// 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
/// </remarks>
- 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!;
-
- /// <summary>
- /// Handles an entity turning into a zombie when they die or go into crit
- /// </summary>
- private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args)
+ //Don't zombfiy zombies
+ if (HasComp<ZombieComponent>(target) || HasComp<ZombieImmuneComponent>(target))
+ return;
+
+ if (!Resolve(target, ref mobState, logMissing: false))
+ return;
+
+ //you're a real zombie now, son.
+ var zombiecomp = AddComp<ZombieComponent>(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<RespiratorComponent>(target);
+ RemComp<BarotraumaComponent>(target);
+ RemComp<HungerComponent>(target);
+ RemComp<ThirstComponent>(target);
+ RemComp<ReproductiveComponent>(target);
+ RemComp<ReproductivePartnerComponent>(target);
+ RemComp<LegsParalyzedComponent>(target);
+ RemComp<ComplexInteractionComponent>(target);
+
+ //funny voice
+ var accentType = "zombie";
+ if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
+ accentType = accent.Accent;
+
+ EnsureComp<ReplacementAccentComponent>(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<CombatModeComponent>(target);
+ RemComp<PacifiedComponent>(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<MeleeWeaponComponent>(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<EmoteOnDamageComponent>(target);
+ _emoteOnDamage.AddEmote(target, "Scream");
+
+ // Random groaning
+ EnsureComp<AutoEmoteComponent>(target);
+ _autoEmote.AddEmote(target, "ZombieGroan");
}
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="target">the entity being zombified</param>
- /// <param name="mobState"></param>
- /// <remarks>
- /// 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
- /// </remarks>
- public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null)
+ //We have specific stuff for humanoid zombies because they matter more
+ if (TryComp<HumanoidAppearanceComponent>(target, out var huApComp)) //huapcomp
{
- //Don't zombfiy zombies
- if (HasComp<ZombieComponent>(target) || HasComp<ZombieImmuneComponent>(target))
- return;
-
- if (!Resolve(target, ref mobState, logMissing: false))
- return;
-
- //you're a real zombie now, son.
- var zombiecomp = AddComp<ZombieComponent>(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<RespiratorComponent>(target);
- RemComp<BarotraumaComponent>(target);
- RemComp<HungerComponent>(target);
- RemComp<ThirstComponent>(target);
- RemComp<ReproductiveComponent>(target);
- RemComp<ReproductivePartnerComponent>(target);
- RemComp<LegsParalyzedComponent>(target);
- RemComp<ComplexInteractionComponent>(target);
-
- //funny voice
- var accentType = "zombie";
- if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
- accentType = accent.Accent;
-
- EnsureComp<ReplacementAccentComponent>(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<CombatModeComponent>(target);
- RemComp<PacifiedComponent>(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<MeleeWeaponComponent>(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<BloodstreamComponent>(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<EmoteOnDamageComponent>(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<PryingComponent>(target);
+ pryComp.SpeedModifier = 0.75f;
+ pryComp.PryPowered = true;
+ pryComp.Force = true;
+
+ Dirty(target, pryComp);
+ }
- // Random groaning
- EnsureComp<AutoEmoteComponent>(target);
- _autoEmote.AddEmote(target, "ZombieGroan");
- }
+ Dirty(target, melee);
- //We have specific stuff for humanoid zombies because they matter more
- if (TryComp<HumanoidAppearanceComponent>(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<BloodstreamComponent>(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<PryingComponent>(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<TemperatureComponent>(target, out var tempComp))
- tempComp.ColdDamage.ClampMax(0);
-
- //Heals the zombie from all the damage it took while human
- if (TryComp<DamageableComponent>(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<HTNComponent>(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<GhostRoleMobSpawnerComponent>(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<GhostRoleComponent>(target);
- EnsureComp<GhostTakeoverAvailableComponent>(target);
- ghostRole.RoleName = Loc.GetString("zombie-generic");
- ghostRole.RoleDescription = Loc.GetString("zombie-role-desc");
- ghostRole.RoleRules = Loc.GetString("zombie-role-rules");
- }
-
- if (TryComp<HandsComponent>(target, out var handsComp))
- {
- _hands.RemoveHands(target);
- RemComp(target, handsComp);
- }
-
- // Sloth: What the fuck?
- // How long until compregistry lmao.
- RemComp<PullerComponent>(target);
-
- // No longer waiting to become a zombie:
- // Requires deferral because this is (probably) the event which called ZombifyEntity in the first place.
- RemCompDeferred<PendingZombieComponent>(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<TemperatureComponent>(target, out var tempComp))
+ tempComp.ColdDamage.ClampMax(0);
+
+ //Heals the zombie from all the damage it took while human
+ if (TryComp<DamageableComponent>(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<HTNComponent>(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<GhostRoleMobSpawnerComponent>(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<GhostRoleComponent>(target);
+ EnsureComp<GhostTakeoverAvailableComponent>(target);
+ ghostRole.RoleName = Loc.GetString("zombie-generic");
+ ghostRole.RoleDescription = Loc.GetString("zombie-role-desc");
+ ghostRole.RoleRules = Loc.GetString("zombie-role-rules");
+ }
+
+ if (TryComp<HandsComponent>(target, out var handsComp))
+ {
+ _hands.RemoveHands(target);
+ RemComp(target, handsComp);
}
+
+ // Sloth: What the fuck?
+ // How long until compregistry lmao.
+ RemComp<PullerComponent>(target);
+
+ // No longer waiting to become a zombie:
+ // Requires deferral because this is (probably) the event which called ZombifyEntity in the first place.
+ RemCompDeferred<PendingZombieComponent>(target);
+
+ //zombie gamemode stuff
+ var ev = new EntityZombifiedEvent(target);
+ RaiseLocalEvent(target, ref ev, true);
+ //zombies get slowdown once they convert
+ _movementSpeedModifier.RefreshMovementSpeedModifiers(target);
}
}
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;
}
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();
}
}
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;
}
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameStates;
-namespace Content.Shared.Mind.Components
+namespace Content.Shared.Mind.Components;
+
+/// <summary>
+/// This component indicates that this entity may have mind, which is simply an entity with a <see cref="MindComponent"/>.
+/// The mind entity is not actually stored in a "container", but is simply stored in nullspace.
+/// </summary>
+[RegisterComponent, Access(typeof(SharedMindSystem)), NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class MindContainerComponent : Component
{
/// <summary>
- /// This component indicates that this entity may have mind, which is simply an entity with a <see cref="MindComponent"/>.
- /// The mind entity is not actually stored in a "container", but is simply stored in nullspace.
+ /// The mind controlling this mob. Can be null.
/// </summary>
- [RegisterComponent, Access(typeof(SharedMindSystem)), NetworkedComponent, AutoGenerateComponentState]
- public sealed partial class MindContainerComponent : Component
- {
- /// <summary>
- /// The mind controlling this mob. Can be null.
- /// </summary>
- [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; }
- /// <summary>
- /// True if we have a mind, false otherwise.
- /// </summary>
- [MemberNotNullWhen(true, nameof(Mind))]
- public bool HasMind => Mind != null;
+ /// <summary>
+ /// True if we have a mind, false otherwise.
+ /// </summary>
+ [MemberNotNullWhen(true, nameof(Mind))]
+ public bool HasMind => Mind != null;
- /// <summary>
- /// Whether examining should show information about the mind or not.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("showExamineInfo"), AutoNetworkedField]
- public bool ShowExamineInfo { get; set; }
+ /// <summary>
+ /// Whether examining should show information about the mind or not.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("showExamineInfo"), AutoNetworkedField]
+ public bool ShowExamineInfo { get; set; }
- /// <summary>
- /// Whether the mind will be put on a ghost after this component is shutdown.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("ghostOnShutdown")]
- [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
- public bool GhostOnShutdown { get; set; } = true;
- }
+ /// <summary>
+ /// Whether the mind will be put on a ghost after this component is shutdown.
+ /// </summary>
+ [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<MindComponent> Mind;
- public readonly Entity<MindContainerComponent> Container;
+public abstract class MindEvent : EntityEventArgs
+{
+ public readonly Entity<MindComponent> Mind;
+ public readonly Entity<MindContainerComponent> Container;
- public MindEvent(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
- {
- Mind = mind;
- Container = container;
- }
+ public MindEvent(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
+ {
+ Mind = mind;
+ Container = container;
}
+}
- /// <summary>
- /// Event raised directed at a mind-container when a mind gets removed.
- /// </summary>
- public sealed class MindRemovedMessage : MindEvent
+/// <summary>
+/// Event raised directed at a mind-container when a mind gets removed.
+/// </summary>
+public sealed class MindRemovedMessage : MindEvent
+{
+ public MindRemovedMessage(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
+ : base(mind, container)
{
- public MindRemovedMessage(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
- : base(mind, container)
- {
- }
}
+}
- /// <summary>
- /// Event raised directed at a mind when it gets removed from a mind-container.
- /// </summary>
- public sealed class MindGotRemovedEvent : MindEvent
+/// <summary>
+/// Event raised directed at a mind when it gets removed from a mind-container.
+/// </summary>
+public sealed class MindGotRemovedEvent : MindEvent
+{
+ public MindGotRemovedEvent(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
+ : base(mind, container)
{
- public MindGotRemovedEvent(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
- : base(mind, container)
- {
- }
}
+}
- /// <summary>
- /// Event raised directed at a mind-container when a mind gets added.
- /// </summary>
- public sealed class MindAddedMessage : MindEvent
+/// <summary>
+/// Event raised directed at a mind-container when a mind gets added.
+/// </summary>
+public sealed class MindAddedMessage : MindEvent
+{
+ public MindAddedMessage(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
+ : base(mind, container)
{
- public MindAddedMessage(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
- : base(mind, container)
- {
- }
}
+}
- /// <summary>
- /// Event raised directed at a mind when it gets added to a mind-container.
- /// </summary>
- public sealed class MindGotAddedEvent : MindEvent
+/// <summary>
+/// Event raised directed at a mind when it gets added to a mind-container.
+/// </summary>
+public sealed class MindGotAddedEvent : MindEvent
+{
+ public MindGotAddedEvent(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
+ : base(mind, container)
{
- public MindGotAddedEvent(Entity<MindComponent> mind, Entity<MindContainerComponent> container)
- : base(mind, container)
- {
- }
}
}
using Robust.Shared.Network;
using Robust.Shared.Player;
-namespace Content.Shared.Mind
+namespace Content.Shared.Mind;
+
+/// <summary>
+/// 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
+/// <see cref="MindContainerComponent"/>.
+/// </summary>
+/// <remarks>
+/// 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
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+public sealed partial class MindComponent : Component
{
+ [DataField, AutoNetworkedField]
+ public List<EntityUid> Objectives = new();
+
/// <summary>
- /// 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
- /// <see cref="MindContainerComponent"/>.
+ /// The session ID of the player owning this mind.
/// </summary>
- /// <remarks>
- /// 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
- /// </remarks>
- [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
- public sealed partial class MindComponent : Component
- {
- [DataField, AutoNetworkedField]
- public List<EntityUid> Objectives = new();
-
- /// <summary>
- /// The session ID of the player owning this mind.
- /// </summary>
- [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))]
- public NetUserId? UserId { get; set; }
+ [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))]
+ public NetUserId? UserId { get; set; }
- /// <summary>
- /// 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)
- /// </summary>
- [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))]
- public NetUserId? OriginalOwnerUserId { get; set; }
+ /// <summary>
+ /// 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)
+ /// </summary>
+ [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))]
+ public NetUserId? OriginalOwnerUserId { get; set; }
- /// <summary>
- /// The first entity that this mind controlled. Used for round end information.
- /// Might be relevant if the player has ghosted since.
- /// </summary>
- [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.
+ /// <summary>
+ /// The first entity that this mind controlled. Used for round end information.
+ /// Might be relevant if the player has ghosted since.
+ /// </summary>
+ [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; }
- /// <summary>
- /// The time of death for this Mind.
- /// Can be null - will be null if the Mind is not considered "dead".
- /// </summary>
- [DataField]
- public TimeSpan? TimeOfDeath { get; set; }
+ /// <summary>
+ /// The time of death for this Mind.
+ /// Can be null - will be null if the Mind is not considered "dead".
+ /// </summary>
+ [DataField]
+ public TimeSpan? TimeOfDeath { get; set; }
- /// <summary>
- /// The entity currently owned by this mind.
- /// Can be null.
- /// </summary>
- [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))]
- public EntityUid? OwnedEntity { get; set; }
+ /// <summary>
+ /// The entity currently owned by this mind.
+ /// Can be null.
+ /// </summary>
+ [DataField, AutoNetworkedField, Access(typeof(SharedMindSystem))]
+ public EntityUid? OwnedEntity { get; set; }
- /// <summary>
- /// An enumerable over all the objective entities this mind has.
- /// </summary>
- [ViewVariables, Obsolete("Use Objectives field")]
- public IEnumerable<EntityUid> AllObjectives => Objectives;
+ /// <summary>
+ /// An enumerable over all the objective entities this mind has.
+ /// </summary>
+ [ViewVariables, Obsolete("Use Objectives field")]
+ public IEnumerable<EntityUid> AllObjectives => Objectives;
- /// <summary>
- /// Prevents user from ghosting out
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("preventGhosting")]
- public bool PreventGhosting { get; set; }
+ /// <summary>
+ /// Prevents user from ghosting out
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("preventGhosting")]
+ public bool PreventGhosting { get; set; }
- /// <summary>
- /// Prevents user from suiciding
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("preventSuicide")]
- public bool PreventSuicide { get; set; }
+ /// <summary>
+ /// Prevents user from suiciding
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("preventSuicide")]
+ public bool PreventSuicide { get; set; }
- /// <summary>
- /// The session of the player owning this mind.
- /// Can be null, in which case the player is currently not logged in.
- /// </summary>
- [ViewVariables, Access(typeof(SharedMindSystem), typeof(SharedGameTicker))]
- // TODO remove this after moving IPlayerManager functions to shared
- public ICommonSession? Session { get; set; }
- }
+ /// <summary>
+ /// The session of the player owning this mind.
+ /// Can be null, in which case the player is currently not logged in.
+ /// </summary>
+ [ViewVariables, Access(typeof(SharedMindSystem), typeof(SharedGameTicker))]
+ // TODO remove this after moving IPlayerManager functions to shared
+ public ICommonSession? Session { get; set; }
}