HandsSystem Refactor (#38438)

* checkpoint

* pt 2

* pt... i forgot

* pt 4

* patch

* More test fixes

* optimization!!!

* the REAL hand system

* fix RetractableItemActionSystem.cs oversight

* the review

* test

* remove test usage of body prototype

* Update Content.IntegrationTests/Tests/Interaction/InteractionTest.cs

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>

* hellcode

* hellcode 2

* Minor cleanup

* test

* Chasing the last of the bugs

* changes

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
This commit is contained in:
Nemanja
2025-06-25 09:13:03 -04:00
committed by GitHub
parent 6cffa8aabe
commit 524725d378
79 changed files with 849 additions and 897 deletions

View File

@@ -16,7 +16,6 @@ using Robust.Client.UserInterface;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Hands.Systems
@@ -27,16 +26,13 @@ namespace Content.Client.Hands.Systems
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly StrippableSystem _stripSys = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly ExamineSystem _examine = default!;
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
public event Action<string, HandLocation>? OnPlayerAddHand;
public event Action<string>? OnPlayerRemoveHand;
public event Action<string?>? OnPlayerSetActiveHand;
public event Action<HandsComponent>? OnPlayerHandsAdded;
public event Action<Entity<HandsComponent>>? OnPlayerHandsAdded;
public event Action? OnPlayerHandsRemoved;
public event Action<string, EntityUid>? OnPlayerItemAdded;
public event Action<string, EntityUid>? OnPlayerItemRemoved;
@@ -58,67 +54,28 @@ namespace Content.Client.Hands.Systems
}
#region StateHandling
private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args)
private void HandleComponentState(Entity<HandsComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not HandsComponentState state)
return;
var handsModified = component.Hands.Count != state.Hands.Count;
// we need to check that, even if we have the same amount, that the individual hands didn't change.
if (!handsModified)
var newHands = state.Hands.Keys.Except(ent.Comp.Hands.Keys); // hands that were added between states
var oldHands = ent.Comp.Hands.Keys.Except(state.Hands.Keys); // hands that were removed between states
foreach (var handId in oldHands)
{
foreach (var hand in component.Hands.Values)
{
if (state.Hands.Contains(hand))
continue;
handsModified = true;
break;
}
RemoveHand(ent.AsNullable(), handId);
}
var manager = EnsureComp<ContainerManagerComponent>(uid);
if (handsModified)
foreach (var handId in state.SortedHands.Intersect(newHands))
{
List<Hand> addedHands = new();
foreach (var hand in state.Hands)
{
if (component.Hands.ContainsKey(hand.Name))
continue;
var container = _containerSystem.EnsureContainer<ContainerSlot>(uid, hand.Name, manager);
var newHand = new Hand(hand.Name, hand.Location, container);
component.Hands.Add(hand.Name, newHand);
addedHands.Add(newHand);
AddHand(ent.AsNullable(), handId, state.Hands[handId]);
}
ent.Comp.SortedHands = new (state.SortedHands);
foreach (var name in component.Hands.Keys)
{
if (!state.HandNames.Contains(name))
{
RemoveHand(uid, name, component);
}
}
SetActiveHand(ent.AsNullable(), state.ActiveHandId);
component.SortedHands.Clear();
component.SortedHands.AddRange(state.HandNames);
var sorted = addedHands.OrderBy(hand => component.SortedHands.IndexOf(hand.Name));
foreach (var hand in sorted)
{
AddHand(uid, hand, component);
}
}
_stripSys.UpdateUi(uid);
if (component.ActiveHand == null && state.ActiveHand == null)
return; //edge case
if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name)
{
SetActiveHand(uid, component.Hands[state.ActiveHand!], component);
}
_stripSys.UpdateUi(ent);
}
#endregion
@@ -129,47 +86,52 @@ namespace Content.Client.Hands.Systems
return;
}
OnPlayerHandsAdded?.Invoke(hands);
OnPlayerHandsAdded?.Invoke(hands.Value);
}
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
public override void DoDrop(Entity<HandsComponent?> ent,
string handId,
bool doDropInteraction = true,
bool log = true)
{
base.DoDrop(uid, hand, doDropInteraction, hands, log);
base.DoDrop(ent, handId, doDropInteraction, log);
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
if (TryGetHeldItem(ent, handId, out var held) && TryComp(held, out SpriteComponent? sprite))
sprite.RenderOrder = EntityManager.CurrentTick.Value;
}
public EntityUid? GetActiveHandEntity()
{
return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null;
return TryGetPlayerHands(out var hands) ? GetActiveItem(hands.Value.AsNullable()) : null;
}
/// <summary>
/// Get the hands component of the local player
/// </summary>
public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands)
public bool TryGetPlayerHands([NotNullWhen(true)] out Entity<HandsComponent>? hands)
{
var player = _playerManager.LocalEntity;
hands = null;
return player != null && TryComp(player.Value, out hands);
if (player == null || !TryComp<HandsComponent>(player.Value, out var handsComp))
return false;
hands = (player.Value, handsComp);
return true;
}
/// <summary>
/// Called when a user clicked on their hands GUI
/// </summary>
public void UIHandClick(HandsComponent hands, string handName)
public void UIHandClick(Entity<HandsComponent> ent, string handName)
{
if (!hands.Hands.TryGetValue(handName, out var pressedHand))
var hands = ent.Comp;
if (hands.ActiveHandId == null)
return;
if (hands.ActiveHand == null)
return;
var pressedEntity = GetHeldItem(ent.AsNullable(), handName);
var activeEntity = GetActiveItem(ent.AsNullable());
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = hands.ActiveHand.HeldEntity;
if (pressedHand == hands.ActiveHand && activeEntity != null)
if (handName == hands.ActiveHandId && activeEntity != null)
{
// use item in hand
// it will always be attack_self() in my heart.
@@ -177,24 +139,24 @@ namespace Content.Client.Hands.Systems
return;
}
if (pressedHand != hands.ActiveHand && pressedEntity == null)
if (handName != hands.ActiveHandId && pressedEntity == null)
{
// change active hand
EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName));
return;
}
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null)
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity != null)
{
// use active item on held item
EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name));
EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(handName));
return;
}
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null)
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity == null)
{
// move the item to the active hand
EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name));
EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(handName));
}
}
@@ -210,13 +172,12 @@ namespace Content.Client.Hands.Systems
public void UIInventoryExamine(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
{
return;
}
_examine.DoExamine(entity);
_examine.DoExamine(heldEntity.Value);
}
/// <summary>
@@ -226,13 +187,12 @@ namespace Content.Client.Hands.Systems
public void UIHandOpenContextMenu(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
{
return;
}
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(heldEntity.Value);
}
public void UIHandAltActivateItem(string handName)
@@ -246,60 +206,67 @@ namespace Content.Client.Hands.Systems
{
base.HandleEntityInserted(uid, hands, args);
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
if (!hands.Hands.ContainsKey(args.Container.ID))
return;
UpdateHandVisuals(uid, args.Entity, hand);
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
_stripSys.UpdateUi(uid);
if (uid != _playerManager.LocalEntity)
return;
OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
OnPlayerItemAdded?.Invoke(args.Container.ID, args.Entity);
if (HasComp<VirtualItemComponent>(args.Entity))
OnPlayerHandBlocked?.Invoke(hand.Name);
OnPlayerHandBlocked?.Invoke(args.Container.ID);
}
protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
{
base.HandleEntityRemoved(uid, hands, args);
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
if (!hands.Hands.ContainsKey(args.Container.ID))
return;
UpdateHandVisuals(uid, args.Entity, hand);
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
_stripSys.UpdateUi(uid);
if (uid != _playerManager.LocalEntity)
return;
OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
OnPlayerItemRemoved?.Invoke(args.Container.ID, args.Entity);
if (HasComp<VirtualItemComponent>(args.Entity))
OnPlayerHandUnblocked?.Invoke(hand.Name);
OnPlayerHandUnblocked?.Invoke(args.Container.ID);
}
/// <summary>
/// Update the players sprite with new in-hand visuals.
/// </summary>
private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null)
private void UpdateHandVisuals(Entity<HandsComponent?, SpriteComponent?> ent, EntityUid held, string handId)
{
if (!Resolve(uid, ref handComp, ref sprite, false))
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false))
return;
var handComp = ent.Comp1;
var sprite = ent.Comp2;
if (!TryGetHand((ent, handComp), handId, out var hand))
return;
// visual update might involve changes to the entity's effective sprite -> need to update hands GUI.
if (uid == _playerManager.LocalEntity)
OnPlayerItemAdded?.Invoke(hand.Name, held);
if (ent == _playerManager.LocalEntity)
OnPlayerItemAdded?.Invoke(handId, held);
if (!handComp.ShowInHands)
return;
// Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this
// may eventually bloat the player with lots of layers.
if (handComp.RevealedLayers.TryGetValue(hand.Location, out var revealedLayers))
if (handComp.RevealedLayers.TryGetValue(hand.Value.Location, out var revealedLayers))
{
foreach (var key in revealedLayers)
{
_sprite.RemoveLayer((uid, sprite), key);
_sprite.RemoveLayer((ent, sprite), key);
}
revealedLayers.Clear();
@@ -307,22 +274,22 @@ namespace Content.Client.Hands.Systems
else
{
revealedLayers = new();
handComp.RevealedLayers[hand.Location] = revealedLayers;
handComp.RevealedLayers[hand.Value.Location] = revealedLayers;
}
if (hand.HeldEntity == null)
if (HandIsEmpty((ent, handComp), handId))
{
// the held item was removed.
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
return;
}
var ev = new GetInhandVisualsEvent(uid, hand.Location);
var ev = new GetInhandVisualsEvent(ent, hand.Value.Location);
RaiseLocalEvent(held, ev);
if (ev.Layers.Count == 0)
{
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
return;
}
@@ -335,7 +302,7 @@ namespace Content.Client.Hands.Systems
continue;
}
var index = _sprite.LayerMapReserve((uid, sprite), key);
var index = _sprite.LayerMapReserve((ent, sprite), key);
// In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
if (layerData.RsiPath == null
@@ -343,35 +310,34 @@ namespace Content.Client.Hands.Systems
&& sprite[index].Rsi == null)
{
if (TryComp<ItemComponent>(held, out var itemComponent) && itemComponent.RsiPath != null)
_sprite.LayerSetRsi((uid, sprite), index, new ResPath(itemComponent.RsiPath));
_sprite.LayerSetRsi((ent, sprite), index, new ResPath(itemComponent.RsiPath));
else if (TryComp(held, out SpriteComponent? clothingSprite))
_sprite.LayerSetRsi((uid, sprite), index, clothingSprite.BaseRSI);
_sprite.LayerSetRsi((ent, sprite), index, clothingSprite.BaseRSI);
}
_sprite.LayerSetData((uid, sprite), index, layerData);
_sprite.LayerSetData((ent, sprite), index, layerData);
// Add displacement maps
var displacement = hand.Location switch
var displacement = hand.Value.Location switch
{
HandLocation.Left => handComp.LeftHandDisplacement,
HandLocation.Right => handComp.RightHandDisplacement,
_ => handComp.HandDisplacement
};
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (uid, sprite), index, key, out var displacementKey))
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (ent, sprite), index, key, out var displacementKey))
revealedLayers.Add(displacementKey);
}
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
}
private void OnVisualsChanged(EntityUid uid, HandsComponent component, VisualsChangedEvent args)
{
// update hands visuals if this item is in a hand (rather then inventory or other container).
if (component.Hands.TryGetValue(args.ContainerId, out var hand))
{
UpdateHandVisuals(uid, GetEntity(args.Item), hand, component);
}
if (!component.Hands.ContainsKey(args.ContainerId))
return;
UpdateHandVisuals((uid, component), GetEntity(args.Item), args.ContainerId);
}
#endregion
@@ -379,7 +345,7 @@ namespace Content.Client.Hands.Systems
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, LocalPlayerAttachedEvent args)
{
OnPlayerHandsAdded?.Invoke(component);
OnPlayerHandsAdded?.Invoke((uid, component));
}
private void HandlePlayerDetached(EntityUid uid, HandsComponent component, LocalPlayerDetachedEvent args)
@@ -390,7 +356,7 @@ namespace Content.Client.Hands.Systems
private void OnHandsStartup(EntityUid uid, HandsComponent component, ComponentStartup args)
{
if (_playerManager.LocalEntity == uid)
OnPlayerHandsAdded?.Invoke(component);
OnPlayerHandsAdded?.Invoke((uid, component));
}
private void OnHandsShutdown(EntityUid uid, HandsComponent component, ComponentShutdown args)
@@ -400,36 +366,6 @@ namespace Content.Client.Hands.Systems
}
#endregion
private void AddHand(EntityUid uid, Hand newHand, HandsComponent? handsComp = null)
{
AddHand(uid, newHand.Name, newHand.Location, handsComp);
}
public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null)
{
base.AddHand(uid, handName, handLocation, handsComp);
if (uid == _playerManager.LocalEntity)
OnPlayerAddHand?.Invoke(handName, handLocation);
if (handsComp == null)
return;
if (handsComp.ActiveHand == null)
SetActiveHand(uid, handsComp.Hands[handName], handsComp);
}
public override void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
{
if (uid == _playerManager.LocalEntity && handsComp != null &&
handsComp.Hands.ContainsKey(handName) && uid ==
_playerManager.LocalEntity)
{
OnPlayerRemoveHand?.Invoke(handName);
}
base.RemoveHand(uid, handName, handsComp);
}
private void OnHandActivated(Entity<HandsComponent>? ent)
{
if (ent is not { } hand)
@@ -438,13 +374,7 @@ namespace Content.Client.Hands.Systems
if (_playerManager.LocalEntity != hand.Owner)
return;
if (hand.Comp.ActiveHand == null)
{
OnPlayerSetActiveHand?.Invoke(null);
return;
}
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHand.Name);
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHandId);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Linq;
using System.Numerics;
using Content.Client.Examine;
using Content.Client.Hands.Systems;
using Content.Client.Strip;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
@@ -34,6 +35,7 @@ namespace Content.Client.Inventory
[Dependency] private readonly IUserInterfaceManager _ui = default!;
private readonly ExamineSystem _examine;
private readonly HandsSystem _hands;
private readonly InventorySystem _inv;
private readonly SharedCuffableSystem _cuffable;
private readonly StrippableSystem _strippable;
@@ -65,6 +67,7 @@ namespace Content.Client.Inventory
public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_examine = EntMan.System<ExamineSystem>();
_hands = EntMan.System<HandsSystem>();
_inv = EntMan.System<InventorySystem>();
_cuffable = EntMan.System<SharedCuffableSystem>();
_strippable = EntMan.System<StrippableSystem>();
@@ -120,28 +123,28 @@ namespace Content.Client.Inventory
{
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
// and not gui hands... which are different...
foreach (var hand in handsComp.Hands.Values)
foreach (var (id, hand) in handsComp.Hands)
{
if (hand.Location != HandLocation.Right)
continue;
AddHandButton(hand);
AddHandButton((Owner, handsComp), id, hand);
}
foreach (var hand in handsComp.Hands.Values)
foreach (var (id, hand) in handsComp.Hands)
{
if (hand.Location != HandLocation.Middle)
continue;
AddHandButton(hand);
AddHandButton((Owner, handsComp), id, hand);
}
foreach (var hand in handsComp.Hands.Values)
foreach (var (id, hand) in handsComp.Hands)
{
if (hand.Location != HandLocation.Left)
continue;
AddHandButton(hand);
AddHandButton((Owner, handsComp), id, hand);
}
}
@@ -177,20 +180,21 @@ namespace Content.Client.Inventory
_strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize);
}
private void AddHandButton(Hand hand)
private void AddHandButton(Entity<HandsComponent> ent, string handId, Hand hand)
{
var button = new HandButton(hand.Name, hand.Location);
var button = new HandButton(handId, hand.Location);
button.Pressed += SlotPressed;
if (EntMan.TryGetComponent<VirtualItemComponent>(hand.HeldEntity, out var virt))
var heldEntity = _hands.GetHeldItem(ent.AsNullable(), handId);
if (EntMan.TryGetComponent<VirtualItemComponent>(heldEntity, out var virt))
{
button.Blocked = true;
if (EntMan.TryGetComponent<CuffableComponent>(Owner, out var cuff) && _cuffable.GetAllCuffs(cuff).Contains(virt.BlockingEntity))
button.BlockedRect.MouseFilter = MouseFilterMode.Ignore;
}
UpdateEntityIcon(button, hand.HeldEntity);
UpdateEntityIcon(button, heldEntity);
_strippingMenu!.HandsContainer.AddChild(button);
LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation));
_handCount++;

