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 0000000000..72cd1736f6 Binary files /dev/null and b/Resources/Textures/Structures/Machines/computers.rsi/comm_syndie.png differ 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]