Merge branch 'master' into offmed-staging
This commit is contained in:
243
Content.IntegrationTests/Tests/Construction/RCDTest.cs
Normal file
243
Content.IntegrationTests/Tests/Construction/RCDTest.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System.Numerics;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.RCD;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Construction;
|
||||
|
||||
public sealed class RCDTest : InteractionTest
|
||||
{
|
||||
private static readonly EntProtoId RCDProtoId = "RCD";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingWall = "WallSolid";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingAirlock = "Airlock";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingPlating = "Plating";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingFloorSteel = "FloorSteel";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingDeconstruct = "Deconstruct";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingDeconstructTile = "DeconstructTile";
|
||||
private static readonly ProtoId<RCDPrototype> RCDSettingDeconstructLattice = "DeconstructLattice";
|
||||
|
||||
/// <summary>
|
||||
/// Tests RCD construction and deconstruction, as well as selecting options from the radial menu.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task RCDConstructionDeconstructionTest()
|
||||
{
|
||||
// Place some tiles around the player so that we have space to build.
|
||||
var pNorth = new EntityCoordinates(SPlayer, new Vector2(0, 1));
|
||||
var pSouth = new EntityCoordinates(SPlayer, new Vector2(0, -1));
|
||||
var pEast = new EntityCoordinates(SPlayer, new Vector2(1, 0));
|
||||
var pWest = new EntityCoordinates(SPlayer, new Vector2(-1, 0));
|
||||
|
||||
// Use EntityCoordinates relative to the grid because the player turns around when interacting.
|
||||
pNorth = Transform.WithEntityId(pNorth, MapData.Grid);
|
||||
pSouth = Transform.WithEntityId(pSouth, MapData.Grid);
|
||||
pEast = Transform.WithEntityId(pEast, MapData.Grid);
|
||||
pWest = Transform.WithEntityId(pWest, MapData.Grid);
|
||||
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pNorth), MapData.Grid);
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pSouth), MapData.Grid);
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pEast), MapData.Grid);
|
||||
await SetTile(Lattice, SEntMan.GetNetCoordinates(pWest), MapData.Grid);
|
||||
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingWall, out var settingWall), $"RCDPrototype not found: {RCDSettingWall}.");
|
||||
Assert.That(settingWall.Prototype, Is.Not.Null, "RCDPrototype has a null spawning prototype.");
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingAirlock, out var settingAirlock), $"RCDPrototype not found: {RCDSettingAirlock}.");
|
||||
Assert.That(settingAirlock.Prototype, Is.Not.Null, $"RCDPrototype has a null spawning prototype.");
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingPlating, out var settingPlating), $"RCDPrototype not found: {RCDSettingPlating}.");
|
||||
Assert.That(settingPlating.Prototype, Is.Not.Null, "RCDPrototype has a null spawning prototype.");
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingFloorSteel, out var settingFloorSteel), $"RCDPrototype not found: {RCDSettingFloorSteel}.");
|
||||
Assert.That(settingFloorSteel.Prototype, Is.Not.Null, "RCDPrototype has a null spawning prototype.");
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingDeconstructTile, out var settingDeconstructTile), $"RCDPrototype not found: {RCDSettingDeconstructTile}.");
|
||||
Assert.That(ProtoMan.TryIndex(RCDSettingDeconstructLattice, out var settingDeconstructLattice), $"RCDPrototype not found: {RCDSettingDeconstructLattice}.");
|
||||
|
||||
var rcd = await PlaceInHands(RCDProtoId);
|
||||
|
||||
// Give the RCD enough charges to do everything.
|
||||
var sCharges = SEntMan.System<SharedChargesSystem>();
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
sCharges.SetMaxCharges(ToServer(rcd), 10000);
|
||||
sCharges.SetCharges(ToServer(rcd), 10000);
|
||||
});
|
||||
var initialCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges, Is.EqualTo(10000), "RCD did not have the correct amount of charges.");
|
||||
|
||||
// Make sure that picking it up did not open the UI.
|
||||
Assert.That(IsUiOpen(RcdUiKey.Key), Is.False, "RCD UI was opened when picking it up.");
|
||||
|
||||
// Switch to building walls.
|
||||
await SetRcdProto(rcd, RCDSettingWall);
|
||||
|
||||
// Build a wall next to the player.
|
||||
await Interact(null, pNorth);
|
||||
|
||||
// Check that there is exactly one wall.
|
||||
await RunSeconds(settingWall.Delay + 1); // wait for the construction to finish
|
||||
await AssertEntityLookup((settingWall.Prototype, 1));
|
||||
|
||||
// Check that the wall is in the correct tile.
|
||||
var wallUid = await FindEntity(settingWall.Prototype);
|
||||
var wallNetUid = FromServer(wallUid);
|
||||
AssertLocation(wallNetUid, FromServer(pNorth));
|
||||
|
||||
// Check that the cost of the wall was subtracted from the current charges.
|
||||
var newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingWall.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Try building another wall in the same spot.
|
||||
await Interact(null, pNorth);
|
||||
await RunSeconds(settingWall.Delay + 1); // wait for the construction to finish
|
||||
|
||||
// Check that there is still exactly one wall.
|
||||
await AssertEntityLookup((settingWall.Prototype, 1));
|
||||
|
||||
// Check that the failed construction did not cost us any charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges, Is.EqualTo(newCharges), "RCD has wrong amount of charges after failing to build something.");
|
||||
|
||||
// Switch to building airlocks.
|
||||
await SetRcdProto(rcd, RCDSettingAirlock);
|
||||
|
||||
// Build an airlock next to the player.
|
||||
await Interact(null, pSouth);
|
||||
|
||||
// Check that there is exactly one airlock.
|
||||
await RunSeconds(settingAirlock.Delay + 1); // wait for the construction to finish
|
||||
await AssertEntityLookup(
|
||||
(settingWall.Prototype, 1),
|
||||
(settingAirlock.Prototype, 1)
|
||||
);
|
||||
|
||||
// Check that the airlock is in the correct tile.
|
||||
var airlockUid = await FindEntity(settingAirlock.Prototype);
|
||||
var airlockNetUid = FromServer(airlockUid);
|
||||
AssertLocation(airlockNetUid, FromServer(pSouth));
|
||||
|
||||
// Check that the cost of the airlock was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingAirlock.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Switch to building plating.
|
||||
await SetRcdProto(rcd, RCDSettingPlating);
|
||||
|
||||
// Try building plating on existing plating.
|
||||
await AssertTile(settingPlating.Prototype, FromServer(pEast));
|
||||
await Interact(null, pEast);
|
||||
|
||||
// Check that the tile did not change.
|
||||
await AssertTile(settingPlating.Prototype, FromServer(pEast));
|
||||
|
||||
// Check that the failed construction did not cost us any charges.
|
||||
await RunSeconds(settingPlating.Delay + 1); // wait for the construction to finish
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges, Is.EqualTo(newCharges), "RCD has wrong amount of charges after failing to build something.");
|
||||
|
||||
// Try building plating on top of lattice.
|
||||
await AssertTile(Lattice, FromServer(pWest));
|
||||
await Interact(null, pWest);
|
||||
await RunSeconds(settingPlating.Delay + 1); // wait for the construction to finish
|
||||
|
||||
// Check that the tile is now plating.
|
||||
await AssertTile(settingPlating.Prototype, FromServer(pWest));
|
||||
|
||||
// Check that the cost of the plating was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingPlating.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Switch to building steel tiles.
|
||||
await SetRcdProto(rcd, RCDSettingFloorSteel);
|
||||
|
||||
// Try building a steel tile on top of plating.
|
||||
await Interact(null, pEast);
|
||||
|
||||
// Check that the tile is now a steel tile.
|
||||
await RunSeconds(settingFloorSteel.Delay + 1); // wait for the construction to finish
|
||||
await AssertTile(settingFloorSteel.Prototype, FromServer(pEast));
|
||||
|
||||
// Check that the cost of the plating was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingFloorSteel.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Switch to deconstruction mode.
|
||||
await SetRcdProto(rcd, RCDSettingDeconstruct);
|
||||
|
||||
// Deconstruct the wall.
|
||||
Assert.That(SEntMan.TryGetComponent<RCDDeconstructableComponent>(wallUid, out var wallComp), "Wall entity did not have the RCDDeconstructableComponent.");
|
||||
await Interact(wallUid, pNorth);
|
||||
await RunSeconds(wallComp.Delay + 1); // wait for the deconstruction to finish
|
||||
AssertDeleted(wallNetUid);
|
||||
|
||||
// Check that the cost of the deconstruction was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - wallComp.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Deconstruct the airlock.
|
||||
Assert.That(SEntMan.TryGetComponent<RCDDeconstructableComponent>(airlockUid, out var airlockComp), "Wall entity did not have the RCDDeconstructableComponent.");
|
||||
await Interact(airlockUid, pSouth);
|
||||
await RunSeconds(airlockComp.Delay + 1); // wait for the deconstruction to finish
|
||||
AssertDeleted(airlockNetUid);
|
||||
|
||||
// Check that the cost of the deconstruction was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - airlockComp.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Deconstruct the steel tile.
|
||||
await Interact(null, pEast);
|
||||
await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish
|
||||
await AssertTile(Lattice, FromServer(pEast));
|
||||
|
||||
// Check that the cost of the deconstruction was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingDeconstructTile.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Deconstruct the plating.
|
||||
await Interact(null, pWest);
|
||||
await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish
|
||||
await AssertTile(Lattice, FromServer(pWest));
|
||||
|
||||
// Check that the cost of the deconstruction was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingDeconstructTile.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
|
||||
initialCharges = newCharges;
|
||||
|
||||
// Deconstruct the lattice.
|
||||
await Interact(null, pWest);
|
||||
await RunSeconds(settingDeconstructLattice.Delay + 1); // wait for the deconstruction to finish
|
||||
await AssertTile(null, FromServer(pWest));
|
||||
|
||||
// Check that the cost of the deconstruction was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
Assert.That(initialCharges - settingDeconstructLattice.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
|
||||
|
||||
// Wait for the visual effect to disappear.
|
||||
await RunSeconds(3);
|
||||
|
||||
// Check that there are no entities left.
|
||||
await AssertEntityLookup();
|
||||
}
|
||||
|
||||
private async Task SetRcdProto(NetEntity rcd, ProtoId<RCDPrototype> protoId)
|
||||
{
|
||||
await UseInHand();
|
||||
await RunTicks(3);
|
||||
Assert.That(IsUiOpen(RcdUiKey.Key), Is.True, "RCD UI was not opened when using the RCD while holding it.");
|
||||
|
||||
// Simulating a click on the right control for nested radial menus is very complicated.
|
||||
// So we just manually send a networking message from the client to tell the server we selected an option.
|
||||
// TODO: Write a separate test for clicking through a SimpleRadialMenu.
|
||||
await SendBui(RcdUiKey.Key, new RCDSystemMessage(protoId), rcd);
|
||||
await CloseBui(RcdUiKey.Key, rcd);
|
||||
Assert.That(IsUiOpen(RcdUiKey.Key), Is.False, "RCD UI is still open.");
|
||||
}
|
||||
}
|
||||
@@ -726,7 +726,7 @@ public abstract partial class InteractionTest
|
||||
tile = MapSystem.GetTileRef(gridUid, grid, serverCoords).Tile;
|
||||
});
|
||||
|
||||
Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId));
|
||||
Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId), $"Expected tile at NetCoordinates {coords}: {TileMan[targetTile.TypeId].Name}. But was: {TileMan[tile.TypeId].Name}");
|
||||
}
|
||||
|
||||
protected void AssertGridCount(int value)
|
||||
@@ -742,6 +742,20 @@ public abstract partial class InteractionTest
|
||||
Assert.That(count, Is.EqualTo(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that some entity is close to a certain coordinate location.
|
||||
/// </summary>
|
||||
/// <param name="target">The entity to check the location for. Defaults to <see cref="target"/></param>
|
||||
/// <param name="coordinates">The coordinates the entity should be at.</param>
|
||||
/// <param name="radius">The maximum allowed distance from the target coords</param>
|
||||
protected void AssertLocation(NetEntity? target, NetCoordinates coords, float radius = 0.01f)
|
||||
{
|
||||
target ??= Target;
|
||||
Assert.That(target, Is.Not.Null, "No target specified");
|
||||
Assert.That(Position(target!.Value).TryDelta(SEntMan, Transform, ToServer(coords), out var delta), "Could not calculate distance between coordinates.");
|
||||
Assert.That(delta.Length(), Is.LessThanOrEqualTo(radius), $"{SEntMan.ToPrettyString(SEntMan.GetEntity(target.Value))} was not at the intended location. Distance: {delta}, allowed distance: {radius}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entity lookups
|
||||
@@ -986,9 +1000,9 @@ public abstract partial class InteractionTest
|
||||
/// <summary>
|
||||
/// Sends a bui message using the given bui key.
|
||||
/// </summary>
|
||||
protected async Task SendBui(Enum key, BoundUserInterfaceMessage msg, EntityUid? _ = null)
|
||||
protected async Task SendBui(Enum key, BoundUserInterfaceMessage msg, NetEntity? target = null)
|
||||
{
|
||||
if (!TryGetBui(key, out var bui))
|
||||
if (!TryGetBui(key, out var bui, target))
|
||||
return;
|
||||
|
||||
await Client.WaitPost(() => bui.SendMessage(msg));
|
||||
@@ -1000,9 +1014,9 @@ public abstract partial class InteractionTest
|
||||
/// <summary>
|
||||
/// Sends a bui message using the given bui key.
|
||||
/// </summary>
|
||||
protected async Task CloseBui(Enum key, EntityUid? _ = null)
|
||||
protected async Task CloseBui(Enum key, NetEntity? target = null)
|
||||
{
|
||||
if (!TryGetBui(key, out var bui))
|
||||
if (!TryGetBui(key, out var bui, target))
|
||||
return;
|
||||
|
||||
await Client.WaitPost(() => bui.Close());
|
||||
@@ -1424,15 +1438,25 @@ public abstract partial class InteractionTest
|
||||
protected EntityUid? ToServer(NetEntity? nent) => SEntMan.GetEntity(nent);
|
||||
protected EntityUid? ToClient(NetEntity? nent) => CEntMan.GetEntity(nent);
|
||||
protected EntityUid ToServer(EntityUid cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid));
|
||||
protected EntityUid ToClient(EntityUid cuid) => CEntMan.GetEntity(SEntMan.GetNetEntity(cuid));
|
||||
protected EntityUid ToClient(EntityUid suid) => CEntMan.GetEntity(SEntMan.GetNetEntity(suid));
|
||||
protected EntityUid? ToServer(EntityUid? cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid));
|
||||
protected EntityUid? ToClient(EntityUid? cuid) => CEntMan.GetEntity(SEntMan.GetNetEntity(cuid));
|
||||
protected EntityUid? ToClient(EntityUid? suid) => CEntMan.GetEntity(SEntMan.GetNetEntity(suid));
|
||||
|
||||
protected EntityCoordinates ToServer(NetCoordinates coords) => SEntMan.GetCoordinates(coords);
|
||||
protected EntityCoordinates ToClient(NetCoordinates coords) => CEntMan.GetCoordinates(coords);
|
||||
protected EntityCoordinates? ToServer(NetCoordinates? coords) => SEntMan.GetCoordinates(coords);
|
||||
protected EntityCoordinates? ToClient(NetCoordinates? coords) => CEntMan.GetCoordinates(coords);
|
||||
|
||||
protected NetEntity FromServer(EntityUid suid) => SEntMan.GetNetEntity(suid);
|
||||
protected NetEntity FromClient(EntityUid cuid) => CEntMan.GetNetEntity(cuid);
|
||||
protected NetEntity? FromServer(EntityUid? suid) => SEntMan.GetNetEntity(suid);
|
||||
protected NetEntity? FromClient(EntityUid? cuid) => CEntMan.GetNetEntity(cuid);
|
||||
|
||||
protected NetCoordinates FromServer(EntityCoordinates scoords) => SEntMan.GetNetCoordinates(scoords);
|
||||
protected NetCoordinates FromClient(EntityCoordinates ccoords) => CEntMan.GetNetCoordinates(ccoords);
|
||||
protected NetCoordinates? FromServer(EntityCoordinates? scoords) => SEntMan.GetNetCoordinates(scoords);
|
||||
protected NetCoordinates? FromClient(EntityCoordinates? ccoords) => CEntMan.GetNetCoordinates(ccoords);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metadata & Transforms
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#nullable enable
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Power.Nodes;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.Power.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.Shared.EntitySerialization;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user