View File

@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Client.Gameplay;
using Content.Client.Hands.Systems;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.RCD.Components;
@@ -17,6 +18,7 @@ public sealed class AlignRCDConstruction : PlacementMode
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly SharedMapSystem _mapSystem;
private readonly HandsSystem _handsSystem;
private readonly RCDSystem _rcdSystem;
private readonly SharedTransformSystem _transformSystem;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -34,6 +36,7 @@ public sealed class AlignRCDConstruction : PlacementMode
{
IoCManager.InjectDependencies(this);
_mapSystem = _entityManager.System<SharedMapSystem>();
_handsSystem = _entityManager.System<HandsSystem>();
_rcdSystem = _entityManager.System<RCDSystem>();
_transformSystem = _entityManager.System<SharedTransformSystem>();
@@ -88,11 +91,9 @@ public sealed class AlignRCDConstruction : PlacementMode
}
// Determine if player is carrying an RCD in their active hand
if (!_entityManager.TryGetComponent<HandsComponent>(player, out var hands))
if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity))
return false;
var heldEntity = hands.ActiveHand?.HeldEntity;
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
return false;

View File

@@ -1,3 +1,4 @@
using Content.Client.Hands.Systems;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.RCD;
@@ -15,6 +16,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly HandsSystem _hands = default!;
private string _placementMode = typeof(AlignRCDConstruction).Name;
private Direction _placementDirection = default;
@@ -33,12 +35,11 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
return;
// Determine if player is carrying an RCD in their active hand
var player = _playerManager.LocalSession?.AttachedEntity;
if (!TryComp<HandsComponent>(player, out var hands))
if (_playerManager.LocalSession?.AttachedEntity is not { } player)
return;
var heldEntity = hands.ActiveHand?.HeldEntity;
if (!_hands.TryGetActiveItem(player, out var heldEntity))
return;
if (!TryComp<RCDComponent>(heldEntity, out var rcd))
{

View File

@@ -68,11 +68,15 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem
foreach (var hand in _hands.EnumerateHands(player.Value))
{
if (!scannerQuery.TryGetComponent(hand.HeldEntity, out var heldScanner) || !heldScanner.Enabled)
if (!_hands.TryGetHeldItem(player.Value, hand, out var heldEntity))
continue;
if (!scannerQuery.TryGetComponent(heldEntity, out var heldScanner) || !heldScanner.Enabled)
continue;
range = MathF.Max(heldScanner.Range, range);
canSee = true;
break;
}
inRange = new HashSet<Entity<SubFloorHideComponent>>();

View File

@@ -7,6 +7,7 @@ using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Timing;
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
@@ -28,7 +29,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
private readonly Dictionary<string, int> _handContainerIndices = new();
private readonly Dictionary<string, HandButton> _handLookup = new();
private HandsComponent? _playerHandsComponent;
private HandButton? _activeHand = null;
private HandButton? _activeHand;
// We only have two item status controls (left and right hand),
// but we may have more than two hands.
@@ -38,7 +39,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
private HandButton? _statusHandLeft;
private HandButton? _statusHandRight;
private int _backupSuffix = 0; //this is used when autogenerating container names if they don't have names
private int _backupSuffix; //this is used when autogenerating container names if they don't have names
private HotbarGui? HandsGui => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
@@ -48,7 +49,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
_handsSystem.OnPlayerItemAdded += OnItemAdded;
_handsSystem.OnPlayerItemRemoved += OnItemRemoved;
_handsSystem.OnPlayerSetActiveHand += SetActiveHand;
_handsSystem.OnPlayerRemoveHand += RemoveHand;
_handsSystem.OnPlayerRemoveHand += OnRemoveHand;
_handsSystem.OnPlayerHandsAdded += LoadPlayerHands;
_handsSystem.OnPlayerHandsRemoved += UnloadPlayerHands;
_handsSystem.OnPlayerHandBlocked += HandBlocked;
@@ -61,28 +62,35 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
_handsSystem.OnPlayerItemAdded -= OnItemAdded;
_handsSystem.OnPlayerItemRemoved -= OnItemRemoved;
_handsSystem.OnPlayerSetActiveHand -= SetActiveHand;
_handsSystem.OnPlayerRemoveHand -= RemoveHand;
_handsSystem.OnPlayerRemoveHand -= OnRemoveHand;
_handsSystem.OnPlayerHandsAdded -= LoadPlayerHands;
_handsSystem.OnPlayerHandsRemoved -= UnloadPlayerHands;
_handsSystem.OnPlayerHandBlocked -= HandBlocked;
_handsSystem.OnPlayerHandUnblocked -= HandUnblocked;
}
private void OnAddHand(string name, HandLocation location)
private void OnAddHand(Entity<HandsComponent> entity, string name, HandLocation location)
{
if (entity.Owner != _player.LocalEntity)
return;
AddHand(name, location);
}
private void OnRemoveHand(Entity<HandsComponent> entity, string name)
{
if (entity.Owner != _player.LocalEntity)
return;
RemoveHand(name);
}
private void HandPressed(GUIBoundKeyEventArgs args, SlotControl hand)
{
if (_playerHandsComponent == null)
{
if (!_handsSystem.TryGetPlayerHands(out var hands))
return;
}
if (args.Function == EngineKeyFunctions.UIClick)
{
_handsSystem.UIHandClick(_playerHandsComponent, hand.SlotName);
_handsSystem.UIHandClick(hands.Value, hand.SlotName);
args.Handle();
}
else if (args.Function == EngineKeyFunctions.UseSecondary)
@@ -122,33 +130,33 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
}
}
private void LoadPlayerHands(HandsComponent handsComp)
private void LoadPlayerHands(Entity<HandsComponent> handsComp)
{
DebugTools.Assert(_playerHandsComponent == null);
if (HandsGui != null)
HandsGui.Visible = true;
_playerHandsComponent = handsComp;
foreach (var (name, hand) in handsComp.Hands)
foreach (var (name, hand) in handsComp.Comp.Hands)
{
var handButton = AddHand(name, hand.Location);
if (_entities.TryGetComponent(hand.HeldEntity, out VirtualItemComponent? virt))
if (_handsSystem.TryGetHeldItem(handsComp.AsNullable(), name, out var held) &&
_entities.TryGetComponent(held, out VirtualItemComponent? virt))
{
handButton.SetEntity(virt.BlockingEntity);
handButton.Blocked = true;
}
else
{
handButton.SetEntity(hand.HeldEntity);
handButton.SetEntity(held);
handButton.Blocked = false;
}
}
var activeHand = handsComp.ActiveHand;
if (activeHand == null)
if (handsComp.Comp.ActiveHandId == null)
return;
SetActiveHand(activeHand.Name);
SetActiveHand(handsComp.Comp.ActiveHandId);
}
private void HandBlocked(string handName)
@@ -260,19 +268,21 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
if (HandsGui != null &&
_playerHandsComponent != null &&
_player.LocalSession?.AttachedEntity is { } playerEntity &&
_handsSystem.TryGetHand(playerEntity, handName, out var hand, _playerHandsComponent))
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), handName, out var hand))
{
var foldedLocation = hand.Location.GetUILocation();
var heldEnt = _handsSystem.GetHeldItem((playerEntity, _playerHandsComponent), handName);
var foldedLocation = hand.Value.Location.GetUILocation();
if (foldedLocation == HandUILocation.Left)
{
_statusHandLeft = handControl;
HandsGui.UpdatePanelEntityLeft(hand.HeldEntity);
HandsGui.UpdatePanelEntityLeft(heldEnt);
}
else
{
// Middle or right
_statusHandRight = handControl;
HandsGui.UpdatePanelEntityRight(hand.HeldEntity);
HandsGui.UpdatePanelEntityRight(heldEnt);
}
HandsGui.SetHighlightHand(foldedLocation);
@@ -292,7 +302,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
button.Pressed += HandPressed;
if (!_handLookup.TryAdd(handName, button))
throw new Exception("Tried to add hand with duplicate name to UI. Name:" + handName);
return _handLookup[handName];
if (HandsGui != null)
{
@@ -362,6 +372,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
RemoveHand(handName, out _);
}
[PublicAPI]
private bool RemoveHand(string handName, out HandButton? handButton)
{
if (!_handLookup.TryGetValue(handName, out handButton))
@@ -377,7 +388,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
_statusHandRight = null;
_handLookup.Remove(handName);
handButton.Dispose();
handButton.Orphan();
UpdateVisibleStatusPanels();
return true;
}

View File

@@ -329,9 +329,8 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
var player = _playerUid;
if (!control.MouseIsHovering ||
_playerInventory == null ||
!_entities.TryGetComponent<HandsComponent>(player, out var hands) ||
hands.ActiveHandEntity is not { } held ||
player == null ||
!_handsSystem.TryGetActiveItem(player.Value, out var held) ||
!_entities.TryGetComponent(held, out SpriteComponent? sprite) ||
!_inventorySystem.TryGetSlotContainer(player.Value, control.SlotName, out var container, out var slotDef))
{
@@ -342,12 +341,12 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
// Set green / red overlay at 50% transparency
var hoverEntity = _entities.SpawnEntity("hoverentity", MapCoordinates.Nullspace);
var hoverSprite = _entities.GetComponent<SpriteComponent>(hoverEntity);
var fits = _inventorySystem.CanEquip(player.Value, held, control.SlotName, out _, slotDef) &&
_container.CanInsert(held, container);
var fits = _inventorySystem.CanEquip(player.Value, held.Value, control.SlotName, out _, slotDef) &&
_container.CanInsert(held.Value, container);
if (!fits && _entities.TryGetComponent<StorageComponent>(container.ContainedEntity, out var storage))
{
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held, out _, storage);
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held.Value, out _, storage);
}
else if (!fits && _entities.TryGetComponent<ItemSlotsComponent>(container.ContainedEntity, out var itemSlots))
{
@@ -357,14 +356,14 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
if (!slot.InsertOnInteract)
continue;
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held, null, slot))
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held.Value, null, slot))
continue;
fits = true;
break;
}
}
_sprite.CopySprite((held, sprite), (hoverEntity, hoverSprite));
_sprite.CopySprite((held.Value, sprite), (hoverEntity, hoverSprite));
_sprite.SetColor((hoverEntity, hoverSprite), fits ? new Color(0, 255, 0, 127) : new Color(255, 0, 0, 127));
control.HoverSpriteView.SetEntity(hoverEntity);

View File

@@ -25,7 +25,7 @@ public sealed class RetractableItemActionTest : InteractionTest
await Server.WaitAssertion(() =>
{
// Make sure the player's hand starts empty
var heldItem = Hands.ActiveHandEntity;
var heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) at start of test.");
// Inspect the action prototype to find the item it spawns
@@ -43,14 +43,14 @@ public sealed class RetractableItemActionTest : InteractionTest
var actionEnt = actionsSystem.GetAction(actionUid);
// Make sure the player's hand is still empty
heldItem = Hands.ActiveHandEntity;
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) after adding action.");
// Activate the arm blade
actionsSystem.PerformAction(ToServer(Player), actionEnt!.Value);
// Make sure the player is now holding the expected item
heldItem = Hands.ActiveHandEntity;
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Not.Null, $"Expected player to be holding {spawnedProtoId} but was holding nothing.");
AssertPrototype(spawnedProtoId, SEntMan.GetNetEntity(heldItem));
@@ -58,7 +58,7 @@ public sealed class RetractableItemActionTest : InteractionTest
actionsSystem.PerformAction(ToServer(Player), actionEnt.Value);
// Make sure the player's hand is empty again
heldItem = Hands.ActiveHandEntity;
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
Assert.That(heldItem, Is.Null, $"Player is still holding an item ({SEntMan.ToPrettyString(heldItem)}) after second use.");
});
}

View File

@@ -293,9 +293,9 @@ namespace Content.IntegrationTests.Tests.Buckle
Assert.That(buckle.Buckled);
// With items in all hands
foreach (var hand in hands.Hands.Values)
foreach (var hand in hands.Hands.Keys)
{
Assert.That(hand.HeldEntity, Is.Not.Null);
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Not.Null);
}
var bodySystem = entityManager.System<BodySystem>();
@@ -316,9 +316,9 @@ namespace Content.IntegrationTests.Tests.Buckle
Assert.That(buckle.Buckled);
// Now with no item in any hand
foreach (var hand in hands.Hands.Values)
foreach (var hand in hands.Hands.Keys)
{
Assert.That(hand.HeldEntity, Is.Null);
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Null);
}
buckleSystem.Unbuckle(human, human);

View File

@@ -1,7 +1,6 @@
using Content.Client.Chemistry.UI;
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Chemistry;
using Content.Server.Chemistry.Components;
using Content.Shared.Containers.ItemSlots;
namespace Content.IntegrationTests.Tests.Chemistry;
@@ -19,7 +18,7 @@ public sealed class DispenserTest : InteractionTest
// Insert beaker
await InteractUsing("Beaker");
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
// Open BUI
await Interact();
@@ -29,18 +28,18 @@ public sealed class DispenserTest : InteractionTest
await SendBui(ReagentDispenserUiKey.Key, ev);
// Beaker is back in the player's hands
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
// Re-insert the beaker
await Interact();
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
// Re-eject using the button directly instead of sending a BUI event. This test is really just a test of the
// bui/window helper methods.
await ClickControl<ReagentDispenserWindow>(nameof(ReagentDispenserWindow.EjectButton));
await RunTicks(5);
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
}
}

View File

@@ -267,7 +267,7 @@ public sealed class SuicideCommandTests
await server.WaitPost(() =>
{
var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
Assert.That(executionComponent, Is.Not.EqualTo(null));
});
@@ -342,7 +342,7 @@ public sealed class SuicideCommandTests
await server.WaitPost(() =>
{
var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
Assert.That(executionComponent, Is.Not.EqualTo(null));
});

View File

@@ -13,10 +13,10 @@ public sealed class WallConstruction : InteractionTest
{
await StartConstruction(Wall);
await InteractUsing(Steel, 2);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
ClientAssertPrototype(Girder, Target);
await InteractUsing(Steel, 2);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
AssertPrototype(WallSolid);
}

View File

@@ -53,20 +53,20 @@ public sealed class HandTests
var xform = entMan.GetComponent<TransformComponent>(player);
item = entMan.SpawnEntity("Crowbar", tSys.GetMapCoordinates(player, xform: xform));
hands = entMan.GetComponent<HandsComponent>(player);
sys.TryPickup(player, item, hands.ActiveHand!);
sys.TryPickup(player, item, hands.ActiveHandId!);
});
// run ticks here is important, as errors may happen within the container system's frame update methods.
await pair.RunTicksSync(5);
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
await server.WaitPost(() =>
{
sys.TryDrop(player, item, null!);
sys.TryDrop(player, item);
});
await pair.RunTicksSync(5);
Assert.That(hands.ActiveHandEntity, Is.Null);
Assert.That(sys.GetActiveItem((player, hands)), Is.Null);
await server.WaitPost(() => mapSystem.DeleteMap(data.MapId));
await pair.CleanReturnAsync();
@@ -105,10 +105,10 @@ public sealed class HandTests
player = playerMan.Sessions.First().AttachedEntity!.Value;
tSys.PlaceNextTo(player, item);
hands = entMan.GetComponent<HandsComponent>(player);
sys.TryPickup(player, item, hands.ActiveHand!);
sys.TryPickup(player, item, hands.ActiveHandId!);
});
await pair.RunTicksSync(5);
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
// Open then close the box to place the player, who is holding the crowbar, inside of it
var storage = server.System<EntityStorageSystem>();
@@ -125,12 +125,12 @@ public sealed class HandTests
// with the item not being in the player's hands
await server.WaitPost(() =>
{
sys.TryDrop(player, item, null!);
sys.TryDrop(player, item);
});
await pair.RunTicksSync(5);
var xform = entMan.GetComponent<TransformComponent>(player);
var itemXform = entMan.GetComponent<TransformComponent>(item);
Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item));
Assert.That(sys.GetActiveItem((player, hands)), Is.Not.EqualTo(item));
Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform)));
await server.WaitPost(() => mapSystem.DeleteMap(map.MapId));

View File

