diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index 0b4dd907621..fa7f9d45422 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -1,19 +1,17 @@ #nullable enable using System; -using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; +using Content.Server.Mind; using Content.Server.Warps; using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; -using Robust.Shared.Enums; using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; using Robust.Shared.Map; -using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; @@ -58,15 +56,20 @@ public void Setup() _pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false); _sys = _entMan.System(); + SetupAsync().Wait(); + } + + private async Task SetupAsync() + { // Spawn the map _pair.Server.ResolveDependency().SetSeed(42); - _pair.Server.WaitPost(() => + await _pair.Server.WaitPost(() => { var success = _entMan.System().TryLoad(_mapId, Map, out _); if (!success) throw new Exception("Map load failed"); _pair.Server.MapMan.DoMapInitialize(_mapId); - }).Wait(); + }); // Get list of ghost warp positions _spawns = _entMan.AllComponentsList() @@ -76,17 +79,19 @@ public void Setup() Array.Resize(ref _players, PlayerCount); - // Spawn "Players". - _pair.Server.WaitPost(() => + // Spawn "Players" + _players = await _pair.Server.AddDummySessions(PlayerCount); + await _pair.Server.WaitPost(() => { + var mind = _pair.Server.System(); for (var i = 0; i < PlayerCount; i++) { var pos = _spawns[i % _spawns.Length]; var uid =_entMan.SpawnEntity("MobHuman", pos); _pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear"); - _players[i] = new DummySession{AttachedEntity = uid}; + mind.ControlMob(_players[i].UserId, uid); } - }).Wait(); + }); // Repeatedly move players around so that they "explore" the map and see lots of entities. // This will populate their PVS data with out-of-view entities. @@ -168,20 +173,4 @@ public void CycleTick() }).Wait(); _pair.Server.PvsTick(_players); } - - private sealed class DummySession : ICommonSession - { - public SessionStatus Status => SessionStatus.InGame; - public EntityUid? AttachedEntity {get; set; } - public NetUserId UserId => default; - public string Name => string.Empty; - public short Ping => default; - public INetChannel Channel { get; set; } = default!; - public LoginType AuthType => default; - public HashSet ViewSubscriptions { get; } = new(); - public DateTime ConnectedTime { get; set; } - public SessionState State => default!; - public SessionData Data => default!; - public bool ClientSide { get; set; } - } } diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index cc83232a066..1e8306a02c6 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -7,6 +7,8 @@ using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.UnitTesting; @@ -136,10 +138,15 @@ public List GetPrototypesWithComponent( /// Helper method for enabling or disabling a antag role /// public async Task SetAntagPref(ProtoId id, bool value) + { + await SetAntagPref(Client.User!.Value, id, value); + } + + public async Task SetAntagPref(NetUserId user, ProtoId id, bool value) { var prefMan = Server.ResolveDependency(); - var prefs = prefMan.GetPreferences(Client.User!.Value); + var prefs = prefMan.GetPreferences(user); // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; @@ -148,11 +155,11 @@ public async Task SetAntagPref(ProtoId id, bool value) await Server.WaitPost(() => { - prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); + prefMan.SetProfile(user, 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 newPrefs = prefMan.GetPreferences(user); var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); } diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index 8d1e425553b..4cae4affc4d 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -34,6 +34,8 @@ private async Task OnDirtyDispose() private async Task OnCleanDispose() { + await Server.RemoveAllDummySessions(); + if (TestMap != null) { await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid)); diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 7ee5dbd55c7..0b18c382390 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -37,7 +37,10 @@ public void Deconstruct( client = Client; } - public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault(); + public ICommonSession? Player => Client.User == null + ? null + : Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User.Value); + public ContentPlayerData? PlayerData => Player?.Data.ContentData(); public PoolTestLogHandler ServerLogHandler { get; private set; } = default!; diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 2360ea0bf4f..f56baba3426 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -57,8 +57,17 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(client.AttachedEntity, Is.Null); Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + // Add several dummy players + var dummies = await pair.Server.AddDummySessions(3); + await pair.RunTicksSync(5); + // Opt into the nukies role. await pair.SetAntagPref("NukeopsCommander", true); + await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", true); + + // Initially, the players have no attached entities + Assert.That(pair.Player?.AttachedEntity, Is.Null); + Assert.That(dummies.All(x => x.AttachedEntity == null)); // There are no grids or maps Assert.That(entMan.Count(), Is.Zero); @@ -75,17 +84,20 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(entMan.Count(), Is.Zero); // Ready up and start nukeops - await pair.WaitClientCommand("toggleready True"); - Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + ticker.ToggleReadyAll(true); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay)); await pair.WaitCommand("forcepreset Nukeops"); 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(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame)); Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + + var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray(); var player = pair.Player!.AttachedEntity!.Value; Assert.That(entMan.EntityExists(player)); + Assert.That(dummyEnts.All(e => entMan.EntityExists(e))); // Maps now exist Assert.That(entMan.Count(), Is.GreaterThan(0)); @@ -96,8 +108,8 @@ public async Task TryStopNukeOpsFromConstantlyFailing() // And we now have nukie related components Assert.That(entMan.Count(), Is.EqualTo(1)); - Assert.That(entMan.Count(), Is.EqualTo(1)); - Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(2)); + Assert.That(entMan.Count(), Is.EqualTo(2)); Assert.That(entMan.Count(), Is.EqualTo(1)); // The player entity should be the nukie commander @@ -107,11 +119,36 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(roleSys.MindHasRole(mind)); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); - var roles = roleSys.MindGetAllRoles(mind); var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + // The second dummy player should be a medic + var dummyMind = mindSys.GetMind(dummyEnts[1])!.Value; + Assert.That(entMan.HasComponent(dummyEnts[1])); + Assert.That(roleSys.MindIsAntagonist(dummyMind)); + Assert.That(roleSys.MindHasRole(dummyMind)); + Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); + Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); + roles = roleSys.MindGetAllRoles(dummyMind); + cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent); + Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + + // The other two players should have just spawned in as normal. + CheckDummy(0); + CheckDummy(2); + void CheckDummy(int i) + { + var ent = dummyEnts[i]; + var mind = mindSys.GetMind(ent)!.Value; + Assert.That(entMan.HasComponent(ent), Is.False); + Assert.That(roleSys.MindIsAntagonist(mind), Is.False); + Assert.That(roleSys.MindHasRole(mind), Is.False); + Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); + Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); + Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False); + } + // The game rule exists, and all the stations/shuttles/maps are properly initialized var rule = entMan.AllComponents().Single().Component; var gridsRule = entMan.AllComponents().Single().Component; @@ -178,7 +215,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing() // While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be // likely to have in the future. But nukies should probably have at least 3 slots with something in them. var enumerator = invSys.GetSlotEnumerator(player); - int total = 0; + var total = 0; while (enumerator.NextItem(out _)) { total++; @@ -200,6 +237,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing() ticker.SetGamePreset((GamePresetPrototype?)null); await pair.SetAntagPref("NukeopsCommander", false); + await pair.SetAntagPref(dummies[1].UserId, "NukeopsMedic", false); await pair.CleanReturnAsync(); } }