diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs new file mode 100644 index 0000000000..1fed226bee --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -0,0 +1,152 @@ +#nullable enable +using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Presets; +using Content.Shared.CCVar; +using Content.Shared.GameTicking; +using Robust.Shared.GameObjects; + +namespace Content.IntegrationTests.Tests.GameRules; + +[TestFixture] +public sealed class FailAndStartPresetTest +{ + [TestPrototypes] + private const string Prototypes = @" +- type: gamePreset + id: TestPreset + alias: + - nukeops + name: Test Preset + description: """" + showInVote: false + rules: + - TestRule + +- type: gamePreset + id: TestPresetTenPlayers + alias: + - nukeops + name: Test Preset 10 players + description: """" + showInVote: false + rules: + - TestRuleTenPlayers + +- type: entity + id: TestRule + parent: BaseGameRule + noSpawn: true + components: + - type: GameRule + minPlayers: 0 + - type: TestRule + +- type: entity + id: TestRuleTenPlayers + parent: BaseGameRule + noSpawn: true + components: + - type: GameRule + minPlayers: 10 + - type: TestRule +"; + + /// + /// Test that a nuke ops gamemode can start after failing to start once. + /// + [Test] + public async Task FailAndStartTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + Dirty = true, + DummyTicker = false, + Connected = true, + InLobby = true + }); + + var server = pair.Server; + var client = pair.Client; + var entMan = server.EntMan; + var ticker = server.System(); + server.System().Run = true; + + Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); + Assert.That(server.CfgMan.GetCVar(CCVars.GameLobbyFallbackEnabled), Is.True); + Assert.That(server.CfgMan.GetCVar(CCVars.GameLobbyDefaultPreset), Is.EqualTo("secret")); + server.CfgMan.SetCVar(CCVars.GridFill, true); + server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, false); + server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "TestPreset"); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + // Try to start nukeops without readying up + await pair.WaitCommand("setgamepreset TestPresetTenPlayers"); + await pair.WaitCommand("startround"); + await pair.RunTicksSync(10); + + // Game should not have started + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + Assert.That(!client.EntMan.EntityExists(client.AttachedEntity)); + var player = pair.Player!.AttachedEntity; + Assert.That(!entMan.EntityExists(player)); + + // Ready up and start nukeops + await pair.WaitClientCommand("toggleready True"); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + await pair.WaitCommand("setgamepreset TestPreset"); + await pair.WaitCommand("startround"); + await pair.RunTicksSync(10); + + // Game should have started + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + player = pair.Player!.AttachedEntity!.Value; + Assert.That(entMan.EntityExists(player)); + + ticker.SetGamePreset((GamePresetPrototype?)null); + server.CfgMan.SetCVar(CCVars.GridFill, false); + server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, true); + server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "secret"); + server.System().Run = false; + await pair.CleanReturnAsync(); + } +} + +public sealed class TestRuleSystem : EntitySystem +{ + public bool Run; + + public override void Initialize() + { + SubscribeLocalEvent(OnRoundStartAttempt); + } + + private void OnRoundStartAttempt(RoundStartAttemptEvent args) + { + if (!Run) + return; + + if (args.Forced || args.Cancelled) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out _, out _, out var gameRule)) + { + var minPlayers = gameRule.MinPlayers; + if (args.Players.Length >= minPlayers) + continue; + + args.Cancel(); + } + } +} + +[RegisterComponent] +public sealed partial class TestRuleComponent : Component;