@@ -120,18 +120,18 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task DeleteHeldEntity()
{
if (Hands.ActiveHandEntity is { } held)
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is { } held)
{
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), null, false, true, Hands));
Assert.That(HandSys.TryDrop((SEntMan.GetEntity(Player), Hands), null, false, true));
SEntMan.DeleteEntity(held);
SLogger.Debug($"Deleting held entity");
});
}
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
}
/// <summary>
@@ -152,7 +152,7 @@ public abstract partial class InteractionTest
/// <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)
if (Hands.ActiveHandId == null)
{
Assert.Fail("No active hand");
return default;
@@ -169,7 +169,7 @@ public abstract partial class InteractionTest
{
var playerEnt = SEntMan.GetEntity(Player);
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands));
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHandId, false, false, false, Hands));
// turn on welders
if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
@@ -179,7 +179,7 @@ public abstract partial class InteractionTest
});
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item));
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(item));
if (enableToggleable && itemToggle != null)
Assert.That(itemToggle.Activated);
@@ -193,7 +193,7 @@ public abstract partial class InteractionTest
{
entity ??= Target;
if (Hands.ActiveHand == null)
if (Hands.ActiveHandId == null)
{
Assert.Fail("No active hand");
return;
@@ -212,11 +212,11 @@ public abstract partial class InteractionTest
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, Hands, item));
Assert.That(HandSys.TryPickup(ToServer(Player), uid.Value, Hands.ActiveHandId, false, false, false, Hands, item));
});
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(uid));
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(uid));
}
/// <summary>
@@ -224,7 +224,7 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task Drop()
{
if (Hands.ActiveHandEntity == null)
if (HandSys.GetActiveItem((ToServer(Player), Hands)) == null)
{
Assert.Fail("Not holding any entity to drop");
return;
@@ -232,11 +232,11 @@ public abstract partial class InteractionTest
await Server.WaitPost(() =>
{
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), handsComp: Hands));
Assert.That(HandSys.TryDrop((ToServer(Player), Hands)));
});
await RunTicks(1);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
}
#region Interact
@@ -246,7 +246,7 @@ public abstract partial class InteractionTest
/// </summary>
protected async Task UseInHand()
{
if (Hands.ActiveHandEntity is not { } target)
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is not { } target)
{
Assert.Fail("Not holding any entity");
return;

View File

@@ -1,15 +1,12 @@
#nullable enable
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 Content.Server.Stack;
using Content.Server.Tools;
using Content.Shared.Body.Part;
using Content.Shared.DoAfter;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
@@ -135,10 +132,13 @@ public abstract partial class InteractionTest
- type: entity
id: InteractionTestMob
components:
- type: Body
prototype: Aghost
- type: DoAfter
- type: Hands
hands:
hand_right: # only one hand, so that they do not accidentally pick up deconstruction products
location: Right
sortedHands:
- hand_right
- type: ComplexInteraction
- type: MindContainer
- type: Stripping
@@ -230,20 +230,6 @@ public abstract partial class InteractionTest
SEntMan.DeleteEntity(old.Value);
});
// Ensure that the player only has one hand, so that they do not accidentally pick up deconstruction products
await Server.WaitPost(() =>
{
// I lost an hour of my life trying to track down how the hell interaction tests were breaking
// so greatz to this. Just make your own body prototype!
var bodySystem = SEntMan.System<BodySystem>();
var hands = bodySystem.GetBodyChildrenOfType(SEntMan.GetEntity(Player), BodyPartType.Hand).ToArray();
for (var i = 1; i < hands.Length; i++)
{
SEntMan.DeleteEntity(hands[i].Id);
}
});
// Change UI state to in-game.
var state = Client.ResolveDependency<IStateManager>();
await Client.WaitPost(() => state.RequestStateChange<GameplayState>());

View File

@@ -17,7 +17,7 @@ public sealed class TileConstructionTests : InteractionTest
await SetTile(null);
await InteractUsing(Rod);
await AssertTile(Lattice);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
await InteractUsing(Cut);
await AssertTile(null);
await AssertEntityLookup((Rod, 1));
@@ -49,7 +49,7 @@ public sealed class TileConstructionTests : InteractionTest
AssertGridCount(1);
// Cut lattice
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
await InteractUsing(Cut);
await AssertTile(null);
AssertGridCount(0);
@@ -83,13 +83,13 @@ public sealed class TileConstructionTests : InteractionTest
// Lattice -> Plating
await InteractUsing(FloorItem);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
await AssertTile(Plating);
AssertGridCount(1);
// Plating -> Tile
await InteractUsing(FloorItem);
Assert.That(Hands.ActiveHandEntity, Is.Null);
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
await AssertTile(Floor);
AssertGridCount(1);

View File

@@ -42,13 +42,12 @@ public sealed class StripAllCommand : LocalizedEntityCommands
if (EntityManager.TryGetComponent<HandsComponent>(targetEntity, out var hands))
{
foreach (var hand in _handsSystem.EnumerateHands(targetEntity.Value, hands))
foreach (var hand in _handsSystem.EnumerateHands((targetEntity.Value, hands)))
{
_handsSystem.TryDrop(targetEntity.Value,
_handsSystem.TryDrop((targetEntity.Value, hands),
hand,
checkActionBlocker: false,
doDropInteraction: false,
handsComp: hands);
doDropInteraction: false);
}
}
}

View File

@@ -433,9 +433,9 @@ public sealed class AdminSystem : EntitySystem
if (TryComp(entity, out HandsComponent? hands))
{
foreach (var hand in _hands.EnumerateHands(entity, hands))
foreach (var hand in _hands.EnumerateHands((entity, hands)))
{
_hands.TryDrop(entity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
_hands.TryDrop((entity, hands), hand, checkActionBlocker: false, doDropInteraction: false);
}
}

View File

@@ -820,7 +820,7 @@ public sealed partial class AdminVerbSystem
}
else if (TryComp<HandsComponent>(target, out var hands))
{
foreach (var held in _handsSystem.EnumerateHeld(target, hands))
foreach (var held in _handsSystem.EnumerateHeld((target, hands)))
{
if (HasComp<AccessComponent>(held))
{

View File

@@ -97,8 +97,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
EntityUid? entity = null;
if (args.Type == CryostorageRemoveItemBuiMessage.RemovalType.Hand)
{
if (_hands.TryGetHand(cryoContained, args.Key, out var hand))
entity = hand.HeldEntity;
entity = _hands.GetHeldItem(cryoContained, args.Key);
}
else
{
@@ -320,10 +319,10 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
foreach (var hand in _hands.EnumerateHands(uid))
{
if (hand.HeldEntity == null)
if (!_hands.TryGetHeldItem(uid, hand, out var heldEntity))
continue;
data.HeldItems.Add(hand.Name, Name(hand.HeldEntity.Value));
data.HeldItems.Add(hand, Name(heldEntity.Value));
}
return data;

View File

@@ -1,5 +1,6 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Botany.Components;
using Content.Server.Hands.Systems;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.EntitySystems;
@@ -37,6 +38,7 @@ public sealed class PlantHolderSystem : EntitySystem
[Dependency] private readonly MutationSystem _mutation = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
@@ -706,9 +708,9 @@ public sealed class PlantHolderSystem : EntitySystem
if (component.Harvest && !component.Dead)
{
if (TryComp<HandsComponent>(user, out var hands))
if (_hands.TryGetActiveItem(user, out var activeItem))
{
if (!_botany.CanHarvest(component.Seed, hands.ActiveHandEntity))
if (!_botany.CanHarvest(component.Seed, activeItem))
{
_popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user);
return false;

View File

@@ -1,9 +1,9 @@
using Content.Server.Ghost;
using Content.Server.Hands.Systems;
using Content.Shared.Administration.Logs;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
@@ -22,6 +22,7 @@ public sealed class SuicideSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
@@ -116,10 +117,9 @@ public sealed class SuicideSystem : EntitySystem
var suicideByEnvironmentEvent = new SuicideByEnvironmentEvent(victim);
// Try to suicide by raising an event on the held item
if (EntityManager.TryGetComponent(victim, out HandsComponent? handsComponent)
&& handsComponent.ActiveHandEntity is { } item)
if (_hands.TryGetActiveItem(victim.Owner, out var item))
{
RaiseLocalEvent(item, suicideByEnvironmentEvent);
RaiseLocalEvent(item.Value, suicideByEnvironmentEvent);
if (suicideByEnvironmentEvent.Handled)
{
args.Handled = suicideByEnvironmentEvent.Handled;

View File

@@ -471,7 +471,7 @@ namespace Content.Server.Construction
}
if (!_actionBlocker.CanInteract(user, null)
|| !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.ActiveHandEntity == null)
|| !EntityManager.TryGetComponent(user, out HandsComponent? hands) || _handsSystem.GetActiveItem((user, hands)) == null)
{
Cleanup();
return;
@@ -496,7 +496,7 @@ namespace Content.Server.Construction
var valid = false;
if (hands.ActiveHandEntity is not {Valid: true} holding)
if (_handsSystem.GetActiveItem((user, hands)) is not {Valid: true} holding)
{
Cleanup();
return;

View File

@@ -1,5 +1,4 @@
using System.Numerics;
using Content.Server.Inventory;
using Content.Server.Stack;
using Content.Server.Stunnable;
using Content.Shared.ActionBlocker;
@@ -10,9 +9,7 @@ using Content.Shared.Explosion;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Events;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Stacks;
using Content.Shared.Standing;
@@ -24,7 +21,6 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Hands.Systems
{
@@ -87,10 +83,9 @@ namespace Content.Server.Hands.Systems
if (ent.Comp.DisableExplosionRecursion)
return;
foreach (var hand in ent.Comp.Hands.Values)
foreach (var held in EnumerateHeld(ent.AsNullable()))
{
if (hand.HeldEntity is { } uid)
args.Contents.Add(uid);
args.Contents.Add(held);
}
}
@@ -112,7 +107,7 @@ namespace Content.Server.Hands.Systems
args.Handled = true; // no shove/stun.
}
private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
private void HandleBodyPartAdded(Entity<HandsComponent> ent, ref BodyPartAddedEvent args)
{
if (args.Part.Comp.PartType != BodyPartType.Hand)
return;
@@ -127,7 +122,7 @@ namespace Content.Server.Hands.Systems
_ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry))
};
AddHand(uid, args.Slot, location);
AddHand(ent.AsNullable(), args.Slot, location);
}
private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
@@ -155,8 +150,8 @@ namespace Content.Server.Hands.Systems
{
if (ContainerSystem.IsEntityInContainer(player) ||
!TryComp(player, out HandsComponent? hands) ||
hands.ActiveHandEntity is not { } throwEnt ||
!_actionBlockerSystem.CanThrow(player, throwEnt))
!TryGetActiveItem((player, hands), out var throwEnt) ||
!_actionBlockerSystem.CanThrow(player, throwEnt.Value))
return false;
if (_timing.CurTime < hands.NextThrowTime)
@@ -165,7 +160,7 @@ namespace Content.Server.Hands.Systems
if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
{
var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
var splitStack = _stackSystem.Split(throwEnt.Value, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
if (splitStack is not {Valid: true})
return false;
@@ -185,14 +180,14 @@ namespace Content.Server.Hands.Systems
// Let other systems change the thrown entity (useful for virtual items)
// or the throw strength.
var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player);
var ev = new BeforeThrowEvent(throwEnt.Value, direction, throwSpeed, player);
RaiseLocalEvent(player, ref ev);
if (ev.Cancelled)
return true;
// This can grief the above event so we raise it afterwards
if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands))
if (IsHolding((player, hands), throwEnt, out _) && !TryDrop(player, throwEnt.Value))
return false;
_throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp<LandAtCursorComponent>(ev.ItemUid));
@@ -207,20 +202,20 @@ namespace Content.Server.Hands.Systems
var spreadMaxAngle = Angle.FromDegrees(DropHeldItemsSpread);
var fellEvent = new FellDownEvent(entity);
RaiseLocalEvent(entity, fellEvent, false);
RaiseLocalEvent(entity, fellEvent);
foreach (var hand in entity.Comp.Hands.Values)
foreach (var hand in entity.Comp.Hands.Keys)
{
if (hand.HeldEntity is not EntityUid held)
if (!TryGetHeldItem(entity.AsNullable(), hand, out var heldEntity))
continue;
var throwAttempt = new FellDownThrowAttemptEvent(entity);
RaiseLocalEvent(hand.HeldEntity.Value, ref throwAttempt);
RaiseLocalEvent(heldEntity.Value, ref throwAttempt);
if (throwAttempt.Cancelled)
continue;
if (!TryDrop(entity, hand, null, checkActionBlocker: false, handsComp: entity.Comp))
if (!TryDrop(entity.AsNullable(), hand, checkActionBlocker: false))
continue;
// Rotate the item's throw vector a bit for each item
@@ -231,12 +226,12 @@ namespace Content.Server.Hands.Systems
itemVelocity *= _random.NextFloat(1f);
// Heavier objects don't get thrown as far
// If the item doesn't have a physics component, it isn't going to get thrown anyway, but we'll assume infinite mass
itemVelocity *= _physicsQuery.TryComp(held, out var heldPhysics) ? heldPhysics.InvMass : 0;
itemVelocity *= _physicsQuery.TryComp(heldEntity, out var heldPhysics) ? heldPhysics.InvMass : 0;
// Throw at half the holder's intentional throw speed and
// vary the speed a little to make it look more interesting
var throwSpeed = entity.Comp.BaseThrowspeed * _random.NextFloat(0.45f, 0.55f);
_throwingSystem.TryThrow(held,
_throwingSystem.TryThrow(heldEntity.Value,
itemVelocity,
throwSpeed,
entity,

View File

@@ -43,7 +43,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
if (!TryComp<HandsComponent>(hitEntity, out var hands))
continue;
if (!_hands.IsHolding(hitEntity, uid, out _, hands) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands))
if (!_hands.IsHolding((hitEntity, hands), uid, out _) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands))
{
_popup.PopupEntity(Loc.GetString("hot-potato-passed",
("from", args.User), ("to", hitEntity)), uid, PopupType.Medium);

View File

@@ -1,4 +1,4 @@
using Content.Shared.Hands.Components;
using Content.Server.Hands.Systems;
using Robust.Shared.Prototypes;
namespace Content.Server.NPC.HTN.Preconditions;
@@ -18,14 +18,18 @@ public sealed partial class ActiveHandComponentPrecondition : HTNPrecondition
public override bool IsMet(NPCBlackboard blackboard)
{
if (!blackboard.TryGetValue<Hand>(NPCBlackboard.ActiveHand, out var hand, _entManager) || hand.HeldEntity == null)
if (!blackboard.TryGetValue<EntityUid>(NPCBlackboard.Owner, out var owner, _entManager) ||
!blackboard.TryGetValue<string>(NPCBlackboard.ActiveHand, out var hand, _entManager))
{
return Invert;
}
if (!_entManager.System<HandsSystem>().TryGetHeldItem(owner, hand, out var entity))
return Invert;
foreach (var comp in Components)
{
var hasComp = _entManager.HasComponent(hand.HeldEntity, comp.Value.Component.GetType());
var hasComp = _entManager.HasComponent(entity, comp.Value.Component.GetType());
if (!hasComp ||
Invert && hasComp)

View File

@@ -1,4 +1,4 @@
using Content.Shared.Hands.Components;
using Content.Server.Hands.Systems;
namespace Content.Server.NPC.HTN.Preconditions;
@@ -11,11 +11,12 @@ public sealed partial class ActiveHandEntityPrecondition : HTNPrecondition
public override bool IsMet(NPCBlackboard blackboard)
{
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager))
if (!blackboard.TryGetValue(NPCBlackboard.Owner, out EntityUid owner, _entManager) ||
!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, _entManager))
{
return false;
}
return activeHand.HeldEntity != null;
return !_entManager.System<HandsSystem>().HandIsEmpty(owner, activeHand);
}
}

View File

@@ -12,7 +12,7 @@ public sealed partial class DropOperator : HTNOperator
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
{
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager))
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, _entManager))
{
return HTNOperatorStatus.Finished;
}

View File

