Files
tbd-station-14/Content.IntegrationTests/Tests/Station/StationJobsTest.cs
Moony 36181334b5 StationSystem/jobs/partial spawning refactor (#7580)
* Partial work on StationSystem refactor.

* WIP station jobs API.

* forgor to fire off grid events.

* Partial implementation of StationSpawningSystem

* whoops infinite loop.

* Spawners should work now.

* it compiles.

* tfw

* Vestigial code cleanup.

* fix station deletion.

* attempt to make tests go brr

* add latejoin spawnpoints to test maps.

* make sure the station still exists while destructing spawners.

* forgot an exists check.

* destruction order check.

* hopefully fix final test.

* fail-safe radstorm.

* Deep-clean job code further. This is bugged!!!!!

* Fix job bug. (init order moment)

* whooo cleanup

* New job selection algorithm that tries to distribute fairly across stations.

* small nitpicks

* Give the heads their weights to replace the head field.

* make overflow assign take a station list.

* moment

* Fixes and test #1 of many.

* please fix nullspace

* AssignJobs should no longer even consider showing up on a trace.

* add comment.

* Introduce station configs, praying i didn't miss something.

* in one small change stations are now fully serializable.

* Further doc comments.

* whoops.

* Solve bug where assignjobs didn't account for roundstart.

* Fix spawning, improve the API.
Caught an oversight in stationsystem that should've broke everything but didn't, whoops.

* Goodbye JobController.

* minor fix..

* fix test fail, remove debug logs.

* quick serialization fixes.

* fixes..

* sus

* partialing

* Update Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs

Co-authored-by: Kara <lunarautomaton6@gmail.com>

* Use dirtying to avoid rebuilding the list 2,100 times.

* add a bajillion more lines of docs (mostly in AssignJobs so i don't ever forget how it works)

* Update Content.IntegrationTests/Tests/Station/StationJobsTest.cs

Co-authored-by: Kara <lunarautomaton6@gmail.com>

* Add the Mysteriously Missing Captain Check.

* Put maprender back the way it belongs.

* I love addressing reviews.

* Update Content.Server/Station/Systems/StationJobsSystem.cs

Co-authored-by: Kara <lunarautomaton6@gmail.com>

* doc cleanup.

* Fix bureaucratic error, add job slot tests.

* zero cost abstractions when

* cri

* saner error.

* Fix spawning failing certain tests due to gameticker not handling falliability correctly.
Can't fix this until I refactor the rest of spawning code.

* submodule gaming

* Packedenger.

* Documentation consistency.

Co-authored-by: Kara <lunarautomaton6@gmail.com>
2022-05-10 13:43:30 -05:00

216 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Maps;
using Content.Server.Station.Systems;
using Content.Shared.Preferences;
using NUnit.Framework;
using Robust.Server;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.IntegrationTests.Tests.Station;
[TestFixture]
[TestOf(typeof(StationJobsSystem))]
public sealed class StationJobsTest : ContentIntegrationTest
{
private const string Prototypes = @"
- type: gameMap
id: FooStation
minPlayers: 0
mapName: FooStation
mapPath: Maps/Tests/empty.yml
stations:
Station:
mapNameTemplate: FooStation
overflowJobs:
- Assistant
availableJobs:
TMime: [0, -1]
TAssistant: [-1, -1]
TCaptain: [5, 5]
TClown: [5, 6]
- type: job
id: TAssistant
- type: job
id: TMime
weight: 20
- type: job
id: TClown
weight: -10
- type: job
id: TCaptain
weight: 10
- type: job
id: TChaplain
";
private const int StationCount = 100;
private const int CaptainCount = StationCount;
private const int PlayerCount = 2000;
private const int TotalPlayers = PlayerCount + CaptainCount;
[Test]
public async Task AssignJobsTest()
{
var options = new ServerContentIntegrationOption {ExtraPrototypes = Prototypes, Options = new ServerOptions() { LoadContentResources = false }};
var server = StartServer(options);
await server.WaitIdleAsync();
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
var stationSystem = entSysMan.GetEntitySystem<StationSystem>();
List<EntityUid> stations = new();
await server.WaitPost(() =>
{
mapManager.CreateNewMapEntity(MapId.Nullspace);
for (var i = 0; i < StationCount; i++)
{
stations.Add(stationSystem.InitializeNewStation(fooStationProto.Stations["Station"], null, $"Foo {StationCount}"));
}
});
await server.WaitAssertion(() =>
{
var fakePlayers = new Dictionary<NetUserId, HumanoidCharacterProfile>()
.AddJob("TAssistant", JobPriority.Medium, PlayerCount)
.AddPreference("TClown", JobPriority.Low)
.AddPreference("TMime", JobPriority.High)
.WithPlayers(
new Dictionary<NetUserId, HumanoidCharacterProfile>()
.AddJob("TCaptain", JobPriority.High, CaptainCount)
);
var start = new Stopwatch();
start.Start();
var assigned = stationJobs.AssignJobs(fakePlayers, stations);
var time = start.Elapsed.TotalMilliseconds;
Logger.Info($"Took {time} ms to distribute {TotalPlayers} players.");
foreach (var station in stations)
{
var assignedHere = assigned
.Where(x => x.Value.Item2 == station)
.ToDictionary(x => x.Key, x => x.Value);
// Each station should have SOME players.
Assert.That(assignedHere, Is.Not.Empty);
// And it should have at least the minimum players to be considered a "fair" share, as they're all the same.
Assert.That(assignedHere, Has.Count.GreaterThanOrEqualTo(TotalPlayers/stations.Count), "Station has too few players.");
// And it shouldn't have ALL the players, either.
Assert.That(assignedHere, Has.Count.LessThan(TotalPlayers), "Station has too many players.");
// And there should be *A* captain, as there's one player with captain enabled per station.
Assert.That(assignedHere.Where(x => x.Value.Item1 == "TCaptain").ToList(), Has.Count.EqualTo(1));
}
// All clown players have assistant as a higher priority.
Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Not.Contain("TClown"));
// Mime isn't an open job-slot at round-start.
Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Not.Contain("TMime"));
// All players have slots they can fill.
Assert.That(assigned.Values, Has.Count.EqualTo(TotalPlayers), $"Expected {TotalPlayers} players.");
// There must be assistants present.
Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Contain("TAssistant"));
// There must be captains present, too.
Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Contain("TCaptain"));
});
}
[Test]
public async Task AdjustJobsTest()
{
var options = new ServerContentIntegrationOption {ExtraPrototypes = Prototypes, Options = new ServerOptions() { LoadContentResources = false }};
var server = StartServer(options);
await server.WaitIdleAsync();
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
var stationSystem = entSysMan.GetEntitySystem<StationSystem>();
var station = EntityUid.Invalid;
await server.WaitPost(() =>
{
mapManager.CreateNewMapEntity(MapId.Nullspace);
station = stationSystem.InitializeNewStation(fooStationProto.Stations["Station"], null, $"Foo Station");
});
await server.WaitAssertion(() =>
{
// Verify jobs are/are not unlimited.
Assert.Multiple(() =>
{
Assert.That(stationJobs.IsJobUnlimited(station, "TAssistant"), "TAssistant is expected to be unlimited.");
Assert.That(stationJobs.IsJobUnlimited(station, "TMime"), "TMime is expected to be unlimited.");
Assert.That(!stationJobs.IsJobUnlimited(station, "TCaptain"), "TCaptain is expected to not be unlimited.");
Assert.That(!stationJobs.IsJobUnlimited(station, "TClown"), "TClown is expected to not be unlimited.");
});
Assert.Multiple(() =>
{
Assert.That(stationJobs.TrySetJobSlot(station, "TClown", 0), "Could not set TClown to have zero slots.");
Assert.That(stationJobs.TryGetJobSlot(station, "TClown", out var clownSlots), "Could not get the number of TClown slots.");
Assert.That(clownSlots, Is.EqualTo(0));
Assert.That(!stationJobs.TryAdjustJobSlot(station, "TCaptain", -9999), "Was able to adjust TCaptain by -9999 without clamping.");
Assert.That(stationJobs.TryAdjustJobSlot(station, "TCaptain", -9999, false, true), "Could not adjust TCaptain by -9999.");
Assert.That(stationJobs.TryGetJobSlot(station, "TCaptain", out var captainSlots), "Could not get the number of TCaptain slots.");
Assert.That(captainSlots, Is.EqualTo(0));
});
Assert.Multiple(() =>
{
Assert.That(stationJobs.TrySetJobSlot(station, "TChaplain", 10, true), "Could not create 10 TChaplain slots.");
stationJobs.MakeJobUnlimited(station, "TChaplain");
Assert.That(stationJobs.IsJobUnlimited(station, "TChaplain"), "Could not make TChaplain unlimited.");
});
});
}
}
internal static class JobExtensions
{
public static Dictionary<NetUserId, HumanoidCharacterProfile> AddJob(
this Dictionary<NetUserId, HumanoidCharacterProfile> inp, string jobId, JobPriority prio = JobPriority.Medium,
int amount = 1)
{
for (var i = 0; i < amount; i++)
{
inp.Add(new NetUserId(Guid.NewGuid()), HumanoidCharacterProfile.Random().WithJobPriority(jobId, prio));
}
return inp;
}
public static Dictionary<NetUserId, HumanoidCharacterProfile> AddPreference(
this Dictionary<NetUserId, HumanoidCharacterProfile> inp, string jobId, JobPriority prio = JobPriority.Medium)
{
return inp.ToDictionary(x => x.Key, x => x.Value.WithJobPriority(jobId, prio));
}
public static Dictionary<NetUserId, HumanoidCharacterProfile> WithPlayers(
this Dictionary<NetUserId, HumanoidCharacterProfile> inp,
Dictionary<NetUserId, HumanoidCharacterProfile> second)
{
return new[] {inp, second}.SelectMany(x => x).ToDictionary(x => x.Key, x => x.Value);
}
}