hands ECS (#7081)
This commit is contained in:
39
Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs
Normal file
39
Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
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 bool TrySelect(EntityUid uid, EntityUid? entity, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
return false;
|
||||
|
||||
SetActiveHand(uid, hand, handsComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySelect<TComponent>(EntityUid uid, [NotNullWhen(true)] out TComponent? component, SharedHandsComponent? handsComp = null) where TComponent : Component
|
||||
{
|
||||
component = null;
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
{
|
||||
if (TryComp(hand.HeldEntity, out component))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TrySelectEmptyHand(EntityUid uid, SharedHandsComponent? handsComp = null) => TrySelect(uid, null, handsComp);
|
||||
}
|
||||
159
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs
Normal file
159
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
public abstract partial class SharedHandsSystem : EntitySystem
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (hand.HeldEntity == null)
|
||||
return false;
|
||||
|
||||
if (!hand.Container!.CanRemove(hand.HeldEntity.Value, EntityManager))
|
||||
return false;
|
||||
|
||||
if (checkActionBlocker && !_actionBlocker.CanDrop(uid))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHand == null)
|
||||
return false;
|
||||
|
||||
return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
|
||||
}
|
||||
|
||||
/// <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, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
return false;
|
||||
|
||||
return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
|
||||
}
|
||||
|
||||
/// <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, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return false;
|
||||
|
||||
if (!CanDropHeld(uid, hand, checkActionBlocker))
|
||||
return false;
|
||||
|
||||
var entity = hand.HeldEntity!.Value;
|
||||
DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (targetDropLocation == null)
|
||||
{
|
||||
// TODO recursively check upwards for containers
|
||||
Transform(entity).AttachParentToContainerOrGrid(EntityManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
var target = targetDropLocation.Value.ToMap(EntityManager);
|
||||
Transform(entity).WorldPosition = GetFinalDropCoordinates(uid, xform.MapPosition, target);
|
||||
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, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
return false;
|
||||
|
||||
if (!CanDropHeld(uid, hand, checkActionBlocker))
|
||||
return false;
|
||||
|
||||
if (!targetContainer.CanInsert(entity, EntityManager))
|
||||
return false;
|
||||
|
||||
DoDrop(uid, hand, false, handsComp);
|
||||
targetContainer.Insert(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path.
|
||||
/// </summary>
|
||||
private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, MapCoordinates target)
|
||||
{
|
||||
var dropVector = target.Position - origin.Position;
|
||||
var requestedDropDistance = dropVector.Length;
|
||||
|
||||
if (dropVector.Length > SharedInteractionSystem.InteractionRange)
|
||||
{
|
||||
dropVector = dropVector.Normalized * SharedInteractionSystem.InteractionRange;
|
||||
target = new MapCoordinates(origin.Position + dropVector, target.MapId);
|
||||
}
|
||||
|
||||
var dropLength = _interactionSystem.UnobstructedDistance(origin, target, predicate: e => e == user);
|
||||
|
||||
if (dropLength < requestedDropDistance)
|
||||
return origin.Position + dropVector.Normalized * dropLength;
|
||||
return target.Position;
|
||||
}
|
||||
|
||||
/// <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, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return;
|
||||
|
||||
if (hand.Container?.ContainedEntity == null)
|
||||
return;
|
||||
|
||||
var entity = hand.Container.ContainedEntity.Value;
|
||||
|
||||
if (!hand.Container!.Remove(entity, EntityManager))
|
||||
{
|
||||
Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not remove {entity} from {hand.Container}.");
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty(handsComp);
|
||||
|
||||
if (doDropInteraction)
|
||||
_interactionSystem.DroppedInteraction(uid, entity);
|
||||
|
||||
var gotUnequipped = new GotUnequippedHandEvent(uid, entity, hand);
|
||||
RaiseLocalEvent(entity, gotUnequipped, false);
|
||||
|
||||
var didUnequip = new DidUnequipHandEvent(uid, entity, hand);
|
||||
RaiseLocalEvent(uid, didUnequip);
|
||||
|
||||
if (hand == handsComp.ActiveHand)
|
||||
RaiseLocalEvent(entity, new HandDeselectedEvent(uid), false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
public abstract partial class SharedHandsSystem : EntitySystem
|
||||
{
|
||||
private void InitializeInteractions()
|
||||
{
|
||||
SubscribeAllEvent<RequestSetHandEvent>(HandleSetHand);
|
||||
SubscribeAllEvent<RequestActivateInHandEvent>(HandleActivateItemInHand);
|
||||
SubscribeAllEvent<RequestHandInteractUsingEvent>(HandleInteractUsingInHand);
|
||||
SubscribeAllEvent<RequestUseInHandEvent>(HandleUseInHand);
|
||||
SubscribeAllEvent<RequestMoveHandItemEvent>(HandleMoveItemFromHand);
|
||||
|
||||
SubscribeLocalEvent<SharedHandsComponent, ExaminedEvent>(HandleExamined);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.UseItemInHand, InputCmdHandler.FromDelegate(HandleUseItem, handle: false))
|
||||
.Bind(ContentKeyFunctions.AltUseItemInHand, InputCmdHandler.FromDelegate(HandleAltUseInHand, handle: false))
|
||||
.Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false))
|
||||
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed))
|
||||
.Register<SharedHandsSystem>();
|
||||
}
|
||||
|
||||
#region Event and Key-binding Handlers
|
||||
private void HandleAltUseInHand(ICommonSession? session)
|
||||
{
|
||||
if (session?.AttachedEntity != null)
|
||||
TryUseItemInHand(session.AttachedEntity.Value, true);
|
||||
}
|
||||
|
||||
private void HandleUseItem(ICommonSession? session)
|
||||
{
|
||||
if (session?.AttachedEntity != null)
|
||||
TryUseItemInHand(session.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void HandleMoveItemFromHand(RequestMoveHandItemEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity != null)
|
||||
TryMoveHeldEntityToActiveHand(args.SenderSession.AttachedEntity.Value, msg.HandName);
|
||||
}
|
||||
|
||||
private void HandleUseInHand(RequestUseInHandEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity != null)
|
||||
TryUseItemInHand(args.SenderSession.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void HandleActivateItemInHand(RequestActivateInHandEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity != null)
|
||||
TryActivateItemInHand(args.SenderSession.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
private void HandleInteractUsingInHand(RequestHandInteractUsingEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity != null)
|
||||
TryInteractHandWithActiveHand(args.SenderSession.AttachedEntity.Value, msg.HandName);
|
||||
}
|
||||
|
||||
private void SwapHandsPressed(ICommonSession? session)
|
||||
{
|
||||
if (!TryComp(session?.AttachedEntity, out SharedHandsComponent? component))
|
||||
return;
|
||||
|
||||
if (component.ActiveHand == null || component.Hands.Count < 2)
|
||||
return;
|
||||
|
||||
var newActiveIndex = component.SortedHands.IndexOf(component.ActiveHand.Name) + 1;
|
||||
var nextHand = component.SortedHands[newActiveIndex % component.Hands.Count];
|
||||
|
||||
TrySetActiveHand(component.Owner, nextHand, component);
|
||||
}
|
||||
|
||||
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (TryComp(session?.AttachedEntity, out SharedHandsComponent? hands) && hands.ActiveHand != null)
|
||||
TryDrop(session!.AttachedEntity!.Value, hands.ActiveHand, coords, handsComp: hands);
|
||||
|
||||
// always send to server.
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool TryActivateItemInHand(EntityUid uid, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHandEntity == null)
|
||||
return false;
|
||||
|
||||
return _interactionSystem.InteractionActivate(uid, handsComp.ActiveHandEntity.Value);
|
||||
}
|
||||
|
||||
public bool TryInteractHandWithActiveHand(EntityUid uid, string handName, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHandEntity == null)
|
||||
return false;
|
||||
|
||||
if (!handsComp.Hands.TryGetValue(handName, out var hand))
|
||||
return false;
|
||||
|
||||
if (hand.HeldEntity == null)
|
||||
return false;
|
||||
|
||||
_interactionSystem.InteractUsing(uid, handsComp.ActiveHandEntity.Value, hand.HeldEntity.Value, Transform(hand.HeldEntity.Value).Coordinates);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryUseItemInHand(EntityUid uid, bool altInteract = false, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHandEntity == null)
|
||||
return false;
|
||||
|
||||
if (altInteract)
|
||||
return _interactionSystem.AltInteract(uid, handsComp.ActiveHandEntity.Value);
|
||||
else
|
||||
return _interactionSystem.UseInHandInteraction(uid, handsComp.ActiveHandEntity.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an entity from one hand to the active hand.
|
||||
/// </summary>
|
||||
public bool TryMoveHeldEntityToActiveHand(EntityUid uid, string handName, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHand == null || !handsComp.ActiveHand.IsEmpty)
|
||||
return false;
|
||||
|
||||
if (!handsComp.Hands.TryGetValue(handName, out var hand))
|
||||
return false;
|
||||
|
||||
if (!CanDropHeld(uid, hand, checkActionBlocker))
|
||||
return false;
|
||||
|
||||
var entity = hand.HeldEntity!.Value;
|
||||
|
||||
if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp))
|
||||
return false;
|
||||
|
||||
DoDrop(uid, hand, false, handsComp);
|
||||
DoPickup(uid, handsComp.ActiveHand, entity, handsComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: Actually shows all items/clothing/etc.
|
||||
private void HandleExamined(EntityUid uid, SharedHandsComponent handsComp, ExaminedEvent args)
|
||||
{
|
||||
foreach (var inhand in EnumerateHeld(uid, handsComp))
|
||||
{
|
||||
if (HasComp<HandVirtualItemComponent>(inhand))
|
||||
continue;
|
||||
|
||||
args.PushText(Loc.GetString("comp-hands-examine", ("user", handsComp.Owner), ("item", inhand)));
|
||||
}
|
||||
}
|
||||
}
|
||||
161
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs
Normal file
161
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
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;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
public abstract partial class SharedHandsSystem : EntitySystem
|
||||
{
|
||||
/// <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, SharedHandsComponent? handsComp = null, SharedItemComponent? 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, animateUser, handsComp, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If one empty hand fails to pick up the item, this will NOT check other hands. If ever hand-specific item
|
||||
/// restrictions are added, there a might need to be a TryPickupAllHands or something like that.
|
||||
/// </remarks>
|
||||
public bool TryPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!TryGetEmptyHand(uid, out var hand, handsComp))
|
||||
return false;
|
||||
|
||||
return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, handsComp, item);
|
||||
}
|
||||
|
||||
public bool TryPickup(EntityUid uid, EntityUid entity, Hand hand, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!Resolve(entity, ref item, false))
|
||||
return false;
|
||||
|
||||
if (!CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item))
|
||||
return false;
|
||||
|
||||
// animation
|
||||
var xform = Transform(uid);
|
||||
var coordinateEntity = xform.ParentUid.IsValid() ? xform.ParentUid : uid;
|
||||
var initialPosition = EntityCoordinates.FromMap(EntityManager, coordinateEntity, Transform(entity).MapPosition);
|
||||
|
||||
PickupAnimation(entity, initialPosition, xform.LocalPosition, animateUser ? null : uid);
|
||||
DoPickup(uid, hand, entity, handsComp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!TryGetEmptyHand(uid, out var hand, handsComp))
|
||||
return false;
|
||||
|
||||
return CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item);
|
||||
}
|
||||
|
||||
/// <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, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
var handContainer = hand.Container;
|
||||
if (handContainer == null || handContainer.ContainedEntity != null)
|
||||
return false;
|
||||
|
||||
if (!Resolve(entity, ref item, false))
|
||||
return false;
|
||||
|
||||
if (TryComp(entity, out PhysicsComponent? physics) && physics.BodyType == BodyType.Static)
|
||||
return false;
|
||||
|
||||
if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity))
|
||||
return false;
|
||||
|
||||
// check can insert (including raising attempt events).
|
||||
return handContainer.CanInsert(entity, EntityManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts an item any hand, preferring the active hand, or puts it on the floor.
|
||||
/// </summary>
|
||||
public void PickupOrDrop(EntityUid? uid, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if (uid == null
|
||||
|| !Resolve(uid.Value, ref handsComp, false)
|
||||
|| !TryGetEmptyHand(uid.Value, out var hand, handsComp)
|
||||
|| !TryPickup(uid.Value, entity, hand, checkActionBlocker, animateUser, 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.
|
||||
Transform(entity).AttachParentToContainerOrGrid(EntityManager);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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, SharedHandsComponent? hands = null)
|
||||
{
|
||||
if (!Resolve(uid, ref hands))
|
||||
return;
|
||||
|
||||
var handContainer = hand.Container;
|
||||
if (handContainer == null || handContainer.ContainedEntity != null)
|
||||
return;
|
||||
|
||||
if (!handContainer.Insert(entity, EntityManager))
|
||||
{
|
||||
Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not insert {entity} into {handContainer}.");
|
||||
return;
|
||||
}
|
||||
|
||||
_adminLogSystem.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
|
||||
|
||||
Dirty(hands);
|
||||
|
||||
var didEquip = new DidEquipHandEvent(uid, entity, hand);
|
||||
RaiseLocalEvent(uid, didEquip, false);
|
||||
|
||||
var gotEquipped = new GotEquippedHandEvent(uid, entity, hand);
|
||||
RaiseLocalEvent(entity, gotEquipped);
|
||||
|
||||
// TODO this should REALLY be a cancellable thing, not a handled event.
|
||||
// If one of the interactions resulted in the item being dropped, return early.
|
||||
if (gotEquipped.Handled)
|
||||
return;
|
||||
|
||||
if (hand == hands.ActiveHand)
|
||||
RaiseLocalEvent(entity, new HandSelectedEvent(uid), false);
|
||||
}
|
||||
|
||||
public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition,
|
||||
EntityUid? exclude);
|
||||
}
|
||||
209
Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
Normal file
209
Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
public abstract partial class SharedHandsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeInteractions();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
CommandBinds.Unregister<SharedHandsSystem>();
|
||||
}
|
||||
|
||||
public void AddHand(EntityUid uid, string handName, HandLocation handLocation, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
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), false);
|
||||
Dirty(handsComp);
|
||||
}
|
||||
|
||||
public void RemoveHand(EntityUid uid, string handName, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return;
|
||||
|
||||
if (!handsComp.Hands.Remove(handName, out var hand))
|
||||
return;
|
||||
|
||||
TryDrop(uid, hand, null, false, true, handsComp);
|
||||
hand.Container?.Shutdown();
|
||||
handsComp.SortedHands.Remove(hand.Name);
|
||||
|
||||
if (handsComp.ActiveHand == hand)
|
||||
TrySetActiveHand(uid, handsComp.SortedHands.FirstOrDefault(), handsComp);
|
||||
|
||||
RaiseLocalEvent(uid, new HandCountChangedEvent(uid), false);
|
||||
Dirty(handsComp);
|
||||
}
|
||||
|
||||
private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.SenderSession.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
TrySetActiveHand(eventArgs.SenderSession.AttachedEntity.Value, msg.HandName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get any empty hand. Prioritizes the currently active hand.
|
||||
/// </summary>
|
||||
public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHand, SharedHandsComponent? handComp = null)
|
||||
{
|
||||
emptyHand = null;
|
||||
if (!Resolve(uid, ref handComp, false))
|
||||
return false;
|
||||
|
||||
foreach (var hand in EnumerateHands(uid, handComp))
|
||||
{
|
||||
if (hand.IsEmpty)
|
||||
{
|
||||
emptyHand = hand;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate over hands, starting with the currently active hand.
|
||||
/// </summary>
|
||||
public IEnumerable<Hand> EnumerateHands(EntityUid uid, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
yield break;
|
||||
|
||||
if (handsComp.ActiveHand != null)
|
||||
yield return handsComp.ActiveHand;
|
||||
|
||||
foreach (var name in handsComp.SortedHands)
|
||||
{
|
||||
if (name != handsComp.ActiveHand?.Name)
|
||||
yield return handsComp.Hands[name];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate over hands, with the active hand being first.
|
||||
/// </summary>
|
||||
public IEnumerable<EntityUid> EnumerateHeld(EntityUid uid, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
yield break;
|
||||
|
||||
if (handsComp.ActiveHandEntity != null)
|
||||
yield return handsComp.ActiveHandEntity.Value;
|
||||
|
||||
foreach (var name in handsComp.SortedHands)
|
||||
{
|
||||
if (name == handsComp.ActiveHand?.Name)
|
||||
continue;
|
||||
|
||||
if (handsComp.Hands[name].HeldEntity is EntityUid held)
|
||||
yield return held;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the currently active hand and raise hand (de)selection events directed at the held entities.
|
||||
/// </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, SharedHandsComponent? handComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handComp))
|
||||
return false;
|
||||
|
||||
if (name == handComp.ActiveHand?.Name)
|
||||
return false;
|
||||
|
||||
Hand? hand = null;
|
||||
if (name != null && !handComp.Hands.TryGetValue(name, out hand))
|
||||
return false;
|
||||
|
||||
return SetActiveHand(uid, hand, handComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the currently active hand and raise hand (de)selection events directed at the held entities.
|
||||
/// </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 SetActiveHand(EntityUid uid, Hand? hand, SharedHandsComponent? handComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handComp))
|
||||
return false;
|
||||
|
||||
if (hand == handComp.ActiveHand)
|
||||
return false;
|
||||
|
||||
if (handComp.ActiveHand?.HeldEntity is EntityUid held)
|
||||
RaiseLocalEvent(held, new HandDeselectedEvent(uid), false);
|
||||
|
||||
if (hand == null)
|
||||
{
|
||||
handComp.ActiveHand = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
handComp.ActiveHand = hand;
|
||||
|
||||
if (hand.HeldEntity != null)
|
||||
RaiseLocalEvent(hand.HeldEntity.Value, new HandSelectedEvent(uid), false);
|
||||
|
||||
Dirty(handComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsHolding(EntityUid uid, EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, SharedHandsComponent? handsComp = null)
|
||||
{
|
||||
inHand = null;
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
{
|
||||
if (hand.HeldEntity == entity)
|
||||
{
|
||||
inHand = hand;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user