@@ -1,11 +1,10 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Content.Server.Interaction;
using Content.Server.Hands.Systems;
using Content.Shared.Access.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using JetBrains.Annotations;
using Robust.Shared.Utility;
@@ -152,6 +151,8 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
value = default;
EntityUid owner;
var handSys = entManager.System<HandsSystem>();
switch (key)
{
case Access:
@@ -168,25 +169,24 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
case ActiveHand:
{
if (!TryGetValue(Owner, out owner, entManager) ||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
hands.ActiveHand == null)
handSys.GetActiveHand(owner) is not { } activeHand)
{
return false;
}
value = hands.ActiveHand;
value = activeHand;
return true;
}
case ActiveHandFree:
{
if (!TryGetValue(Owner, out owner, entManager) ||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
hands.ActiveHand == null)
handSys.GetActiveHand(owner) is not { } activeHand)
{
return false;
}
value = hands.ActiveHand.IsEmpty;
value = handSys.HandIsEmpty((owner, hands), activeHand);
return true;
}
case CanMove:
@@ -204,16 +204,16 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
{
if (!TryGetValue(Owner, out owner, entManager) ||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
hands.ActiveHand == null)
handSys.GetActiveHand(owner) is null)
{
return false;
}
var handos = new List<string>();
foreach (var (id, hand) in hands.Hands)
foreach (var id in hands.Hands.Keys)
{
if (!hand.IsEmpty)
if (!handSys.HandIsEmpty((owner, hands), id))
continue;
handos.Add(id);
@@ -226,16 +226,16 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
{
if (!TryGetValue(Owner, out owner, entManager) ||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
hands.ActiveHand == null)
handSys.GetActiveHand(owner) is null)
{
return false;
}
var handos = new List<string>();
foreach (var (id, hand) in hands.Hands)
foreach (var id in hands.Hands.Keys)
{
if (!hand.IsEmpty)
if (!handSys.HandIsEmpty((owner, hands), id))
continue;
handos.Add(id);

View File

@@ -1,5 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.NPC.Queries;
using Content.Server.NPC.Queries.Considerations;
using Content.Server.NPC.Queries.Curves;
@@ -44,6 +45,7 @@ public sealed class NPCUtilitySystem : EntitySystem
[Dependency] private readonly DrinkSystem _drink = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly FoodSystem _food = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
@@ -256,8 +258,9 @@ public sealed class NPCUtilitySystem : EntitySystem
}
case TargetAmmoMatchesCon:
{
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, EntityManager) ||
!TryComp<BallisticAmmoProviderComponent>(activeHand.HeldEntity, out var heldGun))
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, EntityManager) ||
!_hands.TryGetHeldItem(owner, activeHand, out var heldEntity) ||
!TryComp<BallisticAmmoProviderComponent>(heldEntity, out var heldGun))
{
return 0f;
}

View File

@@ -152,7 +152,7 @@ public sealed class FTLDiskCommand : LocalizedCommands
if (_entManager.TryGetComponent<StorageComponent>(cdCaseUid, out var storage) && storageSystem.Insert(cdCaseUid, cdUid, out _, storageComp: storage, playSound: false))
{
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand))
{
handsSystem.TryPickup(entity, cdCaseUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
}
@@ -161,7 +161,7 @@ public sealed class FTLDiskCommand : LocalizedCommands
{
_entManager.DeleteEntity(cdCaseUid); // something went wrong so just yeet the chaf
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand))
{
handsSystem.TryPickup(entity, cdUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
}

View File

@@ -217,8 +217,8 @@ public sealed partial class BorgSystem
var handId = $"{uid}-item{component.HandCounter}";
component.HandCounter++;
_hands.AddHand(chassis, handId, HandLocation.Middle, hands);
_hands.DoPickup(chassis, hands.Hands[handId], item, hands);
_hands.AddHand((chassis, hands), handId, HandLocation.Middle);
_hands.DoPickup(chassis, handId, item, hands);
EnsureComp<UnremoveableComponent>(item);
component.ProvidedItems.Add(handId, item);
}
@@ -239,7 +239,7 @@ public sealed partial class BorgSystem
foreach (var (hand, item) in component.ProvidedItems)
{
QueueDel(item);
_hands.RemoveHand(chassis, hand, hands);
_hands.RemoveHand(chassis, hand);
}
component.ProvidedItems.Clear();
return;
@@ -252,7 +252,7 @@ public sealed partial class BorgSystem
RemComp<UnremoveableComponent>(item);
_container.Insert(item, component.ProvidedContainer);
}
_hands.RemoveHand(chassis, handId, hands);
_hands.RemoveHand(chassis, handId);
}
component.ProvidedItems.Clear();
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Hands.Systems;
using Content.Server.Popups;
using Content.Server.Tabletop.Components;
using Content.Shared.CCVar;
@@ -22,6 +23,7 @@ namespace Content.Server.Tabletop
{
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly EyeSystem _eye = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -84,18 +86,13 @@ namespace Content.Server.Tabletop
if (component.Session is not { } session)
return;
if (hands.ActiveHand == null)
if (!_hands.TryGetActiveItem(uid, out var handEnt))
return;
if (hands.ActiveHand.HeldEntity == null)
return;
var handEnt = hands.ActiveHand.HeldEntity.Value;
if (!TryComp<ItemComponent>(handEnt, out var item))
return;
var meta = MetaData(handEnt);
var meta = MetaData(handEnt.Value);
var protoId = meta.EntityPrototype?.ID;
var hologram = Spawn(protoId, session.Position.Offset(-1, 0));

View File

@@ -90,9 +90,9 @@ public sealed class InnateToolSystem : EntitySystem
if (TryComp<HandsComponent>(uid, out var hands))
{
foreach (var hand in hands.Hands)
foreach (var hand in hands.Hands.Keys)
{
_sharedHandsSystem.TryDrop(uid, hand.Value, checkActionBlocker: false, handsComp: hands);
_sharedHandsSystem.TryDrop((uid, hands), hand, checkActionBlocker: false);
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Linq;
using Content.Server.Administration.Managers;
using Content.Server.Hands.Systems;
using Content.Server.Popups;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Verbs;
using Robust.Shared.Utility;
@@ -14,6 +14,7 @@ namespace Content.Server.Verbs
public sealed class VerbSystem : SharedVerbSystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IAdminManager _adminMgr = default!;
@@ -91,8 +92,7 @@ namespace Content.Server.Verbs
{
// first get the held item. again.
EntityUid? holding = null;
if (TryComp(user, out HandsComponent? hands) &&
hands.ActiveHandEntity is EntityUid heldEntity)
if (_hands.GetActiveItem(user) is { } heldEntity)
{
holding = heldEntity;
}

View File

@@ -2,7 +2,6 @@ using Content.Server.Hands.Systems;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Content.Shared.Popups;
using Content.Shared.Storage;
@@ -40,24 +39,20 @@ public sealed class StorageVoiceControlSystem : EntitySystem
if (!TryComp<StorageComponent>(ent, out var storage))
return;
// Get the hands component
if (!TryComp<HandsComponent>(args.Source, out var hands))
return;
// If the player has something in their hands, try to insert it into the storage
if (hands.ActiveHand != null && hands.ActiveHand.HeldEntity.HasValue)
if (_hands.TryGetActiveItem(ent.Owner, out var activeItem))
{
// Disallow insertion and provide a reason why if the person decides to insert the item into itself
if (ent.Owner.Equals(hands.ActiveHand.HeldEntity.Value))
if (ent.Owner.Equals(activeItem.Value))
{
_popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", hands.ActiveHand.HeldEntity.Value)), ent, args.Source);
_popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", activeItem.Value)), ent, args.Source);
return;
}
if (_storage.CanInsert(ent, hands.ActiveHand.HeldEntity.Value, out var failedReason))
if (_storage.CanInsert(ent, activeItem.Value, out var failedReason))
{
// We adminlog before insertion, otherwise the logger will attempt to pull info on an entity that no longer is present and throw an exception
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control");
_storage.Insert(ent, hands.ActiveHand.HeldEntity.Value, out _);
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(activeItem.Value)} into {ToPrettyString(ent)} via voice control");
_storage.Insert(ent, activeItem.Value, out _);
return;
}
{
@@ -67,7 +62,7 @@ public sealed class StorageVoiceControlSystem : EntitySystem
_popup.PopupEntity(Loc.GetString(failedReason), ent, args.Source);
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control");
$"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(activeItem.Value)} into {ToPrettyString(ent)} via voice control");
}
return;
}
@@ -80,7 +75,7 @@ public sealed class StorageVoiceControlSystem : EntitySystem
// E.g "go go s" would give you the screwdriver because "screwdriver" contains "s"
if (Name(item).Contains(args.MessageWithoutPhrase))
{
ExtractItemFromStorage(ent, item, args.Source, hands);
ExtractItemFromStorage(ent, item, args.Source);
break;
}
}
@@ -92,16 +87,14 @@ public sealed class StorageVoiceControlSystem : EntitySystem
/// <param name="ent">The entity with the <see cref="StorageVoiceControlComponent"/></param>
/// <param name="item">The entity to be extracted from the attached storage</param>
/// <param name="source">The entity wearing the item</param>
/// <param name="hands">The <see cref="HandsComponent"/> of the person wearing the item</param>
private void ExtractItemFromStorage(Entity<StorageVoiceControlComponent> ent,
EntityUid item,
EntityUid source,
HandsComponent hands)
EntityUid source)
{
_container.RemoveEntity(ent, item);
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(source)} retrieved {ToPrettyString(item)} from {ToPrettyString(ent)} via voice control");
_hands.TryPickup(source, item, handsComp: hands);
_hands.TryPickup(source, item);
}
}

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Hands.Systems;
using Content.Server.Power.Components;
using Content.Shared.DoAfter;
using Content.Shared.GameTicking;
@@ -24,6 +25,7 @@ public sealed class WiresSystem : SharedWiresSystem
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -405,19 +407,13 @@ public sealed class WiresSystem : SharedWiresSystem
return;
}
var activeHand = handsComponent.ActiveHand;
if (activeHand == null)
if (!_hands.TryGetActiveItem((player, handsComponent), out var heldEntity))
return;
if (activeHand.HeldEntity == null)
if (!EntityManager.TryGetComponent(heldEntity, out ToolComponent? tool))
return;
var activeHandEntity = activeHand.HeldEntity.Value;
if (!EntityManager.TryGetComponent(activeHandEntity, out ToolComponent? tool))
return;
TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
TryDoWireAction(uid, player, heldEntity.Value, args.Id, args.Action, component, tool);
}
private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args)

View File

@@ -3,7 +3,7 @@ using Content.Shared.Access.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.PDA;
@@ -21,6 +21,7 @@ public abstract class SharedIdCardSystem : EntitySystem
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAccessSystem _access = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -83,8 +84,7 @@ public abstract class SharedIdCardSystem : EntitySystem
public bool TryFindIdCard(EntityUid uid, out Entity<IdCardComponent> idCard)
{
// check held item?
if (TryComp(uid, out HandsComponent? hands) &&
hands.ActiveHandEntity is EntityUid heldItem &&
if (_hands.GetActiveItem(uid) is { } heldItem &&
TryGetIdCard(heldItem, out idCard))
{
return true;

View File

@@ -97,7 +97,7 @@ public sealed partial class BlockingSystem : EntitySystem
if (!handQuery.TryGetComponent(args.Performer, out var hands))
return;
var shields = _handsSystem.EnumerateHeld(args.Performer, hands).ToArray();
var shields = _handsSystem.EnumerateHeld((args.Performer, hands)).ToArray();
foreach (var shield in shields)
{
@@ -277,7 +277,7 @@ public sealed partial class BlockingSystem : EntitySystem
if (!handQuery.TryGetComponent(user, out var hands))
return;
var shields = _handsSystem.EnumerateHeld(user, hands).ToArray();
var shields = _handsSystem.EnumerateHeld((user, hands)).ToArray();
foreach (var shield in shields)
{

View File

@@ -947,12 +947,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
if (!entity.Comp.HeldOnly)
return true;
if (TryComp(examiner, out HandsComponent? handsComp))
{
return Hands.IsHolding(examiner, entity, out _, handsComp);
}
return true;
return Hands.IsHolding(examiner, entity, out _);
}
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)

View File

@@ -255,7 +255,7 @@ namespace Content.Shared.Containers.ItemSlots
}
// Drop the held item onto the floor. Return if the user cannot drop.
if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands))
if (!_handsSystem.TryDrop(args.User, args.Used))
return;
slots.Sort(SortEmpty);
@@ -395,17 +395,17 @@ namespace Content.Shared.Containers.ItemSlots
if (!Resolve(user, ref hands, false))
return false;
if (hands.ActiveHand?.HeldEntity is not { } held)
if (!_handsSystem.TryGetActiveItem((uid, hands), out var held))
return false;
if (!CanInsert(uid, held, user, slot))
if (!CanInsert(uid, held.Value, user, slot))
return false;
// hands.Drop(item) checks CanDrop action blocker
if (!_handsSystem.TryDrop(user, hands.ActiveHand))
if (!_handsSystem.TryDrop(user, hands.ActiveHandId!))
return false;
Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio);
Insert(uid, slot, held.Value, user, excludeUserAudio: excludeUserAudio);
return true;
}
@@ -428,16 +428,14 @@ namespace Content.Shared.Containers.ItemSlots
if (!Resolve(ent, ref ent.Comp, false))
return false;
TryComp(user, out HandsComponent? handsComp);
if (!TryGetAvailableSlot(ent,
item,
user == null ? null : (user.Value, handsComp),
user,
out var itemSlot,
emptyOnly: true))
return false;
if (user != null && !_handsSystem.TryDrop(user.Value, item, handsComp: handsComp))
if (user != null && !_handsSystem.TryDrop(user.Value, item))
return false;
Insert(ent, itemSlot, item, user, excludeUserAudio: excludeUserAudio);
@@ -466,7 +464,7 @@ namespace Content.Shared.Containers.ItemSlots
&& Resolve(user, ref user.Comp)
&& _handsSystem.IsHolding(user, item))
{
if (!_handsSystem.CanDrop(user, item, user.Comp))
if (!_handsSystem.CanDrop(user, item))
return false;
}

View File

@@ -397,6 +397,10 @@ namespace Content.Shared.Cuffs
/// </summary>
private void OnHandCountChanged(Entity<CuffableComponent> ent, ref HandCountChangedEvent message)
{
// TODO: either don't store a container ref, or make it actually nullable.
if (ent.Comp.Container == default!)
return;
var dirty = false;
var handCount = CompOrNull<HandsComponent>(ent.Owner)?.Count ?? 0;
@@ -431,19 +435,19 @@ namespace Content.Shared.Cuffs
return;
var freeHands = 0;
foreach (var hand in _hands.EnumerateHands(uid, handsComponent))
foreach (var hand in _hands.EnumerateHands((uid, handsComponent)))
{
if (hand.HeldEntity == null)
if (!_hands.TryGetHeldItem((uid, handsComponent), hand, out var held))
{
freeHands++;
continue;
}
// Is this entity removable? (it might be an existing handcuff blocker)
if (HasComp<UnremoveableComponent>(hand.HeldEntity))
if (HasComp<UnremoveableComponent>(held))
continue;
_hands.DoDrop(uid, hand, true, handsComponent);
_hands.DoDrop(uid, hand, true);
freeHands++;
if (freeHands == 2)
break;

View File

@@ -141,7 +141,7 @@ public abstract class SharedDisposalUnitSystem : EntitySystem
Category = VerbCategory.Insert,
Act = () =>
{
_handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands);
_handsSystem.TryDropIntoContainer((args.User, args.Hands), args.Using.Value, component.Container, checkActionBlocker: false);
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}");
AfterInsert(uid, component, args.Using.Value, args.User);
}

View File

@@ -233,7 +233,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
return true;
// If the user changes which hand is active at all, interrupt the do-after
if (args.BreakOnHandChange && hands.ActiveHand?.Name != doAfter.InitialHand)
if (args.BreakOnHandChange && hands.ActiveHandId != doAfter.InitialHand)
return true;
}

View File

@@ -221,8 +221,8 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
if (!TryComp(args.User, out HandsComponent? handsComponent))
return false;
doAfter.InitialHand = handsComponent.ActiveHand?.Name;
doAfter.InitialItem = handsComponent.ActiveHandEntity;
doAfter.InitialHand = handsComponent.ActiveHandId;
doAfter.InitialItem = _hands.GetActiveItem((args.User, handsComponent));
}
doAfter.NetInitialItem = GetNetEntity(doAfter.InitialItem);

View File

@@ -70,7 +70,7 @@ public sealed class DeployFoldableSystem : EntitySystem
}
if (!TryComp(args.User, out HandsComponent? hands)
|| !_hands.TryDrop(args.User, args.Used, targetDropLocation: args.ClickLocation, handsComp: hands))
|| !_hands.TryDrop((args.User, hands), args.Used, targetDropLocation: args.ClickLocation))
return;
if (!_foldable.TrySetFolded(ent, foldable, false))

View File

