Add climb & slip tests (#15459)
This commit is contained in:
@@ -564,7 +564,8 @@ we are just going to end this here to save a lot of time. This is the exception
|
|||||||
mapData.MapId = mapManager.CreateMap();
|
mapData.MapId = mapManager.CreateMap();
|
||||||
mapData.MapUid = mapManager.GetMapEntityId(mapData.MapId);
|
mapData.MapUid = mapManager.GetMapEntityId(mapData.MapId);
|
||||||
mapData.MapGrid = mapManager.CreateGrid(mapData.MapId);
|
mapData.MapGrid = mapManager.CreateGrid(mapData.MapId);
|
||||||
mapData.GridCoords = new EntityCoordinates(mapData.MapGrid.Owner, 0, 0);
|
mapData.GridUid = mapData.MapGrid.Owner;
|
||||||
|
mapData.GridCoords = new EntityCoordinates(mapData.GridUid, 0, 0);
|
||||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||||
var plating = tileDefinitionManager["Plating"];
|
var plating = tileDefinitionManager["Plating"];
|
||||||
var platingTile = new Tile(plating.TileId);
|
var platingTile = new Tile(plating.TileId);
|
||||||
@@ -793,6 +794,7 @@ public sealed class PoolSettings
|
|||||||
public sealed class TestMapData
|
public sealed class TestMapData
|
||||||
{
|
{
|
||||||
public EntityUid MapUid { get; set; }
|
public EntityUid MapUid { get; set; }
|
||||||
|
public EntityUid GridUid { get; set; }
|
||||||
public MapId MapId { get; set; }
|
public MapId MapId { get; set; }
|
||||||
public MapGridComponent MapGrid { get; set; }
|
public MapGridComponent MapGrid { get; set; }
|
||||||
public EntityCoordinates GridCoords { get; set; }
|
public EntityCoordinates GridCoords { get; set; }
|
||||||
|
|||||||
64
Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs
Normal file
64
Content.IntegrationTests/Tests/Climbing/ClimbingTest.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.IntegrationTests.Tests.Interaction;
|
||||||
|
using Content.Server.Climbing;
|
||||||
|
using Content.Shared.Climbing;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Climbing;
|
||||||
|
|
||||||
|
public sealed class ClimbingTest : MovementTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task ClimbTableTest()
|
||||||
|
{
|
||||||
|
// Spawn a table to the right of the player.
|
||||||
|
await SpawnTarget("Table");
|
||||||
|
Assert.That(Delta(), Is.GreaterThan(0));
|
||||||
|
|
||||||
|
// Player is not initially climbing anything.
|
||||||
|
var comp = Comp<ClimbingComponent>(Player);
|
||||||
|
Assert.That(comp.IsClimbing, Is.False);
|
||||||
|
Assert.That(comp.DisabledFixtureMasks.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
|
// Attempt (and fail) to walk past the table.
|
||||||
|
await Move(DirectionFlag.East, 1f);
|
||||||
|
Assert.That(Delta(), Is.GreaterThan(0));
|
||||||
|
|
||||||
|
// Try to start climbing
|
||||||
|
var sys = SEntMan.System<ClimbSystem>();
|
||||||
|
await Server.WaitPost(() => sys.TryClimb(Player, Player, Target.Value));
|
||||||
|
await AwaitDoAfters();
|
||||||
|
|
||||||
|
// Player should now be climbing
|
||||||
|
Assert.That(comp.IsClimbing, Is.True);
|
||||||
|
Assert.That(comp.DisabledFixtureMasks.Count, Is.GreaterThan(0));
|
||||||
|
|
||||||
|
// Can now walk over the table.
|
||||||
|
await Move(DirectionFlag.East, 1f);
|
||||||
|
Assert.That(Delta(), Is.LessThan(0));
|
||||||
|
|
||||||
|
// After walking away from the table, player should have stopped climbing.
|
||||||
|
Assert.That(comp.IsClimbing, Is.False);
|
||||||
|
Assert.That(comp.DisabledFixtureMasks.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
|
// Try to walk back to the other side (and fail).
|
||||||
|
await Move(DirectionFlag.West, 1f);
|
||||||
|
Assert.That(Delta(), Is.LessThan(0));
|
||||||
|
|
||||||
|
// Start climbing
|
||||||
|
await Server.WaitPost(() => sys.TryClimb(Player, Player, Target.Value));
|
||||||
|
await AwaitDoAfters();
|
||||||
|
Assert.That(comp.IsClimbing, Is.True);
|
||||||
|
Assert.That(comp.DisabledFixtureMasks.Count, Is.GreaterThan(0));
|
||||||
|
|
||||||
|
// Walk past table and stop climbing again.
|
||||||
|
await Move(DirectionFlag.West, 1f);
|
||||||
|
Assert.That(Delta(), Is.GreaterThan(0));
|
||||||
|
Assert.That(comp.IsClimbing, Is.False);
|
||||||
|
Assert.That(comp.DisabledFixtureMasks.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Content.Server.Climbing.Components;
|
|
||||||
using Content.Shared.Climbing;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.GameObjects.Components.Movement
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
[TestOf(typeof(ClimbableComponent))]
|
|
||||||
[TestOf(typeof(ClimbingComponent))]
|
|
||||||
public sealed class ClimbUnitTest
|
|
||||||
{
|
|
||||||
private const string Prototypes = @"
|
|
||||||
- type: entity
|
|
||||||
name: HumanDummy
|
|
||||||
id: HumanDummy
|
|
||||||
components:
|
|
||||||
- type: Climbing
|
|
||||||
- type: Physics
|
|
||||||
|
|
||||||
- type: entity
|
|
||||||
name: TableDummy
|
|
||||||
id: TableDummy
|
|
||||||
components:
|
|
||||||
- type: Climbable
|
|
||||||
- type: Physics
|
|
||||||
";
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task Test()
|
|
||||||
{
|
|
||||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
|
|
||||||
var server = pairTracker.Pair.Server;
|
|
||||||
|
|
||||||
EntityUid human;
|
|
||||||
EntityUid table;
|
|
||||||
ClimbingComponent climbing;
|
|
||||||
|
|
||||||
await server.WaitAssertion(() =>
|
|
||||||
{
|
|
||||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
|
|
||||||
// Spawn the entities
|
|
||||||
human = entityManager.SpawnEntity("HumanDummy", MapCoordinates.Nullspace);
|
|
||||||
table = entityManager.SpawnEntity("TableDummy", MapCoordinates.Nullspace);
|
|
||||||
|
|
||||||
// Test for climb components existing
|
|
||||||
// Players and tables should have these in their prototypes.
|
|
||||||
Assert.That(entityManager.TryGetComponent(human, out climbing!), "Human has no climbing");
|
|
||||||
Assert.That(entityManager.TryGetComponent(table, out ClimbableComponent? _), "Table has no climbable");
|
|
||||||
|
|
||||||
// TODO ShadowCommander: Implement climbing test
|
|
||||||
// // Now let's make the player enter a climbing transitioning state.
|
|
||||||
// climbing.IsClimbing = true;
|
|
||||||
// EntitySystem.Get<ClimbSystem>().MoveEntityToward(human, table, climbing:climbing);
|
|
||||||
// var body = entityManager.GetComponent<PhysicsComponent>(human);
|
|
||||||
// // TODO: Check it's climbing
|
|
||||||
//
|
|
||||||
// // Force the player out of climb state. It should immediately remove the ClimbController.
|
|
||||||
// climbing.IsClimbing = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
await pairTracker.CleanReturnAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,6 @@ namespace Content.IntegrationTests.Tests.Interaction;
|
|||||||
// Should make it easier to mass-change hard coded strings if prototypes get renamed.
|
// Should make it easier to mass-change hard coded strings if prototypes get renamed.
|
||||||
public abstract partial class InteractionTest
|
public abstract partial class InteractionTest
|
||||||
{
|
{
|
||||||
protected const string PlayerEntity = "AdminObserver";
|
|
||||||
|
|
||||||
// Tiles
|
// Tiles
|
||||||
protected const string Floor = "FloorSteel";
|
protected const string Floor = "FloorSteel";
|
||||||
protected const string FloorItem = "FloorTileItemSteel";
|
protected const string FloorItem = "FloorTileItemSteel";
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ using System.Reflection;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Client.Chemistry.UI;
|
using Content.Client.Chemistry.UI;
|
||||||
using Content.Client.Construction;
|
using Content.Client.Construction;
|
||||||
|
using Content.Server.Atmos;
|
||||||
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Construction.Components;
|
using Content.Server.Construction.Components;
|
||||||
|
using Content.Server.Gravity;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Tools.Components;
|
using Content.Server.Tools.Components;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Construction.Prototypes;
|
using Content.Shared.Construction.Prototypes;
|
||||||
|
using Content.Shared.Gravity;
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using OpenToolkit.GraphicsLibraryFramework;
|
using OpenToolkit.GraphicsLibraryFramework;
|
||||||
@@ -84,8 +89,10 @@ public abstract partial class InteractionTest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spawn an entity entity and set it as the target.
|
/// Spawn an entity entity and set it as the target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[MemberNotNull(nameof(Target))]
|
||||||
protected async Task SpawnTarget(string prototype)
|
protected async Task SpawnTarget(string prototype)
|
||||||
{
|
{
|
||||||
|
Target = EntityUid.Invalid;
|
||||||
await Server.WaitPost(() =>
|
await Server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
Target = SEntMan.SpawnEntity(prototype, TargetCoords);
|
Target = SEntMan.SpawnEntity(prototype, TargetCoords);
|
||||||
@@ -493,6 +500,19 @@ public abstract partial class InteractionTest
|
|||||||
Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId));
|
Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void AssertGridCount(int value)
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
var query = SEntMan.AllEntityQueryEnumerator<MapGridComponent, TransformComponent>();
|
||||||
|
while (query.MoveNext(out _, out var xform))
|
||||||
|
{
|
||||||
|
if (xform.MapUid == MapData.MapUid)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(count, Is.EqualTo(value));
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Entity lookups
|
#region Entity lookups
|
||||||
@@ -669,13 +689,20 @@ public abstract partial class InteractionTest
|
|||||||
await RunTicks(5);
|
await RunTicks(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Time/Tick managment
|
||||||
|
|
||||||
protected async Task RunTicks(int ticks)
|
protected async Task RunTicks(int ticks)
|
||||||
{
|
{
|
||||||
await PoolManager.RunTicksSync(PairTracker.Pair, ticks);
|
await PoolManager.RunTicksSync(PairTracker.Pair, ticks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int SecondsToTicks(float seconds)
|
||||||
|
=> (int) Math.Ceiling(seconds / TickPeriod);
|
||||||
|
|
||||||
protected async Task RunSeconds(float seconds)
|
protected async Task RunSeconds(float seconds)
|
||||||
=> await RunTicks((int) Math.Ceiling(seconds / TickPeriod));
|
=> await RunTicks(SecondsToTicks(seconds));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region BUI
|
#region BUI
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -723,9 +750,6 @@ public abstract partial class InteractionTest
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var first = ui.Interfaces.First();
|
|
||||||
|
|
||||||
|
|
||||||
bui = ui.Interfaces.FirstOrDefault(x => x.UiKey.Equals(key));
|
bui = ui.Interfaces.FirstOrDefault(x => x.UiKey.Equals(key));
|
||||||
if (bui == null)
|
if (bui == null)
|
||||||
{
|
{
|
||||||
@@ -878,4 +902,110 @@ public abstract partial class InteractionTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Map Setup
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds gravity to a given entity. Defaults to the grid if no entity is specified.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task AddGravity(EntityUid? uid = null)
|
||||||
|
{
|
||||||
|
var target = uid ?? MapData.GridUid;
|
||||||
|
await Server.WaitPost(() =>
|
||||||
|
{
|
||||||
|
var gravity = SEntMan.EnsureComponent<GravityComponent>(target);
|
||||||
|
SEntMan.System<GravitySystem>().EnableGravity(target, gravity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a default atmosphere to the test map.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task AddAtmosphere(EntityUid? uid = null)
|
||||||
|
{
|
||||||
|
var target = uid ?? MapData.MapUid;
|
||||||
|
await Server.WaitPost(() =>
|
||||||
|
{
|
||||||
|
var atmos = SEntMan.EnsureComponent<MapAtmosphereComponent>(target);
|
||||||
|
atmos.Space = false;
|
||||||
|
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||||
|
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||||
|
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||||
|
|
||||||
|
atmos.Mixture = new GasMixture(2500)
|
||||||
|
{
|
||||||
|
Temperature = 293.15f,
|
||||||
|
Moles = moles,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Inputs
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make the client press and then release a key. This assumes the key is currently released.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task PressKey(
|
||||||
|
BoundKeyFunction key,
|
||||||
|
int ticks = 1,
|
||||||
|
EntityCoordinates? coordinates = null,
|
||||||
|
EntityUid cursorEntity = default)
|
||||||
|
{
|
||||||
|
await SetKey(key, BoundKeyState.Down, coordinates, cursorEntity);
|
||||||
|
await RunTicks(ticks);
|
||||||
|
await SetKey(key, BoundKeyState.Up, coordinates, cursorEntity);
|
||||||
|
await RunTicks(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make the client press or release a key
|
||||||
|
/// </summary>
|
||||||
|
protected async Task SetKey(
|
||||||
|
BoundKeyFunction key,
|
||||||
|
BoundKeyState state,
|
||||||
|
EntityCoordinates? coordinates = null,
|
||||||
|
EntityUid cursorEntity = default)
|
||||||
|
{
|
||||||
|
var coords = coordinates ?? TargetCoords;
|
||||||
|
ScreenCoordinates screen = default;
|
||||||
|
|
||||||
|
var funcId = InputManager.NetworkBindMap.KeyFunctionID(key);
|
||||||
|
var message = new FullInputCmdMessage(CTiming.CurTick, CTiming.TickFraction, funcId, state,
|
||||||
|
coords, screen, cursorEntity);
|
||||||
|
|
||||||
|
await Client.WaitPost(() => InputSystem.HandleInputCommand(ClientSession, key, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Variant of <see cref="SetKey"/> for setting movement keys.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task SetMovementKey(DirectionFlag dir, BoundKeyState state)
|
||||||
|
{
|
||||||
|
if ((dir & DirectionFlag.South) != 0)
|
||||||
|
await SetKey(EngineKeyFunctions.MoveDown, state);
|
||||||
|
|
||||||
|
if ((dir & DirectionFlag.East) != 0)
|
||||||
|
await SetKey(EngineKeyFunctions.MoveRight, state);
|
||||||
|
|
||||||
|
if ((dir & DirectionFlag.North) != 0)
|
||||||
|
await SetKey(EngineKeyFunctions.MoveUp, state);
|
||||||
|
|
||||||
|
if ((dir & DirectionFlag.West) != 0)
|
||||||
|
await SetKey(EngineKeyFunctions.MoveLeft, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make the client hold the move key in some direction for some amount of time.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task Move(DirectionFlag dir, float seconds)
|
||||||
|
{
|
||||||
|
await SetMovementKey(dir, BoundKeyState.Down);
|
||||||
|
await RunSeconds(seconds);
|
||||||
|
await SetMovementKey(dir, BoundKeyState.Up);
|
||||||
|
await RunTicks(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ using Content.Shared.Hands.EntitySystems;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Input;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.UnitTesting;
|
using Robust.UnitTesting;
|
||||||
@@ -34,6 +37,8 @@ namespace Content.IntegrationTests.Tests.Interaction;
|
|||||||
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
|
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
|
||||||
public abstract partial class InteractionTest
|
public abstract partial class InteractionTest
|
||||||
{
|
{
|
||||||
|
protected virtual string PlayerPrototype => "AdminObserver";
|
||||||
|
|
||||||
protected PairTracker PairTracker = default!;
|
protected PairTracker PairTracker = default!;
|
||||||
protected TestMapData MapData = default!;
|
protected TestMapData MapData = default!;
|
||||||
|
|
||||||
@@ -59,6 +64,9 @@ public abstract partial class InteractionTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected EntityUid Player;
|
protected EntityUid Player;
|
||||||
|
|
||||||
|
protected ICommonSession ClientSession = default!;
|
||||||
|
protected IPlayerSession ServerSession = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current target entity. This is the default entity for various helper functions.
|
/// The current target entity. This is the default entity for various helper functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -79,7 +87,7 @@ public abstract partial class InteractionTest
|
|||||||
protected ITileDefinitionManager TileMan = default!;
|
protected ITileDefinitionManager TileMan = default!;
|
||||||
protected IMapManager MapMan = default!;
|
protected IMapManager MapMan = default!;
|
||||||
protected IPrototypeManager ProtoMan = default!;
|
protected IPrototypeManager ProtoMan = default!;
|
||||||
protected IGameTiming Timing = default!;
|
protected IGameTiming STiming = default!;
|
||||||
protected IComponentFactory Factory = default!;
|
protected IComponentFactory Factory = default!;
|
||||||
protected SharedHandsSystem HandSys = default!;
|
protected SharedHandsSystem HandSys = default!;
|
||||||
protected StackSystem Stack = default!;
|
protected StackSystem Stack = default!;
|
||||||
@@ -92,16 +100,20 @@ public abstract partial class InteractionTest
|
|||||||
|
|
||||||
// CLIENT dependencies
|
// CLIENT dependencies
|
||||||
protected IEntityManager CEntMan = default!;
|
protected IEntityManager CEntMan = default!;
|
||||||
|
protected IGameTiming CTiming = default!;
|
||||||
protected IUserInterfaceManager UiMan = default!;
|
protected IUserInterfaceManager UiMan = default!;
|
||||||
|
protected IInputManager InputManager = default!;
|
||||||
|
protected InputSystem InputSystem = default!;
|
||||||
protected ConstructionSystem CConSys = default!;
|
protected ConstructionSystem CConSys = default!;
|
||||||
protected ExamineSystem ExamineSys = default!;
|
protected ExamineSystem ExamineSys = default!;
|
||||||
protected InteractionTestSystem CTestSystem = default!;
|
protected InteractionTestSystem CTestSystem = default!;
|
||||||
|
protected UserInterfaceSystem CUISystem = default!;
|
||||||
|
|
||||||
// player components
|
// player components
|
||||||
protected HandsComponent Hands = default!;
|
protected HandsComponent Hands = default!;
|
||||||
protected DoAfterComponent DoAfters = default!;
|
protected DoAfterComponent DoAfters = default!;
|
||||||
|
|
||||||
public float TickPeriod => (float)Timing.TickPeriod.TotalSeconds;
|
public float TickPeriod => (float)STiming.TickPeriod.TotalSeconds;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public virtual async Task Setup()
|
public virtual async Task Setup()
|
||||||
@@ -114,7 +126,7 @@ public abstract partial class InteractionTest
|
|||||||
MapMan = Server.ResolveDependency<IMapManager>();
|
MapMan = Server.ResolveDependency<IMapManager>();
|
||||||
ProtoMan = Server.ResolveDependency<IPrototypeManager>();
|
ProtoMan = Server.ResolveDependency<IPrototypeManager>();
|
||||||
Factory = Server.ResolveDependency<IComponentFactory>();
|
Factory = Server.ResolveDependency<IComponentFactory>();
|
||||||
Timing = Server.ResolveDependency<IGameTiming>();
|
STiming = Server.ResolveDependency<IGameTiming>();
|
||||||
HandSys = SEntMan.System<SharedHandsSystem>();
|
HandSys = SEntMan.System<SharedHandsSystem>();
|
||||||
InteractSys = SEntMan.System<SharedInteractionSystem>();
|
InteractSys = SEntMan.System<SharedInteractionSystem>();
|
||||||
ToolSys = SEntMan.System<ToolSystem>();
|
ToolSys = SEntMan.System<ToolSystem>();
|
||||||
@@ -127,6 +139,9 @@ public abstract partial class InteractionTest
|
|||||||
// client dependencies
|
// client dependencies
|
||||||
CEntMan = Client.ResolveDependency<IEntityManager>();
|
CEntMan = Client.ResolveDependency<IEntityManager>();
|
||||||
UiMan = Client.ResolveDependency<IUserInterfaceManager>();
|
UiMan = Client.ResolveDependency<IUserInterfaceManager>();
|
||||||
|
CTiming = Client.ResolveDependency<IGameTiming>();
|
||||||
|
InputManager = Client.ResolveDependency<IInputManager>();
|
||||||
|
InputSystem = CEntMan.System<InputSystem>();
|
||||||
CTestSystem = CEntMan.System<InteractionTestSystem>();
|
CTestSystem = CEntMan.System<InteractionTestSystem>();
|
||||||
CConSys = CEntMan.System<ConstructionSystem>();
|
CConSys = CEntMan.System<ConstructionSystem>();
|
||||||
ExamineSys = CEntMan.System<ExamineSystem>();
|
ExamineSys = CEntMan.System<ExamineSystem>();
|
||||||
@@ -142,16 +157,16 @@ public abstract partial class InteractionTest
|
|||||||
var cPlayerMan = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
|
var cPlayerMan = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
|
||||||
if (cPlayerMan.LocalPlayer?.Session == null)
|
if (cPlayerMan.LocalPlayer?.Session == null)
|
||||||
Assert.Fail("No player");
|
Assert.Fail("No player");
|
||||||
var cSession = cPlayerMan.LocalPlayer!.Session!;
|
ClientSession = cPlayerMan.LocalPlayer!.Session!;
|
||||||
var sSession = sPlayerMan.GetSessionByUserId(cSession.UserId);
|
ServerSession = sPlayerMan.GetSessionByUserId(ClientSession.UserId);
|
||||||
|
|
||||||
// Spawn player entity & attach
|
// Spawn player entity & attach
|
||||||
EntityUid? old = default;
|
EntityUid? old = default;
|
||||||
await Server.WaitPost(() =>
|
await Server.WaitPost(() =>
|
||||||
{
|
{
|
||||||
old = cPlayerMan.LocalPlayer.ControlledEntity;
|
old = cPlayerMan.LocalPlayer.ControlledEntity;
|
||||||
Player = SEntMan.SpawnEntity(PlayerEntity, PlayerCoords);
|
Player = SEntMan.SpawnEntity(PlayerPrototype, PlayerCoords);
|
||||||
sSession.AttachToEntity(Player);
|
ServerSession.AttachToEntity(Player);
|
||||||
Hands = SEntMan.GetComponent<HandsComponent>(Player);
|
Hands = SEntMan.GetComponent<HandsComponent>(Player);
|
||||||
DoAfters = SEntMan.GetComponent<DoAfterComponent>(Player);
|
DoAfters = SEntMan.GetComponent<DoAfterComponent>(Player);
|
||||||
});
|
});
|
||||||
@@ -189,7 +204,7 @@ public abstract partial class InteractionTest
|
|||||||
// Final player asserts/checks.
|
// Final player asserts/checks.
|
||||||
await PoolManager.ReallyBeIdle(PairTracker.Pair, 5);
|
await PoolManager.ReallyBeIdle(PairTracker.Pair, 5);
|
||||||
Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.EqualTo(Player));
|
Assert.That(cPlayerMan.LocalPlayer.ControlledEntity, Is.EqualTo(Player));
|
||||||
Assert.That(sPlayerMan.GetSessionByUserId(cSession.UserId).AttachedEntity, Is.EqualTo(Player));
|
Assert.That(sPlayerMan.GetSessionByUserId(ClientSession.UserId).AttachedEntity, Is.EqualTo(Player));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
|||||||
65
Content.IntegrationTests/Tests/Interaction/MovementTest.cs
Normal file
65
Content.IntegrationTests/Tests/Interaction/MovementTest.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Interaction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a variation of <see cref="InteractionTest"/> that sets up the player with a normal human entity and a simple
|
||||||
|
/// linear grid with gravity and an atmosphere. It is intended to make it easier to test interactions that involve
|
||||||
|
/// walking (e.g., slipping or climbing tables).
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MovementTest : InteractionTest
|
||||||
|
{
|
||||||
|
protected override string PlayerPrototype => "MobHuman";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of tiles to add either side of the player.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual int Tiles => 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the tiles at the ends of the grid will have a wall placed on them to avoid players moving off grid.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool AddWalls => true;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public override async Task Setup()
|
||||||
|
{
|
||||||
|
await base.Setup();
|
||||||
|
for (var i = -Tiles; i <= Tiles; i++)
|
||||||
|
{
|
||||||
|
await SetTile(Plating, PlayerCoords.Offset((i,0)), MapData.MapGrid);
|
||||||
|
}
|
||||||
|
AssertGridCount(1);
|
||||||
|
|
||||||
|
if (AddWalls)
|
||||||
|
{
|
||||||
|
await SpawnEntity("WallSolid", PlayerCoords.Offset((-Tiles,0)));
|
||||||
|
await SpawnEntity("WallSolid", PlayerCoords.Offset((Tiles,0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
await AddGravity();
|
||||||
|
await AddAtmosphere();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the relative horizontal between two entities. Defaults to using the target & player entity.
|
||||||
|
/// </summary>
|
||||||
|
protected float Delta(EntityUid? target = null, EntityUid? other = null)
|
||||||
|
{
|
||||||
|
target ??= Target;
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
Assert.Fail("No target specified");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var delta = Transform.GetWorldPosition(target.Value) - Transform.GetWorldPosition(other ?? Player);
|
||||||
|
return delta.X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
54
Content.IntegrationTests/Tests/Slipping/SlippingTest.cs
Normal file
54
Content.IntegrationTests/Tests/Slipping/SlippingTest.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.IntegrationTests.Tests.Interaction;
|
||||||
|
using Content.Shared.Slippery;
|
||||||
|
using Content.Shared.Stunnable;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Slipping;
|
||||||
|
|
||||||
|
public sealed class SlippingTest : MovementTest
|
||||||
|
{
|
||||||
|
public sealed class SlipTestSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public HashSet<EntityUid> Slipped = new();
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<SlipperyComponent, SlipEvent>(OnSlip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSlip(EntityUid uid, SlipperyComponent component, ref SlipEvent args)
|
||||||
|
{
|
||||||
|
Slipped.Add(args.Slipped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task BananaSlipTest()
|
||||||
|
{
|
||||||
|
var sys = SEntMan.System<SlipTestSystem>();
|
||||||
|
await SpawnTarget("TrashBananaPeel");
|
||||||
|
|
||||||
|
// Player is to the left of the banana peel and has not slipped.
|
||||||
|
Assert.That(Delta(), Is.GreaterThan(0.5f));
|
||||||
|
Assert.That(sys.Slipped.Contains(Player), Is.False);
|
||||||
|
|
||||||
|
// Walking over the banana slowly does not trigger a slip.
|
||||||
|
await SetKey(EngineKeyFunctions.Walk, BoundKeyState.Down);
|
||||||
|
await Move(DirectionFlag.East, 1f);
|
||||||
|
Assert.That(Delta(), Is.LessThan(0.5f));
|
||||||
|
Assert.That(sys.Slipped.Contains(Player), Is.False);
|
||||||
|
AssertComp<KnockedDownComponent>(false, Player);
|
||||||
|
|
||||||
|
// Moving at normal speeds does trigger a slip.
|
||||||
|
await SetKey(EngineKeyFunctions.Walk, BoundKeyState.Up);
|
||||||
|
await Move(DirectionFlag.West, 1f);
|
||||||
|
Assert.That(sys.Slipped.Contains(Player), Is.True);
|
||||||
|
AssertComp<KnockedDownComponent>(true, Player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,19 +9,6 @@ namespace Content.IntegrationTests.Tests.Tiles;
|
|||||||
|
|
||||||
public sealed class TileConstructionTests : InteractionTest
|
public sealed class TileConstructionTests : InteractionTest
|
||||||
{
|
{
|
||||||
private void AssertGridCount(int value)
|
|
||||||
{
|
|
||||||
var count = 0;
|
|
||||||
var query = SEntMan.AllEntityQueryEnumerator<MapGridComponent, TransformComponent>();
|
|
||||||
while (query.MoveNext(out _, out var xform))
|
|
||||||
{
|
|
||||||
if (xform.MapUid == MapData.MapUid)
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.That(count, Is.EqualTo(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test placing and cutting a single lattice.
|
/// Test placing and cutting a single lattice.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -93,23 +93,31 @@ public sealed class ClimbSystem : SharedClimbSystem
|
|||||||
// TODO VERBS ICON add a climbing icon?
|
// TODO VERBS ICON add a climbing icon?
|
||||||
args.Verbs.Add(new AlternativeVerb
|
args.Verbs.Add(new AlternativeVerb
|
||||||
{
|
{
|
||||||
Act = () => TryMoveEntity(component, args.User, args.User, args.Target),
|
Act = () => TryClimb(args.User, args.User, args.Target, component),
|
||||||
Text = Loc.GetString("comp-climbable-verb-climb")
|
Text = Loc.GetString("comp-climbable-verb-climb")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClimbableDragDrop(EntityUid uid, ClimbableComponent component, ref DragDropTargetEvent args)
|
private void OnClimbableDragDrop(EntityUid uid, ClimbableComponent component, ref DragDropTargetEvent args)
|
||||||
{
|
{
|
||||||
TryMoveEntity(component, args.User, args.Dragged, uid);
|
TryClimb(args.User, args.Dragged, uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryMoveEntity(ClimbableComponent component, EntityUid user, EntityUid entityToMove,
|
public void TryClimb(EntityUid user,
|
||||||
EntityUid climbable)
|
EntityUid entityToMove,
|
||||||
|
EntityUid climbable,
|
||||||
|
ClimbableComponent? comp = null,
|
||||||
|
ClimbingComponent? climbing = null)
|
||||||
{
|
{
|
||||||
if (!TryComp(entityToMove, out ClimbingComponent? climbingComponent) || climbingComponent.IsClimbing)
|
if (!Resolve(climbable, ref comp) || !Resolve(entityToMove, ref climbing))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var args = new DoAfterArgs(user, component.ClimbDelay, new ClimbDoAfterEvent(), entityToMove, target: climbable, used: entityToMove)
|
// Note, IsClimbing does not mean a DoAfter is active, it means the target has already finished a DoAfter and
|
||||||
|
// is currently on top of something..
|
||||||
|
if (climbing.IsClimbing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var args = new DoAfterArgs(user, comp.ClimbDelay, new ClimbDoAfterEvent(), entityToMove, target: climbable, used: entityToMove)
|
||||||
{
|
{
|
||||||
BreakOnTargetMove = true,
|
BreakOnTargetMove = true,
|
||||||
BreakOnUserMove = true,
|
BreakOnUserMove = true,
|
||||||
|
|||||||
@@ -121,5 +121,8 @@ public sealed class SlipAttemptEvent : CancellableEntityEventArgs, IInventoryRel
|
|||||||
public SlotFlags TargetSlots { get; } = SlotFlags.FEET;
|
public SlotFlags TargetSlots { get; } = SlotFlags.FEET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is raised directed at an entity that CAUSED some other entity to slip (e.g., the banana peel).
|
||||||
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct SlipEvent(EntityUid Slipped);
|
public readonly record struct SlipEvent(EntityUid Slipped);
|
||||||
|
|||||||
Reference in New Issue
Block a user