From 7b24700a79511dbc78aef10b1a68f2ea3abf7700 Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Wed, 22 May 2024 07:14:36 +0100 Subject: [PATCH 01/13] fix antag selection being evil --- Content.Server/Antag/AntagSelectionSystem.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 57df82d6fd9bd7..0c748337a37f71 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -325,16 +325,11 @@ public AntagSelectionPlayerPool GetPlayerPool(Entity en { var preferredList = new List(); var fallbackList = new List(); - var unwantedList = new List(); - var invalidList = new List(); foreach (var session in sessions) { if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def)) - { - invalidList.Add(session); continue; - } var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p))) @@ -345,13 +340,9 @@ public AntagSelectionPlayerPool GetPlayerPool(Entity en { fallbackList.Add(session); } - else - { - unwantedList.Add(session); - } } - return new AntagSelectionPlayerPool(new() { preferredList, fallbackList, unwantedList, invalidList }); + return new AntagSelectionPlayerPool(new() { preferredList, fallbackList }); } /// From 0cf2f88cc99f86ea7f881640ccd94f0637cd7f1f Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Wed, 22 May 2024 07:32:17 +0100 Subject: [PATCH 02/13] fix test --- Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 5bada98a3aa518..a83f45b1682e77 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -50,6 +50,9 @@ public async Task TryStopNukeOpsFromConstantlyFailing() var invSys = server.System(); var factionSys = server.System(); + // test urist is a noob let him be nukie + server.CfgMan.SetCVar(CCVars.GameRoleTimers, false); + Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); server.CfgMan.SetCVar(CCVars.GridFill, true); From 12963df1f16247c2d0c6bc441a036998b5e5a3f5 Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Thu, 23 May 2024 07:24:01 +0100 Subject: [PATCH 03/13] untroll the other tests --- Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index a83f45b1682e77..d660bc5c644e87 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -51,6 +51,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing() var factionSys = server.System(); // test urist is a noob let him be nukie + Assert.That(server.CfgMan.GetCVar(CCVars.GameRoleTimers), Is.True); server.CfgMan.SetCVar(CCVars.GameRoleTimers, false); Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); @@ -201,6 +202,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing() ticker.SetGamePreset((GamePresetPrototype?)null); server.CfgMan.SetCVar(CCVars.GridFill, false); + server.CfgMan.SetCVar(CCVars.GameRoleTimers, true); await pair.CleanReturnAsync(); } } From 0ee3eae8a535a539fed6079cd469c3c52aa743ba Mon Sep 17 00:00:00 2001 From: deltanedas <@deltanedas:kde.org> Date: Thu, 23 May 2024 08:52:02 +0100 Subject: [PATCH 04/13] remove role timer troll --- Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index d660bc5c644e87..5bada98a3aa518 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -50,10 +50,6 @@ public async Task TryStopNukeOpsFromConstantlyFailing() var invSys = server.System(); var factionSys = server.System(); - // test urist is a noob let him be nukie - Assert.That(server.CfgMan.GetCVar(CCVars.GameRoleTimers), Is.True); - server.CfgMan.SetCVar(CCVars.GameRoleTimers, false); - Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); server.CfgMan.SetCVar(CCVars.GridFill, true); @@ -202,7 +198,6 @@ public async Task TryStopNukeOpsFromConstantlyFailing() ticker.SetGamePreset((GamePresetPrototype?)null); server.CfgMan.SetCVar(CCVars.GridFill, false); - server.CfgMan.SetCVar(CCVars.GameRoleTimers, true); await pair.CleanReturnAsync(); } } From 34ea5b352ae6c536a63be97e8fd705e5fd82f4bb Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 18:11:34 +1200 Subject: [PATCH 05/13] Allow tests to modify antag preferences --- .../Pair/TestPair.Helpers.cs | 28 +++++++++++++++++ .../Tests/GameRules/NukeOpsTest.cs | 4 +++ .../Managers/IServerPreferencesManager.cs | 2 ++ .../Managers/ServerPreferencesManager.cs | 30 +++++++++---------- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 0ea6d3e2dcc079..cc83232a066374 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Preferences.Managers; +using Content.Shared.Preferences; +using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -128,4 +131,29 @@ public async Task WaitClientCommand(string cmd, int numTicks = 10) return list; } + + /// + /// Helper method for enabling or disabling a antag role + /// + public async Task SetAntagPref(ProtoId id, bool value) + { + var prefMan = Server.ResolveDependency(); + + var prefs = prefMan.GetPreferences(Client.User!.Value); + // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? + var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; + + Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value)); + var newProfile = profile.WithAntagPreference(id, value); + + await Server.WaitPost(() => + { + prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); + }); + + // And why the fuck does it always create a new preference and profile object instead of just reusing them? + var newPrefs = prefMan.GetPreferences(Client.User.Value); + var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; + Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); + } } diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 5bada98a3aa518..3a77834b72596a 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -58,6 +58,9 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(client.AttachedEntity, Is.Null); Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + // Opt into the nukies role. + await pair.SetAntagPref("NukeopsCommander", true); + // There are no grids or maps Assert.That(entMan.Count(), Is.Zero); Assert.That(entMan.Count(), Is.Zero); @@ -198,6 +201,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing() ticker.SetGamePreset((GamePresetPrototype?)null); server.CfgMan.SetCVar(CCVars.GridFill, false); + await pair.SetAntagPref("NukeopsCommander", false); await pair.CleanReturnAsync(); } } diff --git a/Content.Server/Preferences/Managers/IServerPreferencesManager.cs b/Content.Server/Preferences/Managers/IServerPreferencesManager.cs index 63c267f154acc0..cdb53bb4be77b0 100644 --- a/Content.Server/Preferences/Managers/IServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/IServerPreferencesManager.cs @@ -20,5 +20,7 @@ public interface IServerPreferencesManager PlayerPreferences? GetPreferencesOrNull(NetUserId? userId); IEnumerable> GetSelectedProfilesForPlayers(List userIds); bool HavePreferencesLoaded(ICommonSession session); + + Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile); } } diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index 1aad61715bb76b..e32af589e958ac 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -29,11 +29,14 @@ public sealed class ServerPreferencesManager : IServerPreferencesManager [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!; + [Dependency] private readonly ILogManager _log = default!; // Cache player prefs on the server so we don't need as much async hell related to them. private readonly Dictionary _cachedPlayerPrefs = new(); + private ISawmill _sawmill = default!; + private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots); public void Init() @@ -42,6 +45,7 @@ public void Init() _netManager.RegisterNetMessage(HandleSelectCharacterMessage); _netManager.RegisterNetMessage(HandleUpdateCharacterMessage); _netManager.RegisterNetMessage(HandleDeleteCharacterMessage); + _sawmill = _log.GetSawmill("prefs"); } private async void HandleSelectCharacterMessage(MsgSelectCharacter message) @@ -78,27 +82,25 @@ private async void HandleSelectCharacterMessage(MsgSelectCharacter message) private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) { - var slot = message.Slot; - var profile = message.Profile; var userId = message.MsgChannel.UserId; - if (profile == null) - { - Logger.WarningS("prefs", - $"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {slot}."); - return; - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (message.Profile == null) + _sawmill.Error($"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {message.Slot}."); + else + await SetProfile(userId, message.Slot, message.Profile); + } + public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile) + { if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded) { - Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded."); + _sawmill.Error($"Tried to modify user {userId} preferences before they loaded."); return; } if (slot < 0 || slot >= MaxCharacterSlots) - { return; - } var curPrefs = prefsData.Prefs!; var session = _playerManager.GetSessionById(userId); @@ -112,10 +114,8 @@ private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor); - if (ShouldStorePrefs(message.MsgChannel.AuthType)) - { - await _db.SaveCharacterSlotAsync(message.MsgChannel.UserId, message.Profile, message.Slot); - } + if (ShouldStorePrefs(session.Channel.AuthType)) + await _db.SaveCharacterSlotAsync(userId, profile, slot); } private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message) From 0781d5a799f40107cae2aeb7e48063f08e1c30f7 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 18:13:17 +1200 Subject: [PATCH 06/13] Fix antag selection --- Content.Server/Antag/AntagSelectionSystem.cs | 11 ++++++++--- Content.Shared/Roles/Jobs/SharedJobSystem.cs | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 0c748337a37f71..d25e6e32dc868e 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -353,14 +353,18 @@ public bool IsSessionValid(Entity ent, ICommonSession? if (session == null) return true; - mind ??= session.GetMind(); - if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) return false; if (ent.Comp.SelectedSessions.Contains(session)) return false; + mind ??= session.GetMind(); + + // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. + if (mind == null) + return true; + //todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds) switch (def.MultiAntagSetting) @@ -391,8 +395,9 @@ public bool IsSessionValid(Entity ent, ICommonSession? /// private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) { + // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. if (entity == null) - return false; + return true; if (HasComp(entity)) return false; diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 04ac45c4c5f4d9..fcf76052785e04 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -146,8 +146,10 @@ public string MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId) public bool CanBeAntag(ICommonSession player) { + // If the player does not have any mind associated with them (e.g., has not spawned in or is in the lobby), then + // they are eligible to be given an antag role/entity. if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) - return false; + return true; if (!MindTryGetJob(mindId, out _, out var prototype)) return true; From 2df3926bbf59453c0ab6bccbb861de33000ab8c7 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 18:13:28 +1200 Subject: [PATCH 07/13] Misc test fixes --- Content.IntegrationTests/PoolManager.cs | 4 ++-- .../Tests/Interaction/Click/InteractionSystemTests.cs | 1 - Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 25e6c7ef26f5bc..3b49ffcf847757 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -65,11 +65,11 @@ public static partial class PoolManager options.BeforeStart += () => { + // Server-only systems (i.e., systems that subscribe to events with server-only components) var entSysMan = IoCManager.Resolve(); - entSysMan.LoadExtraSystemType(); - entSysMan.LoadExtraSystemType(); entSysMan.LoadExtraSystemType(); entSysMan.LoadExtraSystemType(); + IoCManager.Resolve().GetSawmill("loc").Level = LogLevel.Error; IoCManager.Resolve() .OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true); diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index 317aa10400b959..4415eddf376d25 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -407,7 +407,6 @@ public async Task InsideContainerInteractionBlockTest() await pair.CleanReturnAsync(); } - [Reflect(false)] public sealed class TestInteractionSystem : EntitySystem { public EntityEventHandler? InteractUsingEvent; diff --git a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs index d5c2a9124dd9b6..40457f54883570 100644 --- a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs +++ b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs @@ -9,7 +9,6 @@ namespace Content.IntegrationTests.Tests [TestOf(typeof(RoundRestartCleanupEvent))] public sealed class ResettingEntitySystemTests { - [Reflect(false)] public sealed class TestRoundRestartCleanupEvent : EntitySystem { public bool HasBeenReset { get; set; } @@ -49,8 +48,6 @@ public async Task ResettingEntitySystemResetTest() system.HasBeenReset = false; - Assert.That(system.HasBeenReset, Is.False); - gameTicker.RestartRound(); Assert.That(system.HasBeenReset); From 3345ade8ef86fce395c73fb10eef69788f23d105 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 18:37:22 +1200 Subject: [PATCH 08/13] Add AntagPreferenceTest --- .../Tests/GameRules/AntagPreferenceTest.cs | 78 +++++++++++++++++++ Content.Server/Antag/AntagSelectionSystem.cs | 2 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs new file mode 100644 index 00000000000000..5f21863f680747 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -0,0 +1,78 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Server.Antag; +using Content.Server.Antag.Components; +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using Robust.Shared.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.IntegrationTests.Tests.GameRules; + +// Once upon a time, players in the lobby weren't ever considered eligible for antag roles. +// Lets not let that happen again. +[TestFixture] +public sealed class AntagPreferenceTest +{ + /// + /// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded. + /// + [Test] + public async Task TestLobbyPlayersValid() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + var server = pair.Server; + var client = pair.Client; + var ticker = server.System(); + var sys = server.System(); + + // 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)); + + EntityUid uid = default; + await server.WaitPost(() => uid = server.EntMan.Spawn("Traitor")); + var rule = new Entity(uid, server.EntMan.GetComponent(uid)); + var def = rule.Comp.Definitions.Single(); + + // IsSessionValid & IsEntityValid are preference agnostic and should always be true for players in the lobby. + // Though maybe that will change in the future, but then GetPlayerPool() needs to be updated to reflect that. + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + + // By default, traitor/antag preferences are disabled, so the pool should be empty. + var sessions = new List{pair.Player!}; + var pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(0)); + + // Opt into the traitor role. + await pair.SetAntagPref("Traitor", true); + + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(1)); + pool.TryPickAndTake(pair.Server.ResolveDependency(), out var picked); + Assert.That(picked, Is.EqualTo(pair.Player)); + Assert.That(sessions.Count, Is.EqualTo(1)); + + // opt back out + await pair.SetAntagPref("Traitor", false); + + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(0)); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index d25e6e32dc868e..dcf32e052718ba 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -393,7 +393,7 @@ public bool IsSessionValid(Entity ent, ICommonSession? /// /// Checks if a given entity (mind/session not included) is valid for a given antagonist. /// - private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) + public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) { // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. if (entity == null) From 5b25629bf90cebf23ff607e731473658eeb786dd Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 19:02:47 +1200 Subject: [PATCH 09/13] Fix lazy mistakes --- .../Tests/GameRules/AntagPreferenceTest.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs index 5f21863f680747..84912734a147cf 100644 --- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -16,9 +16,6 @@ namespace Content.IntegrationTests.Tests.GameRules; [TestFixture] public sealed class AntagPreferenceTest { - /// - /// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded. - /// [Test] public async Task TestLobbyPlayersValid() { From 820e6c2d42db11562af95f519bc28400953fd1df Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 19:11:48 +1200 Subject: [PATCH 10/13] Test cleanup --- Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs index 84912734a147cf..662ea3b97470fa 100644 --- a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -70,6 +70,7 @@ public async Task TestLobbyPlayersValid() pool = sys.GetPlayerPool(rule, sessions, def); Assert.That(pool.Count, Is.EqualTo(0)); + await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); await pair.CleanReturnAsync(); } } From d329aafc1c2539d171366289a23f052b882ce284 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 19:49:59 +1200 Subject: [PATCH 11/13] Try stop players in lobbies from being assigned mid-round antags --- Content.Server/Antag/AntagSelectionSystem.cs | 41 ++++++++----------- .../GameTicking/GameTicker.RoundFlow.cs | 2 +- Content.Shared/Antag/AntagAcceptability.cs | 8 ++++ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index dcf32e052718ba..28b74e483862c2 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -13,6 +13,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Station.Systems; using Content.Shared.Antag; +using Content.Shared.GameTicking; using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Players; @@ -82,10 +83,9 @@ private void OnPlayerSpawning(RulePlayerSpawningEvent args) continue; if (comp.SelectionsComplete) - return; + continue; ChooseAntags((uid, comp), pool); - comp.SelectionsComplete = true; foreach (var session in comp.SelectedSessions) { @@ -103,11 +103,7 @@ private void OnJobsAssigned(RulePlayerJobsAssignedEvent args) if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn) continue; - if (comp.SelectionsComplete) - continue; - - ChooseAntags((uid, comp)); - comp.SelectionsComplete = true; + ChooseAntags((uid, comp), args.Players); } } @@ -161,43 +157,40 @@ protected override void Started(EntityUid uid, AntagSelectionComponent component { base.Started(uid, component, gameRule, args); - if (component.SelectionsComplete) - return; - + // If the round has not yet started, we defer antag selection until roundstart if (GameTicker.RunLevel != GameRunLevel.InRound) return; - if (GameTicker.RunLevel == GameRunLevel.InRound && component.SelectionTime == AntagSelectionTime.PrePlayerSpawn) + if (component.SelectionsComplete) return; - ChooseAntags((uid, component)); - component.SelectionsComplete = true; - } + var players = _playerManager.Sessions + .Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) + .ToList(); - /// - /// Chooses antagonists from the current selection of players - /// - public void ChooseAntags(Entity ent) - { - var sessions = _playerManager.Sessions.ToList(); - ChooseAntags(ent, sessions); + ChooseAntags((uid, component), players); } /// /// Chooses antagonists from the given selection of players /// - public void ChooseAntags(Entity ent, List pool) + public void ChooseAntags(Entity ent, IList pool) { + if (ent.Comp.SelectionsComplete) + return; + foreach (var def in ent.Comp.Definitions) { ChooseAntags(ent, pool, def); } + + ent.Comp.SelectionsComplete = true; } /// /// Chooses antagonists from the given selection of players for the given antag definition. /// - public void ChooseAntags(Entity ent, List pool, AntagSelectionDefinition def) + public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def) { var playerPool = GetPlayerPool(ent, pool, def); var count = GetTargetAntagCount(ent, playerPool, def); @@ -321,7 +314,7 @@ public void MakeAntag(Entity ent, ICommonSession? sessi /// /// Gets an ordered player pool based on player preferences and the antagonist definition. /// - public AntagSelectionPlayerPool GetPlayerPool(Entity ent, List sessions, AntagSelectionDefinition def) + public AntagSelectionPlayerPool GetPlayerPool(Entity ent, IList sessions, AntagSelectionDefinition def) { var preferredList = new List(); var fallbackList = new List(); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index df597e69b2fa5b..6eb42b65c03a49 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -795,7 +795,7 @@ public RulePlayerSpawningEvent(List playerPool, IReadOnlyDiction } /// - /// Event raised after players were assigned jobs by the GameTicker. + /// Event raised after players were assigned jobs by the GameTicker and have been spawned in. /// You can give on-station people special roles by listening to this event. /// public sealed class RulePlayerJobsAssignedEvent diff --git a/Content.Shared/Antag/AntagAcceptability.cs b/Content.Shared/Antag/AntagAcceptability.cs index 02d0b5f58fee93..f56be97503353d 100644 --- a/Content.Shared/Antag/AntagAcceptability.cs +++ b/Content.Shared/Antag/AntagAcceptability.cs @@ -22,6 +22,14 @@ public enum AntagAcceptability public enum AntagSelectionTime : byte { + /// + /// Antag roles are assigned before players are assigned jobs and spawned in. + /// This prevents antag selection from happening if the round is on-going. + /// PrePlayerSpawn, + + /// + /// Antag roles get assigned after players have been assigned jobs and have spawned in. + /// PostPlayerSpawn } From 25bc7dc059efea3b20c1798875b9a5b896f9256f Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 20:04:01 +1200 Subject: [PATCH 12/13] ranting --- Content.Server/Antag/AntagSelectionSystem.API.cs | 5 +++++ Content.Server/Antag/AntagSelectionSystem.cs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index 6acd17a35b273f..dd397d1fcb387d 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -27,6 +27,11 @@ public sealed partial class AntagSelectionSystem if (mindCount >= totalTargetCount) return false; + // TODO ANTAG fix this + // If here are two definitions with 1/10 and 10/10 slots filled, this will always return the second definition + // even though it has already met its target + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA I fucking hate game ticker code. + // It needs to track selected minds for each definition independently. foreach (var def in ent.Comp.Definitions) { var target = GetTargetAntagCount(ent, null, def); diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 28b74e483862c2..85d332018bc6e8 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -25,6 +25,7 @@ using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.Antag; @@ -119,12 +120,18 @@ private void OnSpawnComplete(PlayerSpawnCompleteEvent args) var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var antag, out _)) { + // TODO ANTAG + // what why aasdiuhasdopiuasdfhksad + // stop this insanity please + // probability of antag assignment shouldn't depend on the order in which rules are returned by the query. if (!RobustRandom.Prob(LateJoinRandomChance)) continue; if (!antag.Definitions.Any(p => p.LateJoinAdditional)) continue; + DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn); + if (!TryGetNextAvailableDefinition((uid, antag), out var def)) continue; From d8a0647a609849b32be1484bf684302adfe11a10 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 24 May 2024 20:08:33 +1200 Subject: [PATCH 13/13] I am going insane --- Content.Server/Antag/AntagSelectionSystem.API.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index dd397d1fcb387d..279703c875406c 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -69,6 +69,10 @@ public int GetTargetAntagCount(Entity ent, AntagSelecti /// public int GetTargetAntagCount(Entity ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def) { + // TODO ANTAG + // make pool non-nullable + // Review uses and ensure that people are INTENTIONALLY including players in the lobby if this is a mid-round + // antag selection. var poolSize = pool?.Count ?? _playerManager.Sessions .Count(s => s.State.Status is not SessionStatus.Disconnected and not SessionStatus.Zombie);