@@ -1,44 +0,0 @@
using System.Linq;
using Content.Shared.Hands.EntitySystems;
namespace Content.Shared.Hands.Components;
/// <summary>
/// These helpers exist to make getting basic information out of the hands component more convenient, without
/// needing to resolve hands system or something like that.
/// </summary>
public static class HandHelpers
{
/// <summary>
/// Returns true if any hand is free. This is a LinQ method, not a property, so
/// cache it instead of accessing this multiple times.
/// </summary>
public static bool IsAnyHandFree(this HandsComponent component) => component.Hands.Values.Any(hand => hand.IsEmpty);
/// <summary>
/// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so
/// cache it instead of accessing this multiple times.
/// </summary>
public static int CountFreeHands(this HandsComponent component) => component.Hands.Values.Count(hand => hand.IsEmpty);
/// <summary>
/// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so
/// cache it instead of accessing this multiple times.
/// </summary>
public static int CountFreeableHands(this Entity<HandsComponent> component, SharedHandsSystem system)
{
return system.CountFreeableHands(component);
}
/// <summary>
/// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache
/// it instead of accessing this multiple times.
/// </summary>
public static IEnumerable<Hand> GetFreeHands(this HandsComponent component) => component.Hands.Values.Where(hand => !hand.IsEmpty);
/// <summary>
/// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache
/// it instead of accessing this multiple times.
/// </summary>
public static IEnumerable<string> GetFreeHandNames(this HandsComponent component) => GetFreeHands(component).Select(hand => hand.Name);
}

View File

@@ -1,6 +1,5 @@
using Content.Shared.DisplacementMap;
using Content.Shared.Hands.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
@@ -13,50 +12,50 @@ public sealed partial class HandsComponent : Component
/// <summary>
/// The currently active hand.
/// </summary>
[ViewVariables]
public Hand? ActiveHand;
[DataField]
public string? ActiveHandId;
/// <summary>
/// The item currently held in the active hand.
/// Dictionary relating a unique hand ID corresponding to a container slot on the attached entity to a class containing information about the Hand itself.
/// </summary>
[ViewVariables]
public EntityUid? ActiveHandEntity => ActiveHand?.HeldEntity;
[ViewVariables]
[DataField]
public Dictionary<string, Hand> Hands = new();
/// <summary>
/// The number of hands
/// </summary>
[ViewVariables]
public int Count => Hands.Count;
/// <summary>
/// List of hand-names. These are keys for <see cref="Hands"/>. The order of this list determines the order in which hands are iterated over.
/// </summary>
[DataField]
public List<string> SortedHands = new();
/// <summary>
/// If true, the items in the hands won't be affected by explosions.
/// </summary>
[DataField]
public bool DisableExplosionRecursion = false;
public bool DisableExplosionRecursion;
/// <summary>
/// Modifies the speed at which items are thrown.
/// </summary>
[DataField]
[ViewVariables(VVAccess.ReadWrite)]
public float BaseThrowspeed { get; set; } = 11f;
public float BaseThrowspeed = 11f;
/// <summary>
/// Distance after which longer throw targets stop increasing throw impulse.
/// </summary>
[DataField("throwRange")]
[ViewVariables(VVAccess.ReadWrite)]
public float ThrowRange { get; set; } = 8f;
[DataField]
public float ThrowRange = 8f;
/// <summary>
/// Whether or not to add in-hand sprites for held items. Some entities (e.g., drones) don't want these.
/// Used by the client.
/// </summary>
[DataField("showInHands")]
[DataField]
public bool ShowInHands = true;
/// <summary>
@@ -68,14 +67,13 @@ public sealed partial class HandsComponent : Component
/// <summary>
/// The time at which throws will be allowed again.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
[DataField, AutoPausedField]
public TimeSpan NextThrowTime;
/// <summary>
/// The minimum time inbetween throws.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[DataField]
public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f);
/// <summary>
@@ -103,48 +101,37 @@ public sealed partial class HandsComponent : Component
public bool CanBeStripped = true;
}
[DataDefinition]
[Serializable, NetSerializable]
public sealed class Hand //TODO: This should definitely be a struct - Jezi
public partial record struct Hand
{
[ViewVariables]
public string Name { get; }
[DataField]
public HandLocation Location = HandLocation.Right;
[ViewVariables]
public HandLocation Location { get; }
/// <summary>
/// The container used to hold the contents of this hand. Nullable because the client must get the containers via <see cref="ContainerManagerComponent"/>,
/// which may not be synced with the server when the client hands are created.
/// </summary>
[ViewVariables, NonSerialized]
public ContainerSlot? Container;
[ViewVariables]
public EntityUid? HeldEntity => Container?.ContainedEntity;
public bool IsEmpty => HeldEntity == null;
public Hand(string name, HandLocation location, ContainerSlot? container = null)
public Hand()
{
}
public Hand(HandLocation location)
{
Name = name;
Location = location;
Container = container;
}
}
[Serializable, NetSerializable]
public sealed class HandsComponentState : ComponentState
{
public readonly List<Hand> Hands;
public readonly List<string> HandNames;
public readonly string? ActiveHand;
public readonly Dictionary<string, Hand> Hands;
public readonly List<string> SortedHands;
public readonly string? ActiveHandId;
public HandsComponentState(HandsComponent handComp)
{
// cloning lists because of test networking.
Hands = new(handComp.Hands.Values);
HandNames = new(handComp.SortedHands);
ActiveHand = handComp.ActiveHand?.Name;
Hands = new(handComp.Hands);
SortedHands = new(handComp.SortedHands);
ActiveHandId = handComp.ActiveHandId;
}
}

View File

@@ -6,17 +6,17 @@ namespace Content.Shared.Hands.EntitySystems;
// These functions are mostly unused except for some AI operator stuff
// Nothing stops them from being used in general. If they ever get used elsewhere, then this file probably needs to be renamed.
public abstract partial class SharedHandsSystem : EntitySystem
public abstract partial class SharedHandsSystem
{
public bool TrySelect(EntityUid uid, EntityUid? entity, HandsComponent? handsComp = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
if (!IsHolding(uid, entity, out var hand, handsComp))
if (!IsHolding((uid, handsComp), entity, out var hand))
return false;
SetActiveHand(uid, hand, handsComp);
SetActiveHand((uid, handsComp), hand);
return true;
}
@@ -26,9 +26,12 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
foreach (var hand in handsComp.Hands.Values)
foreach (var hand in handsComp.Hands.Keys)
{
if (TryComp(hand.HeldEntity, out component))
if (!TryGetHeldItem((uid, handsComp), hand, out var held))
continue;
if (TryComp(held, out component))
return true;
}

View File

@@ -7,6 +7,7 @@ using Content.Shared.Tag;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Hands.EntitySystems;
@@ -28,10 +29,10 @@ public abstract partial class SharedHandsSystem
return;
}
var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand);
var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand.Value);
RaiseLocalEvent(args.Entity, gotUnequipped);
var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand);
var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand.Value);
RaiseLocalEvent(uid, didUnequip);
if (TryComp(args.Entity, out VirtualItemComponent? @virtual))
@@ -47,26 +48,29 @@ public abstract partial class SharedHandsSystem
/// <summary>
/// Checks whether an entity can drop a given entity. Will return false if they are not holding the entity.
/// </summary>
public bool CanDrop(EntityUid uid, EntityUid entity, HandsComponent? handsComp = null, bool checkActionBlocker = true)
public bool CanDrop(Entity<HandsComponent?> ent, EntityUid entity, bool checkActionBlocker = true)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (!IsHolding(uid, entity, out var hand, handsComp))
if (!IsHolding(ent, entity, out var hand))
return false;
return CanDropHeld(uid, hand, checkActionBlocker);
return CanDropHeld(ent, hand, checkActionBlocker);
}
/// <summary>
/// Checks if the contents of a hand is able to be removed from its container.
/// </summary>
public bool CanDropHeld(EntityUid uid, Hand hand, bool checkActionBlocker = true)
public bool CanDropHeld(EntityUid uid, string handId, bool checkActionBlocker = true)
{
if (hand.Container?.ContainedEntity is not {} held)
if (!ContainerSystem.TryGetContainer(uid, handId, out var container))
return false;
if (!ContainerSystem.CanRemove(held, hand.Container))
if (container.ContainedEntities.FirstOrNull() is not {} held)
return false;
if (!ContainerSystem.CanRemove(held, container))
return false;
if (checkActionBlocker && !_actionBlocker.CanDrop(uid))
@@ -78,98 +82,100 @@ public abstract partial class SharedHandsSystem
/// <summary>
/// Attempts to drop the item in the currently active hand.
/// </summary>
public bool TryDrop(EntityUid uid, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
public bool TryDrop(Entity<HandsComponent?> ent, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (handsComp.ActiveHand == null)
if (ent.Comp.ActiveHandId == null)
return false;
return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
return TryDrop(ent, ent.Comp.ActiveHandId, targetDropLocation, checkActionBlocker, doDropInteraction);
}
/// <summary>
/// Drops an item at the target location.
/// </summary>
public bool TryDrop(EntityUid uid, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
public bool TryDrop(Entity<HandsComponent?> ent, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (!IsHolding(uid, entity, out var hand, handsComp))
if (!IsHolding(ent, entity, out var hand))
return false;
return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
return TryDrop(ent, hand, targetDropLocation, checkActionBlocker, doDropInteraction);
}
/// <summary>
/// Drops a hands contents at the target location.
/// </summary>
public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
public bool TryDrop(Entity<HandsComponent?> ent, string handId, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (!CanDropHeld(uid, hand, checkActionBlocker))
if (!CanDropHeld(ent, handId, checkActionBlocker))
return false;
var entity = hand.HeldEntity!.Value;
if (!TryGetHeldItem(ent, handId, out var entity))
return false;
// if item is a fake item (like with pulling), just delete it rather than bothering with trying to drop it into the world
if (TryComp(entity, out VirtualItemComponent? @virtual))
_virtualSystem.DeleteVirtualItem((entity, @virtual), uid);
_virtualSystem.DeleteVirtualItem((entity.Value, @virtual), ent);
if (TerminatingOrDeleted(entity))
return true;
var itemXform = Transform(entity);
var itemXform = Transform(entity.Value);
if (itemXform.MapUid == null)
return true;
var userXform = Transform(uid);
var isInContainer = ContainerSystem.IsEntityOrParentInContainer(uid, xform: userXform);
var userXform = Transform(ent);
var isInContainer = ContainerSystem.IsEntityOrParentInContainer(ent, xform: userXform);
// if the user is in a container, drop the item inside the container
if (isInContainer) {
TransformSystem.DropNextTo((entity, itemXform), (uid, userXform));
if (isInContainer)
{
TransformSystem.DropNextTo((entity.Value, itemXform), (ent, userXform));
return true;
}
// drop the item with heavy calculations from their hands and place it at the calculated interaction range position
// The DoDrop is handle if there's no drop target
DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
DoDrop(ent, handId, doDropInteraction: doDropInteraction);
// if there's no drop location stop here
if (targetDropLocation == null)
return true;
// otherwise, also move dropped item and rotate it properly according to grid/map
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity);
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity.Value);
var origin = new MapCoordinates(itemPos, itemXform.MapID);
var target = TransformSystem.ToMapCoordinates(targetDropLocation.Value);
TransformSystem.SetWorldPositionRotation(entity, GetFinalDropCoordinates(uid, origin, target, entity), itemRot);
TransformSystem.SetWorldPositionRotation(entity.Value, GetFinalDropCoordinates(ent, origin, target, entity.Value), itemRot);
return true;
}
/// <summary>
/// Attempts to move a held item from a hand into a container that is not another hand, without dropping it on the floor in-between.
/// </summary>
public bool TryDropIntoContainer(EntityUid uid, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true, HandsComponent? handsComp = null)
public bool TryDropIntoContainer(Entity<HandsComponent?> ent, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (!IsHolding(uid, entity, out var hand, handsComp))
if (!IsHolding(ent, entity, out var hand))
return false;
if (!CanDropHeld(uid, hand, checkActionBlocker))
if (!CanDropHeld(ent, hand, checkActionBlocker))
return false;
if (!ContainerSystem.CanInsert(entity, targetContainer))
return false;
DoDrop(uid, hand, false, handsComp);
DoDrop(ent, hand, false);
ContainerSystem.Insert(entity, targetContainer);
return true;
}
@@ -202,34 +208,38 @@ public abstract partial class SharedHandsSystem
/// <summary>
/// Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly.
/// </summary>
public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null, bool log = true)
public virtual void DoDrop(Entity<HandsComponent?> ent,
string handId,
bool doDropInteraction = true,
bool log = true)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return;
if (hand.Container?.ContainedEntity == null)
if (!ContainerSystem.TryGetContainer(ent, handId, out var container))
return;
var entity = hand.Container.ContainedEntity.Value;
if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(entity))
if (!TryGetHeldItem(ent, handId, out var entity))
return;
if (!ContainerSystem.Remove(entity, hand.Container))
if (TerminatingOrDeleted(ent) || TerminatingOrDeleted(entity))
return;
if (!ContainerSystem.Remove(entity.Value, container))
{
Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(uid)}. Hand: {hand.Name}.");
Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(ent)}. Hand: {handId}.");
return;
}
Dirty(uid, handsComp);
Dirty(ent);
if (doDropInteraction)
_interactionSystem.DroppedInteraction(uid, entity);
_interactionSystem.DroppedInteraction(ent, entity.Value);
if (log)
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(uid):user} dropped {ToPrettyString(entity):entity}");
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(ent):user} dropped {ToPrettyString(entity):entity}");
if (hand == handsComp.ActiveHand)
RaiseLocalEvent(entity, new HandDeselectedEvent(uid));
if (handId == ent.Comp.ActiveHandId)
RaiseLocalEvent(entity.Value, new HandDeselectedEvent(ent));
}
}

View File

