* HandsGuiState * Gui state setting methods * code cleanup * Removes TryGetHands * ClientHand * Gui Hands * Refactor WIP 1 * Hand index * refactors 2 * wip 3 * wip 4 * wiip 4 * wip 5 * wip 6 * wip 7 * wip 8 * wip 9 * wip 11 * Hand ui mostly looks fine * hands gui cleanup 1 * cleanup 2 * wip 13 * hand enabled * stuff * Hands gui gap fix * onpressed test * hand gui buttons events work * bag activation works * fix item use * todo comment * hands activate fix * Moves Client Hands back to using strings to identify active hand * fixes action hand highlighting * diff fix * serverhand * SharedHand * SharedHand, IReadOnlyHand * Client Hands only stores SharedHand * cleanup server hands * server hand container shutdown * misc renames, refactors of serverhand * stuff 1 * stuff 3 * server hand refactor 1 * Undo API changes to remove massive diff * More API name fixes * server hands cleanup 2 * cleanup 3 * dropping cleanup * Cleanup 4 * MoveItemFromHand * Stuff * region sorting * Hand Putter methods cleanup * stuff 2 * Merges all of serverhand and clienthand into sharedhand * Other hands systems, hack to make inhands update (gui state set every frame, visualzier updated every frame) * GetFinalDropCoordinates cleanup * SwapHands cleanup * Moves server hands code to shared hands * Fixed hand selected and deselected * Naming fixes * Server hands system cleanup * Hands privacy fixes * Client hand updates when containers are modified * HeldItemVisualizer * Fixes hand gui item status panel * method name fix * Swap hands prediction * Dropping prediction * Fixes pickup entity animation * Fixes HeldItemsVisualizer * moves item pickup to shared * PR cleanup * fixes hand enabling/disabling * build fix * Conflict fixes * Fixes pickup animation * Uses component directed message subscriptions * event unsubscriptions in hand system * unsubscribe fix * CanInsertEntityIntoHand checks if hand is enabled * Moving items from one hand to another checks if the hands can pick up and drop * Fixes stop pulling not re-enabling hand * Fixes pickup animation for entities containers on the floor * Fixes using held items * Fixes multiple hands guis appearing * test fix * removes obsolete system sunsubscribes * Checks IsFirstTimePredicted before playing drop animation * fixes hand item deleted crash * Uses Get to get other system * Replaces AppearanceComponent with SharedAppearanceComponent * Replaces EnsureComponent with TryGetComponent * Improves event class names * Moves property up to top of class * Moves code for determining the hand visualizer rsi state into the visualizer instead of being determined on hand component * Eventbus todo comment * Yaml fix for changed visualizer name * Makes HandsVisuals a byte * Removes state from HandsVisualizer * Fixes hand using interaction method name * Namespace changes fixes * Fix for changed hand interaction method * missing } * conflict build fix * Moves cleint HandsSystem to correct folder * Moved namespace fix for interaction test * Moves Handsvisualizer ot correct folder * Moves SharedHandsSystem to correct folder * Fixes errors from moving namespace of hand systems * Fixes PDA component changes * Fixes ActionsComponent diff * Fixes inventory component diff * fixes null ref * Replaces obsolete Loc.GetString usage with fluent translations * Fluent for hands disarming * SwapHands and Drop user input specify to the server which hand * Pickup animation WorldPosiiton todo * Cleans up hands gui subscription handling * Fixes change in ActionBlockerSystem access * Namespace references fixes * HandsComponent PlayerAttached/Detached messages are handled through eventbus * Fixes GasCanisterSystem drop method usage * Fix gameticker equipping method at new location Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
377 lines
18 KiB
C#
377 lines
18 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Content.Client.Actions;
|
|
using Content.Client.Actions.UI;
|
|
using Content.Client.UserInterface;
|
|
using Content.Server.Actions;
|
|
using Content.Server.Hands.Components;
|
|
using Content.Server.Items;
|
|
using Content.Shared.Actions;
|
|
using Content.Shared.Actions.Components;
|
|
using Content.Shared.Actions.Prototypes;
|
|
using Content.Shared.Cooldown;
|
|
using NUnit.Framework;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
using IPlayerManager = Robust.Server.Player.IPlayerManager;
|
|
|
|
namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
|
{
|
|
[TestFixture]
|
|
[TestOf(typeof(SharedActionsComponent))]
|
|
[TestOf(typeof(ClientActionsComponent))]
|
|
[TestOf(typeof(ServerActionsComponent))]
|
|
[TestOf(typeof(ItemActionsComponent))]
|
|
public class ActionsComponentTests : ContentIntegrationTest
|
|
{
|
|
const string Prototypes = @"
|
|
- type: entity
|
|
name: flashlight
|
|
parent: BaseItem
|
|
id: TestFlashlight
|
|
components:
|
|
- type: HandheldLight
|
|
- type: ItemActions
|
|
actions:
|
|
- actionType: ToggleLight
|
|
- type: PowerCellSlot
|
|
- type: Sprite
|
|
sprite: Objects/Tools/flashlight.rsi
|
|
layers:
|
|
- state: lantern_off
|
|
- state: HandheldLightOnOverlay
|
|
shader: unshaded
|
|
visible: false
|
|
- type: Item
|
|
sprite: Objects/Tools/flashlight.rsi
|
|
HeldPrefix: off
|
|
- type: PointLight
|
|
enabled: false
|
|
radius: 3
|
|
- type: LoopingSound
|
|
- type: Appearance
|
|
visuals:
|
|
- type: FlashLightVisualizer
|
|
";
|
|
|
|
[Test]
|
|
public async Task GrantsAndRevokesActionsTest()
|
|
{
|
|
var (client, server) = await StartConnectedServerClientPair();
|
|
|
|
await server.WaitIdleAsync();
|
|
await client.WaitIdleAsync();
|
|
|
|
var serverPlayerManager = server.ResolveDependency<IPlayerManager>();
|
|
var innateActions = new List<ActionType>();
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
var player = serverPlayerManager.GetAllPlayers().Single();
|
|
var playerEnt = player.AttachedEntity;
|
|
var actionsComponent = playerEnt!.GetComponent<ServerActionsComponent>();
|
|
|
|
// player should begin with their innate actions granted
|
|
innateActions.AddRange(actionsComponent.InnateActions);
|
|
foreach (var innateAction in actionsComponent.InnateActions)
|
|
{
|
|
Assert.That(actionsComponent.TryGetActionState(innateAction, out var innateState));
|
|
Assert.That(innateState.Enabled);
|
|
}
|
|
Assert.That(innateActions.Count, Is.GreaterThan(0));
|
|
|
|
actionsComponent.Grant(ActionType.DebugInstant);
|
|
Assert.That(actionsComponent.TryGetActionState(ActionType.HumanScream, out var state) && state.Enabled);
|
|
});
|
|
|
|
// check that client has the actions
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
|
|
var clientPlayerMgr = client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
|
|
var clientUIMgr = client.ResolveDependency<IUserInterfaceManager>();
|
|
var expectedOrder = new List<ActionType>();
|
|
|
|
await client.WaitAssertion(() =>
|
|
{
|
|
var local = clientPlayerMgr.LocalPlayer;
|
|
var controlled = local!.ControlledEntity;
|
|
var actionsComponent = controlled!.GetComponent<ClientActionsComponent>();
|
|
|
|
// we should have our innate actions and debug1.
|
|
foreach (var innateAction in innateActions)
|
|
{
|
|
Assert.That(actionsComponent.TryGetActionState(innateAction, out var innateState));
|
|
Assert.That(innateState.Enabled);
|
|
}
|
|
Assert.That(actionsComponent.TryGetActionState(ActionType.DebugInstant, out var state) && state.Enabled);
|
|
|
|
// innate actions should've auto-populated into our slots (in non-deterministic order),
|
|
// but debug1 should be in the last slot
|
|
var actionsUI =
|
|
clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is ActionsUI) as ActionsUI;
|
|
Assert.That(actionsUI, Is.Not.Null);
|
|
|
|
var expectedInnate = new HashSet<ActionType>(innateActions);
|
|
var expectEmpty = false;
|
|
expectedOrder.Clear();
|
|
foreach (var slot in actionsUI.Slots)
|
|
{
|
|
if (expectEmpty)
|
|
{
|
|
Assert.That(slot.HasAssignment, Is.False);
|
|
Assert.That(slot.Item, Is.Null);
|
|
Assert.That(slot.Action, Is.Null);
|
|
Assert.That(slot.ActionEnabled, Is.False);
|
|
continue;
|
|
}
|
|
Assert.That(slot.HasAssignment);
|
|
// all the actions we gave so far are not tied to an item
|
|
Assert.That(slot.Item, Is.Null);
|
|
Assert.That(slot.Action, Is.Not.Null);
|
|
Assert.That(slot.ActionEnabled);
|
|
var asAction = slot.Action as ActionPrototype;
|
|
Assert.That(asAction, Is.Not.Null);
|
|
expectedOrder.Add(asAction.ActionType);
|
|
|
|
if (expectedInnate.Count != 0)
|
|
{
|
|
Assert.That(expectedInnate.Remove(asAction.ActionType));
|
|
}
|
|
else
|
|
{
|
|
Assert.That(asAction.ActionType, Is.EqualTo(ActionType.DebugInstant));
|
|
Assert.That(slot.Cooldown, Is.Null);
|
|
expectEmpty = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
// now revoke the action and check that the client sees it as revoked
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
var player = serverPlayerManager.GetAllPlayers().Single();
|
|
var playerEnt = player.AttachedEntity;
|
|
var actionsComponent = playerEnt!.GetComponent<ServerActionsComponent>();
|
|
actionsComponent.Revoke(ActionType.DebugInstant);
|
|
});
|
|
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
|
|
await client.WaitAssertion(() =>
|
|
{
|
|
var local = clientPlayerMgr.LocalPlayer;
|
|
var controlled = local!.ControlledEntity;
|
|
var actionsComponent = controlled!.GetComponent<ClientActionsComponent>();
|
|
|
|
// we should have our innate actions, but debug1 should be revoked
|
|
foreach (var innateAction in innateActions)
|
|
{
|
|
Assert.That(actionsComponent.TryGetActionState(innateAction, out var innateState));
|
|
Assert.That(innateState.Enabled);
|
|
}
|
|
Assert.That(actionsComponent.TryGetActionState(ActionType.DebugInstant, out _), Is.False);
|
|
|
|
// all actions should be in the same order as before, but the slot with DebugInstant should appear
|
|
// disabled.
|
|
var actionsUI =
|
|
clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is ActionsUI) as ActionsUI;
|
|
Assert.That(actionsUI, Is.Not.Null);
|
|
|
|
var idx = 0;
|
|
foreach (var slot in actionsUI.Slots)
|
|
{
|
|
if (idx < expectedOrder.Count)
|
|
{
|
|
var expected = expectedOrder[idx++];
|
|
Assert.That(slot.HasAssignment);
|
|
// all the actions we gave so far are not tied to an item
|
|
Assert.That(slot.Item, Is.Null);
|
|
Assert.That(slot.Action, Is.Not.Null);
|
|
var asAction = slot.Action as ActionPrototype;
|
|
Assert.That(asAction, Is.Not.Null);
|
|
Assert.That(expected, Is.EqualTo(asAction.ActionType));
|
|
|
|
if (asAction.ActionType == ActionType.DebugInstant)
|
|
{
|
|
Assert.That(slot.ActionEnabled, Is.False);
|
|
}
|
|
else
|
|
{
|
|
Assert.That(slot.ActionEnabled);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert.That(slot.HasAssignment, Is.False);
|
|
Assert.That(slot.Item, Is.Null);
|
|
Assert.That(slot.Action, Is.Null);
|
|
Assert.That(slot.ActionEnabled, Is.False);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task GrantsAndRevokesItemActions()
|
|
{
|
|
var serverOptions = new ServerIntegrationOptions { ExtraPrototypes = Prototypes };
|
|
var clientOptions = new ClientIntegrationOptions { ExtraPrototypes = Prototypes };
|
|
var (client, server) = await StartConnectedServerClientPair(serverOptions: serverOptions, clientOptions: clientOptions);
|
|
|
|
await server.WaitIdleAsync();
|
|
await client.WaitIdleAsync();
|
|
|
|
var serverPlayerManager = server.ResolveDependency<IPlayerManager>();
|
|
var serverEntManager = server.ResolveDependency<IEntityManager>();
|
|
var serverGameTiming = server.ResolveDependency<IGameTiming>();
|
|
|
|
var cooldown = Cooldowns.SecondsFromNow(30, serverGameTiming);
|
|
|
|
ServerActionsComponent serverActionsComponent = null;
|
|
ClientActionsComponent clientActionsComponent = null;
|
|
IEntity serverPlayerEnt = null;
|
|
IEntity serverFlashlight = null;
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
serverPlayerEnt = serverPlayerManager.GetAllPlayers().Single().AttachedEntity;
|
|
serverActionsComponent = serverPlayerEnt!.GetComponent<ServerActionsComponent>();
|
|
|
|
// spawn and give them an item that has actions
|
|
serverFlashlight = serverEntManager.SpawnEntity("TestFlashlight",
|
|
new EntityCoordinates(new EntityUid(1), (0, 0)));
|
|
Assert.That(serverFlashlight.TryGetComponent<ItemActionsComponent>(out var itemActions));
|
|
// we expect this only to have a toggle light action initially
|
|
var actionConfigs = itemActions.ActionConfigs.ToList();
|
|
Assert.That(actionConfigs.Count == 1);
|
|
Assert.That(actionConfigs[0].ActionType == ItemActionType.ToggleLight);
|
|
Assert.That(actionConfigs[0].Enabled);
|
|
|
|
// grant an extra item action, before pickup, initially disabled
|
|
itemActions.GrantOrUpdate(ItemActionType.DebugToggle, false);
|
|
serverPlayerEnt.GetComponent<HandsComponent>().PutInHand(serverFlashlight.GetComponent<ItemComponent>(), false);
|
|
// grant an extra item action, after pickup, with a cooldown
|
|
itemActions.GrantOrUpdate(ItemActionType.DebugInstant, cooldown: cooldown);
|
|
|
|
Assert.That(serverActionsComponent.TryGetItemActionStates(serverFlashlight.Uid, out var state));
|
|
// they should have been granted all 3 actions
|
|
Assert.That(state.Count == 3);
|
|
Assert.That(state.TryGetValue(ItemActionType.ToggleLight, out var toggleLightState));
|
|
Assert.That(toggleLightState.Equals(new ActionState(true)));
|
|
Assert.That(state.TryGetValue(ItemActionType.DebugInstant, out var debugInstantState));
|
|
Assert.That(debugInstantState.Equals(new ActionState(true, cooldown: cooldown)));
|
|
Assert.That(state.TryGetValue(ItemActionType.DebugToggle, out var debugToggleState));
|
|
Assert.That(debugToggleState.Equals(new ActionState(false)));
|
|
});
|
|
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
|
|
// check that client has the actions, and toggle the light on via the action slot it was auto-assigned to
|
|
var clientPlayerMgr = client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
|
|
var clientUIMgr = client.ResolveDependency<IUserInterfaceManager>();
|
|
EntityUid clientFlashlight = default;
|
|
await client.WaitAssertion(() =>
|
|
{
|
|
var local = clientPlayerMgr.LocalPlayer;
|
|
var controlled = local!.ControlledEntity;
|
|
clientActionsComponent = controlled!.GetComponent<ClientActionsComponent>();
|
|
|
|
var lightEntry = clientActionsComponent.ItemActionStates()
|
|
.Where(entry => entry.Value.ContainsKey(ItemActionType.ToggleLight))
|
|
.FirstOrNull();
|
|
clientFlashlight = lightEntry!.Value.Key;
|
|
Assert.That(lightEntry, Is.Not.Null);
|
|
Assert.That(lightEntry.Value.Value.TryGetValue(ItemActionType.ToggleLight, out var lightState));
|
|
Assert.That(lightState.Equals(new ActionState(true)));
|
|
Assert.That(lightEntry.Value.Value.TryGetValue(ItemActionType.DebugInstant, out var debugInstantState));
|
|
Assert.That(debugInstantState.Equals(new ActionState(true, cooldown: cooldown)));
|
|
Assert.That(lightEntry.Value.Value.TryGetValue(ItemActionType.DebugToggle, out var debugToggleState));
|
|
Assert.That(debugToggleState.Equals(new ActionState(false)));
|
|
|
|
var actionsUI = clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is ActionsUI) as ActionsUI;
|
|
Assert.That(actionsUI, Is.Not.Null);
|
|
|
|
var toggleLightSlot = actionsUI.Slots.FirstOrDefault(slot => slot.Action is ItemActionPrototype
|
|
{
|
|
ActionType: ItemActionType.ToggleLight
|
|
});
|
|
Assert.That(toggleLightSlot, Is.Not.Null);
|
|
|
|
clientActionsComponent.AttemptAction(toggleLightSlot);
|
|
});
|
|
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
|
|
// server should see the action toggled on
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
Assert.That(serverActionsComponent.ItemActionStates().TryGetValue(serverFlashlight.Uid, out var lightStates));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.ToggleLight, out var lightState));
|
|
Assert.That(lightState, Is.EqualTo(new ActionState(true, toggledOn: true)));
|
|
});
|
|
|
|
// client should see it toggled on.
|
|
await client.WaitAssertion(() =>
|
|
{
|
|
Assert.That(clientActionsComponent.ItemActionStates().TryGetValue(clientFlashlight, out var lightStates));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.ToggleLight, out var lightState));
|
|
Assert.That(lightState, Is.EqualTo(new ActionState(true, toggledOn: true)));
|
|
});
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
// drop the item, and the item actions should go away
|
|
serverPlayerEnt.GetComponent<HandsComponent>()
|
|
.TryDropEntity(serverFlashlight, serverPlayerEnt.Transform.Coordinates, false);
|
|
Assert.That(serverActionsComponent.ItemActionStates().ContainsKey(serverFlashlight.Uid), Is.False);
|
|
});
|
|
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
|
|
// client should see they have no item actions for that item either.
|
|
await client.WaitAssertion(() =>
|
|
{
|
|
Assert.That(clientActionsComponent.ItemActionStates().ContainsKey(clientFlashlight), Is.False);
|
|
});
|
|
|
|
await server.WaitAssertion(() =>
|
|
{
|
|
// pick the item up again, the states should be back to what they were when dropped,
|
|
// as the states "stick" with the item
|
|
serverPlayerEnt.GetComponent<HandsComponent>().PutInHand(serverFlashlight.GetComponent<ItemComponent>(), false);
|
|
Assert.That(serverActionsComponent.ItemActionStates().TryGetValue(serverFlashlight.Uid, out var lightStates));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.ToggleLight, out var lightState));
|
|
Assert.That(lightState.Equals(new ActionState(true, toggledOn: true)));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.DebugInstant, out var debugInstantState));
|
|
Assert.That(debugInstantState.Equals(new ActionState(true, cooldown: cooldown)));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.DebugToggle, out var debugToggleState));
|
|
Assert.That(debugToggleState.Equals(new ActionState(false)));
|
|
});
|
|
|
|
await server.WaitRunTicks(5);
|
|
await client.WaitRunTicks(5);
|
|
|
|
// client should see the actions again, with their states back to what they were
|
|
await client.WaitAssertion(() =>
|
|
{
|
|
Assert.That(clientActionsComponent.ItemActionStates().TryGetValue(clientFlashlight, out var lightStates));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.ToggleLight, out var lightState));
|
|
Assert.That(lightState.Equals(new ActionState(true, toggledOn: true)));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.DebugInstant, out var debugInstantState));
|
|
Assert.That(debugInstantState.Equals(new ActionState(true, cooldown: cooldown)));
|
|
Assert.That(lightStates.TryGetValue(ItemActionType.DebugToggle, out var debugToggleState));
|
|
Assert.That(debugToggleState.Equals(new ActionState(false)));
|
|
});
|
|
}
|
|
}
|
|
}
|