ToggleNeedPower();
// Insert beaker
- await Interact("Beaker");
+ await InteractUsing("Beaker");
Assert.That(Hands.ActiveHandEntity, Is.Null);
// Open BUI
await StartConstruction(Computer);
// Initial interaction (ghost turns into real entity)
- await Interact(Steel, 5);
- ClientAssertPrototype(ComputerFrame, ClientTarget);
- Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()];
- ClientTarget = null;
+ await InteractUsing(Steel, 5);
+ ClientAssertPrototype(ComputerFrame, Target);
// Perform construction steps
await Interact(
await StartDeconstruction(ComputerId);
// Initial interaction turns id computer into generic computer
- await Interact(Screw);
+ await InteractUsing(Screw);
AssertPrototype(ComputerFrame);
// Perform deconstruction steps
await SpawnTarget(ComputerId);
// Initial interaction turns id computer into generic computer
- await Interact(Screw);
+ await InteractUsing(Screw);
AssertPrototype(ComputerFrame);
// Perform partial deconstruction steps
{
// Construct Grille
await StartConstruction(Grille);
- await Interact(Rod, 10);
- ClientAssertPrototype(Grille, ClientTarget);
-
- Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()];
+ await InteractUsing(Rod, 10);
+ ClientAssertPrototype(Grille, Target);
var grille = Target;
// Construct Window
await StartConstruction(Window);
- await Interact(Glass, 10);
- ClientAssertPrototype(Window, ClientTarget);
- Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()];
+ await InteractUsing(Glass, 10);
+ ClientAssertPrototype(Window, Target);
// Deconstruct Window
await Interact(Screw, Wrench);
// Deconstruct Grille
Target = grille;
- await Interact(Cut);
+ await InteractUsing(Cut);
AssertDeleted();
}
public async Task ConstructProtolathe()
{
await StartConstruction(MachineFrame);
- await Interact(Steel, 5);
- ClientAssertPrototype(Unfinished, ClientTarget);
- Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()];
+ await InteractUsing(Steel, 5);
+ ClientAssertPrototype(Unfinished, Target);
await Interact(Wrench, Cable);
AssertPrototype(MachineFrame);
await Interact(ProtolatheBoard, Bin1, Bin1, Manipulator1, Manipulator1, Beaker, Beaker, Screw);
AssertPrototype(MachineFrame);
// Change it into an autolathe
- await Interact("AutolatheMachineCircuitboard");
+ await InteractUsing("AutolatheMachineCircuitboard");
AssertPrototype(MachineFrame);
await Interact(Bin1, Bin1, Bin1, Manipulator1, Glass, Screw);
AssertPrototype("Autolathe");
// Open & close panel
Assert.That(comp.Open, Is.False);
- await Interact(Screw);
+ await InteractUsing(Screw);
Assert.That(comp.Open, Is.True);
- await Interact(Screw);
+ await InteractUsing(Screw);
Assert.That(comp.Open, Is.False);
// Interrupted DoAfters
- await Interact(Screw, awaitDoAfters: false);
+ await InteractUsing(Screw, awaitDoAfters: false);
await CancelDoAfters();
Assert.That(comp.Open, Is.False);
- await Interact(Screw);
+ await InteractUsing(Screw);
Assert.That(comp.Open, Is.True);
- await Interact(Screw, awaitDoAfters: false);
+ await InteractUsing(Screw, awaitDoAfters: false);
await CancelDoAfters();
Assert.That(comp.Open, Is.True);
- await Interact(Screw);
+ await InteractUsing(Screw);
Assert.That(comp.Open, Is.False);
}
}
{
await StartDeconstruction("Table");
Assert.That(Comp<PlaceableSurfaceComponent>().IsPlaceable);
- await Interact(Wrench);
+ await InteractUsing(Wrench);
AssertPrototype("TableFrame");
- await Interact(Wrench);
+ await InteractUsing(Wrench);
AssertDeleted();
await AssertEntityLookup((Steel, 1), (Rod, 2));
}
public async Task ConstructWall()
{
await StartConstruction(Wall);
- await Interact(Steel, 2);
+ await InteractUsing(Steel, 2);
Assert.That(Hands.ActiveHandEntity, Is.Null);
- ClientAssertPrototype(Girder, ClientTarget);
- Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()];
- await Interact(Steel, 2);
+ ClientAssertPrototype(Girder, Target);
+ await InteractUsing(Steel, 2);
Assert.That(Hands.ActiveHandEntity, Is.Null);
AssertPrototype(WallSolid);
}
public async Task DeconstructWall()
{
await StartDeconstruction(WallSolid);
- await Interact(Weld);
+ await InteractUsing(Weld);
AssertPrototype(Girder);
await Interact(Wrench, Screw);
AssertDeleted();
public async Task ConstructWindow()
{
await StartConstruction(Window);
- await Interact(Glass, 5);
- ClientAssertPrototype(Window, ClientTarget);
+ await InteractUsing(Glass, 5);
+ ClientAssertPrototype(Window, Target);
}
[Test]
public async Task ConstructReinforcedWindow()
{
await StartConstruction(RWindow);
- await Interact(RGlass, 5);
- ClientAssertPrototype(RWindow, ClientTarget);
+ await InteractUsing(RGlass, 5);
+ ClientAssertPrototype(RWindow, Target);
}
[Test]
Assert.That(comp.Damage.GetTotal(), Is.GreaterThan(FixedPoint2.Zero));
// Repair the entity
- await Interact(Weld);
+ await InteractUsing(Weld);
Assert.That(comp.Damage.GetTotal(), Is.EqualTo(FixedPoint2.Zero));
// Validate that we can still deconstruct the entity (i.e., that welding deconstruction is not blocked).
public async Task CancelWallDeconstruct()
{
await StartDeconstruction(WallConstruction.WallSolid);
- await Interact(Weld, awaitDoAfters: false);
+ await InteractUsing(Weld, awaitDoAfters: false);
// Failed do-after has no effect
await CancelDoAfters();
AssertPrototype(WallConstruction.WallSolid);
// Second attempt works fine
- await Interact(Weld);
+ await InteractUsing(Weld);
AssertPrototype(WallConstruction.Girder);
// Repeat for wrenching interaction
AssertAnchored();
- await Interact(Wrench, awaitDoAfters: false);
+ await InteractUsing(Wrench, awaitDoAfters: false);
await CancelDoAfters();
AssertAnchored();
AssertPrototype(WallConstruction.Girder);
- await Interact(Wrench);
+ await InteractUsing(Wrench);
AssertAnchored(false);
// Repeat for screwdriver interaction.
AssertExists();
- await Interact(Screw, awaitDoAfters: false);
+ await InteractUsing(Screw, awaitDoAfters: false);
await CancelDoAfters();
AssertExists();
- await Interact(Screw);
+ await InteractUsing(Screw);
AssertDeleted();
}
public async Task CancelWallConstruct()
{
await StartConstruction(WallConstruction.Wall);
- await Interact(Steel, 5, awaitDoAfters: false);
+ await InteractUsing(Steel, 5, awaitDoAfters: false);
await CancelDoAfters();
- await Interact(Steel, 5);
- ClientAssertPrototype(WallConstruction.Girder, ClientTarget);
- Target = CTestSystem.Ghosts[ClientTarget!.Value.GetHashCode()];
- await Interact(Steel, 5, awaitDoAfters: false);
+ await InteractUsing(Steel, 5);
+ ClientAssertPrototype(WallConstruction.Girder, Target);
+ await InteractUsing(Steel, 5, awaitDoAfters: false);
await CancelDoAfters();
AssertPrototype(WallConstruction.Girder);
- await Interact(Steel, 5);
+ await InteractUsing(Steel, 5);
AssertPrototype(WallConstruction.WallSolid);
}
public async Task CancelTilePry()
{
await SetTile(Floor);
- await Interact(Pry, awaitDoAfters: false);
+ await InteractUsing(Pry, awaitDoAfters: false);
await CancelDoAfters();
await AssertTile(Floor);
- await Interact(Pry);
+ await InteractUsing(Pry);
await AssertTile(Plating);
}
public async Task CancelRepeatedTilePry()
{
await SetTile(Floor);
- await Interact(Pry, awaitDoAfters: false);
+ await InteractUsing(Pry, awaitDoAfters: false);
await RunTicks(1);
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(1));
await AssertTile(Floor);
await AssertTile(Floor);
// Third do after will work fine
- await Interact(Pry);
+ await InteractUsing(Pry);
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0));
await AssertTile(Plating);
}
Assert.That(comp.IsWelded, Is.False);
- await Interact(Weld, awaitDoAfters: false);
+ await InteractUsing(Weld, awaitDoAfters: false);
await RunTicks(1);
Assert.Multiple(() =>
{
});
// Third do after will work fine
- await Interact(Weld);
+ await InteractUsing(Weld);
Assert.Multiple(() =>
{
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0));
});
// Repeat test for un-welding
- await Interact(Weld, awaitDoAfters: false);
+ await InteractUsing(Weld, awaitDoAfters: false);
await RunTicks(1);
Assert.Multiple(() =>
{
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0));
Assert.That(comp.IsWelded, Is.True);
});
- await Interact(Weld);
+ await InteractUsing(Weld);
Assert.Multiple(() =>
{
Assert.That(ActiveDoAfters.Count(), Is.EqualTo(0));
});
// Remove the key
- await Interact(Screw);
+ await InteractUsing(Screw);
Assert.Multiple(() =>
{
Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0));
await AssertEntityLookup(("EncryptionKeyCommon", 1));
// Re-insert a key.
- await Interact("EncryptionKeyCentCom");
+ await InteractUsing("EncryptionKeyCentCom");
Assert.Multiple(() =>
{
Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(1));
});
// cannot remove keys without opening panel
- await Interact(Pry);
+ await InteractUsing(Pry);
Assert.Multiple(() =>
{
Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.GreaterThan(0));
});
// Open panel
- await Interact(Screw);
+ await InteractUsing(Screw);
Assert.Multiple(() =>
{
Assert.That(panel.Open, Is.True);
});
// Now remove the keys
- await Interact(Pry);
+ await InteractUsing(Pry);
Assert.Multiple(() =>
{
Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0));
});
// Reinsert a key
- await Interact("EncryptionKeyCentCom");
+ await InteractUsing("EncryptionKeyCentCom");
Assert.Multiple(() =>
{
Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(1));
});
// Remove it again
- await Interact(Pry);
+ await InteractUsing(Pry);
Assert.Multiple(() =>
{
Assert.That(comp.KeyContainer.ContainedEntities, Has.Count.EqualTo(0));
// Prying again will start deconstructing the machine.
AssertPrototype("TelecomServerFilled");
- await Interact(Pry);
+ await InteractUsing(Pry);
AssertPrototype("MachineFrame");
}
}
public int Quantity;
/// <summary>
- /// If true, a check has been performed to see if the prototype ia an entity prototype with a stack component,
+ /// If true, a check has been performed to see if the prototype is an entity prototype with a stack component,
/// in which case the specifier was converted into a stack-specifier
/// </summary>
public bool Converted;
if (!ProtoMan.TryIndex<EntityPrototype>(spec.Prototype, out var entProto))
{
- Assert.Fail($"Unkown prototype: {spec.Prototype}");
+ Assert.Fail($"Unknown prototype: {spec.Prototype}");
return default;
}
/// <summary>
/// Convert an entity-uid to a matching entity specifier. Useful when doing entity lookups & checking that the
- /// right quantity of entities/materials werre produced. Returns null if passed an entity with a null prototype.
+ /// right quantity of entities/materials were produced. Returns null if passed an entity with a null prototype.
/// </summary>
protected EntitySpecifier? ToEntitySpecifier(EntityUid uid)
{
using Content.Shared.Gravity;
using Content.Shared.Item;
using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
return;
var comp = CEntMan.GetComponent<ConstructionGhostComponent>(clientTarget!.Value);
- ClientTarget = clientTarget;
- ConstructionGhostId = comp.Owner.Id;
+ Target = CEntMan.GetNetEntity(clientTarget.Value);
+ Assert.That(Target.Value.IsClientSide());
+ ConstructionGhostId = clientTarget.Value.GetHashCode();
});
await RunTicks(1);
/// <summary>
/// Place an entity prototype into the players hand. Deletes any currently held entity.
/// </summary>
- /// <remarks>
- /// Automatically enables welders.
- /// </remarks>
- protected async Task<NetEntity> PlaceInHands(string id, int quantity = 1, bool enableWelder = true)
+ /// <param name="id">The entity or stack prototype to spawn and place into the users hand</param>
+ /// <param name="quantity">The number of entities to spawn. If the prototype is a stack, this sets the stack count.</param>
+ /// <param name="enableToggleable">Whether or not to automatically enable any toggleable items</param>
+ protected async Task<NetEntity> PlaceInHands(string id, int quantity = 1, bool enableToggleable = true)
{
- return await PlaceInHands((id, quantity), enableWelder);
+ return await PlaceInHands((id, quantity), enableToggleable);
}
/// <summary>
/// Place an entity prototype into the players hand. Deletes any currently held entity.
/// </summary>
- /// <remarks>
- /// Automatically enables welders.
- /// </remarks>
- protected async Task<NetEntity> PlaceInHands(EntitySpecifier entity, bool enableWelder = true)
+ /// <param name="entity">The entity type & quantity to spawn and place into the users hand</param>
+ /// <param name="enableToggleable">Whether or not to automatically enable any toggleable items</param>
+ protected async Task<NetEntity> PlaceInHands(EntitySpecifier entity, bool enableToggleable = true)
{
if (Hands.ActiveHand == null)
{
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands));
// turn on welders
- if (enableWelder && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
+ if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
{
Assert.That(ItemToggleSys.TryActivate(item, playerEnt, itemToggle: itemToggle));
}
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item));
- if (enableWelder && itemToggle != null)
+ if (enableToggleable && itemToggle != null)
Assert.That(itemToggle.Activated);
return SEntMan.GetNetEntity(item);
/// <summary>
/// Place an entity prototype into the players hand and interact with the given entity (or target position)
/// </summary>
- /// <remarks>
- /// Empty strings imply empty hands.
- /// </remarks>
- protected async Task Interact(string id, int quantity = 1, bool shouldSucceed = true, bool awaitDoAfters = true)
+ /// <param name="id">The entity or stack prototype to spawn and place into the users hand</param>
+ /// <param name="quantity">The number of entities to spawn. If the prototype is a stack, this sets the stack count.</param>
+ /// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param>
+ protected async Task InteractUsing(string id, int quantity = 1, bool awaitDoAfters = true)
{
- await Interact((id, quantity), shouldSucceed, awaitDoAfters);
+ await InteractUsing((id, quantity), awaitDoAfters);
}
/// <summary>
- /// Place an entity prototype into the players hand and interact with the given entity (or target position)
+ /// Place an entity prototype into the players hand and interact with the given entity (or target position).
/// </summary>
- /// <remarks>
- /// Empty strings imply empty hands.
- /// </remarks>
- protected async Task Interact(EntitySpecifier entity, bool shouldSucceed = true, bool awaitDoAfters = true)
+ /// <param name="entity">The entity type & quantity to spawn and place into the users hand</param>
+ /// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param>
+ protected async Task InteractUsing(EntitySpecifier entity, bool awaitDoAfters = true)
{
// For every interaction, we will also examine the entity, just in case this breaks something, somehow.
// (e.g., servers attempt to assemble construction examine hints).
}
await PlaceInHands(entity);
- await Interact(shouldSucceed, awaitDoAfters);
+ await Interact(awaitDoAfters);
}
/// <summary>
/// Interact with an entity using the currently held entity.
/// </summary>
- protected async Task Interact(bool shouldSucceed = true, bool awaitDoAfters = true)
+ /// <param name="awaitDoAfters">Whether or not to wait for any do-afters to complete</param>
+ protected async Task Interact(bool awaitDoAfters = true)
{
- var clientTarget = ClientTarget;
-
- if ((clientTarget?.IsValid() != true || CEntMan.Deleted(clientTarget)) && (Target == null || Target.Value.IsValid()))
+ if (Target == null || !Target.Value.IsClientSide())
{
- await Server.WaitPost(() => InteractSys.UserInteraction(SEntMan.GetEntity(Player), SEntMan.GetCoordinates(TargetCoords), SEntMan.GetEntity(Target)));
- await RunTicks(1);
+ await Interact(Target, TargetCoords, awaitDoAfters);
+ return;
}
- else
- {
- // The entity is client-side, so attempt to start construction
- var clientEnt = ClientTarget ?? CEntMan.GetEntity(Target);
- await Client.WaitPost(() => CConSys.TryStartConstruction(clientEnt!.Value));
- await RunTicks(5);
- }
+ // The target is a client-side entity, so we will just attempt to start construction under the assumption that
+ // it is a construction ghost.
+
+ await Client.WaitPost(() => CConSys.TryStartConstruction(CTarget!.Value));
+ await RunTicks(5);
+
+ if (awaitDoAfters)
+ await AwaitDoAfters();
+
+ await CheckTargetChange();
+ }
+
+ /// <inheritdoc cref="Interact(EntityUid?,EntityCoordinates,bool)"/>
+ protected async Task Interact(NetEntity? target, NetCoordinates coordinates, bool awaitDoAfters = true)
+ {
+ Assert.That(SEntMan.TryGetEntity(target, out var sTarget) || target == null);
+ var coords = SEntMan.GetCoordinates(coordinates);
+ Assert.That(coords.IsValid(SEntMan));
+ await Interact(sTarget, coords, awaitDoAfters);
+ }
+
+ /// <summary>
+ /// Interact with an entity using the currently held entity.
+ /// </summary>
+ protected async Task Interact(EntityUid? target, EntityCoordinates coordinates, bool awaitDoAfters = true)
+ {
+ Assert.That(SEntMan.TryGetEntity(Player, out var player));
+
+ await Server.WaitPost(() => InteractSys.UserInteraction(player!.Value, coordinates, target));
+ await RunTicks(1);
+
+ if (awaitDoAfters)
+ await AwaitDoAfters();
+
+ await CheckTargetChange();
+ }
+
+ /// <summary>
+ /// Activate an entity.
+ /// </summary>
+ protected async Task Activate(NetEntity? target = null, bool awaitDoAfters = true)
+ {
+ target ??= Target;
+ Assert.That(target, Is.Not.Null);
+ Assert.That(SEntMan.TryGetEntity(target!.Value, out var sTarget));
+ Assert.That(SEntMan.TryGetEntity(Player, out var player));
+
+ await Server.WaitPost(() => InteractSys.InteractionActivate(player!.Value, sTarget!.Value));
+ await RunTicks(1);
if (awaitDoAfters)
- await AwaitDoAfters(shouldSucceed);
+ await AwaitDoAfters();
- await CheckTargetChange(shouldSucceed && awaitDoAfters);
+ await CheckTargetChange();
}
/// <summary>
- /// Variant of <see cref="InteractUsing"/> that performs several interactions using different entities.
+ /// Variant of <see cref="InteractUsing(string,int,bool)"/> that performs several interactions using different entities.
+ /// Useful for quickly finishing multiple construction steps.
/// </summary>
/// <remarks>
/// Empty strings imply empty hands.
{
foreach (var spec in specifiers)
{
- await Interact(spec);
+ await InteractUsing(spec);
}
}
/// <summary>
/// Wait for any currently active DoAfters to finish.
/// </summary>
- protected async Task AwaitDoAfters(bool shouldSucceed = true, int maxExpected = 1)
+ protected async Task AwaitDoAfters(int maxExpected = 1)
{
if (!ActiveDoAfters.Any())
return;
await RunTicks(10);
}
- if (!shouldSucceed)
- return;
-
foreach (var doAfter in doAfters)
{
Assert.That(!doAfter.Cancelled);
}
+
+ await RunTicks(5);
}
/// <summary>
/// Check if the test's target entity has changed. E.g., construction interactions will swap out entities while
/// a structure is being built.
/// </summary>
- protected async Task CheckTargetChange(bool shouldSucceed)
+ protected async Task CheckTargetChange()
{
if (Target == null)
return;
- var target = Target.Value;
+ var originalTarget = Target.Value;
await RunTicks(5);
- if (ClientTarget != null && CEntMan.IsClientSide(ClientTarget.Value))
+ if (Target.Value.IsClientSide() && CTestSystem.Ghosts.TryGetValue(ConstructionGhostId, out var newWeh))
{
- Assert.That(CEntMan.Deleted(ClientTarget.Value), Is.EqualTo(shouldSucceed),
- $"Construction ghost was {(shouldSucceed ? "not deleted" : "deleted")}.");
-
- if (shouldSucceed)
- {
- Assert.That(CTestSystem.Ghosts.TryGetValue(ConstructionGhostId, out var newWeh),
- $"Failed to get construction entity from ghost Id");
-
- await Client.WaitPost(() => CLogger.Debug($"Construction ghost {ConstructionGhostId} became entity {newWeh}"));
- Target = newWeh;
- }
+ CLogger.Debug($"Construction ghost {ConstructionGhostId} became entity {newWeh}");
+ Target = newWeh;
}
if (STestSystem.EntChanges.TryGetValue(Target.Value, out var newServerWeh))
{
- await Server.WaitPost(
- () => SLogger.Debug($"Construction entity {Target.Value} changed to {newServerWeh}"));
-
+ SLogger.Debug($"Construction entity {Target.Value} changed to {newServerWeh}");
Target = newServerWeh;
}
- if (Target != target)
- await CheckTargetChange(shouldSucceed);
+ if (Target != originalTarget)
+ await CheckTargetChange();
}
#region Asserts
return;
}
- var meta = SEntMan.GetComponent<MetaDataComponent>(SEntMan.GetEntity(target.Value));
+ var meta = CEntMan.GetComponent<MetaDataComponent>(CEntMan.GetEntity(target.Value));
Assert.That(meta.EntityPrototype?.ID, Is.EqualTo(prototype));
}
- protected void ClientAssertPrototype(string? prototype, EntityUid? target)
- {
- var netEnt = CTestSystem.Ghosts[target.GetHashCode()];
- AssertPrototype(prototype, netEnt);
- }
-
protected void AssertPrototype(string? prototype, NetEntity? target = null)
{
target ??= Target;
protected IEnumerable<Shared.DoAfter.DoAfter> ActiveDoAfters
=> DoAfters.DoAfters.Values.Where(x => !x.Cancelled && !x.Completed);
+ #region Component
+
/// <summary>
/// Convenience method to get components on the target. Returns SERVER-SIDE components.
/// </summary>
if (target == null)
Assert.Fail("No target specified");
- return SEntMan.GetComponent<T>(SEntMan.GetEntity(target!.Value));
+ return SEntMan.GetComponent<T>(ToServer(target!.Value));
+ }
+
+ /// <inheritdoc cref="Comp{T}"/>
+ protected bool TryComp<T>(NetEntity? target, [NotNullWhen(true)] out T? comp) where T : IComponent
+ {
+ return SEntMan.TryGetComponent(ToServer(target), out comp);
+ }
+
+ /// <inheritdoc cref="Comp{T}"/>
+ protected bool TryComp<T>([NotNullWhen(true)] out T? comp) where T : IComponent
+ {
+ return SEntMan.TryGetComponent(STarget, out comp);
}
+ #endregion
+
/// <summary>
/// Set the tile at the target position to some prototype.
/// </summary>
return true;
}
+ protected bool IsUiOpen(Enum key)
+ {
+ if (!TryComp(Player, out UserInterfaceUserComponent? user))
+ return false;
+
+ foreach (var keys in user.OpenInterfaces.Values)
+ {
+ if (keys.Contains(key))
+ return true;
+ }
+
+ return false;
+ }
+
#endregion
#region UI
/// <summary>
- /// Presses and releases a button on some client-side window. Will fail if the button cannot be found.
+ /// Attempts to find, and then presses and releases a control on some client-side window.
+ /// Will fail if the control cannot be found.
+ /// </summary>
+ protected async Task ClickControl<TWindow, TControl>(string name, BoundKeyFunction? function = null)
+ where TWindow : BaseWindow
+ where TControl : Control
+ {
+ var window = GetWindow<TWindow>();
+ var control = GetControlFromField<TControl>(name, window);
+ await ClickControl(control, function);
+ }
+
+ /// <summary>
+ /// Attempts to find, and then presses and releases a control on some client-side widget.
+ /// Will fail if the control cannot be found.
/// </summary>
- protected async Task ClickControl<TWindow>(string name) where TWindow : BaseWindow
+ protected async Task ClickWidgetControl<TWidget, TControl>(string name, BoundKeyFunction? function = null)
+ where TWidget : UIWidget, new()
+ where TControl : Control
+ {
+ var widget = GetWidget<TWidget>();
+ var control = GetControlFromField<TControl>(name, widget);
+ await ClickControl(control, function);
+ }
+
+ /// <inheritdoc cref="ClickControl{TWindow,TControl}"/>
+ protected async Task ClickControl<TWindow>(string name, BoundKeyFunction? function = null)
+ where TWindow : BaseWindow
{
- await ClickControl(GetControl<TWindow, Control>(name));
+ await ClickControl<TWindow, Control>(name, function);
+ }
+
+ /// <inheritdoc cref="ClickWidgetControl{TWidget,TControl}"/>
+ protected async Task ClickWidgetControl<TWidget>(string name, BoundKeyFunction? function = null)
+ where TWidget : UIWidget, new()
+ {
+ await ClickWidgetControl<TWidget, Control>(name, function);
}
/// <summary>
- /// Simulates a click and release at the center of some UI Constrol.
+ /// Simulates a click and release at the center of some UI control.
/// </summary>
- protected async Task ClickControl(Control control)
+ protected async Task ClickControl(Control control, BoundKeyFunction? function = null)
{
+ function ??= EngineKeyFunctions.UIClick;
var screenCoords = new ScreenCoordinates(
control.GlobalPixelPosition + control.PixelSize / 2,
control.Window?.Id ?? default);
var relativePixelPos = screenCoords.Position - control.GlobalPixelPosition;
var args = new GUIBoundKeyEventArgs(
- EngineKeyFunctions.UIClick,
+ function.Value,
BoundKeyState.Down,
screenCoords,
default,
await RunTicks(1);
args = new GUIBoundKeyEventArgs(
- EngineKeyFunctions.UIClick,
+ function.Value,
BoundKeyState.Up,
screenCoords,
default,
}
/// <summary>
- /// Attempts to find a control on some client-side window. Will fail if the control cannot be found.
+ /// Attempt to retrieve a control by looking for a field on some other control.
/// </summary>
- protected TControl GetControl<TWindow, TControl>(string name)
- where TWindow : BaseWindow
+ /// <remarks>
+ /// Will fail if the control cannot be found.
+ /// </remarks>
+ protected TControl GetControlFromField<TControl>(string name, Control parent)
where TControl : Control
- {
- var control = GetControl<TWindow>(name);
- Assert.That(control.GetType().IsAssignableTo(typeof(TControl)));
- return (TControl) control;
- }
-
- protected Control GetControl<TWindow>(string name) where TWindow : BaseWindow
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
- var field = typeof(TWindow).GetField(name, flags);
- var prop = typeof(TWindow).GetProperty(name, flags);
+ var parentType = parent.GetType();
+ var field = parentType.GetField(name, flags);
+ var prop = parentType.GetProperty(name, flags);
if (field == null && prop == null)
{
- Assert.Fail($"Window {typeof(TWindow).Name} does not have a field or property named {name}");
+ Assert.Fail($"Window {parentType.Name} does not have a field or property named {name}");
return default!;
}
- var window = GetWindow<TWindow>();
- var fieldOrProp = field?.GetValue(window) ?? prop?.GetValue(window);
+ var fieldOrProp = field?.GetValue(parent) ?? prop?.GetValue(parent);
if (fieldOrProp is not Control control)
{
return default!;
}
- return control;
+ Assert.That(control.GetType().IsAssignableTo(typeof(TControl)));
+ return (TControl) control;
+ }
+
+ /// <summary>
+ /// Attempt to retrieve a control that matches some predicate by iterating through a control's children.
+ /// </summary>
+ /// <remarks>
+ /// Will fail if the control cannot be found.
+ /// </remarks>
+ protected TControl GetControlFromChildren<TControl>(Func<TControl, bool> predicate, Control parent, bool recursive = true)
+ where TControl : Control
+ {
+ if (TryGetControlFromChildren(predicate, parent, out var control, recursive))
+ return control;
+
+ Assert.Fail($"Failed to find a {nameof(TControl)} that satisfies the predicate in {parent.Name}");
+ return default!;
+ }
+
+ /// <summary>
+ /// Attempt to retrieve a control of a given type by iterating through a control's children.
+ /// </summary>
+ protected TControl GetControlFromChildren<TControl>(Control parent, bool recursive = false)
+ where TControl : Control
+ {
+ return GetControlFromChildren<TControl>(static _ => true, parent, recursive);
+ }
+
+ /// <summary>
+ /// Attempt to retrieve a control that matches some predicate by iterating through a control's children.
+ /// </summary>
+ protected bool TryGetControlFromChildren<TControl>(
+ Func<TControl, bool> predicate,
+ Control parent,
+ [NotNullWhen(true)] out TControl? control,
+ bool recursive = true)
+ where TControl : Control
+ {
+ foreach (var ctrl in parent.Children)
+ {
+ if (ctrl is TControl cast && predicate(cast))
+ {
+ control = cast;
+ return true;
+ }
+
+ if (recursive && TryGetControlFromChildren(predicate, ctrl, out control))
+ return true;
+ }
+
+ control = null;
+ return false;
}
/// <summary>
return window != null;
}
-
/// <summary>
/// Attempts to find a currently open client-side window.
/// </summary>
return window != null;
}
+
+ /// <summary>
+ /// Attempts to find client-side UI widget.
+ /// </summary>
+ protected UIWidget GetWidget<TWidget>()
+ where TWidget : UIWidget, new()
+ {
+ if (TryFindWidget(out TWidget? widget))
+ return widget;
+
+ Assert.Fail($"Could not find a {typeof(TWidget).Name} widget");
+ return default!;
+ }
+
+ /// <summary>
+ /// Attempts to find client-side UI widget.
+ /// </summary>
+ private bool TryFindWidget<TWidget>([NotNullWhen(true)] out TWidget? uiWidget)
+ where TWidget : UIWidget, new()
+ {
+ uiWidget = null;
+ var screen = UiMan.ActiveScreen;
+ if (screen == null)
+ return false;
+
+ return screen.TryGetWidget(out uiWidget);
+ }
+
#endregion
#region Power
#nullable enable
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Content.Client.Construction;
using Content.Client.Examine;
+using Content.Client.Gameplay;
using Content.IntegrationTests.Pair;
using Content.Server.Body.Systems;
using Content.Server.Hands.Systems;
using Robust.Shared.Timing;
using Robust.UnitTesting;
using Content.Shared.Item.ItemToggle;
+using Robust.Client.State;
namespace Content.IntegrationTests.Tests.Interaction;
/// The player entity that performs all these interactions. Defaults to an admin-observer with 1 hand.
/// </summary>
protected NetEntity Player;
-
- protected EntityUid SPlayer => ToServer(Player);
- protected EntityUid CPlayer => ToClient(Player);
+ protected EntityUid SPlayer;
+ protected EntityUid CPlayer;
protected ICommonSession ClientSession = default!;
protected ICommonSession ServerSession = default!;
- public EntityUid? ClientTarget;
-
/// <summary>
/// The current target entity. This is the default entity for various helper functions.
/// </summary>
protected InteractionTestSystem STestSystem = default!;
protected SharedTransformSystem Transform = default!;
protected ISawmill SLogger = default!;
+ protected SharedUserInterfaceSystem SUiSys = default!;
// CLIENT dependencies
protected IEntityManager CEntMan = default!;
protected ExamineSystem ExamineSys = default!;
protected InteractionTestSystem CTestSystem = default!;
protected ISawmill CLogger = default!;
+ protected SharedUserInterfaceSystem CUiSys = default!;
// player components
protected HandsComponent Hands = default!;
STestSystem = SEntMan.System<InteractionTestSystem>();
Stack = SEntMan.System<StackSystem>();
SLogger = Server.ResolveDependency<ILogManager>().RootSawmill;
+ SUiSys = Client.System<SharedUserInterfaceSystem>();
// client dependencies
CEntMan = Client.ResolveDependency<IEntityManager>();
CConSys = CEntMan.System<ConstructionSystem>();
ExamineSys = CEntMan.System<ExamineSystem>();
CLogger = Client.ResolveDependency<ILogManager>().RootSawmill;
+ CUiSys = Client.System<SharedUserInterfaceSystem>();
// Setup map.
await Pair.CreateTestMap();
old = cPlayerMan.LocalEntity;
Player = SEntMan.GetNetEntity(SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords)));
- var serverPlayerEnt = SEntMan.GetEntity(Player);
- Server.PlayerMan.SetAttachedEntity(ServerSession, serverPlayerEnt);
- Hands = SEntMan.GetComponent<HandsComponent>(serverPlayerEnt);
- DoAfters = SEntMan.GetComponent<DoAfterComponent>(serverPlayerEnt);
+ SPlayer = SEntMan.GetEntity(Player);
+ Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer);
+ Hands = SEntMan.GetComponent<HandsComponent>(SPlayer);
+ DoAfters = SEntMan.GetComponent<DoAfterComponent>(SPlayer);
});
// Check player got attached.
await RunTicks(5);
- Assert.That(CEntMan.GetNetEntity(cPlayerMan.LocalEntity), Is.EqualTo(Player));
+ CPlayer = ToClient(Player);
+ Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(CPlayer));
// Delete old player entity.
await Server.WaitPost(() =>
}
});
+ // Change UI state to in-game.
+ var state = Client.ResolveDependency<IStateManager>();
+ await Client.WaitPost(() => state.RequestStateChange<GameplayState>());
+
// Final player asserts/checks.
await Pair.ReallyBeIdle(5);
Assert.Multiple(() =>
Target = SEntMan.GetNetEntity(await FindEntity("ModularGrenade"));
await Drop();
- await Interact(Cable);
+ await InteractUsing(Cable);
// Insert & remove trigger
AssertComp<OnUseTimerTriggerComponent>(false);
- await Interact(Trigger);
+ await InteractUsing(Trigger);
AssertComp<OnUseTimerTriggerComponent>();
await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false);
- await Interact(Pry);
+ await InteractUsing(Pry);
AssertComp<OnUseTimerTriggerComponent>(false);
// Trigger was dropped to floor, not deleted.
await FindEntity(Trigger, LookupFlags.Uncontained);
// Re-insert
- await Interact(Trigger);
+ await InteractUsing(Trigger);
AssertComp<OnUseTimerTriggerComponent>();
// Insert & remove payload.
- await Interact(Payload);
+ await InteractUsing(Payload);
await FindEntity(Payload, LookupFlags.Uncontained, shouldSucceed: false);
- await Interact(Pry);
+ await InteractUsing(Pry);
var ent = await FindEntity(Payload, LookupFlags.Uncontained);
await Delete(ent);
// successfully insert a second time
- await Interact(Payload);
+ await InteractUsing(Payload);
ent = await FindEntity(Payload);
var sys = SEntMan.System<SharedContainerSystem>();
Assert.That(sys.IsEntityInContainer(ent));
--- /dev/null
+using Content.Client.UserInterface.Systems.Hotbar.Widgets;
+using Content.Client.UserInterface.Systems.Storage.Controls;
+using Content.IntegrationTests.Tests.Interaction;
+using Content.Shared.Input;
+using Content.Shared.PDA;
+using Content.Shared.Storage;
+using Robust.Client.UserInterface;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+
+namespace Content.IntegrationTests.Tests.Storage;
+
+public sealed class StorageInteractionTest : InteractionTest
+{
+ /// <summary>
+ /// Check that players can interact with items in storage if the storage UI is open
+ /// </summary>
+ [Test]
+ public async Task UiInteractTest()
+ {
+ var sys = Server.System<SharedContainerSystem>();
+
+ await SpawnTarget("ClothingBackpack");
+ var backpack = ToServer(Target);
+
+ // Initially no BUI is open.
+ Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False);
+ Assert.That(IsUiOpen(PdaUiKey.Key), Is.False);
+
+ // Activating the backpack opens the UI
+ await Activate();
+ Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
+ Assert.That(IsUiOpen(PdaUiKey.Key), Is.False);
+
+ // Pick up a PDA
+ var pda = await PlaceInHands("PassengerPDA");
+ var sPda = ToServer(pda);
+ Assert.That(sys.IsEntityInContainer(sPda), Is.True);
+ Assert.That(sys.TryGetContainingContainer((sPda, null), out var container));
+ Assert.That(container!.Owner, Is.EqualTo(SPlayer));
+
+ // Insert the PDA into the backpack
+ await Interact();
+ Assert.That(sys.TryGetContainingContainer((sPda, null), out container));
+ Assert.That(container!.Owner, Is.EqualTo(backpack));
+
+ // Use "e" / ActivateInWorld to open the PDA UI while it is still in the backpack.
+ var ctrl = GetStorageControl(pda);
+ await ClickControl(ctrl, ContentKeyFunctions.ActivateItemInWorld);
+ await RunTicks(10);
+ Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
+ Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
+
+ // Click on the pda to pick it up and remove it from the backpack.
+ await ClickControl(ctrl, ContentKeyFunctions.MoveStoredItem);
+ await RunTicks(10);
+ Assert.That(sys.TryGetContainingContainer((sPda, null), out container));
+ Assert.That(container!.Owner, Is.EqualTo(SPlayer));
+
+ // UIs should still be open
+ Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
+ Assert.That(IsUiOpen(PdaUiKey.Key), Is.True);
+ }
+
+ /// <summary>
+ /// Retrieve the control that corresponds to the given entity in the currently open storage UI.
+ /// </summary>
+ private ItemGridPiece GetStorageControl(NetEntity target)
+ {
+ var uid = ToClient(target);
+ var hotbar = GetWidget<HotbarGui>();
+ var storageContainer = GetControlFromField<Control>(nameof(HotbarGui.StorageContainer), hotbar);
+ return GetControlFromChildren<ItemGridPiece>(c => c.Entity == uid, storageContainer);
+ }
+}
await AssertTile(Plating, PlayerCoords);
AssertGridCount(1);
await SetTile(null);
- await Interact(Rod);
+ await InteractUsing(Rod);
await AssertTile(Lattice);
Assert.That(Hands.ActiveHandEntity, Is.Null);
- await Interact(Cut);
+ await InteractUsing(Cut);
await AssertTile(null);
await AssertEntityLookup((Rod, 1));
AssertGridCount(1);
// Place Lattice
var oldPos = TargetCoords;
TargetCoords = SEntMan.GetNetCoordinates(new EntityCoordinates(MapData.MapUid, 1, 0));
- await Interact(Rod);
+ await InteractUsing(Rod);
TargetCoords = oldPos;
await AssertTile(Lattice);
AssertGridCount(1);
// Cut lattice
Assert.That(Hands.ActiveHandEntity, Is.Null);
- await Interact(Cut);
+ await InteractUsing(Cut);
await AssertTile(null);
AssertGridCount(0);
// Space -> Lattice
var oldPos = TargetCoords;
TargetCoords = SEntMan.GetNetCoordinates(new EntityCoordinates(MapData.MapUid, 1, 0));
- await Interact(Rod);
+ await InteractUsing(Rod);
TargetCoords = oldPos;
await AssertTile(Lattice);
AssertGridCount(1);
// Lattice -> Plating
- await Interact(Steel);
+ await InteractUsing(Steel);
Assert.That(Hands.ActiveHandEntity, Is.Null);
await AssertTile(Plating);
AssertGridCount(1);
// Plating -> Tile
- await Interact(FloorItem);
+ await InteractUsing(FloorItem);
Assert.That(Hands.ActiveHandEntity, Is.Null);
await AssertTile(Floor);
AssertGridCount(1);
// Tile -> Plating
- await Interact(Pry);
+ await InteractUsing(Pry);
await AssertTile(Plating);
AssertGridCount(1);
Assert.That(comp.IsWelded, Is.False);
- await Interact(Weld);
+ await InteractUsing(Weld);
Assert.That(comp.IsWelded, Is.True);
AssertPrototype(Locker); // Prototype did not change.
}