@@ -97,20 +97,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!_actionBlocker.CanInteract(session.AttachedEntity.Value, null))
return;
if (component.ActiveHand == null || component.Hands.Count < 2)
if (component.ActiveHandId == null || component.Hands.Count < 2)
return;
var currentIndex = component.SortedHands.IndexOf(component.ActiveHand.Name);
var currentIndex = component.SortedHands.IndexOf(component.ActiveHandId);
var newActiveIndex = (currentIndex + (reverse ? -1 : 1) + component.Hands.Count) % component.Hands.Count;
var nextHand = component.SortedHands[newActiveIndex];
TrySetActiveHand(session.AttachedEntity.Value, nextHand, component);
TrySetActiveHand((session.AttachedEntity.Value, component), nextHand);
}
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid netEntity)
{
if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHand != null)
TryDrop(session.AttachedEntity.Value, hands.ActiveHand, coords, handsComp: hands);
if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHandId != null)
TryDrop((session.AttachedEntity.Value, hands), hands.ActiveHandId, coords);
// always send to server.
return false;
@@ -122,14 +122,14 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
Hand? hand;
if (handName == null || !handsComp.Hands.TryGetValue(handName, out hand))
hand = handsComp.ActiveHand;
var hand = handName;
if (!TryGetHand(uid, hand, out _))
hand = handsComp.ActiveHandId;
if (hand?.HeldEntity is not { } held)
if (!TryGetHeldItem((uid, handsComp), hand, out var held))
return false;
return _interactionSystem.InteractionActivate(uid, held);
return _interactionSystem.InteractionActivate(uid, held.Value);
}
public bool TryInteractHandWithActiveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
@@ -137,16 +137,13 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
if (handsComp.ActiveHandEntity == null)
if (!TryGetActiveItem((uid, handsComp), out var activeHeldItem))
return false;
if (!handsComp.Hands.TryGetValue(handName, out var hand))
if (!TryGetHeldItem((uid, handsComp), handName, out var held))
return false;
if (hand.HeldEntity == null)
return false;
_interactionSystem.InteractUsing(uid, handsComp.ActiveHandEntity.Value, hand.HeldEntity.Value, Transform(hand.HeldEntity.Value).Coordinates);
_interactionSystem.InteractUsing(uid, activeHeldItem.Value, held.Value, Transform(held.Value).Coordinates);
return true;
}
@@ -155,17 +152,16 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
Hand? hand;
if (handName == null || !handsComp.Hands.TryGetValue(handName, out hand))
hand = handsComp.ActiveHand;
var hand = handName;
if (!TryGetHand(uid, hand, out _))
hand = handsComp.ActiveHandId;
if (hand?.HeldEntity is not { } held)
if (!TryGetHeldItem((uid, handsComp), hand, out var held))
return false;
if (altInteract)
return _interactionSystem.AltInteract(uid, held);
else
return _interactionSystem.UseInHandInteraction(uid, held);
return _interactionSystem.AltInteract(uid, held.Value);
return _interactionSystem.UseInHandInteraction(uid, held.Value);
}
/// <summary>
@@ -176,22 +172,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp))
return false;
if (handsComp.ActiveHand == null || !handsComp.ActiveHand.IsEmpty)
if (handsComp.ActiveHandId == null || !HandIsEmpty((uid, handsComp), handsComp.ActiveHandId))
return false;
if (!handsComp.Hands.TryGetValue(handName, out var hand))
if (!TryGetHeldItem((uid, handsComp), handName, out var entity))
return false;
if (!CanDropHeld(uid, hand, checkActionBlocker))
if (!CanDropHeld(uid, handName, checkActionBlocker))
return false;
var entity = hand.HeldEntity!.Value;
if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp))
if (!CanPickupToHand(uid, entity.Value, handsComp.ActiveHandId, checkActionBlocker, handsComp))
return false;
DoDrop(uid, hand, false, handsComp, log:false);
DoPickup(uid, handsComp.ActiveHand, entity, handsComp, log: false);
DoDrop(uid, handName, false, log: false);
DoPickup(uid, handsComp.ActiveHandId, entity.Value, handsComp, log: false);
return true;
}
@@ -200,19 +194,19 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (args.Handled)
return;
if (component.ActiveHandEntity.HasValue)
if (TryGetActiveItem((uid, component), out var activeHeldItem))
{
// allow for the item to return a different entity, e.g. virtual items
RaiseLocalEvent(component.ActiveHandEntity.Value, ref args);
RaiseLocalEvent(activeHeldItem.Value, ref args);
}
args.Used ??= component.ActiveHandEntity;
args.Used ??= activeHeldItem;
}
//TODO: Actually shows all items/clothing/etc.
private void HandleExamined(EntityUid examinedUid, HandsComponent handsComp, ExaminedEvent args)
{
var heldItemNames = EnumerateHeld(examinedUid, handsComp)
var heldItemNames = EnumerateHeld((examinedUid, handsComp))
.Where(entity => !HasComp<VirtualItemComponent>(entity))
.Select(item => FormattedMessage.EscapeText(Identity.Name(item, EntityManager)))
.Select(itemName => Loc.GetString("comp-hands-examine-wrapper", ("item", itemName)))

View File

@@ -1,15 +1,15 @@
using Content.Shared.Clothing.Components;
using System.Diagnostics;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Item;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Utility;
namespace Content.Shared.Hands.EntitySystems;
public abstract partial class SharedHandsSystem : EntitySystem
public abstract partial class SharedHandsSystem
{
private void InitializePickup()
{
@@ -23,11 +23,11 @@ public abstract partial class SharedHandsSystem : EntitySystem
return;
}
var didEquip = new DidEquipHandEvent(uid, args.Entity, hand);
RaiseLocalEvent(uid, didEquip, false);
var didEquip = new DidEquipHandEvent(uid, args.Entity, hand.Value);
RaiseLocalEvent(uid, didEquip);
var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand);
RaiseLocalEvent(args.Entity, gotEquipped, false);
var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand.Value);
RaiseLocalEvent(args.Entity, gotEquipped);
}
/// <summary>
@@ -35,32 +35,6 @@ public abstract partial class SharedHandsSystem : EntitySystem
/// </summary>
public const float MaxAnimationRange = 10;
/// <summary>
/// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand.
/// </summary>
public bool TryPickup(
EntityUid uid,
EntityUid entity,
string? handName = null,
bool checkActionBlocker = true,
bool animateUser = false,
bool animate = true,
HandsComponent? handsComp = null,
ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
var hand = handsComp.ActiveHand;
if (handName != null && !handsComp.Hands.TryGetValue(handName, out hand))
return false;
if (hand == null)
return false;
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
}
/// <summary>
/// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand.
/// </summary>
@@ -80,17 +54,21 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
if (!TryGetEmptyHand(uid, out var hand, handsComp))
if (!TryGetEmptyHand((uid, handsComp), out var hand))
return false;
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item);
}
/// <summary>
/// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand.
/// </summary>
public bool TryPickup(
EntityUid uid,
EntityUid entity,
Hand hand,
string? handId = null,
bool checkActionBlocker = true,
bool animateUser = false,
bool animate = true,
HandsComponent? handsComp = null,
ItemComponent? item = null)
@@ -98,10 +76,15 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
handId ??= handsComp.ActiveHandId;
if (handId == null)
return false;
if (!Resolve(entity, ref item, false))
return false;
if (!CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item))
if (!CanPickupToHand(uid, entity, handId, checkActionBlocker, handsComp, item))
return false;
if (animate)
@@ -119,7 +102,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
_storage.PlayPickupAnimation(entity, initialPosition, xform.Coordinates, itemXform.LocalRotation, uid);
}
}
DoPickup(uid, hand, entity, handsComp);
DoPickup(uid, handId, entity, handsComp);
return true;
}
@@ -129,20 +112,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
/// By default it does check if it's possible to drop items.
/// </summary>
public bool TryForcePickup(
EntityUid uid,
Entity<HandsComponent?> ent,
EntityUid entity,
Hand hand,
string hand,
bool checkActionBlocker = true,
bool animate = true,
HandsComponent? handsComp = null,
ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
if (!Resolve(ent, ref ent.Comp, false))
return false;
TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp);
TryDrop(ent, hand, checkActionBlocker: checkActionBlocker);
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
return TryPickup(ent, entity, hand, checkActionBlocker, animate: animate, handsComp: handsComp, item: item);
}
/// <summary>
@@ -157,9 +140,9 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (TryPickupAnyHand(uid, entity, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
return true;
foreach (var hand in handsComp.Hands.Values)
foreach (var hand in handsComp.Hands.Keys)
{
if (TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp) &&
if (TryDrop((uid, handsComp), hand, checkActionBlocker: checkActionBlocker) &&
TryPickup(uid, entity, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
{
return true;
@@ -173,7 +156,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!Resolve(uid, ref handsComp, false))
return false;
if (!TryGetEmptyHand(uid, out var hand, handsComp))
if (!TryGetEmptyHand((uid, handsComp), out var hand))
return false;
return CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item);
@@ -182,13 +165,15 @@ public abstract partial class SharedHandsSystem : EntitySystem
/// <summary>
/// Checks whether a given item will fit into a specific user's hand. Unless otherwise specified, this will also check the general CanPickup action blocker.
/// </summary>
public bool CanPickupToHand(EntityUid uid, EntityUid entity, Hand hand, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
public bool CanPickupToHand(EntityUid uid, EntityUid entity, string handId, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
var handContainer = hand.Container;
if (handContainer == null || handContainer.ContainedEntity != null)
if (!ContainerSystem.TryGetContainer(uid, handId, out var handContainer))
return false;
if (handContainer.ContainedEntities.FirstOrNull() != null)
return false;
if (!Resolve(entity, ref item, false))
@@ -231,8 +216,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
{
if (uid == null
|| !Resolve(uid.Value, ref handsComp, false)
|| !TryGetEmptyHand(uid.Value, out var hand, handsComp)
|| !TryPickup(uid.Value, entity, hand, checkActionBlocker, animate, handsComp, item))
|| !TryPickupAnyHand(uid.Value, entity, checkActionBlocker, animateUser, animate, handsComp, item))
{
// TODO make this check upwards for any container, and parent to that.
// Currently this just checks the direct parent, so items can still teleport through containers.
@@ -248,18 +232,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
/// <summary>
/// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly.
/// </summary>
public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
public virtual void DoPickup(EntityUid uid, string hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
{
if (!Resolve(uid, ref hands))
return;
var handContainer = hand.Container;
if (handContainer == null || handContainer.ContainedEntity != null)
if (!ContainerSystem.TryGetContainer(uid, hand, out var handContainer))
return;
if (handContainer.ContainedEntities.FirstOrNull() != null)
return;
if (!ContainerSystem.Insert(entity, handContainer))
{
Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand.Name}.");
Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand}.");
return;
}
@@ -270,7 +256,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
Dirty(uid, hands);
if (hand == hands.ActiveHand)
RaiseLocalEvent(entity, new HandSelectedEvent(uid), false);
if (hand == hands.ActiveHandId)
RaiseLocalEvent(entity, new HandSelectedEvent(uid));
}
}

View File

@@ -41,7 +41,7 @@ public abstract partial class SharedHandsSystem
{
var ev = new HeldRelayedEvent<T>(args);
foreach (var held in EnumerateHeld(entity, entity.Comp))
foreach (var held in EnumerateHeld(entity.AsNullable()))
{
RaiseLocalEvent(held, ref ev);
}

View File

@@ -9,6 +9,7 @@ using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Input.Binding;
using Robust.Shared.Utility;
namespace Content.Shared.Hands.EntitySystems;
@@ -23,6 +24,8 @@ public abstract partial class SharedHandsSystem
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
[Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!;
public event Action<Entity<HandsComponent>, string, HandLocation>? OnPlayerAddHand;
public event Action<Entity<HandsComponent>, string>? OnPlayerRemoveHand;
protected event Action<Entity<HandsComponent>?>? OnHandSetActive;
public override void Initialize()
@@ -33,6 +36,9 @@ public abstract partial class SharedHandsSystem
InitializeDrop();
InitializePickup();
InitializeRelay();
SubscribeLocalEvent<HandsComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<HandsComponent, MapInitEvent>(OnMapInit);
}
public override void Shutdown()
@@ -41,71 +47,94 @@ public abstract partial class SharedHandsSystem
CommandBinds.Unregister<SharedHandsSystem>();
}
public virtual void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null)
private void OnInit(Entity<HandsComponent> ent, ref ComponentInit args)
{
if (!Resolve(uid, ref handsComp, false))
return;
if (handsComp.Hands.ContainsKey(handName))
return;
var container = ContainerSystem.EnsureContainer<ContainerSlot>(uid, handName);
container.OccludesLight = false;
var newHand = new Hand(handName, handLocation, container);
handsComp.Hands.Add(handName, newHand);
handsComp.SortedHands.Add(handName);
if (handsComp.ActiveHand == null)
SetActiveHand(uid, newHand, handsComp);
RaiseLocalEvent(uid, new HandCountChangedEvent(uid));
Dirty(uid, handsComp);
var container = EnsureComp<ContainerManagerComponent>(ent);
foreach (var id in ent.Comp.Hands.Keys)
{
ContainerSystem.EnsureContainer<ContainerSlot>(ent, id, container);
}
}
public virtual void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
private void OnMapInit(Entity<HandsComponent> ent, ref MapInitEvent args)
{
if (!Resolve(uid, ref handsComp, false))
if (ent.Comp.ActiveHandId == null)
SetActiveHand(ent.AsNullable(), ent.Comp.SortedHands.FirstOrDefault());
}
/// <summary>
/// Adds a hand with the given container id and supplied location to the specified entity.
/// </summary>
public void AddHand(Entity<HandsComponent?> ent, string handName, HandLocation handLocation)
{
AddHand(ent, handName, new Hand(handLocation));
}
/// <summary>
/// Adds a hand with the given container id and supplied hand definition to the given entity.
/// </summary>
public void AddHand(Entity<HandsComponent?> ent, string handName, Hand hand)
{
if (!Resolve(ent, ref ent.Comp, false))
return;
if (!handsComp.Hands.Remove(handName, out var hand))
if (ent.Comp.Hands.ContainsKey(handName))
return;
handsComp.SortedHands.Remove(hand.Name);
TryDrop(uid, hand, null, false, true, handsComp);
if (hand.Container != null)
ContainerSystem.ShutdownContainer(hand.Container);
var container = ContainerSystem.EnsureContainer<ContainerSlot>(ent, handName);
container.OccludesLight = false;
if (handsComp.ActiveHand == hand)
TrySetActiveHand(uid, handsComp.SortedHands.FirstOrDefault(), handsComp);
ent.Comp.Hands.Add(handName, hand);
ent.Comp.SortedHands.Add(handName);
Dirty(ent);
RaiseLocalEvent(uid, new HandCountChangedEvent(uid));
Dirty(uid, handsComp);
OnPlayerAddHand?.Invoke((ent, ent.Comp), handName, hand.Location);
if (ent.Comp.ActiveHandId == null)
SetActiveHand(ent, handName);
RaiseLocalEvent(ent, new HandCountChangedEvent(ent));
}
/// <summary>
/// Removes the specified hand from the specified entity
/// </summary>
public virtual void RemoveHand(Entity<HandsComponent?> ent, string handName)
{
if (!Resolve(ent, ref ent.Comp, false))
return;
OnPlayerRemoveHand?.Invoke((ent, ent.Comp), handName);
TryDrop(ent, handName, null, false);
if (!ent.Comp.Hands.Remove(handName))
return;
if (ContainerSystem.TryGetContainer(ent, handName, out var container))
ContainerSystem.ShutdownContainer(container);
ent.Comp.SortedHands.Remove(handName);
if (ent.Comp.ActiveHandId == handName)
TrySetActiveHand(ent, ent.Comp.SortedHands.FirstOrDefault());
RaiseLocalEvent(ent, new HandCountChangedEvent(ent));
Dirty(ent);
}
/// <summary>
/// Gets rid of all the entity's hands.
/// </summary>
/// <param name="uid"></param>
/// <param name="handsComp"></param>
public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null)
public void RemoveHands(Entity<HandsComponent?> ent)
{
if (!Resolve(uid, ref handsComp))
if (!Resolve(ent, ref ent.Comp, false))
return;
RemoveHands(uid, EnumerateHands(uid), handsComp);
var handIds = new List<string>(ent.Comp.Hands.Keys);
foreach (var handId in handIds)
{
RemoveHand(ent, handId);
}
private void RemoveHands(EntityUid uid, IEnumerable<Hand> hands, HandsComponent handsComp)
{
if (!hands.Any())
return;
var hand = hands.First();
RemoveHand(uid, hand.Name, handsComp);
// Repeats it for any additional hands.
RemoveHands(uid, hands, handsComp);
}
private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
@@ -119,15 +148,15 @@ public abstract partial class SharedHandsSystem
/// <summary>
/// Get any empty hand. Prioritizes the currently active hand.
/// </summary>
public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHand, HandsComponent? handComp = null)
public bool TryGetEmptyHand(Entity<HandsComponent?> ent, [NotNullWhen(true)] out string? emptyHand)
{
emptyHand = null;
if (!Resolve(uid, ref handComp, false))
if (!Resolve(ent, ref ent.Comp, false))
return false;
foreach (var hand in EnumerateHands(uid, handComp))
foreach (var hand in EnumerateHands(ent))
{
if (hand.IsEmpty)
if (HandIsEmpty(ent, hand))
{
emptyHand = hand;
return true;
@@ -137,28 +166,20 @@ public abstract partial class SharedHandsSystem
return false;
}
public bool TryGetActiveHand(Entity<HandsComponent?> entity, [NotNullWhen(true)] out Hand? hand)
{
if (!Resolve(entity, ref entity.Comp, false))
{
hand = null;
return false;
}
hand = entity.Comp.ActiveHand;
return hand != null;
}
/// <summary>
/// Attempts to retrieve the item held in the entity's active hand.
/// </summary>
public bool TryGetActiveItem(Entity<HandsComponent?> entity, [NotNullWhen(true)] out EntityUid? item)
{
if (!TryGetActiveHand(entity, out var hand))
{
item = null;
if (!Resolve(entity, ref entity.Comp, false))
return false;
}
item = hand.HeldEntity;
return item != null;
if (!TryGetHeldItem(entity, entity.Comp.ActiveHandId, out var held))
return false;
item = held;
return true;
}
/// <summary>
@@ -174,55 +195,73 @@ public abstract partial class SharedHandsSystem
return item.Value;
}
public Hand? GetActiveHand(Entity<HandsComponent?> entity)
/// <summary>
/// Gets the current active hand's Id for the specified entity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public string? GetActiveHand(Entity<HandsComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
if (!Resolve(entity, ref entity.Comp, false))
return null;
return entity.Comp.ActiveHand;
return entity.Comp.ActiveHandId;
}
/// <summary>
/// Gets the current active hand's held entity for the specified entity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public EntityUid? GetActiveItem(Entity<HandsComponent?> entity)
{
return GetActiveHand(entity)?.HeldEntity;
if (!Resolve(entity, ref entity.Comp, false))
return null;
return GetHeldItem(entity, entity.Comp.ActiveHandId);
}
public bool ActiveHandIsEmpty(Entity<HandsComponent?> entity)
{
return GetActiveItem(entity) == null;
}
/// <summary>
/// Enumerate over hands, starting with the currently active hand.
/// </summary>
public IEnumerable<Hand> EnumerateHands(EntityUid uid, HandsComponent? handsComp = null)
public IEnumerable<string> EnumerateHands(Entity<HandsComponent?> ent)
{
if (!Resolve(uid, ref handsComp, false))
if (!Resolve(ent, ref ent.Comp, false))
yield break;
if (handsComp.ActiveHand != null)
yield return handsComp.ActiveHand;
if (ent.Comp.ActiveHandId != null)
yield return ent.Comp.ActiveHandId;
foreach (var name in handsComp.SortedHands)
foreach (var name in ent.Comp.SortedHands)
{
if (name != handsComp.ActiveHand?.Name)
yield return handsComp.Hands[name];
if (name != ent.Comp.ActiveHandId)
yield return name;
}
}
/// <summary>
/// Enumerate over held items, starting with the item in the currently active hand (if there is one).
/// </summary>
public IEnumerable<EntityUid> EnumerateHeld(EntityUid uid, HandsComponent? handsComp = null)
public IEnumerable<EntityUid> EnumerateHeld(Entity<HandsComponent?> ent)
{
if (!Resolve(uid, ref handsComp, false))
if (!Resolve(ent, ref ent.Comp, false))
yield break;
if (handsComp.ActiveHandEntity != null)
yield return handsComp.ActiveHandEntity.Value;
if (TryGetActiveItem(ent, out var activeHeld))
yield return activeHeld.Value;
foreach (var name in handsComp.SortedHands)
foreach (var name in ent.Comp.SortedHands)
{
if (name == handsComp.ActiveHand?.Name)
if (name == ent.Comp.ActiveHandId)
continue;
if (handsComp.Hands[name].HeldEntity is { } held)
yield return held;
if (TryGetHeldItem(ent, name, out var held))
yield return held.Value;
}
}
@@ -231,18 +270,17 @@ public abstract partial class SharedHandsSystem
/// </summary>
/// <returns>True if the active hand was set to a NEW value. Setting it to the same value returns false and does
/// not trigger interactions.</returns>
public virtual bool TrySetActiveHand(EntityUid uid, string? name, HandsComponent? handComp = null)
public bool TrySetActiveHand(Entity<HandsComponent?> ent, string? name)
{
if (!Resolve(uid, ref handComp))
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (name == handComp.ActiveHand?.Name)
if (name == ent.Comp.ActiveHandId)
return false;
Hand? hand = null;
if (name != null && !handComp.Hands.TryGetValue(name, out hand))
if (name != null && !ent.Comp.Hands.ContainsKey(name))
return false;
return SetActiveHand(uid, hand, handComp);
return SetActiveHand(ent, name);
}
/// <summary>
@@ -250,50 +288,50 @@ public abstract partial class SharedHandsSystem
/// </summary>
/// <returns>True if the active hand was set to a NEW value. Setting it to the same value returns false and does
/// not trigger interactions.</returns>
public bool SetActiveHand(EntityUid uid, Hand? hand, HandsComponent? handComp = null)
public bool SetActiveHand(Entity<HandsComponent?> ent, string? handId)
{
if (!Resolve(uid, ref handComp))
if (!Resolve(ent, ref ent.Comp))
return false;
if (hand == handComp.ActiveHand)
if (handId == ent.Comp.ActiveHandId)
return false;
if (handComp.ActiveHand?.HeldEntity is { } held)
RaiseLocalEvent(held, new HandDeselectedEvent(uid));
if (TryGetHeldItem(ent, handId, out var oldHeld))
RaiseLocalEvent(oldHeld.Value, new HandDeselectedEvent(ent));
if (hand == null)
if (handId == null)
{
handComp.ActiveHand = null;
ent.Comp.ActiveHandId = null;
return true;
}
handComp.ActiveHand = hand;
OnHandSetActive?.Invoke((uid, handComp));
ent.Comp.ActiveHandId = handId;
OnHandSetActive?.Invoke((ent, ent.Comp));
if (hand.HeldEntity != null)
RaiseLocalEvent(hand.HeldEntity.Value, new HandSelectedEvent(uid));
if (TryGetHeldItem(ent, handId, out var newHeld))
RaiseLocalEvent(newHeld.Value, new HandSelectedEvent(ent));
Dirty(uid, handComp);
Dirty(ent);
return true;
}
public bool IsHolding(Entity<HandsComponent?> entity, [NotNullWhen(true)] EntityUid? item)
{
return IsHolding(entity, item, out _, entity);
return IsHolding(entity, item, out _);
}
public bool IsHolding(EntityUid uid, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null)
public bool IsHolding(Entity<HandsComponent?> ent, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out string? inHand)
{
inHand = null;
if (entity == null)
return false;
if (!Resolve(uid, ref handsComp, false))
if (!Resolve(ent, ref ent.Comp, false))
return false;
foreach (var hand in handsComp.Hands.Values)
foreach (var hand in ent.Comp.Hands.Keys)
{
if (hand.HeldEntity == entity)
if (GetHeldItem(ent, hand) == entity)
{
inHand = hand;
return true;
@@ -303,23 +341,89 @@ public abstract partial class SharedHandsSystem
return false;
}
public bool TryGetHand(EntityUid handsUid, string handId, [NotNullWhen(true)] out Hand? hand,
HandsComponent? hands = null)
/// <summary>
/// Attempts to retrieve the associated hand struct corresponding to a hand ID on a given entity.
/// </summary>
public bool TryGetHand(Entity<HandsComponent?> ent, [NotNullWhen(true)] string? handId, [NotNullWhen(true)] out Hand? hand)
{
hand = null;
if (!Resolve(handsUid, ref hands))
if (handId == null)
return false;
return hands.Hands.TryGetValue(handId, out hand);
if (!Resolve(ent, ref ent.Comp, false))
return false;
if (!ent.Comp.Hands.TryGetValue(handId, out var handsHand))
return false;
hand = handsHand;
return true;
}
/// <summary>
/// Gets the item currently held in the entity's specified hand. Returns null if no hands are present or there is no item.
/// </summary>
public EntityUid? GetHeldItem(Entity<HandsComponent?> ent, string? handId)
{
TryGetHeldItem(ent, handId, out var held);
return held;
}
/// <summary>
/// Gets the item currently held in the entity's specified hand. Returns false if no hands are present or there is no item.
/// </summary>
public bool TryGetHeldItem(Entity<HandsComponent?> ent, string? handId, [NotNullWhen(true)] out EntityUid? held)
{
held = null;
if (!Resolve(ent, ref ent.Comp, false))
return false;
// Sanity check to make sure this is actually a hand.
if (handId == null || !ent.Comp.Hands.ContainsKey(handId))
return false;
if (!ContainerSystem.TryGetContainer(ent, handId, out var container))
return false;
held = container.ContainedEntities.FirstOrNull();
return held != null;
}
public bool HandIsEmpty(Entity<HandsComponent?> ent, string handId)
{
return GetHeldItem(ent, handId) == null;
}
public int GetHandCount(Entity<HandsComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp, false))
return 0;
return ent.Comp.Hands.Count;
}
public int CountFreeHands(Entity<HandsComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp, false))
return 0;
var free = 0;
foreach (var name in ent.Comp.Hands.Keys)
{
if (HandIsEmpty(ent, name))
free++;
}
return free;
}
public int CountFreeableHands(Entity<HandsComponent> hands)
{
var freeable = 0;
foreach (var hand in hands.Comp.Hands.Values)
foreach (var name in hands.Comp.Hands.Keys)
{
if (hand.IsEmpty || CanDropHeld(hands, hand))
if (HandIsEmpty(hands.AsNullable(), name) || CanDropHeld(hands, name))
freeable++;
}

View File

@@ -9,6 +9,7 @@ using Content.Shared.Database;
using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
@@ -58,6 +59,7 @@ namespace Content.Shared.Interaction
[Dependency] private readonly ISharedChatManager _chat = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PullingSystem _pullSystem = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
@@ -343,7 +345,7 @@ namespace Content.Shared.Interaction
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
{
// Always allow attack in these cases
if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
if (target == null || !_handsQuery.TryComp(user, out var hands) || _hands.GetActiveItem((user, hands)) is not null)
return false;
// Only eat input if:

View File

@@ -64,10 +64,10 @@ public sealed class SmartEquipSystem : EntitySystem
return;
// early out if we don't have any hands or a valid inventory slot
if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHand == null)
if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHandId == null)
return;
var handItem = hands.ActiveHand.HeldEntity;
var handItem = _hands.GetActiveItem((uid, hands));
// can the user interact, and is the item interactable? e.g. virtual items
if (!_actionBlocker.CanInteract(uid, handItem))
@@ -80,7 +80,7 @@ public sealed class SmartEquipSystem : EntitySystem
}
// early out if we have an item and cant drop it at all
if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand))
if (hands.ActiveHandId != null && !_hands.CanDropHeld(uid, hands.ActiveHandId))
{
_popup.PopupClient(Loc.GetString("smart-equip-cant-drop"), uid, uid);
return;
@@ -121,7 +121,7 @@ public sealed class SmartEquipSystem : EntitySystem
return;
}
_hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
_hands.TryDrop((uid, hands), hands.ActiveHandId!);
_inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true, checkDoafter:true);
return;
}
@@ -149,7 +149,7 @@ public sealed class SmartEquipSystem : EntitySystem
return;
}
_hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
_hands.TryDrop((uid, hands), hands.ActiveHandId!);
_storage.Insert(slotItem, handItem.Value, out var stacked, out _);
// if the hand item stacked with the things in inventory, but there's no more space left for the rest

