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.MapUid = mapManager.GetMapEntityId(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 plating = tileDefinitionManager["Plating"];
|
||||
var platingTile = new Tile(plating.TileId);
|
||||
@@ -793,6 +794,7 @@ public sealed class PoolSettings
|
||||
public sealed class TestMapData
|
||||
{
|
||||
public EntityUid MapUid { get; set; }
|
||||
public EntityUid GridUid { get; set; }
|
||||
public MapId MapId { get; set; }
|
||||
public MapGridComponent MapGrid { 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.
|
||||
public abstract partial class InteractionTest
|
||||
{
|
||||
protected const string PlayerEntity = "AdminObserver";
|
||||
|
||||
// Tiles
|
||||
protected const string Floor = "FloorSteel";
|
||||
protected const string FloorItem = "FloorTileItemSteel";
|
||||
|
||||
@@ -8,10 +8,15 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Chemistry.UI;
|
||||
using Content.Client.Construction;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Gravity;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Item;
|
||||
using NUnit.Framework;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
@@ -84,8 +89,10 @@ public abstract partial class InteractionTest
|
||||
/// <summary>
|
||||
/// Spawn an entity entity and set it as the target.
|
||||
/// </summary>
|
||||
[MemberNotNull(nameof(Target))]
|
||||
protected async Task SpawnTarget(string prototype)
|
||||
{
|
||||
Target = EntityUid.Invalid;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
Target = SEntMan.SpawnEntity(prototype, TargetCoords);
|
||||
@@ -493,6 +500,19 @@ public abstract partial class InteractionTest
|
||||
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
|
||||
|
||||
#region Entity lookups
|
||||
@@ -669,13 +689,20 @@ public abstract partial class InteractionTest
|
||||
await RunTicks(5);
|
||||
}
|
||||
|
||||
#region Time/Tick managment
|
||||
|
||||
protected async Task RunTicks(int ticks)
|
||||
{
|
||||
await PoolManager.RunTicksSync(PairTracker.Pair, ticks);
|
||||
}
|
||||
|
||||
protected int SecondsToTicks(float seconds)
|
||||
=> (int) Math.Ceiling(seconds / TickPeriod);
|
||||
|
||||
protected async Task RunSeconds(float seconds)
|
||||
=> await RunTicks((int) Math.Ceiling(seconds / TickPeriod));
|
||||
=> await RunTicks(SecondsToTicks(seconds));
|
||||
|
||||
#endregion
|
||||
|
||||
#region BUI
|
||||
/// <summary>
|
||||
@@ -723,9 +750,6 @@ public abstract partial class InteractionTest
|
||||
return false;
|
||||
}
|
||||
|
||||
var first = ui.Interfaces.First();
|
||||
|
||||
|
||||
bui = ui.Interfaces.FirstOrDefault(x => x.UiKey.Equals(key));
|
||||
if (bui == null)
|
||||
{
|
||||
@@ -878,4 +902,110 @@ public abstract partial class InteractionTest
|
||||
}
|
||||
|
||||
#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 NUnit.Framework;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.UnitTesting;
|
||||
@@ -34,6 +37,8 @@ namespace Content.IntegrationTests.Tests.Interaction;
|
||||
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
|
||||
public abstract partial class InteractionTest
|
||||
{
|
||||
protected virtual string PlayerPrototype => "AdminObserver";
|
||||
|
||||
protected PairTracker PairTracker = default!;
|
||||
protected TestMapData MapData = default!;
|
||||
|
||||
@@ -59,6 +64,9 @@ public abstract partial class InteractionTest
|
||||
/// </summary>
|
||||
protected EntityUid Player;
|
||||
|
||||
protected ICommonSession ClientSession = default!;
|
||||
protected IPlayerSession ServerSession = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The current target entity. This is the default entity for various helper functions.
|
||||
/// </summary>
|
||||
@@ -79,7 +87,7 @@ public abstract partial class InteractionTest
|
||||
protected ITileDefinitionManager TileMan = default!;
|
||||
protected IMapManager MapMan = default!;
|
||||
protected IPrototypeManager ProtoMan = default!;
|
||||
protected IGameTiming Timing = default!;
|
||||
protected IGameTiming STiming = default!;
|
||||
protected IComponentFactory Factory = default!;
|
||||
protected SharedHandsSystem HandSys = default!;
|
||||
protected StackSystem Stack = default!;
|
||||
@@ -92,16 +100,20 @@ public abstract partial class InteractionTest
|
||||
|
||||
// CLIENT dependencies
|
||||
protected IEntityManager CEntMan = default!;
|
||||
protected IGameTiming CTiming = default!;
|
||||
protected IUserInterfaceManager UiMan = default!;
|
||||
protected IInputManager InputManager = default!;
|
||||
protected InputSystem InputSystem = default!;
|
||||
protected ConstructionSystem CConSys = default!;
|
||||
protected ExamineSystem ExamineSys = default!;
|
||||
protected InteractionTestSystem CTestSystem = default!;
|
||||
protected UserInterfaceSystem CUISystem = default!;
|
||||
|
||||
// player components
|
||||
protected HandsComponent Hands = default!;
|
||||
protected DoAfterComponent DoAfters = default!;
|
||||
|
||||
public float TickPeriod => (float)Timing.TickPeriod.TotalSeconds;
|
||||
public float TickPeriod => (float)STiming.TickPeriod.TotalSeconds;
|
||||
|
||||
[SetUp]
|
||||
public virtual async Task Setup()
|
||||
@@ -114,7 +126,7 @@ public abstract partial class InteractionTest
|
||||
MapMan = Server.ResolveDependency<IMapManager>();
|
||||
ProtoMan = Server.ResolveDependency<IPrototypeManager>();
|
||||
Factory = Server.ResolveDependency<IComponentFactory>();
|
||||
Timing = Server.ResolveDependency<IGameTiming>();
|
||||
STiming = Server.ResolveDependency<IGameTiming>();
|
||||
HandSys = SEntMan.System<SharedHandsSystem>();
|
||||
InteractSys = SEntMan.System<SharedInteractionSystem>();
|
||||
ToolSys = SEntMan.System<ToolSystem>();
|
||||
@@ -127,6 +139,9 @@ public abstract partial class InteractionTest
|
||||
// client dependencies
|
||||
CEntMan = Client.ResolveDependency<IEntityManager>();
|
||||
UiMan = Client.ResolveDependency<IUserInterfaceManager>();
|
||||
CTiming = Client.ResolveDependency<IGameTiming>();
|
||||
InputManager = Client.ResolveDependency<IInputManager>();
|
||||
InputSystem = CEntMan.System<InputSystem>();
|
||||
CTestSystem = CEntMan.System<InteractionTestSystem>();
|
||||
CConSys = CEntMan.System<ConstructionSystem>();
|
||||
ExamineSys = CEntMan.System<ExamineSystem>();
|
||||
@@ -142,16 +157,16 @@ public abstract partial class InteractionTest
|
||||
var cPlayerMan = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
|
||||
if (cPlayerMan.LocalPlayer?.Session == null)
|
||||
Assert.Fail("No player");
|
||||
var cSession = cPlayerMan.LocalPlayer!.Session!;
|
||||
var sSession = sPlayerMan.GetSessionByUserId(cSession.UserId);
|
||||
ClientSession = cPlayerMan.LocalPlayer!.Session!;
|
||||
ServerSession = sPlayerMan.GetSessionByUserId(ClientSession.UserId);
|
||||
|
||||
// Spawn player entity & attach
|
||||
EntityUid? old = default;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
old = cPlayerMan.LocalPlayer.ControlledEntity;
|
||||
Player = SEntMan.SpawnEntity(PlayerEntity, PlayerCoords);
|
||||
sSession.AttachToEntity(Player);
|
||||
Player = SEntMan.SpawnEntity(PlayerPrototype, PlayerCoords);
|
||||
ServerSession.AttachToEntity(Player);
|
||||
Hands = SEntMan.GetComponent<HandsComponent>(Player);
|
||||
DoAfters = SEntMan.GetComponent<DoAfterComponent>(Player);
|
||||
});
|
||||
@@ -189,7 +204,7 @@ public abstract partial class InteractionTest
|
||||
// Final player asserts/checks.
|
||||
await PoolManager.ReallyBeIdle(PairTracker.Pair, 5);
|
||||
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]
|
||||
|
||||
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
|
||||
{
|
||||
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>
|
||||
/// Test placing and cutting a single lattice.
|
||||
/// </summary>
|
||||
|
||||
@@ -93,23 +93,31 @@ public sealed class ClimbSystem : SharedClimbSystem
|
||||
// TODO VERBS ICON add a climbing icon?
|
||||
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")
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
EntityUid climbable)
|
||||
public void TryClimb(EntityUid user,
|
||||
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;
|
||||
|
||||
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,
|
||||
BreakOnUserMove = true,
|
||||
|
||||
@@ -121,5 +121,8 @@ public sealed class SlipAttemptEvent : CancellableEntityEventArgs, IInventoryRel
|
||||
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]
|
||||
public readonly record struct SlipEvent(EntityUid Slipped);
|
||||
|
||||
Reference in New Issue
Block a user