From 237cb3d0b4fd308d4375ce53f2dd04195200d694 Mon Sep 17 00:00:00 2001 From: Veritius Date: Fri, 3 Jun 2022 21:37:35 +1000 Subject: [PATCH] Communications Console: The ECSining (#8374) Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth --- ...CommunicationsConsoleBoundUserInterface.cs | 3 +- .../UI/CommunicationsConsoleMenu.cs | 8 +- .../Administration/UI/AdminAnnounceEui.cs | 5 +- .../AlertLevel/AlertLevelComponent.cs | 3 +- Content.Server/AlertLevel/AlertLevelSystem.cs | 17 +- .../Announcements/AnnounceCommand.cs | 8 +- Content.Server/Chat/ChatSystem.cs | 66 +++++ Content.Server/Chat/Managers/ChatManager.cs | 34 ++- Content.Server/Chat/Managers/IChatManager.cs | 12 +- .../CommunicationsConsoleComponent.cs | 182 ++++---------- .../CommunicationsConsoleSystem.cs | 233 ++++++++++++++++++ .../GameTicking/GameTicker.RoundFlow.cs | 2 +- .../GameTicking/GameTicker.Spawning.cs | 5 +- Content.Server/GameTicking/GameTicker.cs | 2 + Content.Server/Nuke/NukeCodeSystem.cs | 9 +- Content.Server/Nuke/NukeSystem.cs | 7 +- Content.Server/Roles/Job.cs | 21 +- Content.Server/RoundEnd/RoundEndSystem.cs | 6 +- .../Salvage/SalvageGridComponent.cs | 14 ++ Content.Server/Salvage/SalvageSystem.cs | 38 +-- .../Station/Systems/StationSystem.cs | 4 +- .../StationEvents/Events/DiseaseOutbreak.cs | 22 +- .../StationEvents/Events/RandomSentience.cs | 30 ++- .../StationEvents/Events/StationEvent.cs | 9 +- .../StationEvents/Events/ZombieOutbreak.cs | 20 +- .../Administration/AdminAnnounceEuiState.cs | 5 +- .../communications-console-component.ftl | 21 +- .../Entities/Mobs/Player/admin_ghost.yml | 2 + .../Devices/Circuitboards/computer.yml | 9 + .../Machines/Computers/computers.yml | 28 +++ .../Machines/computers.rsi/comm_syndie.png | Bin 0 -> 9492 bytes .../Machines/computers.rsi/meta.json | 26 +- SpaceStation14.sln.DotSettings | 6 +- 33 files changed, 608 insertions(+), 249 deletions(-) create mode 100644 Content.Server/Communications/CommunicationsConsoleSystem.cs create mode 100644 Content.Server/Salvage/SalvageGridComponent.cs create mode 100644 Resources/Textures/Structures/Machines/computers.rsi/comm_syndie.png diff --git a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs index 955b008e8b..30cb4db8cf 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleBoundUserInterface.cs @@ -23,8 +23,7 @@ namespace Content.Client.Communications.UI public string CurrentLevel { get; private set; } = default!; - public int Countdown => _expectedCountdownTime == null - ? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0); + public int Countdown => _expectedCountdownTime == null ? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0); private TimeSpan? _expectedCountdownTime; public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.cs index bee292b026..c0781f2fd8 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.cs @@ -25,12 +25,12 @@ namespace Content.Client.Communications.UI SetSize = MinSize = (600, 400); IoCManager.InjectDependencies(this); - Title = Loc.GetString("communicationsconsole-menu-title"); + Title = Loc.GetString("comms-console-menu-title"); Owner = owner; _messageInput = new LineEdit { - PlaceHolder = Loc.GetString("communicationsconsole-menu-announcement-placeholder"), + PlaceHolder = Loc.GetString("comms-console-menu-announcement-placeholder"), HorizontalExpand = true, SizeFlagsStretchRatio = 1 }; @@ -127,11 +127,11 @@ namespace Content.Client.Communications.UI if (!Owner.CountdownStarted) { _countdownLabel.SetMessage(""); - EmergencyShuttleButton.Text = Loc.GetString("communicationsconsole-menu-call-shuttle"); + EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-call-shuttle"); return; } - EmergencyShuttleButton.Text = Loc.GetString("communicationsconsole-menu-recall-shuttle"); + EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle"); _countdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s"); } diff --git a/Content.Server/Administration/UI/AdminAnnounceEui.cs b/Content.Server/Administration/UI/AdminAnnounceEui.cs index 8ca8abea9e..543ee92c4d 100644 --- a/Content.Server/Administration/UI/AdminAnnounceEui.cs +++ b/Content.Server/Administration/UI/AdminAnnounceEui.cs @@ -1,4 +1,5 @@ using Content.Server.Administration.Managers; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.EUI; using Content.Shared.Administration; @@ -10,6 +11,7 @@ namespace Content.Server.Administration.UI { [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; public AdminAnnounceEui() { @@ -45,8 +47,9 @@ namespace Content.Server.Administration.UI case AdminAnnounceType.Server: _chatManager.DispatchServerAnnouncement(doAnnounce.Announcement); break; + // TODO: Per-station announcement support case AdminAnnounceType.Station: - _chatManager.DispatchStationAnnouncement(doAnnounce.Announcement, doAnnounce.Announcer, colorOverride: Color.Gold); + _chatSystem.DispatchGlobalStationAnnouncement(doAnnounce.Announcement, doAnnounce.Announcer, colorOverride: Color.Gold); break; } diff --git a/Content.Server/AlertLevel/AlertLevelComponent.cs b/Content.Server/AlertLevel/AlertLevelComponent.cs index 6bb703e886..a597c87aeb 100644 --- a/Content.Server/AlertLevel/AlertLevelComponent.cs +++ b/Content.Server/AlertLevel/AlertLevelComponent.cs @@ -27,7 +27,8 @@ public sealed class AlertLevelComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public bool IsLevelLocked = false; - [ViewVariables] public const float Delay = 300; + [ViewVariables] public const float Delay = 30; + [ViewVariables] public float CurrentDelay = 0; [ViewVariables] public bool ActiveDelay; diff --git a/Content.Server/AlertLevel/AlertLevelSystem.cs b/Content.Server/AlertLevel/AlertLevelSystem.cs index e3a03250ba..291062c2a7 100644 --- a/Content.Server/AlertLevel/AlertLevelSystem.cs +++ b/Content.Server/AlertLevel/AlertLevelSystem.cs @@ -1,9 +1,6 @@ using System.Linq; -using Content.Server.Administration.Logs; -using Content.Server.Chat.Managers; -using Content.Server.Station.Components; +using Content.Server.Chat; using Content.Server.Station.Systems; -using Content.Shared.AlertLevel; using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -13,7 +10,7 @@ namespace Content.Server.AlertLevel; public sealed class AlertLevelSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly StationSystem _stationSystem = default!; // Until stations are a prototype, this is how it's going to have to be. @@ -52,7 +49,7 @@ public sealed class AlertLevelSystem : EntitySystem continue; } - alert.CurrentDelay--; + alert.CurrentDelay -= time; } } @@ -186,11 +183,11 @@ public sealed class AlertLevelSystem : EntitySystem if (announce) { - _chatManager.DispatchStationAnnouncement(announcementFull, playDefaultSound: playDefault, + _chatSystem.DispatchStationAnnouncement(station, announcementFull, playDefaultSound: playDefault, colorOverride: detail.Color, sender: stationName); } - RaiseLocalEvent(new AlertLevelChangedEvent(level)); + RaiseLocalEvent(new AlertLevelChangedEvent(station, level)); } } @@ -202,10 +199,12 @@ public sealed class AlertLevelPrototypeReloadedEvent : EntityEventArgs public sealed class AlertLevelChangedEvent : EntityEventArgs { + public EntityUid Station { get; } public string AlertLevel { get; } - public AlertLevelChangedEvent(string alertLevel) + public AlertLevelChangedEvent(EntityUid station, string alertLevel) { + Station = station; AlertLevel = alertLevel; } } diff --git a/Content.Server/Announcements/AnnounceCommand.cs b/Content.Server/Announcements/AnnounceCommand.cs index 6a53e5e02a..c5eeaa23bf 100644 --- a/Content.Server/Announcements/AnnounceCommand.cs +++ b/Content.Server/Announcements/AnnounceCommand.cs @@ -1,5 +1,5 @@ using Content.Server.Administration; -using Content.Server.Chat.Managers; +using Content.Server.Chat; using Content.Shared.Administration; using Robust.Shared.Console; @@ -13,7 +13,7 @@ namespace Content.Server.Announcements public string Help => $"{Command} or {Command} to send announcement as centcomm."; public void Execute(IConsoleShell shell, string argStr, string[] args) { - var chat = IoCManager.Resolve(); + var chat = IoCManager.Resolve().GetEntitySystem(); if (args.Length == 0) { @@ -23,12 +23,12 @@ namespace Content.Server.Announcements if (args.Length == 1) { - chat.DispatchStationAnnouncement(args[0], colorOverride: Color.Gold); + chat.DispatchGlobalStationAnnouncement(args[0], colorOverride: Color.Gold); } else { var message = string.Join(' ', new ArraySegment(args, 1, args.Length-1)); - chat.DispatchStationAnnouncement(message, args[0], colorOverride: Color.Gold); + chat.DispatchGlobalStationAnnouncement(message, args[0], colorOverride: Color.Gold); } shell.WriteLine("Sent!"); } diff --git a/Content.Server/Chat/ChatSystem.cs b/Content.Server/Chat/ChatSystem.cs index 9d66c36c41..26d4bf9079 100644 --- a/Content.Server/Chat/ChatSystem.cs +++ b/Content.Server/Chat/ChatSystem.cs @@ -8,12 +8,15 @@ using Content.Server.Headset; using Content.Server.Players; using Content.Server.Popups; using Content.Server.Radio.EntitySystems; +using Content.Server.Station.Components; +using Content.Server.Station.Systems; using Content.Shared.ActionBlocker; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Inventory; using Robust.Server.Player; +using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Network; @@ -41,9 +44,11 @@ public sealed class ChatSystem : SharedChatSystem [Dependency] private readonly ListeningSystem _listener = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; private const int VoiceRange = 7; // how far voice goes in world units private const int WhisperRange = 2; // how far whisper goes in world units + private const string AnnouncementSound = "/Audio/Announcements/announce.ogg"; private bool _loocEnabled = true; private readonly bool _adminLoocEnabled = true; @@ -140,6 +145,67 @@ public sealed class ChatSystem : SharedChatSystem } } + #region Announcements + + /// + /// Dispatches an announcement to all stations + /// + /// The contents of the message + /// The sender (Communications Console in Communications Console Announcement) + /// Play the announcement sound + /// Optional color for the announcement message + public void DispatchGlobalStationAnnouncement(string message, string sender = "Central Command", + bool playDefaultSound = true, Color? colorOverride = null) + { + var messageWrap = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender)); + _chatManager.ChatMessageToAll(ChatChannel.Radio, message, messageWrap); + if (playDefaultSound) + { + SoundSystem.Play(Filter.Broadcast(), AnnouncementSound, AudioParams.Default.WithVolume(-2f)); + } + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Global station announcement from {sender}: {message}"); + } + + /// + /// Dispatches an announcement on a specific station + /// + /// The entity making the announcement (used to determine the station) + /// The contents of the message + /// The sender (Communications Console in Communications Console Announcement) + /// Play the announcement sound + /// Optional color for the announcement message + public void DispatchStationAnnouncement(EntityUid source, string message, string sender = "Central Command", bool playDefaultSound = true, Color? colorOverride = null) + { + var messageWrap = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender)); + var station = _stationSystem.GetOwningStation(source); + var filter = Filter.Empty(); + + if (station != null) + { + if (!EntityManager.TryGetComponent(station, out var stationDataComp)) return; + + foreach (var gridEnt in stationDataComp.Grids) + { + filter.AddInGrid(gridEnt); + } + } + else + { + filter = Filter.Pvs(source, entityManager: EntityManager); + } + + _chatManager.ChatMessageToManyFiltered(filter, ChatChannel.Radio, message, messageWrap, source, true, colorOverride); + + if (playDefaultSound) + { + SoundSystem.Play(filter, AnnouncementSound, AudioParams.Default.WithVolume(-2f)); + } + + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement on {station} from {sender}: {message}"); + } + + #endregion + #region Private API private void SendEntitySpeak(EntityUid source, string message, bool hideChat = false) diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index d62b38fcee..4ee5365837 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -3,13 +3,14 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.MoMMI; using Content.Server.Preferences.Managers; +using Content.Server.Station.Systems; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Database; using Robust.Server.Player; -using Robust.Shared.Audio; using Robust.Shared.Configuration; +using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -35,6 +36,10 @@ namespace Content.Server.Chat.Managers [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + private StationSystem _stationSystem = default!; /// /// The maximum length a player-sent message can be sent @@ -46,6 +51,7 @@ namespace Content.Server.Chat.Managers public void Initialize() { + _stationSystem = _entityManager.EntitySysManager.GetEntitySystem(); _netManager.RegisterNetMessage(); _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); @@ -79,18 +85,6 @@ namespace Content.Server.Chat.Managers _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server announcement: {message}"); } - public void DispatchStationAnnouncement(string message, string sender = "Central Command", bool playDefaultSound = true, Color? colorOverride = null) - { - var messageWrap = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender)); - ChatMessageToAll(ChatChannel.Radio, message, messageWrap, colorOverride); - if (playDefaultSound) - { - SoundSystem.Play(Filter.Broadcast(), "/Audio/Announcements/announce.ogg", AudioParams.Default.WithVolume(-2f)); - } - - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement from {sender}: {message}"); - } - public void DispatchServerMessage(IPlayerSession player, string message) { var messageWrap = Loc.GetString("chat-manager-server-wrap-message"); @@ -235,6 +229,20 @@ namespace Content.Server.Chat.Managers _netManager.ServerSendToMany(msg, clients); } + public void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string messageWrap, EntityUid source, + bool hideChat, Color? colorOverride = null) + { + if (!filter.Recipients.Any()) return; + + var clients = new List(); + foreach (var recipient in filter.Recipients) + { + clients.Add(recipient.ConnectedClient); + } + + ChatMessageToMany(channel, message, messageWrap, source, hideChat, clients); + } + public void ChatMessageToAll(ChatChannel channel, string message, string messageWrap, Color? colorOverride = null) { var msg = new MsgChatMessage(); diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index 76a525e6f7..1115a429f2 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -1,6 +1,7 @@ using Content.Shared.Chat; using Robust.Server.Player; using Robust.Shared.Network; +using Robust.Shared.Player; namespace Content.Server.Chat.Managers { @@ -15,16 +16,6 @@ namespace Content.Server.Chat.Managers /// Override the color of the message being sent. void DispatchServerAnnouncement(string message, Color? colorOverride = null); - /// - /// Station announcement to every player - /// - /// - /// - /// If the default 'PA' sound should be played. - /// Override the color of the message being sent. - void DispatchStationAnnouncement(string message, string sender = "CentComm", bool playDefaultSound = true, - Color? colorOverride = null); - void DispatchServerMessage(IPlayerSession player, string message); void TrySendOOCMessage(IPlayerSession player, string message, OOCChatType type); @@ -36,6 +27,7 @@ namespace Content.Server.Chat.Managers INetChannel client); void ChatMessageToMany(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, List clients); + void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, Color? colorOverride); void ChatMessageToAll(ChatChannel channel, string message, string messageWrap, Color? colorOverride = null); bool MessageCharacterLimit(IPlayerSession player, string message); diff --git a/Content.Server/Communications/CommunicationsConsoleComponent.cs b/Content.Server/Communications/CommunicationsConsoleComponent.cs index b31bf5d32b..020736f4ce 100644 --- a/Content.Server/Communications/CommunicationsConsoleComponent.cs +++ b/Content.Server/Communications/CommunicationsConsoleComponent.cs @@ -1,157 +1,59 @@ -using System.Globalization; -using System.Linq; -using System.Threading; -using Content.Server.Access.Systems; -using Content.Server.AlertLevel; -using Content.Server.Chat.Managers; -using Content.Server.Power.Components; -using Content.Server.RoundEnd; -using Content.Server.Station.Systems; using Content.Server.UserInterface; using Content.Shared.Communications; using Robust.Server.GameObjects; -using Robust.Shared.Timing; -using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Communications { - // TODO: ECS [RegisterComponent] - public sealed class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IEntityEventSubscriber + public sealed class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly IEntityManager _entities = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IEntitySystemManager _sysMan = default!; + /// + /// Remaining cooldown between making announcements. + /// + [ViewVariables] + public float AnnouncementCooldownRemaining; - private bool Powered => !_entities.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered; + /// + /// Has the UI already been refreshed after the announcement + /// + [ViewVariables] + public bool AlreadyRefreshed = false; - private RoundEndSystem RoundEndSystem => EntitySystem.Get(); - private AlertLevelSystem AlertLevelSystem => EntitySystem.Get(); - private StationSystem StationSystem => EntitySystem.Get(); + /// + /// Fluent ID for the announcement title + /// If a Fluent ID isn't found, just uses the raw string + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("title", required: true)] + public string AnnouncementDisplayName = "comms-console-announcement-title-station"; - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key); + /// + /// Announcement color + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("color")] + public Color AnnouncementColor = Color.Gold; - public TimeSpan LastAnnounceTime { get; private set; } = TimeSpan.Zero; - public TimeSpan AnnounceCooldown { get; } = TimeSpan.FromSeconds(90); - private CancellationTokenSource _announceCooldownEndedTokenSource = new(); + /// + /// Time in seconds between announcement delays on a per-console basis + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("delay")] + public int DelayBetweenAnnouncements = 90; - protected override void Initialize() - { - base.Initialize(); + /// + /// Can call or recall the shuttle + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("canShuttle")] + public bool CanCallShuttle = true; - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } + /// + /// Announce on all grids (for nukies) + /// + [DataField("global")] + public bool AnnounceGlobal = false; - _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, (s) => UpdateBoundInterface()); - _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, _ => UpdateBoundInterface()); - _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, _ => UpdateBoundInterface()); - } - - protected override void Startup() - { - base.Startup(); - - UpdateBoundInterface(); - } - - private void UpdateBoundInterface() - { - if (!Deleted) - { - var system = RoundEndSystem; - - List? levels = null; - string currentLevel = default!; - float currentDelay = 0; - var stationUid = StationSystem.GetOwningStation(Owner); - if (stationUid != null) - { - if (_entityManager.TryGetComponent(stationUid.Value, out AlertLevelComponent? alerts) - && alerts.AlertLevels != null) - { - if (alerts.IsSelectable) - { - levels = new(); - foreach (var (id, detail) in alerts.AlertLevels.Levels) - { - if (detail.Selectable) - { - levels.Add(id); - } - } - } - - currentLevel = alerts.CurrentLevel; - currentDelay = AlertLevelSystem.GetAlertLevelDelay(stationUid.Value, alerts); - } - } - - UserInterface?.SetState(new CommunicationsConsoleInterfaceState(CanAnnounce(), system.CanCall(), levels, currentLevel, currentDelay, system.ExpectedCountdownEnd)); - } - } - - public bool CanAnnounce() - { - if (LastAnnounceTime == TimeSpan.Zero) - { - return true; - } - return _gameTiming.CurTime >= LastAnnounceTime + AnnounceCooldown; - } - - protected override void OnRemove() - { - _entityManager.EventBus.UnsubscribeEvent(EventSource.Local, this); - base.OnRemove(); - } - - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj) - { - switch (obj.Message) - { - case CommunicationsConsoleCallEmergencyShuttleMessage _: - RoundEndSystem.RequestRoundEnd(obj.Session.AttachedEntity); - break; - - case CommunicationsConsoleRecallEmergencyShuttleMessage _: - RoundEndSystem.CancelRoundEndCountdown(obj.Session.AttachedEntity); - break; - case CommunicationsConsoleAnnounceMessage msg: - if (!CanAnnounce()) - { - return; - } - _announceCooldownEndedTokenSource.Cancel(); - _announceCooldownEndedTokenSource = new CancellationTokenSource(); - LastAnnounceTime = _gameTiming.CurTime; - Timer.Spawn(AnnounceCooldown, UpdateBoundInterface, _announceCooldownEndedTokenSource.Token); - UpdateBoundInterface(); - - var message = msg.Message.Length <= 256 ? msg.Message.Trim() : $"{msg.Message.Trim().Substring(0, 256)}..."; - var sys = _sysMan.GetEntitySystem(); - - var author = "Unknown"; - if (obj.Session.AttachedEntity is {Valid: true} mob && sys.TryFindIdCard(mob, out var id)) - { - author = $"{id.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id.JobTitle ?? string.Empty)})".Trim(); - } - - message += $"\nSent by {author}"; - _chatManager.DispatchStationAnnouncement(message, "Communications Console", colorOverride: Color.Gold); - break; - case CommunicationsConsoleSelectAlertLevelMessage alertMsg: - var stationUid = StationSystem.GetOwningStation(Owner); - if (stationUid != null) - { - AlertLevelSystem.SetLevel(stationUid.Value, alertMsg.Level, true, true); - } - - break; - } - } + public BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key); } } diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs new file mode 100644 index 0000000000..31b0fee2c6 --- /dev/null +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -0,0 +1,233 @@ +using System.Globalization; +using Content.Server.Access.Systems; +using Content.Server.AlertLevel; +using Content.Server.Chat; +using Content.Server.Chat.Managers; +using Content.Server.Popups; +using Content.Server.RoundEnd; +using Content.Server.Station.Systems; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.Communications; +using Robust.Shared.Player; + +namespace Content.Server.Communications +{ + public sealed class CommunicationsConsoleSystem : EntitySystem + { + [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; + [Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + + private const int MaxMessageLength = 256; + + public override void Initialize() + { + // All events that refresh the BUI + SubscribeLocalEvent(OnAlertLevelChanged); + SubscribeLocalEvent((_, comp, _) => UpdateBoundUserInterface(comp)); + SubscribeLocalEvent(_ => OnGenericBroadcastEvent()); + SubscribeLocalEvent(_ => OnGenericBroadcastEvent()); + + // Messages from the BUI + SubscribeLocalEvent(OnSelectAlertLevelMessage); + SubscribeLocalEvent(OnAnnounceMessage); + SubscribeLocalEvent(OnCallShuttleMessage); + SubscribeLocalEvent(OnRecallShuttleMessage); + } + + public override void Update(float frameTime) + { + foreach (var comp in EntityQuery()) + { + // TODO: Find a less ass way of refreshing the UI + if (comp.AlreadyRefreshed) continue; + if (comp.AnnouncementCooldownRemaining <= 0f) + { + UpdateBoundUserInterface(comp); + comp.AlreadyRefreshed = true; + continue; + } + comp.AnnouncementCooldownRemaining -= frameTime; + } + + base.Update(frameTime); + } + + /// + /// Update the UI of every comms console. + /// + private void OnGenericBroadcastEvent() + { + foreach (var comp in EntityQuery()) + { + UpdateBoundUserInterface(comp); + } + } + + /// + /// Updates all comms consoles belonging to the station that the alert level was set on + /// + /// Alert level changed event arguments + private void OnAlertLevelChanged(AlertLevelChangedEvent args) + { + foreach (var comp in EntityQuery()) + { + var entStation = _stationSystem.GetOwningStation(comp.Owner); + if (args.Station == entStation) + { + UpdateBoundUserInterface(comp); + } + } + } + + private void UpdateBoundUserInterface(CommunicationsConsoleComponent comp) + { + var uid = comp.Owner; + + var stationUid = _stationSystem.GetOwningStation(uid); + List? levels = null; + string currentLevel = default!; + float currentDelay = 0; + + if (stationUid != null) + { + if (TryComp(stationUid.Value, out AlertLevelComponent? alertComp) && + alertComp.AlertLevels != null) + { + if (alertComp.IsSelectable) + { + levels = new(); + foreach (var (id, detail) in alertComp.AlertLevels.Levels) + { + if (detail.Selectable) + { + levels.Add(id); + } + } + } + + currentLevel = alertComp.CurrentLevel; + currentDelay = _alertLevelSystem.GetAlertLevelDelay(stationUid.Value, alertComp); + } + } + + comp.UserInterface?.SetState( + new CommunicationsConsoleInterfaceState( + CanAnnounce(comp), + CanCall(comp), + levels, + currentLevel, + currentDelay, + _roundEndSystem.ExpectedCountdownEnd + ) + ); + } + + private bool CanAnnounce(CommunicationsConsoleComponent comp) + { + return comp.AnnouncementCooldownRemaining <= 0f; + } + + private bool CanUse(EntityUid user, EntityUid console) + { + if (TryComp(console, out var accessReaderComponent) && accessReaderComponent.Enabled) + { + return _accessReaderSystem.IsAllowed(user, accessReaderComponent); + } + return true; + } + + private bool CanCall(CommunicationsConsoleComponent comp) + { + return comp.CanCallShuttle && _roundEndSystem.CanCall(); + } + + private void OnSelectAlertLevelMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleSelectAlertLevelMessage message) + { + if (message.Session.AttachedEntity is not {Valid: true} mob) return; + if (!CanUse(mob, uid)) + { + _popupSystem.PopupCursor(Loc.GetString("comms-console-permission-denied"), Filter.Entities(mob)); + return; + } + + var stationUid = _stationSystem.GetOwningStation(uid); + if (stationUid != null) + { + _alertLevelSystem.SetLevel(stationUid.Value, message.Level, true, true); + } + } + + private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent comp, + CommunicationsConsoleAnnounceMessage message) + { + var msg = message.Message.Length <= MaxMessageLength ? message.Message.Trim() : $"{message.Message.Trim().Substring(0, MaxMessageLength)}..."; + var author = Loc.GetString("comms-console-announcement-unknown-sender"); + if (message.Session.AttachedEntity is {Valid: true} mob) + { + if (!CanAnnounce(comp)) + { + return; + } + + if (!CanUse(mob, uid)) + { + _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, Filter.Entities(mob)); + return; + } + + if (_idCardSystem.TryFindIdCard(mob, out var id)) + { + author = $"{id.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id.JobTitle ?? string.Empty)})".Trim(); + } + } + + comp.AnnouncementCooldownRemaining = comp.DelayBetweenAnnouncements; + comp.AlreadyRefreshed = false; + UpdateBoundUserInterface(comp); + + // allow admemes with vv + Loc.TryGetString(comp.AnnouncementDisplayName, out var title); + title ??= comp.AnnouncementDisplayName; + + msg += "\n" + Loc.GetString("comms-console-announcement-sent-by") + " " + author; + if (comp.AnnounceGlobal) + { + _chatSystem.DispatchGlobalStationAnnouncement(msg, title, colorOverride: comp.AnnouncementColor); + } + else + { + _chatSystem.DispatchStationAnnouncement(uid, msg, title, colorOverride: comp.AnnouncementColor); + } + } + + private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message) + { + if (!comp.CanCallShuttle) return; + if (message.Session.AttachedEntity is not {Valid: true} mob) return; + if (!CanUse(mob, uid)) + { + _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, Filter.Entities(mob)); + return; + } + _roundEndSystem.RequestRoundEnd(uid); + } + + private void OnRecallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleRecallEmergencyShuttleMessage message) + { + if (!comp.CanCallShuttle) return; + if (message.Session.AttachedEntity is not {Valid: true} mob) return; + if (!CanUse(mob, uid)) + { + _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, Filter.Entities(mob)); + return; + } + _roundEndSystem.CancelRoundEndCountdown(uid); + } + } +} diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 333ba99831..8ffd628ce8 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -474,7 +474,7 @@ namespace Content.Server.GameTicking if (!proto.GamePresets.Contains(Preset.ID)) continue; if (proto.Message != null) - _chatManager.DispatchStationAnnouncement(Loc.GetString(proto.Message), playDefaultSound: false); + _chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString(proto.Message), playDefaultSound: true); if (proto.Sound != null) SoundSystem.Play(Filter.Broadcast(), proto.Sound.GetSound()); diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 9a6ae7be85..46c105790a 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -157,8 +157,9 @@ namespace Content.Server.GameTicking if (lateJoin) { - _chatManager.DispatchStationAnnouncement(Loc.GetString( - "latejoin-arrival-announcement", + _chatSystem.DispatchStationAnnouncement(station, + Loc.GetString( + "latejoin-arrival-announcement", ("character", character.Name), ("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(job.Name)) ), Loc.GetString("latejoin-arrival-sender"), diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 95b3901b1e..6963de8342 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -1,5 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.Database; using Content.Server.Ghost; @@ -114,6 +115,7 @@ namespace Content.Server.GameTicking [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly GhostSystem _ghosts = default!; [Dependency] private readonly RoleBanManager _roleBanManager = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly ServerUpdateManager _serverUpdates = default!; } } diff --git a/Content.Server/Nuke/NukeCodeSystem.cs b/Content.Server/Nuke/NukeCodeSystem.cs index fa08db88ca..4a362eb902 100644 --- a/Content.Server/Nuke/NukeCodeSystem.cs +++ b/Content.Server/Nuke/NukeCodeSystem.cs @@ -1,5 +1,6 @@ -using Content.Server.Chat.Managers; +using Content.Server.Chat; using Content.Server.Communications; +using Content.Server.Station.Systems; using Content.Shared.GameTicking; using Robust.Shared.Random; @@ -12,7 +13,8 @@ namespace Content.Server.Nuke public sealed class NukeCodeSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; private const int CodeLength = 6; public string Code { get; private set; } = default!; @@ -73,10 +75,11 @@ namespace Content.Server.Nuke wasSent = true; } + // TODO: Allow selecting a station for nuke codes if (wasSent) { var msg = Loc.GetString("nuke-component-announcement-send-codes"); - _chat.DispatchStationAnnouncement(msg, colorOverride: Color.Red); + _chatSystem.DispatchGlobalStationAnnouncement(msg, colorOverride: Color.Red); } return wasSent; diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 188c4c2e59..58c482c699 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -1,4 +1,5 @@ using Content.Server.AlertLevel; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.Coordinates.Helpers; using Content.Server.Explosion.EntitySystems; @@ -24,7 +25,7 @@ namespace Content.Server.Nuke [Dependency] private readonly ExplosionSystem _explosions = default!; [Dependency] private readonly AlertLevelSystem _alertLevel = default!; [Dependency] private readonly StationSystem _stationSystem = default!; - [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; public override void Initialize() { @@ -340,7 +341,7 @@ namespace Content.Server.Nuke var announcement = Loc.GetString("nuke-component-announcement-armed", ("time", (int) component.RemainingTime)); var sender = Loc.GetString("nuke-component-announcement-sender"); - _chat.DispatchStationAnnouncement(announcement, sender, false, Color.Red); + _chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false, Color.Red); // todo: move it to announcements system SoundSystem.Play(Filter.Broadcast(), component.ArmSound.GetSound()); @@ -369,7 +370,7 @@ namespace Content.Server.Nuke // warn a crew var announcement = Loc.GetString("nuke-component-announcement-unarmed"); var sender = Loc.GetString("nuke-component-announcement-sender"); - _chat.DispatchStationAnnouncement(announcement, sender, false); + _chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false); // todo: move it to announcements system SoundSystem.Play(Filter.Broadcast(), component.DisarmSound.GetSound()); diff --git a/Content.Server/Roles/Job.cs b/Content.Server/Roles/Job.cs index b3df59018c..b66d9e3f54 100644 --- a/Content.Server/Roles/Job.cs +++ b/Content.Server/Roles/Job.cs @@ -1,3 +1,4 @@ +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Shared.Roles; @@ -31,17 +32,25 @@ namespace Content.Server.Roles if (Mind.TryGetSession(out var session)) { - var chat = IoCManager.Resolve(); - chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name", ("jobName", Name))); + var chatMgr = IoCManager.Resolve(); + var chatSys = IoCManager.Resolve().GetEntitySystem(); + chatMgr.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name", ("jobName", Name))); if(Prototype.RequireAdminNotify) - chat.DispatchServerMessage(session, Loc.GetString("job-greet-important-disconnect-admin-notify")); + chatMgr.DispatchServerMessage(session, Loc.GetString("job-greet-important-disconnect-admin-notify")); - chat.DispatchServerMessage(session, Loc.GetString("job-greet-supervisors-warning", ("jobName", Name), ("supervisors", Prototype.Supervisors))); + chatMgr.DispatchServerMessage(session, Loc.GetString("job-greet-supervisors-warning", ("jobName", Name), ("supervisors", Prototype.Supervisors))); if(Prototype.JoinNotifyCrew && Mind.CharacterName != null) - chat.DispatchStationAnnouncement(Loc.GetString("job-greet-join-notify-crew", ("jobName", Name), ("characterName", Mind.CharacterName)), - Loc.GetString("job-greet-join-notify-crew-announcer"), false); + { + if (Mind.OwnedEntity != null) + { + chatSys.DispatchStationAnnouncement(Mind.OwnedEntity.Value, + Loc.GetString("job-greet-join-notify-crew", ("jobName", Name), + ("characterName", Mind.CharacterName)), + Loc.GetString("job-greet-join-notify-crew-announcer"), false); + } + } } } } diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index dec7c35417..b40d031ad5 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -1,5 +1,6 @@ using System.Threading; using Content.Server.Administration.Logs; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Shared.Database; @@ -15,6 +16,7 @@ namespace Content.Server.RoundEnd { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; @@ -80,7 +82,7 @@ namespace Content.Server.RoundEnd _adminLogger.Add(LogType.ShuttleCalled, LogImpact.High, $"Shuttle called"); } - _chatManager.DispatchStationAnnouncement(Loc.GetString("round-end-system-shuttle-called-announcement",("minutes", countdownTime.Minutes)), Loc.GetString("Station"), false, Color.Gold); + _chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("round-end-system-shuttle-called-announcement",("minutes", countdownTime.Minutes)), Loc.GetString("Station"), false, Color.Gold); SoundSystem.Play(Filter.Broadcast(), "/Audio/Announcements/shuttlecalled.ogg"); @@ -109,7 +111,7 @@ namespace Content.Server.RoundEnd _adminLogger.Add(LogType.ShuttleRecalled, LogImpact.High, $"Shuttle recalled"); } - _chatManager.DispatchStationAnnouncement(Loc.GetString("round-end-system-shuttle-recalled-announcement"), + _chatSystem.DispatchGlobalStationAnnouncement(Loc.GetString("round-end-system-shuttle-recalled-announcement"), Loc.GetString("Station"), false, colorOverride: Color.Gold); SoundSystem.Play(Filter.Broadcast(), "/Audio/Announcements/shuttlerecalled.ogg"); diff --git a/Content.Server/Salvage/SalvageGridComponent.cs b/Content.Server/Salvage/SalvageGridComponent.cs new file mode 100644 index 0000000000..ba9e89aaa4 --- /dev/null +++ b/Content.Server/Salvage/SalvageGridComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Salvage +{ + /// + /// A grid spawned by a salvage magnet. + /// + [RegisterComponent] + public sealed class SalvageGridComponent : Component + { + /// + /// The magnet that spawned this grid. + /// + public SalvageMagnetComponent? SpawnerMagnet; + } +} diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index d315821795..793adecba3 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -12,24 +12,28 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; +using Content.Server.Chat; +using Content.Server.Station.Systems; namespace Content.Server.Salvage { public sealed class SalvageSystem : EntitySystem { - [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IMapLoader _mapLoader = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; private static readonly TimeSpan AttachingTime = TimeSpan.FromSeconds(30); private static readonly TimeSpan HoldTime = TimeSpan.FromMinutes(4); private static readonly TimeSpan DetachingTime = TimeSpan.FromSeconds(30); private static readonly TimeSpan CooldownTime = TimeSpan.FromMinutes(1); + // TODO: This is probably not compatible with multi-station private readonly Dictionary _salvageGridStates = new(); public override void Initialize() @@ -58,7 +62,9 @@ namespace Content.Server.Salvage // If we ever want to give magnets names, and announce them individually, we would need to loop this, before removing it. if (_salvageGridStates.Remove(ev.GridId)) { - Report("salvage-system-announcement-spawn-magnet-lost"); + var gridUid = _mapManager.GetGridEuid(ev.GridId); + if (EntityManager.TryGetComponent(gridUid, out var salvComp) && salvComp.SpawnerMagnet != null) + Report(salvComp.SpawnerMagnet.Owner, "salvage-system-announcement-spawn-magnet-lost"); // For the very unlikely possibility that the salvage magnet was on a salvage, we will not return here } foreach(var gridState in _salvageGridStates) @@ -85,16 +91,16 @@ namespace Content.Server.Salvage return; } salvageGridState.ActiveMagnets.Remove(component); - Report("salvage-system-announcement-spawn-magnet-lost"); + Report(uid, "salvage-system-announcement-spawn-magnet-lost"); if (component.AttachedEntity.HasValue) { SafeDeleteSalvage(component.AttachedEntity.Value); component.AttachedEntity = null; - Report("salvage-system-announcement-lost"); + Report(uid, "salvage-system-announcement-lost"); } else if (component.MagnetState is { StateType: MagnetStateType.Attaching }) { - Report("salvage-system-announcement-spawn-no-debris-available"); + Report(uid, "salvage-system-announcement-spawn-no-debris-available"); } component.MagnetState = MagnetState.Inactive; } @@ -157,7 +163,7 @@ namespace Content.Server.Salvage } gridState.ActiveMagnets.Add(component); component.MagnetState = new MagnetState(MagnetStateType.Attaching, gridState.CurrentTime + AttachingTime); - Report("salvage-system-report-activate-success"); + Report(component.Owner, "salvage-system-report-activate-success"); break; case MagnetStateType.Attaching: case MagnetStateType.Holding: @@ -260,7 +266,7 @@ namespace Content.Server.Salvage if (map == null) { - Report("salvage-system-announcement-spawn-no-debris-available"); + Report(component.Owner, "salvage-system-announcement-spawn-no-debris-available"); return false; } @@ -272,22 +278,24 @@ namespace Content.Server.Salvage var (_, gridId) = _mapLoader.LoadBlueprint(spl.MapId, map.MapPath.ToString(), opts); if (gridId == null) { - Report("salvage-system-announcement-spawn-debris-disintegrated"); + Report(component.Owner, "salvage-system-announcement-spawn-debris-disintegrated"); return false; } var salvageEntityId = _mapManager.GetGridEuid(gridId.Value); component.AttachedEntity = salvageEntityId; + var gridcomp = EntityManager.EnsureComponent(salvageEntityId); + gridcomp.SpawnerMagnet = component; var pulledTransform = EntityManager.GetComponent(salvageEntityId); pulledTransform.WorldRotation = spAngle; - Report("salvage-system-announcement-arrived", ("timeLeft", HoldTime.TotalSeconds)); + Report(component.Owner, "salvage-system-announcement-arrived", ("timeLeft", HoldTime.TotalSeconds)); return true; } - private void Report(string messageKey) => - _chatManager.DispatchStationAnnouncement(Loc.GetString(messageKey), Loc.GetString("salvage-system-announcement-source"), colorOverride: Color.Orange, playDefaultSound: false); - private void Report(string messageKey, params (string, object)[] args) => - _chatManager.DispatchStationAnnouncement(Loc.GetString(messageKey, args), Loc.GetString("salvage-system-announcement-source"), colorOverride: Color.Orange, playDefaultSound: false); + private void Report(EntityUid source, string messageKey) => + _chatSystem.DispatchStationAnnouncement(source, Loc.GetString(messageKey), Loc.GetString("salvage-system-announcement-source"), colorOverride: Color.Orange, playDefaultSound: false); + private void Report(EntityUid source, string messageKey, params (string, object)[] args) => + _chatSystem.DispatchStationAnnouncement(source, Loc.GetString(messageKey, args), Loc.GetString("salvage-system-announcement-source"), colorOverride: Color.Orange, playDefaultSound: false); private void Transition(SalvageMagnetComponent magnet, TimeSpan currentTime) { @@ -304,7 +312,7 @@ namespace Content.Server.Salvage } break; case MagnetStateType.Holding: - Report("salvage-system-announcement-losing", ("timeLeft", DetachingTime.TotalSeconds)); + Report(magnet.Owner, "salvage-system-announcement-losing", ("timeLeft", DetachingTime.TotalSeconds)); magnet.MagnetState = new MagnetState(MagnetStateType.Detaching, currentTime + DetachingTime); break; case MagnetStateType.Detaching: @@ -316,7 +324,7 @@ namespace Content.Server.Salvage { Logger.ErrorS("salvage", "Salvage detaching was expecting attached entity but it was null"); } - Report("salvage-system-announcement-lost"); + Report(magnet.Owner, "salvage-system-announcement-lost"); magnet.MagnetState = new MagnetState(MagnetStateType.CoolingDown, currentTime + CooldownTime); break; case MagnetStateType.CoolingDown: diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs index 126d85791a..e76571a35a 100644 --- a/Content.Server/Station/Systems/StationSystem.cs +++ b/Content.Server/Station/Systems/StationSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Station.Components; @@ -23,6 +24,7 @@ public sealed class StationSystem : EntitySystem [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly GameTicker _gameTicker = default!; private ISawmill _sawmill = default!; @@ -268,7 +270,7 @@ public sealed class StationSystem : EntitySystem if (loud) { - _chatManager.DispatchStationAnnouncement($"The station {oldName} has been renamed to {name}."); + _chatSystem.DispatchStationAnnouncement(station, $"The station {oldName} has been renamed to {name}."); } RaiseLocalEvent(station, new StationRenamedEvent(oldName, name)); diff --git a/Content.Server/StationEvents/Events/DiseaseOutbreak.cs b/Content.Server/StationEvents/Events/DiseaseOutbreak.cs index ef46469aa9..ca6cc35b79 100644 --- a/Content.Server/StationEvents/Events/DiseaseOutbreak.cs +++ b/Content.Server/StationEvents/Events/DiseaseOutbreak.cs @@ -1,6 +1,7 @@ -using Content.Server.Chat.Managers; +using Content.Server.Chat; using Content.Server.Disease.Components; using Content.Server.Disease; +using Content.Server.Station.Systems; using Content.Shared.Disease; using Content.Shared.MobState.Components; using Content.Shared.Sound; @@ -17,7 +18,6 @@ public sealed class DiseaseOutbreak : StationEvent [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IChatManager _chatManager = default!; /// /// Disease prototypes I decided were not too deadly for a random event @@ -36,6 +36,7 @@ public sealed class DiseaseOutbreak : StationEvent public override SoundSpecifier? StartAudio => new SoundPathSpecifier("/Audio/Announcements/outbreak7.ogg"); protected override float EndAfter => 1.0f; + /// /// Finds 2-5 random, alive entities that can host diseases /// and gives them a randomly selected disease. @@ -44,6 +45,7 @@ public sealed class DiseaseOutbreak : StationEvent public override void Startup() { base.Startup(); + HashSet stationsToNotify = new(); List aliveList = new(); foreach (var (carrier, mobState) in _entityManager.EntityQuery()) { @@ -61,15 +63,25 @@ public sealed class DiseaseOutbreak : StationEvent return; var diseaseSystem = EntitySystem.Get(); - /// Now we give it to people in the list of living disease carriers earlier + var stationSystem = IoCManager.Resolve().GetEntitySystem(); + var chatSystem = IoCManager.Resolve().GetEntitySystem(); + // Now we give it to people in the list of living disease carriers earlier foreach (var target in aliveList) { if (toInfect-- == 0) break; diseaseSystem.TryAddDisease(target.Owner, disease, target); + + var station = stationSystem.GetOwningStation(target.Owner); + if(station == null) continue; + stationsToNotify.Add((EntityUid) station); + } + + foreach (var station in stationsToNotify) + { + chatSystem.DispatchStationAnnouncement(station, Loc.GetString("station-event-disease-outbreak-announcement"), + playDefaultSound: false, colorOverride: Color.YellowGreen); } - _chatManager.DispatchStationAnnouncement(Loc.GetString("station-event-disease-outbreak-announcement"), - playDefaultSound: false, colorOverride: Color.YellowGreen); } } diff --git a/Content.Server/StationEvents/Events/RandomSentience.cs b/Content.Server/StationEvents/Events/RandomSentience.cs index c142debee5..4d65a4df49 100644 --- a/Content.Server/StationEvents/Events/RandomSentience.cs +++ b/Content.Server/StationEvents/Events/RandomSentience.cs @@ -1,7 +1,8 @@ using System.Linq; -using Content.Server.Chat.Managers; +using Content.Server.Chat; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind.Commands; +using Content.Server.Station.Systems; using Content.Server.StationEvents.Components; using Robust.Shared.Random; @@ -11,7 +12,6 @@ public sealed class RandomSentience : StationEvent { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IChatManager _chatManager = default!; public override string Name => "RandomSentience"; @@ -22,6 +22,7 @@ public sealed class RandomSentience : StationEvent public override void Startup() { base.Startup(); + HashSet stationsToNotify = new(); var targetList = _entityManager.EntityQuery().ToList(); _random.Shuffle(targetList); @@ -49,13 +50,26 @@ public sealed class RandomSentience : StationEvent var kind1 = groupList.Count > 0 ? groupList[0] : "???"; var kind2 = groupList.Count > 1 ? groupList[1] : "???"; var kind3 = groupList.Count > 2 ? groupList[2] : "???"; - _chatManager.DispatchStationAnnouncement( - Loc.GetString("station-event-random-sentience-announcement", - ("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count), - ("data", Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}")), - ("strength", Loc.GetString($"random-sentience-event-strength-{_random.Next(1, 8)}"))), + + var stationSystem = IoCManager.Resolve().GetEntitySystem(); + var chatSystem = IoCManager.Resolve().GetEntitySystem(); + foreach (var target in targetList) + { + var station = stationSystem.GetOwningStation(target.Owner); + if(station == null) continue; + stationsToNotify.Add((EntityUid) station); + } + foreach (var station in stationsToNotify) + { + chatSystem.DispatchStationAnnouncement( + (EntityUid) station, + Loc.GetString("station-event-random-sentience-announcement", + ("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count), + ("data", Loc.GetString($"random-sentience-event-data-{_random.Next(1, 6)}")), + ("strength", Loc.GetString($"random-sentience-event-strength-{_random.Next(1, 8)}"))), playDefaultSound: false, colorOverride: Color.Gold - ); + ); + } } } diff --git a/Content.Server/StationEvents/Events/StationEvent.cs b/Content.Server/StationEvents/Events/StationEvent.cs index a85bf324b5..478adc7982 100644 --- a/Content.Server/StationEvents/Events/StationEvent.cs +++ b/Content.Server/StationEvents/Events/StationEvent.cs @@ -1,5 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Station.Components; @@ -140,8 +141,8 @@ namespace Content.Server.StationEvents.Events if (StartAnnouncement != null) { - var chatManager = IoCManager.Resolve(); - chatManager.DispatchStationAnnouncement(StartAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); + var chatSystem = IoCManager.Resolve().GetEntitySystem(); + chatSystem.DispatchGlobalStationAnnouncement(StartAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); } if (StartAudio != null) @@ -163,8 +164,8 @@ namespace Content.Server.StationEvents.Events if (EndAnnouncement != null) { - var chatManager = IoCManager.Resolve(); - chatManager.DispatchStationAnnouncement(EndAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); + var chatSystem = IoCManager.Resolve().GetEntitySystem(); + chatSystem.DispatchGlobalStationAnnouncement(EndAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); } if (EndAudio != null) diff --git a/Content.Server/StationEvents/Events/ZombieOutbreak.cs b/Content.Server/StationEvents/Events/ZombieOutbreak.cs index c6a4509c98..e69af45baf 100644 --- a/Content.Server/StationEvents/Events/ZombieOutbreak.cs +++ b/Content.Server/StationEvents/Events/ZombieOutbreak.cs @@ -1,6 +1,7 @@ +using Content.Server.Chat; using Robust.Shared.Random; -using Content.Server.Chat.Managers; using Content.Server.Disease.Zombie.Components; +using Content.Server.Station.Systems; using Content.Shared.MobState.Components; using Content.Shared.Sound; @@ -13,7 +14,6 @@ namespace Content.Server.StationEvents.Events { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IChatManager _chatManager = default!; public override string Name => "ZombieOutbreak"; public override int EarliestStart => 50; @@ -31,6 +31,7 @@ namespace Content.Server.StationEvents.Events public override void Startup() { base.Startup(); + HashSet stationsToNotify = new(); List deadList = new(); foreach (var mobState in _entityManager.EntityQuery()) { @@ -41,16 +42,25 @@ namespace Content.Server.StationEvents.Events var toInfect = _random.Next(1, 3); - /// Now we give it to people in the list of dead entities earlier. + // Now we give it to people in the list of dead entities earlier. + var stationSystem = IoCManager.Resolve().GetEntitySystem(); + var chatSystem = IoCManager.Resolve().GetEntitySystem(); foreach (var target in deadList) { if (toInfect-- == 0) break; _entityManager.EnsureComponent(target.Owner); + + var station = stationSystem.GetOwningStation(target.Owner); + if(station == null) continue; + stationsToNotify.Add((EntityUid) station); + } + foreach (var station in stationsToNotify) + { + chatSystem.DispatchStationAnnouncement((EntityUid) station, Loc.GetString("station-event-zombie-outbreak-announcement"), + playDefaultSound: false, colorOverride: Color.DarkMagenta); } - _chatManager.DispatchStationAnnouncement(Loc.GetString("station-event-zombie-outbreak-announcement"), - playDefaultSound: false, colorOverride: Color.DarkMagenta); } } } diff --git a/Content.Shared/Administration/AdminAnnounceEuiState.cs b/Content.Shared/Administration/AdminAnnounceEuiState.cs index 67bb121c66..32bfd611fb 100644 --- a/Content.Shared/Administration/AdminAnnounceEuiState.cs +++ b/Content.Shared/Administration/AdminAnnounceEuiState.cs @@ -8,8 +8,11 @@ namespace Content.Shared.Administration Station, Server, } + [Serializable, NetSerializable] - public sealed class AdminAnnounceEuiState : EuiStateBase {} + public sealed class AdminAnnounceEuiState : EuiStateBase + { + } public static class AdminAnnounceEuiMsg { diff --git a/Resources/Locale/en-US/communications/communications-console-component.ftl b/Resources/Locale/en-US/communications/communications-console-component.ftl index b5437e869f..71867bc3de 100644 --- a/Resources/Locale/en-US/communications/communications-console-component.ftl +++ b/Resources/Locale/en-US/communications/communications-console-component.ftl @@ -1,4 +1,17 @@ -communicationsconsole-menu-title = Communications Console -communicationsconsole-menu-announcement-placeholder = Announcement -communicationsconsole-menu-call-shuttle = Call emergency shuttle -communicationsconsole-menu-recall-shuttle = Recall emergency shuttle +# User interface +comms-console-menu-title = Communications Console +comms-console-menu-announcement-placeholder = Announcement +comms-console-menu-call-shuttle = Call emergency shuttle +comms-console-menu-recall-shuttle = Recall emergency shuttle + +# Popup +comms-console-permission-denied = Permission denied + +# Placeholder values +comms-console-announcement-sent-by = Sent by +comms-console-announcement-unknown-sender = Unknown + +# Comms console variant titles +comms-console-announcement-title-station = Communications Console +comms-console-announcement-title-centcom = Central Command +comms-console-announcement-title-nukie = Syndicate Nuclear Operative diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 02bff83aeb..636aed8c31 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -97,6 +97,8 @@ event: !type:ToggleIntrinsicUIEvent - type: SolarControlConsole # look ma i AM the computer! - type: CommunicationsConsole + title: communicationsconsole-announcement-title-centcom + color: "#228b22" - type: RadarConsole - type: CargoConsole - type: CargoOrderDatabase diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 65d62f1488..d2b3608437 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -146,6 +146,15 @@ - type: ComputerBoard prototype: ComputerComms +- type: entity + parent: BaseComputerCircuitboard + id: SyndicateCommsComputerCircuitboard + name: syndicate communications computer board + description: A computer printed circuit board for a syndicate communications console + components: + - type: ComputerBoard + prototype: SyndicateComputerComms + - type: entity parent: BaseComputerCircuitboard id: RadarConsoleCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 974237fabc..76c3cacbf4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -278,7 +278,10 @@ - type: ComputerVisualizer key: generic_key screen: comm + - type: AccessReader + access: [[ "Command" ]] - type: CommunicationsConsole + title: comms-console-announcement-title-station - type: ActivatableUI key: enum.CommunicationsConsoleUiKey.Key - type: ActivatableUIRequiresPower @@ -293,6 +296,31 @@ energy: 1.6 color: "#3c5eb5" +- type: entity + parent: ComputerComms + id: SyndicateComputerComms + name: syndicate communications computer + description: This can be used for various important functions. Still under development. + components: + - type: Appearance + visuals: + - type: ComputerVisualizer + key: syndie_key + screen: comm_syndie + - type: AccessReader + access: [[ "NuclearOperative" ]] + - type: CommunicationsConsole + title: comms-console-announcement-title-nukie + color: "#ff0000" + canShuttle: false + global: true #announce to everyone they're about to fuck shit up + - type: Computer + board: SyndicateCommsComputerCircuitboard + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#f71713" + - type: entity parent: ComputerBase id: ComputerSolarControl diff --git a/Resources/Textures/Structures/Machines/computers.rsi/comm_syndie.png b/Resources/Textures/Structures/Machines/computers.rsi/comm_syndie.png new file mode 100644 index 0000000000000000000000000000000000000000..72cd1736f6bd4311541b8d4d20fa9519e60ba8dd GIT binary patch literal 9492 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 U1sr->Nz)aet!|)JsbUxTKNI>HlK=n! literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/computers.rsi/meta.json b/Resources/Textures/Structures/Machines/computers.rsi/meta.json index f48024b8c1..2ea8b234f6 100644 --- a/Resources/Textures/Structures/Machines/computers.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/computers.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bd6873fd4dd6a61d7e46f1d75cd4d90f64c40894. comm_syndie made by Veritius, based on comm.", "size": { "x": 32, "y": 32 @@ -305,6 +305,28 @@ ] ] }, + { + "name": "comm_syndie", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, { "name": "command", "directions": 4, @@ -1542,4 +1564,4 @@ "name": "detective_television" } ] -} \ No newline at end of file +} diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 86c25ffd11..8e00544248 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -320,7 +320,7 @@ public abstract class $CLASS$ : Component { [Serializable, NetSerializable] public sealed class $CLASS$State : ComponentState { public $CLASS$State($CLASS$ component) { - + } } SS14 @@ -401,11 +401,11 @@ public sealed class $CLASS$ : IPrototype, IInheritingPrototype { /// <inheritdoc/> [IdDataField] public string ID { get; } = default!; - + /// <inheritdoc/> [ParentDataField(typeof(AbstractPrototypeIdSerializer<$CLASS$>))] public string? Parent { get; } - + /// <inheritdoc/> [NeverPushInheritance] [AbstractDataField]