View File

@@ -83,7 +83,7 @@ public abstract partial class InventorySystem
if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp<HandsComponent>(actor, out var hands))
return;
var held = hands.ActiveHandEntity;
var held = _handsSystem.GetActiveItem((actor, hands));
TryGetSlotEntity(actor, ev.Slot, out var itemUid, inventory);
// attempt to perform some interaction
@@ -115,7 +115,7 @@ public abstract partial class InventorySystem
return;
}
if (!_handsSystem.CanDropHeld(actor, hands.ActiveHand!, checkActionBlocker: false))
if (!_handsSystem.CanDropHeld(actor, hands.ActiveHandId!, checkActionBlocker: false))
return;
RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor));

View File

@@ -16,12 +16,9 @@ public partial class InventorySystem
{
if (Resolve(user.Owner, ref user.Comp1, false))
{
foreach (var hand in user.Comp1.Hands.Values)
foreach (var held in _handsSystem.EnumerateHeld(user))
{
if (hand.HeldEntity == null)
continue;
yield return hand.HeldEntity.Value;
yield return held;
}
}

View File

@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
@@ -8,7 +7,6 @@ using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared.Inventory.VirtualItem;
@@ -88,9 +86,9 @@ public abstract class SharedVirtualItemSystem : EntitySystem
// if the user is holding the real item the virtual item points to,
// we allow them to use it in the interaction
foreach (var hand in _handsSystem.EnumerateHands(args.User))
foreach (var held in _handsSystem.EnumerateHeld(args.User))
{
if (hand.HeldEntity == ent.Comp.BlockingEntity)
if (held == ent.Comp.BlockingEntity)
{
args.Used = ent.Comp.BlockingEntity;
return;
@@ -112,7 +110,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
}
/// <inheritdoc cref="TrySpawnVirtualItemInHand(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid,bool)"/>
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, Hand? empty = null)
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, string? empty = null)
{
virtualItem = null;
if (empty == null && !_handsSystem.TryGetEmptyHand(user, out empty))
@@ -122,7 +120,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
foreach (var hand in _handsSystem.EnumerateHands(user))
{
if (hand.HeldEntity is not { } held)
if (!_handsSystem.TryGetHeldItem(user, hand, out var held))
continue;
if (held == blockingEnt)
@@ -155,11 +153,11 @@ public abstract class SharedVirtualItemSystem : EntitySystem
/// </summary>
public void DeleteInHandsMatching(EntityUid user, EntityUid matching)
{
foreach (var hand in _handsSystem.EnumerateHands(user))
foreach (var held in _handsSystem.EnumerateHeld(user))
{
if (TryComp(hand.HeldEntity, out VirtualItemComponent? virt) && virt.BlockingEntity == matching)
if (TryComp(held, out VirtualItemComponent? virt) && virt.BlockingEntity == matching)
{
DeleteVirtualItem((hand.HeldEntity.Value, virt), user);
DeleteVirtualItem((held, virt), user);
}
}
}

View File

@@ -1,5 +1,4 @@
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Popups;
@@ -38,7 +37,7 @@ public sealed class MultiHandedItemSystem : EntitySystem
private void OnAttemptPickup(Entity<MultiHandedItemComponent> ent, ref GettingPickedUpAttemptEvent args)
{
if (TryComp<HandsComponent>(args.User, out var hands) && hands.CountFreeHands() >= ent.Comp.HandsNeeded)
if (_hands.CountFreeHands(ent.Owner) >= ent.Comp.HandsNeeded)
return;
args.Cancel();

View File

@@ -110,7 +110,7 @@ public abstract class SharedItemSystem : EntitySystem
if (args.Handled)
return;
args.Handled = _handsSystem.TryPickup(args.User, uid, animateUser: false);
args.Handled = _handsSystem.TryPickup(args.User, uid, null, animateUser: false);
}
private void AddPickupVerb(EntityUid uid, ItemComponent component, GetVerbsEvent<InteractionVerb> args)

View File

@@ -434,7 +434,7 @@ public abstract class SharedMagicSystem : EntitySystem
return;
EntityUid? wand = null;
foreach (var item in _hands.EnumerateHeld(ev.Performer, handsComp))
foreach (var item in _hands.EnumerateHeld((ev.Performer, handsComp)))
{
if (!_tag.HasTag(item, ev.WandTag))
continue;

View File

@@ -109,16 +109,12 @@ public sealed class PullingSystem : EntitySystem
// Try find hand that is doing this pull.
// and clear it.
foreach (var hand in component.Hands.Values)
{
if (hand.HeldEntity == null
|| !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem)
|| virtualItem.BlockingEntity != args.PulledUid)
foreach (var held in _handsSystem.EnumerateHeld((uid, component)))
{
if (!TryComp(held, out VirtualItemComponent? virtualItem) || virtualItem.BlockingEntity != args.PulledUid)
continue;
}
_handsSystem.TryDrop(args.PullerUid, hand, handsComp: component);
_handsSystem.TryDrop((args.PullerUid, component), held);
break;
}
}

View File

@@ -2,6 +2,7 @@ using Content.Shared.Clothing.Components;
using Content.Shared.CombatMode;
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle;
@@ -19,6 +20,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
@@ -128,8 +130,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
target = args.Target;
return _timing.IsFirstTimePredicted
&& !_combatMode.IsInCombatMode(uid)
&& TryComp<HandsComponent>(uid, out var hands)
&& hands.ActiveHandEntity == null
&& _hands.GetActiveItem(uid) == null
&& _interaction.InRangeUnobstructed(uid, target);
}
}

View File

