Add climb & slip tests (#15459)

This commit is contained in:
Leon Friedrich
2023-04-17 18:07:03 +12:00
committed by GitHub
parent 27fbc4e235
commit 8af149e61c
11 changed files with 360 additions and 106 deletions

View File

@@ -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; }

View 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));
}
}

View File

@@ -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();
}
}
}

View File

@@ -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";

View File

@@ -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
} }

View File

@@ -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]

View 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;
}
}

View 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);
}
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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);