* Implement APC overloading * Add power test * Review * Some more reviews * Show map coordinates for test failures * Widen column 2 * Reduce singularity beacon power consumption * Try to get grid coordinates
148 lines
5.6 KiB
C#
148 lines
5.6 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Content.Server.GameTicking;
|
|
using Content.Server.Maps;
|
|
using Content.Server.Power.Components;
|
|
using Content.Server.Power.NodeGroups;
|
|
using Content.Server.Power.Pow3r;
|
|
using Content.Shared.Power.Components;
|
|
using Content.Shared.NodeContainer;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.EntitySerialization;
|
|
|
|
namespace Content.IntegrationTests.Tests.Power;
|
|
|
|
public sealed class StationPowerTests
|
|
{
|
|
/// <summary>
|
|
/// How long the station should be able to survive on stored power if nothing is changed from round start.
|
|
/// </summary>
|
|
private const float MinimumPowerDurationSeconds = 10 * 60;
|
|
|
|
private static readonly string[] GameMaps =
|
|
[
|
|
"Amber",
|
|
"Bagel",
|
|
"Box",
|
|
"Elkridge",
|
|
"Fland",
|
|
"Marathon",
|
|
"Oasis",
|
|
"Packed",
|
|
"Plasma",
|
|
"Reach",
|
|
"Exo",
|
|
];
|
|
|
|
[Explicit]
|
|
[Test, TestCaseSource(nameof(GameMaps))]
|
|
public async Task TestStationStartingPowerWindow(string mapProtoId)
|
|
{
|
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
|
{
|
|
Dirty = true,
|
|
});
|
|
var server = pair.Server;
|
|
|
|
var entMan = server.EntMan;
|
|
var protoMan = server.ProtoMan;
|
|
var ticker = entMan.System<GameTicker>();
|
|
|
|
// Load the map
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
Assert.That(protoMan.TryIndex<GameMapPrototype>(mapProtoId, out var mapProto));
|
|
var opts = DeserializationOptions.Default with { InitializeMaps = true };
|
|
ticker.LoadGameMap(mapProto, out var mapId, opts);
|
|
});
|
|
|
|
// Let powernet set up
|
|
await server.WaitRunTicks(1);
|
|
|
|
// Find the power network with the greatest stored charge in its batteries.
|
|
// This keeps backup SMESes out of the calculation.
|
|
var networks = new Dictionary<PowerState.Network, float>();
|
|
var batteryQuery = entMan.EntityQueryEnumerator<PowerNetworkBatteryComponent, BatteryComponent, NodeContainerComponent>();
|
|
while (batteryQuery.MoveNext(out var uid, out _, out var battery, out var nodeContainer))
|
|
{
|
|
if (!nodeContainer.Nodes.TryGetValue("output", out var node))
|
|
continue;
|
|
if (node.NodeGroup is not IBasePowerNet group)
|
|
continue;
|
|
networks.TryGetValue(group.NetworkNode, out var charge);
|
|
networks[group.NetworkNode] = charge + battery.CurrentCharge;
|
|
}
|
|
var totalStartingCharge = networks.MaxBy(n => n.Value).Value;
|
|
|
|
// Find how much charge all the APC-connected devices would like to use per second.
|
|
var totalAPCLoad = 0f;
|
|
var receiverQuery = entMan.EntityQueryEnumerator<ApcPowerReceiverComponent>();
|
|
while (receiverQuery.MoveNext(out _, out var receiver))
|
|
{
|
|
totalAPCLoad += receiver.Load;
|
|
}
|
|
|
|
var estimatedDuration = totalStartingCharge / totalAPCLoad;
|
|
var requiredStoredPower = totalAPCLoad * MinimumPowerDurationSeconds;
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(estimatedDuration, Is.GreaterThanOrEqualTo(MinimumPowerDurationSeconds),
|
|
$"Initial power for {mapProtoId} does not last long enough! Needs at least {MinimumPowerDurationSeconds}s " +
|
|
$"but estimated to last only {estimatedDuration}s!");
|
|
Assert.That(totalStartingCharge, Is.GreaterThanOrEqualTo(requiredStoredPower),
|
|
$"Needs at least {requiredStoredPower - totalStartingCharge} more stored power!");
|
|
});
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
|
|
[Test, TestCaseSource(nameof(GameMaps))]
|
|
public async Task TestApcLoad(string mapProtoId)
|
|
{
|
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
|
{
|
|
Dirty = true,
|
|
});
|
|
var server = pair.Server;
|
|
|
|
var entMan = server.EntMan;
|
|
var protoMan = server.ProtoMan;
|
|
var ticker = entMan.System<GameTicker>();
|
|
var xform = entMan.System<TransformSystem>();
|
|
|
|
// Load the map
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
Assert.That(protoMan.TryIndex<GameMapPrototype>(mapProtoId, out var mapProto));
|
|
var opts = DeserializationOptions.Default with { InitializeMaps = true };
|
|
ticker.LoadGameMap(mapProto, out var mapId, opts);
|
|
});
|
|
|
|
// Wait long enough for power to ramp up, but before anything can trip
|
|
await pair.RunSeconds(2);
|
|
|
|
// Check that no APCs start overloaded
|
|
var apcQuery = entMan.EntityQueryEnumerator<ApcComponent, PowerNetworkBatteryComponent>();
|
|
Assert.Multiple(() =>
|
|
{
|
|
while (apcQuery.MoveNext(out var uid, out var apc, out var battery))
|
|
{
|
|
// Uncomment the following line to log starting APC load to the console
|
|
//Console.WriteLine($"ApcLoad:{mapProtoId}:{uid}:{battery.CurrentSupply}");
|
|
if (xform.TryGetMapOrGridCoordinates(uid, out var coord))
|
|
{
|
|
Assert.That(apc.MaxLoad, Is.GreaterThanOrEqualTo(battery.CurrentSupply),
|
|
$"APC {uid} on {mapProtoId} ({coord.Value.X}, {coord.Value.Y}) is overloaded {battery.CurrentSupply} / {apc.MaxLoad}");
|
|
}
|
|
else
|
|
{
|
|
Assert.That(apc.MaxLoad, Is.GreaterThanOrEqualTo(battery.CurrentSupply),
|
|
$"APC {uid} on {mapProtoId} is overloaded {battery.CurrentSupply} / {apc.MaxLoad}");
|
|
}
|
|
}
|
|
});
|
|
|
|
await pair.CleanReturnAsync();
|
|
}
|
|
}
|