diff --git a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs index e5b7714e95..8787de8502 100644 --- a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -38,7 +38,7 @@ namespace Content.IntegrationTests.Tests.GameRules { Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); - sGameTicker.AddGameRule(IoCManager.Resolve().Index(maxTimeMaxTimeRestartRuleSystem.Prototype)); + sGameTicker.StartGameRule(IoCManager.Resolve().Index(maxTimeMaxTimeRestartRuleSystem.Prototype)); maxTimeMaxTimeRestartRuleSystem.RoundMaxTime = TimeSpan.FromSeconds(3); sGameTicker.StartRound(); diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index c40ffedc31..5ad06d1c4b 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -85,6 +85,14 @@ namespace Content.Server.GameTicking return true; } + private void StartGamePresetRules() + { + foreach (var rule in _addedGameRules) + { + StartGameRule(rule); + } + } + public bool OnGhostAttempt(Mind.Mind mind, bool canReturnGlobal) { var handleEv = new GhostAttemptHandleEvent(mind, canReturnGlobal); diff --git a/Content.Server/GameTicking/GameTicker.GameRule.cs b/Content.Server/GameTicking/GameTicker.GameRule.cs index 83b962fa92..993a84b5d4 100644 --- a/Content.Server/GameTicking/GameTicker.GameRule.cs +++ b/Content.Server/GameTicking/GameTicker.GameRule.cs @@ -8,35 +8,80 @@ namespace Content.Server.GameTicking public partial class GameTicker { // No duplicates. - [ViewVariables] private readonly HashSet _gameRules = new(); - public IEnumerable ActiveGameRules => _gameRules; + [ViewVariables] private readonly HashSet _addedGameRules = new(); + public IEnumerable AddedGameRules => _addedGameRules; + [ViewVariables] private readonly HashSet _startedGameRules = new(); + public IEnumerable StartedGameRules => _startedGameRules; + + /// + /// Game rules can be 'started' separately from being added. 'Starting' them usually + /// happens at round start while they can be added and removed before then. + /// + public void StartGameRule(GameRulePrototype rule) + { + if (!GameRuleAdded(rule)) + AddGameRule(rule); + + if (_startedGameRules.Add(rule)) + RaiseLocalEvent(new GameRuleStartedEvent(rule)); + } + + /// + /// Ends a game rule. + /// This always includes removing it (removing it from added game rules) so that behavior + /// is not separate from this. + /// + /// + public void EndGameRule(GameRulePrototype rule) + { + if (!GameRuleAdded(rule)) + return; + + _addedGameRules.Remove(rule); + + if (GameRuleStarted(rule)) + _startedGameRules.Remove(rule); + RaiseLocalEvent(new GameRuleEndedEvent(rule)); + } + + /// + /// Adds a game rule to the list, but does not + /// start it yet, instead waiting until roundstart. + /// public bool AddGameRule(GameRulePrototype rule) { - if (!_gameRules.Add(rule)) + if (!_addedGameRules.Add(rule)) return false; RaiseLocalEvent(new GameRuleAddedEvent(rule)); return true; } - public bool RemoveGameRule(GameRulePrototype rule) + public bool GameRuleAdded(GameRulePrototype rule) { - if (!_gameRules.Remove(rule)) - return false; - - RaiseLocalEvent(new GameRuleRemovedEvent(rule)); - return true; + return _addedGameRules.Contains(rule); } - public bool HasGameRule(GameRulePrototype rule) + public bool GameRuleAdded(string rule) { - return _gameRules.Contains(rule); + foreach (var ruleProto in _addedGameRules) + { + if (ruleProto.ID.Equals(rule)) + return true; + } + + return false; } - public bool HasGameRule(string rule) + public bool GameRuleStarted(GameRulePrototype rule) { - foreach (var ruleProto in _gameRules) + return _startedGameRules.Contains(rule); + } + + public bool GameRuleStarted(string rule) + { + foreach (var ruleProto in _startedGameRules) { if (ruleProto.ID.Equals(rule)) return true; @@ -47,14 +92,17 @@ namespace Content.Server.GameTicking public void ClearGameRules() { - foreach (var rule in _gameRules.ToArray()) + foreach (var rule in _addedGameRules.ToArray()) { - RemoveGameRule(rule); + EndGameRule(rule); } } } - public class GameRuleAddedEvent + /// + /// Raised broadcast when a game rule is selected, but not started yet. + /// + public sealed class GameRuleAddedEvent { public GameRulePrototype Rule { get; } @@ -64,11 +112,21 @@ namespace Content.Server.GameTicking } } - public class GameRuleRemovedEvent + public sealed class GameRuleStartedEvent { public GameRulePrototype Rule { get; } - public GameRuleRemovedEvent(GameRulePrototype rule) + public GameRuleStartedEvent(GameRulePrototype rule) + { + Rule = rule; + } + } + + public sealed class GameRuleEndedEvent + { + public GameRulePrototype Rule { get; } + + public GameRuleEndedEvent(GameRulePrototype rule) { Rule = rule; } diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 2d37e8b680..fcd27ce8bf 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -71,6 +71,8 @@ namespace Content.Server.GameTicking private void PreRoundSetup() { + AddGamePresetRules(); + DefaultMap = _mapManager.CreateMap(); _pauseManager.AddUninitializedMap(DefaultMap); _startingRound = false; @@ -188,7 +190,7 @@ namespace Content.Server.GameTicking SendServerMessage(Loc.GetString("game-ticker-start-round")); - AddGamePresetRules(); + StartGamePresetRules(); List readyPlayers; if (LobbyEnabled) @@ -246,6 +248,7 @@ namespace Content.Server.GameTicking ClearGameRules(); SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset)); AddGamePresetRules(); + StartGamePresetRules(); startAttempt.Uncancel(); RaiseLocalEvent(startAttempt); @@ -508,7 +511,7 @@ namespace Content.Server.GameTicking // Clear up any game rules. ClearGameRules(); - _gameRules.Clear(); + _addedGameRules.Clear(); // Round restart cleanup event, so entity systems can reset. var ev = new RoundRestartCleanupEvent(); diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs index 20d12fd682..b8051ed0bd 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -35,14 +35,14 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem SubscribeLocalEvent(OnHealthChanged); } - public override void Added() + public override void Started() { _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement")); _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; } - public override void Removed() + public override void Ended() { _deadCheckTimer = null; _restartTimer = null; diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.cs index 91d42bd78c..5246d63430 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.cs @@ -27,8 +27,9 @@ public abstract class GameRuleSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnGameRuleAdded); - SubscribeLocalEvent(OnGameRuleRemoved); + SubscribeLocalEvent(OnGameRuleStarted); + SubscribeLocalEvent(OnGameRuleEnded); } private void OnGameRuleAdded(GameRuleAddedEvent ev) @@ -37,25 +38,32 @@ public abstract class GameRuleSystem : EntitySystem return; Enabled = true; - Added(); } - private void OnGameRuleRemoved(GameRuleRemovedEvent ev) + private void OnGameRuleStarted(GameRuleStartedEvent ev) + { + if (ev.Rule.ID != Prototype) + return; + + Started(); + } + + private void OnGameRuleEnded(GameRuleEndedEvent ev) { if (ev.Rule.ID != Prototype) return; Enabled = false; - Removed(); + Ended(); } /// - /// Called when the game rule has been added and this system has been enabled. + /// Called when the game rule has been started.. /// - public abstract void Added(); + public abstract void Started(); /// - /// Called when the game rule has been removed and this system has been disabled. + /// Called when the game rule has ended.. /// - public abstract void Removed(); + public abstract void Ended(); } diff --git a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs index c903558d88..2859847417 100644 --- a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs @@ -28,12 +28,12 @@ public class InactivityTimeRestartRuleSystem : GameRuleSystem SubscribeLocalEvent(RunLevelChanged); } - public override void Added() + public override void Started() { _playerManager.PlayerStatusChanged += PlayerStatusChanged; } - public override void Removed() + public override void Ended() { _playerManager.PlayerStatusChanged -= PlayerStatusChanged; diff --git a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs index 3f3d30b46e..30a60633cb 100644 --- a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs @@ -26,13 +26,13 @@ public sealed class MaxTimeRestartRuleSystem : GameRuleSystem SubscribeLocalEvent(RunLevelChanged); } - public override void Added() + public override void Started() { if(GameTicker.RunLevel == GameRunLevel.InRound) RestartTimer(); } - public override void Removed() + public override void Ended() { RoundMaxTime = TimeSpan.FromMinutes(5); RoundEndDelay = TimeSpan.FromMinutes(10); diff --git a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs index 1b9c5af5da..0863fc1b61 100644 --- a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs @@ -9,12 +9,12 @@ public class SandboxRuleSystem : GameRuleSystem public override string Prototype => "Sandbox"; - public override void Added() + public override void Started() { _sandbox.IsSandboxEnabled = true; } - public override void Removed() + public override void Ended() { _sandbox.IsSandboxEnabled = false; } diff --git a/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs b/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs index 5e777555f7..cf939eb755 100644 --- a/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SuspicionRuleSystem.cs @@ -202,7 +202,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem } } - public override void Added() + public override void Started() { _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; @@ -223,7 +223,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token); } - public override void Removed() + public override void Ended() { _doorSystem.AccessType = SharedDoorSystem.AccessTypes.Id; EndTime = null; diff --git a/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs index 146c7e49bb..86ac08df94 100644 --- a/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorDeathMatchRuleSystem.cs @@ -202,14 +202,14 @@ public class TraitorDeathMatchRuleSystem : GameRuleSystem ev.AddLine(string.Join('\n', lines)); } - public override void Added() + public override void Started() { _restarter.RoundMaxTime = TimeSpan.FromMinutes(30); _restarter.RestartTimer(); _safeToEndRound = true; } - public override void Removed() + public override void Ended() { } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index c70d451b0f..30b185fa63 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -53,13 +53,13 @@ public class TraitorRuleSystem : GameRuleSystem SubscribeLocalEvent(OnRoundEndText); } - public override void Added() + public override void Started() { // This seems silly, but I'll leave it. _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-traitor-added-announcement")); } - public override void Removed() + public override void Ended() { _traitors.Clear(); } diff --git a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs index ec36442feb..613b26cedf 100644 --- a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs @@ -1,10 +1,6 @@ using Content.Server.GameTicking; using Content.Server.Spawners.Components; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Maths; using Robust.Shared.Random; namespace Content.Server.Spawners.EntitySystems @@ -19,7 +15,7 @@ namespace Content.Server.Spawners.EntitySystems { base.Initialize(); - SubscribeLocalEvent(OnRuleAdded); + SubscribeLocalEvent(OnRuleStarted); SubscribeLocalEvent(OnCondSpawnMapInit); SubscribeLocalEvent(OnRandSpawnMapInit); } @@ -35,15 +31,15 @@ namespace Content.Server.Spawners.EntitySystems EntityManager.QueueDeleteEntity(uid); } - private void OnRuleAdded(GameRuleAddedEvent args) + private void OnRuleStarted(GameRuleStartedEvent args) { foreach (var spawner in EntityManager.EntityQuery()) { - RuleAdded(spawner, args); + RuleStarted(spawner, args); } } - public void RuleAdded(ConditionalSpawnerComponent component, GameRuleAddedEvent obj) + public void RuleStarted(ConditionalSpawnerComponent component, GameRuleStartedEvent obj) { if(component.GameRules.Contains(obj.Rule.ID)) Spawn(component); @@ -59,7 +55,7 @@ namespace Content.Server.Spawners.EntitySystems foreach (var rule in component.GameRules) { - if (!_ticker.HasGameRule(rule)) continue; + if (!_ticker.GameRuleStarted(rule)) continue; Spawn(component); return; }