diff --git a/Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs b/Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs new file mode 100644 index 0000000000..0186bf4a01 --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/SuspicionEndTimerSystem.cs @@ -0,0 +1,23 @@ +using System; +using Content.Shared.GameObjects.EntitySystemMessages; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Client.GameObjects.EntitySystems +{ + public sealed class SuspicionEndTimerSystem : EntitySystem + { + public TimeSpan? EndTime { get; private set; } + + public override void Initialize() + { + base.Initialize(); + + SubscribeNetworkEvent(RxTimerMessage); + } + + private void RxTimerMessage(SuspicionMessages.SetSuspicionEndTimerMessage ev) + { + EndTime = ev.EndTime; + } + } +} diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml new file mode 100644 index 0000000000..fb22a6d1fa --- /dev/null +++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/Content.Client/UserInterface/Suspicion/SuspicionGui.cs b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs similarity index 61% rename from Content.Client/UserInterface/Suspicion/SuspicionGui.cs rename to Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs index d2213379fe..a9694fbe20 100644 --- a/Content.Client/UserInterface/Suspicion/SuspicionGui.cs +++ b/Content.Client/UserInterface/Suspicion/SuspicionGui.xaml.cs @@ -1,48 +1,41 @@ -using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Content.Client.GameObjects.Components.Suspicion; +using Content.Client.GameObjects.EntitySystems; using Content.Shared.Interfaces; +using Robust.Client.AutoGenerated; using Robust.Client.Player; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Interfaces.GameObjects; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Timing; using static Robust.Client.UserInterface.Controls.BaseButton; +#nullable enable + namespace Content.Client.UserInterface.Suspicion { - public class SuspicionGui : Control + [GenerateTypedNameReferences] + public partial class SuspicionGui : Control { [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; - private readonly VBoxContainer _container; - private readonly Button _roleButton; - - private string _previousRoleName; + private string? _previousRoleName; private bool _previousAntagonist; public SuspicionGui() { + RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); - AddChild(_container = new VBoxContainer - { - SeparationOverride = 0, - Children = - { - (_roleButton = new Button - { - Name = "Suspicion Role Button" - }) - } - }); - - _roleButton.CustomMinimumSize = (200, 60); - _roleButton.OnPressed += RoleButtonPressed; + RoleButton.OnPressed += RoleButtonPressed; + RoleButton.CustomMinimumSize = (200, 60); } private void RoleButtonPressed(ButtonEventArgs obj) @@ -67,11 +60,15 @@ namespace Content.Client.UserInterface.Suspicion role.Owner.PopupMessage(message); } - private bool TryGetComponent(out SuspicionRoleComponent suspicion) + private bool TryGetComponent([NotNullWhen(true)] out SuspicionRoleComponent? suspicion) { suspicion = default; + if (_playerManager.LocalPlayer?.ControlledEntity == null) + { + return false; + } - return _playerManager?.LocalPlayer?.ControlledEntity?.TryGetComponent(out suspicion) == true; + return _playerManager.LocalPlayer.ControlledEntity.TryGetComponent(out suspicion); } public void UpdateLabel() @@ -88,6 +85,18 @@ namespace Content.Client.UserInterface.Suspicion return; } + var endTime = EntitySystem.Get().EndTime; + if (endTime == null) + { + TimerLabel.Visible = false; + } + else + { + var diff = _timing.CurTime - endTime.Value; + TimerLabel.Visible = true; + TimerLabel.Text = $"{diff:mm\\:ss}"; + } + if (_previousRoleName == suspicion.Role && _previousAntagonist == suspicion.Antagonist) { return; @@ -99,8 +108,8 @@ namespace Content.Client.UserInterface.Suspicion var buttonText = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_previousRoleName); buttonText = Loc.GetString(buttonText); - _roleButton.Text = buttonText; - _roleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.Green; + RoleButton.Text = buttonText; + RoleButton.ModulateSelfOverride = _previousAntagonist ? Color.Red : Color.Green; Visible = true; } diff --git a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs index 641080e643..c436c06b12 100644 --- a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs +++ b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Mobs; -using Content.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.EntitySystems.GameMode; using Content.Server.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Mobs.Roles.Suspicion; diff --git a/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs new file mode 100644 index 0000000000..9e46b5c3a3 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionEndTimerSystem.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using Content.Server.Interfaces.GameTicking; +using Content.Shared.GameObjects.EntitySystemMessages; +using Content.Shared.GameTicking; +using JetBrains.Annotations; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; + +#nullable enable + +namespace Content.Server.GameObjects.EntitySystems.GameMode +{ + [UsedImplicitly] + public sealed class SuspicionEndTimerSystem : EntitySystem, IResettingEntitySystem + { + [Dependency] private readonly IPlayerManager _playerManager = null!; + [Dependency] private readonly IGameTiming _timing = null!; + [Dependency] private readonly IGameTicker _gameTicker = null!; + + private TimeSpan? _endTime; + + public TimeSpan? EndTime + { + get => _endTime; + set + { + _endTime = value; + SendUpdateToAll(); + } + } + + public override void Initialize() + { + base.Initialize(); + + _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; + } + + public override void Shutdown() + { + base.Shutdown(); + + _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged; + } + + private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus == SessionStatus.InGame) + { + SendUpdateTimerMessage(e.Session); + } + } + + private void SendUpdateToAll() + { + foreach (var player in _playerManager.GetAllPlayers().Where(p => p.Status == SessionStatus.InGame)) + { + SendUpdateTimerMessage(player); + } + } + + private void SendUpdateTimerMessage(IPlayerSession player) + { + var msg = new SuspicionMessages.SetSuspicionEndTimerMessage + { + EndTime = EndTime + }; + + EntityNetworkManager.SendSystemNetworkMessage(msg, player.ConnectedClient); + } + + void IResettingEntitySystem.Reset() + { + EndTime = null; + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionRoleSystem.cs similarity index 92% rename from Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs rename to Content.Server/GameObjects/EntitySystems/GameMode/SuspicionRoleSystem.cs index 7bfb191404..e805bab473 100644 --- a/Content.Server/GameObjects/EntitySystems/SuspicionRoleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GameMode/SuspicionRoleSystem.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Content.Server.GameObjects.Components.Suspicion; using Content.Shared.GameTicking; using JetBrains.Annotations; using Robust.Shared.GameObjects.Systems; -namespace Content.Server.GameObjects.EntitySystems +namespace Content.Server.GameObjects.EntitySystems.GameMode { [UsedImplicitly] public class SuspicionRoleSystem : EntitySystem, IResettingEntitySystem diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 610748cd5e..f1d66028b7 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -2,6 +2,7 @@ using System.Threading; using Content.Server.GameObjects.Components.Suspicion; using Content.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.EntitySystems.GameMode; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; using Content.Server.Mobs.Roles.Suspicion; @@ -14,6 +15,7 @@ using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; using Timer = Robust.Shared.Timers.Timer; @@ -31,9 +33,10 @@ namespace Content.Server.GameTicking.GameRules [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IGameTicker _gameTicker = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameTiming _timing = default!; private readonly CancellationTokenSource _checkTimerCancel = new(); - private CancellationTokenSource _maxTimerCancel = new(); + private TimeSpan _endTime; public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromSeconds(CCVars.SuspicionMaxTimeSeconds.DefaultValue); public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10); @@ -42,63 +45,37 @@ namespace Content.Server.GameTicking.GameRules { RoundMaxTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.SuspicionMaxTimeSeconds)); + _endTime = _timing.CurTime + RoundMaxTime; + _chatManager.DispatchServerAnnouncement(Loc.GetString("There are traitors on the station! Find them, and kill them!")); bool Predicate(IPlayerSession session) => session.ContentData()?.Mind?.HasRole() ?? false; EntitySystem.Get().PlayGlobal("/Audio/Misc/tatoralert.ogg", AudioParams.Default, Predicate); + EntitySystem.Get().EndTime = _endTime; EntitySystem.Get().AccessType = DoorSystem.AccessTypes.AllowAllNoExternal; Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token); - - _gameTicker.OnRunLevelChanged += RunLevelChanged; } public override void Removed() { base.Removed(); - _gameTicker.OnRunLevelChanged -= RunLevelChanged; - EntitySystem.Get().AccessType = DoorSystem.AccessTypes.Id; + EntitySystem.Get().EndTime = null; _checkTimerCancel.Cancel(); } - public void RestartTimer() - { - _maxTimerCancel.Cancel(); - _maxTimerCancel = new CancellationTokenSource(); - Timer.Spawn(RoundMaxTime, TimerFired, _maxTimerCancel.Token); - } - - public void StopTimer() - { - _maxTimerCancel.Cancel(); - } - - private void TimerFired() + private void Timeout() { _chatManager.DispatchServerAnnouncement(Loc.GetString("Time has run out for the traitors!")); EndRound(Victory.Innocents); } - private void RunLevelChanged(GameRunLevelChangedEventArgs args) - { - switch (args.NewRunLevel) - { - case GameRunLevel.InRound: - RestartTimer(); - break; - case GameRunLevel.PreRoundLobby: - case GameRunLevel.PostRound: - StopTimer(); - break; - } - } - private void CheckWinConditions() { if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin)) @@ -145,6 +122,11 @@ namespace Content.Server.GameTicking.GameRules _chatManager.DispatchServerAnnouncement(Loc.GetString("The innocents are dead! The traitors win.")); EndRound(Victory.Traitors); } + else if (_timing.CurTime > _endTime) + { + _chatManager.DispatchServerAnnouncement(Loc.GetString("Time has run out for the traitors!")); + EndRound(Victory.Innocents); + } } private enum Victory diff --git a/Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs b/Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs new file mode 100644 index 0000000000..c540b0a4a5 --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystemMessages/SuspicionMessages.cs @@ -0,0 +1,15 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.EntitySystemMessages +{ + public static class SuspicionMessages + { + [Serializable, NetSerializable] + public sealed class SetSuspicionEndTimerMessage : EntitySystemMessage + { + public TimeSpan? EndTime; + } + } +}