@@ -459,7 +459,7 @@ public sealed class FoodSystem : EntitySystem
var usedTypes = UtensilType.None;
foreach (var item in _hands.EnumerateHeld(user, hands))
foreach (var item in _hands.EnumerateHeld((user, hands)))
{
// Is utensil?
if (!TryComp<UtensilComponent>(item, out var utensil))

View File

@@ -5,7 +5,7 @@ using Content.Shared.Construction;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Physics;
@@ -35,6 +35,7 @@ public sealed class RCDSystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly TurfSystem _turf = default!;
@@ -296,11 +297,10 @@ public sealed class RCDSystem : EntitySystem
var uid = GetEntity(ev.NetEntity);
// Determine if player that send the message is carrying the specified RCD in their active hand
if (session.SenderSession.AttachedEntity == null)
if (session.SenderSession.AttachedEntity is not { } player)
return;
if (!TryComp<HandsComponent>(session.SenderSession.AttachedEntity, out var hands) ||
uid != hands.ActiveHand?.HeldEntity)
if (_hands.GetActiveItem(player) != uid)
return;
if (!TryComp<RCDComponent>(uid, out var rcd))

View File

@@ -1,7 +1,6 @@
using Content.Shared.Actions;
using Content.Shared.Cuffs;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
@@ -42,7 +41,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
private void OnRetractableItemAction(Entity<RetractableItemActionComponent> ent, ref OnRetractableItemActionEvent args)
{
if (_hands.GetActiveHand(args.Performer) is not { } userHand)
if (_hands.GetActiveHand(args.Performer) is not { } activeHand)
return;
if (_actions.GetAction(ent.Owner) is not { } action)
@@ -55,7 +54,9 @@ public sealed class RetractableItemActionSystem : EntitySystem
return;
// Don't allow to summon an item if holding an unremoveable item unless that item is summoned by the action.
if (userHand.HeldEntity != null && !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid) && !_hands.CanDropHeld(args.Performer, userHand, false))
if (_hands.GetActiveItem(ent.Owner) != null
&& !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid)
&& !_hands.CanDropHeld(args.Performer, activeHand, false))
{
_popups.PopupClient(Loc.GetString("retractable-item-hand-cannot-drop"), args.Performer, args.Performer);
return;
@@ -67,7 +68,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
}
else
{
SummonRetractableItem(args.Performer, ent.Comp.ActionItemUid.Value, userHand, ent.Owner);
SummonRetractableItem(args.Performer, ent.Comp.ActionItemUid.Value, activeHand, ent.Owner);
}
args.Handled = true;
@@ -93,7 +94,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
if (action.Comp.AttachedEntity == null)
return;
if (_hands.GetActiveHand(action.Comp.AttachedEntity.Value) is not { } userHand)
if (_hands.GetActiveHand(action.Comp.AttachedEntity.Value) is not { })
return;
RetractRetractableItem(action.Comp.AttachedEntity.Value, ent, action.Owner);
@@ -128,7 +129,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
_audio.PlayPredicted(action.Comp.RetractSounds, holder, holder);
}
private void SummonRetractableItem(EntityUid holder, EntityUid item, Hand hand, Entity<RetractableItemActionComponent?> action)
private void SummonRetractableItem(EntityUid holder, EntityUid item, string hand, Entity<RetractableItemActionComponent?> action)
{
if (!Resolve(action, ref action.Comp, false))
return;

View File

@@ -146,7 +146,7 @@ namespace Content.Shared.Stacks
}
// This is shit code until hands get fixed and give an easy way to enumerate over items, starting with the currently active item.
foreach (var held in Hands.EnumerateHeld(user, hands))
foreach (var held in Hands.EnumerateHeld((user, hands)))
{
TryMergeStacks(item, held, out _, donorStack: itemStack);

View File

@@ -141,7 +141,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem
{
var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
if (_handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand))
{
_handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
}

View File

@@ -689,7 +689,7 @@ public abstract class SharedStorageSystem : EntitySystem
return;
// If the user's active hand is empty, try pick up the item.
if (player.Comp.ActiveHandEntity == null)
if (!_sharedHandsSystem.TryGetActiveItem(player.AsNullable(), out var activeItem))
{
_adminLog.Add(
LogType.Storage,
@@ -709,11 +709,11 @@ public abstract class SharedStorageSystem : EntitySystem
_adminLog.Add(
LogType.Storage,
LogImpact.Low,
$"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(player.Comp.ActiveHandEntity):used}");
$"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(activeItem):used}");
// Else, interact using the held item
if (_interactionSystem.InteractUsing(player,
player.Comp.ActiveHandEntity.Value,
activeItem.Value,
item,
Transform(item).Coordinates,
checkCanInteract: false))
@@ -1208,10 +1208,10 @@ public abstract class SharedStorageSystem : EntitySystem
{
if (!Resolve(ent.Owner, ref ent.Comp)
|| !Resolve(player.Owner, ref player.Comp)
|| player.Comp.ActiveHandEntity == null)
|| !_sharedHandsSystem.TryGetActiveItem(player, out var activeItem))
return false;
var toInsert = player.Comp.ActiveHandEntity;
var toInsert = activeItem;
if (!CanInsert(ent, toInsert.Value, out var reason, ent.Comp))
{
@@ -1219,7 +1219,7 @@ public abstract class SharedStorageSystem : EntitySystem
return false;
}
if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, player.Comp))
if (!_sharedHandsSystem.CanDrop(player, toInsert.Value))
{
_popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), ent, player);
return false;
@@ -1933,7 +1933,7 @@ public abstract class SharedStorageSystem : EntitySystem
if (held)
{
if (!_sharedHandsSystem.IsHolding(player, itemUid, out _))
if (!_sharedHandsSystem.IsHolding(player.AsNullable(), itemUid, out _))
return false;
}
else

View File

@@ -104,8 +104,8 @@ public abstract class SharedStrippableSystem : EntitySystem
var hasEnt = _inventorySystem.TryGetSlotEntity(strippable, args.Slot, out var held, inventory);
if (userHands.ActiveHandEntity != null && !hasEnt)
StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot);
if (_handsSystem.GetActiveItem((user, userHands)) is { } activeItem && !hasEnt)
StartStripInsertInventory((user, userHands), strippable.Owner, activeItem, args.Slot);
else if (hasEnt)
StartStripRemoveInventory(user, strippable.Owner, held!.Value, args.Slot);
}
@@ -124,11 +124,10 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!target.Comp.CanBeStripped)
return;
if (!_handsSystem.TryGetHand(target.Owner, handId, out var handSlot))
return;
var heldEntity = _handsSystem.GetHeldItem(target.Owner, handId);
// Is the target a handcuff?
if (TryComp<VirtualItemComponent>(handSlot.HeldEntity, out var virtualItem) &&
if (TryComp<VirtualItemComponent>(heldEntity, out var virtualItem) &&
TryComp<CuffableComponent>(target.Owner, out var cuffable) &&
_cuffableSystem.GetAllCuffs(cuffable).Contains(virtualItem.BlockingEntity))
{
@@ -136,10 +135,10 @@ public abstract class SharedStrippableSystem : EntitySystem
return;
}
if (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null)
StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable);
else if (handSlot.HeldEntity != null)
StartStripRemoveHand(user, target, handSlot.HeldEntity.Value, handId, targetStrippable);
if (_handsSystem.GetActiveItem(user.AsNullable()) is { } activeItem && heldEntity == null)
StartStripInsertHand(user, target, activeItem, handId, targetStrippable);
else if (heldEntity != null)
StartStripRemoveHand(user, target, heldEntity.Value, handId, targetStrippable);
}
/// <summary>
@@ -154,16 +153,10 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!Resolve(user, ref user.Comp))
return false;
if (user.Comp.ActiveHand == null)
if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held)
return false;
if (user.Comp.ActiveHandEntity == null)
return false;
if (user.Comp.ActiveHandEntity != held)
return false;
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
return false;
@@ -210,10 +203,14 @@ public abstract class SharedStrippableSystem : EntitySystem
var (time, stealth) = GetStripTimeModifiers(user, target, held, slotDef.StripTime);
if (!stealth)
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert",
("user", Identity.Entity(user, EntityManager)),
("item", user.Comp.ActiveHandEntity!.Value)),
target, target, PopupType.Large);
("item", _handsSystem.GetActiveItem((user, user.Comp))!.Value)),
target,
target,
PopupType.Large);
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");
@@ -246,7 +243,7 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!CanStripInsertInventory(user, target, held, slot))
return;
if (!_handsSystem.TryDrop(user, handsComp: user.Comp))
if (!_handsSystem.TryDrop(user))
return;
_inventorySystem.TryEquip(user, target, held, slot, triggerHandContact: true);
@@ -305,10 +302,15 @@ public abstract class SharedStrippableSystem : EntitySystem
if (IsStripHidden(slotDef, user))
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target, target, PopupType.Large);
else
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner",
("user", Identity.Entity(user, EntityManager)),
("item", item)),
target, target, PopupType.Large);
target,
target,
PopupType.Large);
}
}
var prefix = stealth ? "stealthily " : "";
@@ -368,23 +370,16 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!target.Comp.CanBeStripped)
return false;
if (user.Comp.ActiveHand == null)
if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held)
return false;
if (user.Comp.ActiveHandEntity == null)
return false;
if (user.Comp.ActiveHandEntity != held)
return false;
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
return false;
}
if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp) ||
!_handsSystem.CanPickupToHand(target, user.Comp.ActiveHandEntity.Value, handSlot, checkActionBlocker: false, target.Comp))
if (!_handsSystem.CanPickupToHand(target, activeItem.Value, handName, checkActionBlocker: false, target.Comp))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-put-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
@@ -414,10 +409,15 @@ public abstract class SharedStrippableSystem : EntitySystem
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);
if (!stealth)
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand",
("user", Identity.Entity(user, EntityManager)),
("item", user.Comp.ActiveHandEntity!.Value)),
target, target, PopupType.Large);
("item", _handsSystem.GetActiveItem(user)!.Value)),
target,
target,
PopupType.Large);
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
@@ -452,7 +452,7 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!CanStripInsertHand(user, target, held, handName))
return;
_handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: user.Comp);
_handsSystem.TryDrop(user, checkActionBlocker: false);
_handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: !stealth, handsComp: target.Comp);
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
@@ -474,22 +474,22 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!target.Comp.CanBeStripped)
return false;
if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp))
if (!_handsSystem.TryGetHand(target, handName, out _))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
}
if (HasComp<VirtualItemComponent>(handSlot.HeldEntity))
if (!_handsSystem.TryGetHeldItem(target, handName, out var heldEntity))
return false;
if (handSlot.HeldEntity == null)
if (HasComp<VirtualItemComponent>(heldEntity))
return false;
if (handSlot.HeldEntity != item)
if (heldEntity != item)
return false;
if (!_handsSystem.CanDropHeld(target, handSlot, false))
if (!_handsSystem.CanDropHeld(target, handName, false))
{
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message", ("owner", Identity.Entity(target, EntityManager))));
return false;
@@ -519,10 +519,13 @@ public abstract class SharedStrippableSystem : EntitySystem
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);
if (!stealth)
{
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner",
("user", Identity.Entity(user, EntityManager)),
("item", item)),
target, target);
target,
target);
}
var prefix = stealth ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");
@@ -560,7 +563,7 @@ public abstract class SharedStrippableSystem : EntitySystem
if (!CanStripRemoveHand(user, target, item, handName))
return;
_handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: target.Comp);
_handsSystem.TryDrop(target, item, checkActionBlocker: false);
_handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp);
_adminLogger.Add(LogType.Stripping, LogImpact.High, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");
@@ -580,15 +583,19 @@ public abstract class SharedStrippableSystem : EntitySystem
{
if ( ev.Event.InsertOrRemove && !CanStripInsertInventory((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) ||
!ev.Event.InsertOrRemove && !CanStripRemoveInventory(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName))
{
ev.Cancel();
}
}
else
{
if ( ev.Event.InsertOrRemove && !CanStripInsertHand((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) ||
!ev.Event.InsertOrRemove && !CanStripRemoveHand(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName))
{
ev.Cancel();
}
}
}
private void OnStrippableDoAfterFinished(Entity<HandsComponent> entity, ref StrippableDoAfterEvent ev)
{

View File

@@ -108,10 +108,10 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (component.InHandsOnly)
{
if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands))
if (!_hands.IsHolding((args.User, args.Hands), uid, out var hand ))
return false;
if (component.RequireActiveHand && args.Hands.ActiveHand != hand)
if (component.RequireActiveHand && args.Hands.ActiveHandId != hand)
return false;
}
}
@@ -202,10 +202,10 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (!TryComp(user, out HandsComponent? hands))
return false;
if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
if (!_hands.IsHolding((user, hands), uiEntity, out var hand))
return false;
if (aui.RequireActiveHand && hands.ActiveHand != hand)
if (aui.RequireActiveHand && hands.ActiveHandId != hand)
return false;
}

View File

@@ -12,6 +12,7 @@ using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
@@ -51,6 +52,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
[Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
[Dependency] protected readonly ActionBlockerSystem Blocker = default!;
[Dependency] protected readonly DamageableSystem Damageable = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly MeleeSoundSystem _meleeSound = default!;
[Dependency] protected readonly MobStateSystem MobState = default!;
@@ -288,15 +290,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
}
// Use inhands entity if we got one.
if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) &&
hands.ActiveHandEntity is { } held)
if (_hands.TryGetActiveItem(entity, out var held))
{
// Make sure the entity is a weapon AND it doesn't need
// to be equipped to be used (E.g boxing gloves).
if (EntityManager.TryGetComponent(held, out melee) &&
!melee.MustBeEquippedToUse)
{
weaponUid = held;
weaponUid = held.Value;
return true;
}
@@ -858,9 +859,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
EntityUid? inTargetHand = null;
if (targetHandsComponent?.ActiveHand is { IsEmpty: false })
if (_hands.TryGetActiveItem(target.Value, out var activeHeldEntity))
{
inTargetHand = targetHandsComponent.ActiveHand.HeldEntity!.Value;
inTargetHand = activeHeldEntity.Value;
}
var attemptEvent = new DisarmAttemptEvent(target.Value, user, inTargetHand);

View File

@@ -1,7 +1,7 @@
using System.Numerics;
using Content.Shared.CombatMode;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Movement.Events;
using Content.Shared.Physics;
@@ -25,6 +25,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedJointSystem _joints = default!;
[Dependency] private readonly SharedGunSystem _gun = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
@@ -80,9 +81,11 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
private void OnGrapplingReel(RequestGrapplingReelMessage msg, EntitySessionEventArgs args)
{
var player = args.SenderSession.AttachedEntity;
if (!TryComp<HandsComponent>(player, out var hands) ||
!TryComp<GrapplingGunComponent>(hands.ActiveHandEntity, out var grappling))
if (args.SenderSession.AttachedEntity is not { } player)
return;
if (!_hands.TryGetActiveItem(player, out var activeItem) ||
!TryComp<GrapplingGunComponent>(activeItem, out var grappling))
{
return;
}
@@ -94,7 +97,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
return;
}
SetReeling(hands.ActiveHandEntity.Value, grappling, msg.Reeling, player.Value);
SetReeling(activeItem.Value, grappling, msg.Reeling, player);
}
private void OnWeightlessMove(ref CanWeightlessMoveEvent ev)

View File

@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using Content.Shared.ActionBlocker;
using Content.Shared.Buckle.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Events;
@@ -23,6 +24,7 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
{
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly MobStateSystem _mob = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -139,14 +141,14 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
gunUid = null;
gun = null;
if (!TryComp<HandsComponent>(user, out var hands) ||
!TryComp(hands.ActiveHandEntity, out gun) ||
if (!_hands.TryGetActiveItem(user, out var activeItem) ||
!TryComp(activeItem, out gun) ||
_container.IsEntityInContainer(user))
{
return false;
}
gunUid = hands.ActiveHandEntity.Value;
gunUid = activeItem.Value;
return true;
}

View File

@@ -10,7 +10,7 @@ using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Gravity;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Projectiles;
using Content.Shared.Tag;
@@ -49,6 +49,7 @@ public abstract partial class SharedGunSystem : EntitySystem
[Dependency] protected readonly ISharedAdminLogManager Logs = default!;
[Dependency] protected readonly DamageableSystem Damageable = default!;
[Dependency] protected readonly ExamineSystemShared Examine = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly ItemSlotsSystem _slots = default!;
[Dependency] private readonly RechargeBasicEntityAmmoSystem _recharge = default!;
[Dependency] protected readonly SharedActionsSystem Actions = default!;
@@ -173,8 +174,7 @@ public abstract partial class SharedGunSystem : EntitySystem
gunEntity = default;
gunComp = null;
if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) &&
hands.ActiveHandEntity is { } held &&
if (_hands.GetActiveItem(entity) is { } held &&
TryComp(held, out GunComponent? gun))
{
gunEntity = held;

View File

@@ -112,7 +112,7 @@ public abstract class SharedWieldableSystem : EntitySystem
private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args)
{
if (_hands.EnumerateHands(args.User).Count() > 2)
if (_hands.GetHandCount(uid) > 2)
return;
TryUnwield(uid, component, args.User);
@@ -168,7 +168,7 @@ public abstract class SharedWieldableSystem : EntitySystem
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
if (!_hands.IsHolding(args.User, uid, out _, args.Hands))
if (!_hands.IsHolding((args.User, args.Hands), uid, out _))
return;
// TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
@@ -252,7 +252,7 @@ public abstract class SharedWieldableSystem : EntitySystem
}
// Is it.. actually in one of their hands?
if (!_hands.IsHolding(user, uid, out _, hands))
if (!_hands.IsHolding((user, hands), uid, out _))
{
if (!quiet)
_popup.PopupClient(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
@@ -373,7 +373,7 @@ public abstract class SharedWieldableSystem : EntitySystem
/// <param name="force">If this is true we will bypass UnwieldAttemptEvent.</param>
public void UnwieldAll(Entity<HandsComponent?> wielder, bool force = false)
{
foreach (var held in _hands.EnumerateHeld(wielder.Owner, wielder.Comp))
foreach (var held in _hands.EnumerateHeld(wielder))
{
if (TryComp<WieldableComponent>(held, out var wieldable))
TryUnwield(held, wieldable, wielder, force);

View File

@@ -1,22 +0,0 @@
- type: body
id: Aghost
name: "aghost"
root: torso
slots:
torso:
part: TorsoHuman
connections:
- right_arm
- left_arm
right_arm:
part: RightArmHuman
connections:
- right_hand
left_arm:
part: LeftArmHuman
connections:
- left_hand
right_hand:
part: RightHandHuman
left_hand:
part: LeftHandHuman

View File

@@ -22,6 +22,14 @@
canInteract: true
- type: GhostHearing
- type: Hands
hands:
hand_right:
location: Right
hand_left:
location: Left
sortedHands:
- hand_right
- hand_left
- type: ComplexInteraction
- type: Puller
needsHands: false
@@ -29,8 +37,6 @@
- type: Physics
ignorePaused: true
bodyType: Kinematic
- type: Body
prototype: Aghost
- type: Access
groups:
- AllAccess