diff --git a/Content.Client/Command/CommunicationsConsoleMenu.cs b/Content.Client/Command/CommunicationsConsoleMenu.cs index 8bfbea72a0..880031f35e 100644 --- a/Content.Client/Command/CommunicationsConsoleMenu.cs +++ b/Content.Client/Command/CommunicationsConsoleMenu.cs @@ -14,6 +14,8 @@ namespace Content.Client.Command { private CommunicationsConsoleBoundUserInterface Owner { get; set; } private readonly CancellationTokenSource _timerCancelTokenSource = new(); + private LineEdit _messageInput { get; set; } + public readonly Button AnnounceButton; public readonly Button EmergencyShuttleButton; private readonly RichTextLabel _countdownLabel; @@ -25,13 +27,27 @@ namespace Content.Client.Command Title = Loc.GetString("Communications Console"); Owner = owner; + _messageInput = new LineEdit + { + PlaceHolder = Loc.GetString("Announcement"), + HorizontalExpand = true, + SizeFlagsStretchRatio = 1 + }; + AnnounceButton = new Button(); + AnnounceButton.Text = "Announce"; + AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(_messageInput.Text.Trim()); + AnnounceButton.Disabled = !owner.CanAnnounce; + _countdownLabel = new RichTextLabel(){MinSize = new Vector2(0, 200)}; EmergencyShuttleButton = new Button(); EmergencyShuttleButton.OnPressed += (_) => Owner.EmergencyShuttleButtonPressed(); EmergencyShuttleButton.Disabled = !owner.CanCall; var vbox = new VBoxContainer() {HorizontalExpand = true, VerticalExpand = true}; - + vbox.AddChild(_messageInput); + vbox.AddChild(new Control(){MinSize = new Vector2(0,10), HorizontalExpand = true}); + vbox.AddChild(AnnounceButton); + vbox.AddChild(new Control(){MinSize = new Vector2(0,10), HorizontalExpand = true}); vbox.AddChild(_countdownLabel); vbox.AddChild(EmergencyShuttleButton); diff --git a/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs index 210aabd491..0a80bef65a 100644 --- a/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs @@ -15,6 +15,7 @@ namespace Content.Client.GameObjects.Components.Command [ViewVariables] private CommunicationsConsoleMenu? _menu; + public bool CanAnnounce { get; private set; } public bool CanCall { get; private set; } public bool CountdownStarted { get; private set; } @@ -44,6 +45,12 @@ namespace Content.Client.GameObjects.Components.Command CallShuttle(); } + public void AnnounceButtonPressed(string message) + { + var msg = message.Length <= 256 ? message.Trim() : $"{message.Trim().Substring(0, 256)}..."; + SendMessage(new CommunicationsConsoleAnnounceMessage(msg)); + } + public void CallShuttle() { SendMessage(new CommunicationsConsoleCallEmergencyShuttleMessage()); @@ -61,6 +68,7 @@ namespace Content.Client.GameObjects.Components.Command if (state is not CommunicationsConsoleInterfaceState commsState) return; + CanAnnounce = commsState.CanAnnounce; CanCall = commsState.CanCall; _expectedCountdownTime = commsState.ExpectedCountdownEnd; CountdownStarted = commsState.CountdownStarted; @@ -69,6 +77,7 @@ namespace Content.Client.GameObjects.Components.Command { _menu.UpdateCountdown(); _menu.EmergencyShuttleButton.Disabled = !CanCall; + _menu.AnnounceButton.Disabled = !CanAnnounce; } } diff --git a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs index 8561077130..ecbad18a3c 100644 --- a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs @@ -1,13 +1,21 @@ #nullable enable +using System; +using System.Globalization; +using System.Threading; +using Content.Server.GameObjects.Components.PDA; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces.Chat; using Content.Server.Utility; using Content.Shared.GameObjects.Components.Command; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Timing; using Robust.Shared.ViewVariables; +using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.GameObjects.Components.Command { @@ -15,12 +23,18 @@ namespace Content.Server.GameObjects.Components.Command [ComponentReference(typeof(IActivate))] public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate { + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IChatManager _chatManager = default!; private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered; private RoundEndSystem RoundEndSystem => EntitySystem.Get(); [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key); + public TimeSpan LastAnnounceTime { get; private set; } = TimeSpan.Zero; + public TimeSpan AnnounceCooldown { get; } = TimeSpan.FromSeconds(90); + private CancellationTokenSource _announceCooldownEndedTokenSource = new(); + public override void Initialize() { base.Initialize(); @@ -49,10 +63,19 @@ namespace Content.Server.GameObjects.Components.Command { var system = RoundEndSystem; - UserInterface?.SetState(new CommunicationsConsoleInterfaceState(system.CanCall(), system.ExpectedCountdownEnd)); + UserInterface?.SetState(new CommunicationsConsoleInterfaceState(CanAnnounce(), system.CanCall(), system.ExpectedCountdownEnd)); } } + public bool CanAnnounce() + { + if (LastAnnounceTime == TimeSpan.Zero) + { + return true; + } + return _gameTiming.CurTime >= LastAnnounceTime + AnnounceCooldown; + } + public override void OnRemove() { RoundEndSystem.OnRoundEndCountdownStarted -= UpdateBoundInterface; @@ -72,6 +95,29 @@ namespace Content.Server.GameObjects.Components.Command case CommunicationsConsoleRecallEmergencyShuttleMessage _: RoundEndSystem.CancelRoundEndCountdown(); 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 author = "Unknown"; + var mob = obj.Session.AttachedEntity; + if (mob != null && mob.TryGetHeldId(out var id)) + { + author = $"{id.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id.JobTitle)})"; + } + + message += $"\nSent by {author}"; + _chatManager.DispatchStationAnnouncement(message, "Communications Console"); + break; } } diff --git a/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs b/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs index 5e8d257a7e..9c1c5c7086 100644 --- a/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs +++ b/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs @@ -13,18 +13,31 @@ namespace Content.Shared.GameObjects.Components.Command [Serializable, NetSerializable] public class CommunicationsConsoleInterfaceState : BoundUserInterfaceState { + public readonly bool CanAnnounce; public readonly bool CanCall; public readonly TimeSpan? ExpectedCountdownEnd; public readonly bool CountdownStarted; - public CommunicationsConsoleInterfaceState(bool canCall, TimeSpan? expectedCountdownEnd = null) + public CommunicationsConsoleInterfaceState(bool canAnnounce, bool canCall, TimeSpan? expectedCountdownEnd = null) { + CanAnnounce = canAnnounce; CanCall = canCall; ExpectedCountdownEnd = expectedCountdownEnd; CountdownStarted = expectedCountdownEnd != null; } } + [Serializable, NetSerializable] + public class CommunicationsConsoleAnnounceMessage : BoundUserInterfaceMessage + { + public readonly string Message; + + public CommunicationsConsoleAnnounceMessage(string message) + { + Message = message; + } + } + [Serializable, NetSerializable] public class CommunicationsConsoleCallEmergencyShuttleMessage : BoundUserInterfaceMessage {