diff --git a/Content.Client/Command/CommunicationsConsoleMenu.cs b/Content.Client/Command/CommunicationsConsoleMenu.cs new file mode 100644 index 0000000000..2213c84dbb --- /dev/null +++ b/Content.Client/Command/CommunicationsConsoleMenu.cs @@ -0,0 +1,78 @@ +using System.Threading; +using Content.Client.GameObjects.Components.Command; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Timer = Robust.Shared.Timers.Timer; + +namespace Content.Client.Command +{ + public class CommunicationsConsoleMenu : SS14Window + { +#pragma warning disable 649 + [Dependency] private readonly ILocalizationManager _localizationManager; +#pragma warning restore 649 + + protected override Vector2? CustomSize => new Vector2(600, 400); + + private CommunicationsConsoleBoundUserInterface Owner { get; set; } + private readonly CancellationTokenSource _timerCancelTokenSource = new CancellationTokenSource(); + + private readonly RichTextLabel _countdownLabel; + + public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner) + { + IoCManager.InjectDependencies(this); + + Title = _localizationManager.GetString("Communications Console"); + Owner = owner; + + _countdownLabel = new RichTextLabel(){CustomMinimumSize = new Vector2(0, 200)}; + var emergencyShuttleButton = new Button() {Text = _localizationManager.GetString("Call emergency shuttle")}; + + emergencyShuttleButton.OnPressed += (e) => Owner.CallShuttle(); + + var vbox = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.FillExpand}; + + vbox.AddChild(_countdownLabel); + vbox.AddChild(emergencyShuttleButton); + + var hbox = new HBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.FillExpand}; + hbox.AddChild(new Control(){CustomMinimumSize = new Vector2(100,0), SizeFlagsHorizontal = SizeFlags.FillExpand}); + hbox.AddChild(vbox); + hbox.AddChild(new Control(){CustomMinimumSize = new Vector2(100,0), SizeFlagsHorizontal = SizeFlags.FillExpand}); + + Contents.AddChild(hbox); + + UpdateCountdown(); + Timer.SpawnRepeating(1000, UpdateCountdown, _timerCancelTokenSource.Token); + } + + private void UpdateCountdown() + { + if (!Owner.CountdownStarted) + { + _countdownLabel.SetMessage(""); + return; + } + + _countdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s"); + } + + public override void Close() + { + base.Close(); + + _timerCancelTokenSource.Cancel(); + } + + protected override void Dispose(bool disposing) + { + if(disposing) + _timerCancelTokenSource.Cancel(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs new file mode 100644 index 0000000000..e123e5ac4d --- /dev/null +++ b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs @@ -0,0 +1,66 @@ +using System; +using Content.Client.Command; +using Content.Shared.GameObjects.Components.Command; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.ViewVariables; + +namespace Content.Client.GameObjects.Components.Command +{ + public class CommunicationsConsoleBoundUserInterface : BoundUserInterface + { + [ViewVariables] + private CommunicationsConsoleMenu _menu; + + public bool CountdownStarted { get; private set; } + + public int Countdown => _expectedCountdownTime == null + ? 0 : Math.Max((int)(_expectedCountdownTime.Value.Subtract(DateTime.Now)).TotalSeconds, 0); + private DateTime? _expectedCountdownTime; + + public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _menu = new CommunicationsConsoleMenu(this); + + _menu.OnClose += Close; + + _menu.OpenCentered(); + } + + public void CallShuttle() + { + SendMessage(new CommunicationsConsoleCallEmergencyShuttleMessage()); + } + + protected override void ReceiveMessage(BoundUserInterfaceMessage message) + { + switch (message) + { + } + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + if (!(state is CommunicationsConsoleInterfaceState commsState)) + return; + + _expectedCountdownTime = commsState.ExpectedCountdownEnd; + CountdownStarted = commsState.CountdownStarted; + + + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + _menu?.Dispose(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs index 3273cb79f3..9e318f04a6 100644 --- a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs @@ -17,9 +17,9 @@ namespace Content.Client.GameObjects.Components.Research private IPrototypeManager _prototypeManager; #pragma warning restore [ViewVariables] - private LatheMenu menu; + private LatheMenu _menu; [ViewVariables] - private LatheQueueMenu queueMenu; + private LatheQueueMenu _queueMenu; public MaterialStorageComponent Storage { get; private set; } public SharedLatheComponent Lathe { get; private set; } @@ -48,30 +48,30 @@ namespace Content.Client.GameObjects.Components.Research Lathe = lathe; Database = database; - menu = new LatheMenu(this); - queueMenu = new LatheQueueMenu { Owner = this }; + _menu = new LatheMenu(this); + _queueMenu = new LatheQueueMenu { Owner = this }; - menu.OnClose += Close; + _menu.OnClose += Close; - menu.Populate(); - menu.PopulateMaterials(); + _menu.Populate(); + _menu.PopulateMaterials(); - menu.QueueButton.OnPressed += (args) => { queueMenu.OpenCentered(); }; + _menu.QueueButton.OnPressed += (args) => { _queueMenu.OpenCentered(); }; - menu.ServerConnectButton.OnPressed += (args) => + _menu.ServerConnectButton.OnPressed += (args) => { SendMessage(new SharedLatheComponent.LatheServerSelectionMessage()); }; - menu.ServerSyncButton.OnPressed += (args) => + _menu.ServerSyncButton.OnPressed += (args) => { SendMessage(new SharedLatheComponent.LatheServerSyncMessage()); }; - storage.OnMaterialStorageChanged += menu.PopulateDisabled; - storage.OnMaterialStorageChanged += menu.PopulateMaterials; + storage.OnMaterialStorageChanged += _menu.PopulateDisabled; + storage.OnMaterialStorageChanged += _menu.PopulateMaterials; - menu.OpenCentered(); + _menu.OpenCentered(); } public void Queue(LatheRecipePrototype recipe, int quantity = 1) @@ -85,10 +85,10 @@ namespace Content.Client.GameObjects.Components.Research { case SharedLatheComponent.LatheProducingRecipeMessage msg: if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe)) break; - queueMenu?.SetInfo(recipe); + _queueMenu?.SetInfo(recipe); break; case SharedLatheComponent.LatheStoppedProducingRecipeMessage _: - queueMenu?.ClearInfo(); + _queueMenu?.ClearInfo(); break; case SharedLatheComponent.LatheFullQueueMessage msg: _queuedRecipes.Clear(); @@ -97,7 +97,7 @@ namespace Content.Client.GameObjects.Components.Research if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype recipePrototype)) break; _queuedRecipes.Enqueue(recipePrototype); } - queueMenu?.PopulateList(); + _queueMenu?.PopulateList(); break; } } @@ -106,8 +106,8 @@ namespace Content.Client.GameObjects.Components.Research { base.Dispose(disposing); if (!disposing) return; - menu?.Dispose(); - queueMenu?.Dispose(); + _menu?.Dispose(); + _queueMenu?.Dispose(); } } } diff --git a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs new file mode 100644 index 0000000000..4ce6a83883 --- /dev/null +++ b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs @@ -0,0 +1,72 @@ +using Content.Server.GameObjects.Components.Power; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces.GameTicking; +using Content.Shared.GameObjects.Components.Command; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.GameObjects.Components.Command +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate + { +#pragma warning disable 649 + [Dependency] private IEntitySystemManager _entitySystemManager; +#pragma warning restore 649 + + private BoundUserInterface _userInterface; + private PowerDeviceComponent _powerDevice; + private bool Powered => _powerDevice.Powered; + private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem(); + + public override void Initialize() + { + base.Initialize(); + + _userInterface = Owner.GetComponent().GetBoundUserInterface(CommunicationsConsoleUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + _powerDevice = Owner.GetComponent(); + + RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface; + RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface; + RoundEndSystem.OnRoundEndCountdownFinished += UpdateBoundInterface; + } + + private void UpdateBoundInterface() + { + _userInterface.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd)); + } + + private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj) + { + switch (obj.Message) + { + case CommunicationsConsoleCallEmergencyShuttleMessage _: + RoundEndSystem.RequestRoundEnd(); + break; + } + } + + public void OpenUserInterface(IPlayerSession session) + { + _userInterface.Open(session); + } + + void IActivate.Activate(ActivateEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + return; + + if (!Powered) + { + return; + } + OpenUserInterface(actor.playerSession); + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs b/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs new file mode 100644 index 0000000000..90bb676f16 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading; +using Content.Server.Interfaces.GameTicking; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.IoC; +using Timer = Robust.Shared.Timers.Timer; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class RoundEndSystem : EntitySystem + { +#pragma warning disable 649 + [Dependency] private IGameTicker _gameTicker; +#pragma warning restore 649 + + private CancellationTokenSource _roundEndCancellationTokenSource = new CancellationTokenSource(); + public bool IsRoundEndCountdownStarted { get; private set; } + public int RoundEndCountdownTime { get; set; } = 5000; + public DateTime? ExpectedCountdownEnd = null; + + public delegate void RoundEndCountdownStarted(); + public event RoundEndCountdownStarted OnRoundEndCountdownStarted; + + public delegate void RoundEndCountdownCancelled(); + public event RoundEndCountdownCancelled OnRoundEndCountdownCancelled; + + public delegate void RoundEndCountdownFinished(); + public event RoundEndCountdownFinished OnRoundEndCountdownFinished; + + public void RequestRoundEnd() + { + if (IsRoundEndCountdownStarted) + return; + + IsRoundEndCountdownStarted = true; + + ExpectedCountdownEnd = DateTime.Now.AddMilliseconds(RoundEndCountdownTime); + Timer.Spawn(RoundEndCountdownTime, EndRound, _roundEndCancellationTokenSource.Token); + OnRoundEndCountdownStarted?.Invoke(); + } + + public void CancelRoundEndCountdown() + { + _roundEndCancellationTokenSource.Cancel(); + _roundEndCancellationTokenSource = new CancellationTokenSource(); + + ExpectedCountdownEnd = null; + + OnRoundEndCountdownCancelled?.Invoke(); + } + + private void EndRound() + { + OnRoundEndCountdownFinished?.Invoke(); + _gameTicker.EndRound(); + } + } +} diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index e8c654b2c3..39905c6737 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -13,6 +13,7 @@ using Content.Server.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Players; using Content.Shared; +using Content.Shared.Chat; using Content.Shared.Jobs; using Content.Shared.Preferences; using Robust.Server.Interfaces.Maps; @@ -123,6 +124,8 @@ namespace Content.Server.GameTicking { Logger.InfoS("ticker", "Restarting round!"); + SendServerMessage("Restarting round..."); + RunLevel = GameRunLevel.PreRoundLobby; _resettingCleanup(); _preRoundSetup(); @@ -147,6 +150,8 @@ namespace Content.Server.GameTicking DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby); Logger.InfoS("ticker", "Starting round!"); + SendServerMessage("The round is starting now..."); + RunLevel = GameRunLevel.InRound; var preset = MakeGamePreset(); @@ -191,6 +196,14 @@ namespace Content.Server.GameTicking _sendStatusToAll(); } + private void SendServerMessage(string message) + { + var msg = _netManager.CreateNetMessage(); + msg.Channel = ChatChannel.Server; + msg.Message = message; + IoCManager.Resolve().ServerSendToAll(msg); + } + private HumanoidCharacterProfile GetPlayerProfile(IPlayerSession p) => (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.SessionId.Username).SelectedCharacter; @@ -200,6 +213,8 @@ namespace Content.Server.GameTicking Logger.InfoS("ticker", "Ending round!"); RunLevel = GameRunLevel.PostRound; + + SendServerMessage("The round has ended!"); } public void Respawn(IPlayerSession targetPlayer) diff --git a/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs b/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs new file mode 100644 index 0000000000..b5ad45c115 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs @@ -0,0 +1,41 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Command +{ + public class SharedCommunicationsConsoleComponent : Component + { + public override string Name => "CommunicationsConsole"; + + } + + [Serializable, NetSerializable] + public class CommunicationsConsoleInterfaceState : BoundUserInterfaceState + { + public readonly DateTime? ExpectedCountdownEnd; + public readonly bool CountdownStarted; + + public CommunicationsConsoleInterfaceState(DateTime? expectedCountdownEnd = null) + { + ExpectedCountdownEnd = expectedCountdownEnd; + CountdownStarted = expectedCountdownEnd != null; + + } + } + + [Serializable, NetSerializable] + public class CommunicationsConsoleCallEmergencyShuttleMessage : BoundUserInterfaceMessage + { + public CommunicationsConsoleCallEmergencyShuttleMessage() + { + } + } + + [Serializable, NetSerializable] + public enum CommunicationsConsoleUiKey + { + Key + } +} diff --git a/Resources/Prototypes/Entities/buildings/computers.yml b/Resources/Prototypes/Entities/buildings/computers.yml index fff70844a8..bc8dbf9fe4 100644 --- a/Resources/Prototypes/Entities/buildings/computers.yml +++ b/Resources/Prototypes/Entities/buildings/computers.yml @@ -176,3 +176,8 @@ - type: ComputerVisualizer2D key: generic_key screen: comm + - type: CommunicationsConsole + - type: UserInterface + interfaces: + - key: enum.CommunicationsConsoleUiKey.Key + type: CommunicationsConsoleBoundUserInterface