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

@@ -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);
}
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);
var handIds = new List<string>(ent.Comp.Hands.Keys);
foreach (var handId in handIds)
{
RemoveHand(ent, handId);
}
}
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;
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)
foreach (var held in _handsSystem.EnumerateHeld((uid, component)))
{
if (hand.HeldEntity == null
|| !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem)
|| virtualItem.BlockingEntity != args.PulledUid)
{
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,13 +583,17 @@ 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();
{
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();
{
ev.Cancel();
}
}
}

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