2023-07-08 14:08:32 +10:00
using System.Numerics ;
2024-10-14 17:05:40 +13:00
using Content.Shared.Database ;
2022-03-17 20:13:31 +13:00
using Content.Shared.Hands.Components ;
using Content.Shared.Interaction ;
2024-01-14 06:18:47 -04:00
using Content.Shared.Inventory.VirtualItem ;
2025-08-26 06:29:12 -04:00
using Content.Shared.Storage.Components ;
2024-01-04 00:31:58 -05:00
using Content.Shared.Tag ;
2022-03-17 20:13:31 +13:00
using Robust.Shared.Containers ;
using Robust.Shared.Map ;
2025-04-05 00:20:19 +00:00
using Robust.Shared.Prototypes ;
2025-06-25 09:13:03 -04:00
using Robust.Shared.Utility ;
2022-03-17 20:13:31 +13:00
namespace Content.Shared.Hands.EntitySystems ;
2023-10-11 02:18:49 -07:00
public abstract partial class SharedHandsSystem
2022-03-17 20:13:31 +13:00
{
2024-01-04 00:31:58 -05:00
[Dependency] private readonly TagSystem _tagSystem = default ! ;
2025-04-05 00:20:19 +00:00
private static readonly ProtoId < TagPrototype > BypassDropChecksTag = "BypassDropChecks" ;
2023-04-23 21:38:52 +02:00
private void InitializeDrop ( )
{
SubscribeLocalEvent < HandsComponent , EntRemovedFromContainerMessage > ( HandleEntityRemoved ) ;
2025-08-26 06:29:12 -04:00
SubscribeLocalEvent < HandsComponent , EntityStorageIntoContainerAttemptEvent > ( OnEntityStorageDump ) ;
2023-04-23 21:38:52 +02:00
}
protected virtual void HandleEntityRemoved ( EntityUid uid , HandsComponent hands , EntRemovedFromContainerMessage args )
{
if ( ! TryGetHand ( uid , args . Container . ID , out var hand ) )
{
return ;
}
2025-06-25 09:13:03 -04:00
var gotUnequipped = new GotUnequippedHandEvent ( uid , args . Entity , hand . Value ) ;
2023-10-11 02:18:49 -07:00
RaiseLocalEvent ( args . Entity , gotUnequipped ) ;
2023-04-23 21:38:52 +02:00
2025-06-25 09:13:03 -04:00
var didUnequip = new DidUnequipHandEvent ( uid , args . Entity , hand . Value ) ;
2023-10-11 02:18:49 -07:00
RaiseLocalEvent ( uid , didUnequip ) ;
2023-12-25 02:33:32 -05:00
2024-01-14 06:18:47 -04:00
if ( TryComp ( args . Entity , out VirtualItemComponent ? @virtual ) )
_virtualSystem . DeleteVirtualItem ( ( args . Entity , @virtual ) , uid ) ;
2023-04-23 21:38:52 +02:00
}
2025-08-26 06:29:12 -04:00
private void OnEntityStorageDump ( Entity < HandsComponent > ent , ref EntityStorageIntoContainerAttemptEvent args )
{
// If you're physically carrying an EntityStroage which tries to dump its contents out,
// we want those contents to fall to the floor.
args . Cancelled = true ;
}
2024-01-04 00:31:58 -05:00
private bool ShouldIgnoreRestrictions ( EntityUid user )
{
//Checks if the Entity is something that shouldn't care about drop distance or walls ie Aghost
2025-04-05 00:20:19 +00:00
return ! _tagSystem . HasTag ( user , BypassDropChecksTag ) ;
2024-01-04 00:31:58 -05:00
}
2023-11-13 23:43:03 +11:00
/// <summary>
/// Checks whether an entity can drop a given entity. Will return false if they are not holding the entity.
/// </summary>
2025-06-25 09:13:03 -04:00
public bool CanDrop ( Entity < HandsComponent ? > ent , EntityUid entity , bool checkActionBlocker = true )
2023-11-13 23:43:03 +11:00
{
2025-06-25 09:13:03 -04:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2023-11-13 23:43:03 +11:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ! IsHolding ( ent , entity , out var hand ) )
2023-11-13 23:43:03 +11:00
return false ;
2025-06-25 09:13:03 -04:00
return CanDropHeld ( ent , hand , checkActionBlocker ) ;
2023-11-13 23:43:03 +11:00
}
2022-03-17 20:13:31 +13:00
/// <summary>
/// Checks if the contents of a hand is able to be removed from its container.
/// </summary>
2025-06-25 09:13:03 -04:00
public bool CanDropHeld ( EntityUid uid , string handId , bool checkActionBlocker = true )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
if ( ! ContainerSystem . TryGetContainer ( uid , handId , out var container ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( container . ContainedEntities . FirstOrNull ( ) is not { } held )
return false ;
if ( ! ContainerSystem . CanRemove ( held , container ) )
2022-03-17 20:13:31 +13:00
return false ;
if ( checkActionBlocker & & ! _actionBlocker . CanDrop ( uid ) )
return false ;
return true ;
}
/// <summary>
/// Attempts to drop the item in the currently active hand.
/// </summary>
2025-06-25 09:13:03 -04:00
public bool TryDrop ( Entity < HandsComponent ? > ent , EntityCoordinates ? targetDropLocation = null , bool checkActionBlocker = true , bool doDropInteraction = true )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ent . Comp . ActiveHandId = = null )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
return TryDrop ( ent , ent . Comp . ActiveHandId , targetDropLocation , checkActionBlocker , doDropInteraction ) ;
2022-03-17 20:13:31 +13:00
}
/// <summary>
/// Drops an item at the target location.
/// </summary>
2025-06-25 09:13:03 -04:00
public bool TryDrop ( Entity < HandsComponent ? > ent , EntityUid entity , EntityCoordinates ? targetDropLocation = null , bool checkActionBlocker = true , bool doDropInteraction = true )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ! IsHolding ( ent , entity , out var hand ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
return TryDrop ( ent , hand , targetDropLocation , checkActionBlocker , doDropInteraction ) ;
2022-03-17 20:13:31 +13:00
}
/// <summary>
/// Drops a hands contents at the target location.
/// </summary>
2025-06-25 09:13:03 -04:00
public bool TryDrop ( Entity < HandsComponent ? > ent , string handId , EntityCoordinates ? targetDropLocation = null , bool checkActionBlocker = true , bool doDropInteraction = true )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ! CanDropHeld ( ent , handId , checkActionBlocker ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ! TryGetHeldItem ( ent , handId , out var entity ) )
return false ;
2024-07-12 02:24:08 -07:00
// 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 ) )
2025-06-25 09:13:03 -04:00
_virtualSystem . DeleteVirtualItem ( ( entity . Value , @virtual ) , ent ) ;
2022-03-17 20:13:31 +13:00
2023-12-25 02:33:32 -05:00
if ( TerminatingOrDeleted ( entity ) )
return true ;
2025-06-25 09:13:03 -04:00
var itemXform = Transform ( entity . Value ) ;
2023-12-25 02:33:32 -05:00
if ( itemXform . MapUid = = null )
return true ;
2025-06-25 09:13:03 -04:00
var userXform = Transform ( ent ) ;
var isInContainer = ContainerSystem . IsEntityOrParentInContainer ( ent , xform : userXform ) ;
2022-03-17 20:13:31 +13:00
2024-08-12 19:18:26 +05:00
// if the user is in a container, drop the item inside the container
2025-06-25 09:13:03 -04:00
if ( isInContainer )
{
TransformSystem . DropNextTo ( ( entity . Value , itemXform ) , ( ent , userXform ) ) ;
2024-08-12 19:18:26 +05:00
return true ;
}
2024-10-14 17:05:40 +13:00
2024-08-12 19:18:26 +05:00
// 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
2025-06-25 09:13:03 -04:00
DoDrop ( ent , handId , doDropInteraction : doDropInteraction ) ;
2024-07-22 17:24:26 -05:00
2024-08-12 19:18:26 +05:00
// if there's no drop location stop here
if ( targetDropLocation = = null )
2022-03-17 20:13:31 +13:00
return true ;
2024-10-14 17:05:40 +13:00
2024-08-12 19:18:26 +05:00
// otherwise, also move dropped item and rotate it properly according to grid/map
2025-06-25 09:13:03 -04:00
var ( itemPos , itemRot ) = TransformSystem . GetWorldPositionRotation ( entity . Value ) ;
2024-04-02 22:31:57 -07:00
var origin = new MapCoordinates ( itemPos , itemXform . MapID ) ;
2024-07-12 02:24:08 -07:00
var target = TransformSystem . ToMapCoordinates ( targetDropLocation . Value ) ;
2025-06-25 09:13:03 -04:00
TransformSystem . SetWorldPositionRotation ( entity . Value , GetFinalDropCoordinates ( ent , origin , target , entity . Value ) , itemRot ) ;
2022-03-17 20:13:31 +13:00
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>
2025-06-25 09:13:03 -04:00
public bool TryDropIntoContainer ( Entity < HandsComponent ? > ent , EntityUid entity , BaseContainer targetContainer , bool checkActionBlocker = true )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ! IsHolding ( ent , entity , out var hand ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
if ( ! CanDropHeld ( ent , hand , checkActionBlocker ) )
2022-03-17 20:13:31 +13:00
return false ;
2023-10-11 02:18:49 -07:00
if ( ! ContainerSystem . CanInsert ( entity , targetContainer ) )
2022-03-17 20:13:31 +13:00
return false ;
2025-06-25 09:13:03 -04:00
DoDrop ( ent , hand , false ) ;
2023-12-27 21:30:03 -08:00
ContainerSystem . Insert ( entity , targetContainer ) ;
2022-03-17 20:13:31 +13:00
return true ;
}
/// <summary>
2024-01-04 00:31:58 -05:00
/// Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path, Does a check to see if a user should bypass those checks as well.
2022-03-17 20:13:31 +13:00
/// </summary>
2024-08-18 15:24:28 -07:00
private Vector2 GetFinalDropCoordinates ( EntityUid user , MapCoordinates origin , MapCoordinates target , EntityUid held )
2022-03-17 20:13:31 +13:00
{
var dropVector = target . Position - origin . Position ;
2023-07-08 14:08:32 +10:00
var requestedDropDistance = dropVector . Length ( ) ;
2024-01-04 00:31:58 -05:00
var dropLength = dropVector . Length ( ) ;
2022-03-17 20:13:31 +13:00
2024-01-04 00:31:58 -05:00
if ( ShouldIgnoreRestrictions ( user ) )
2022-03-17 20:13:31 +13:00
{
2024-01-04 00:31:58 -05:00
if ( dropVector . Length ( ) > SharedInteractionSystem . InteractionRange )
{
dropVector = dropVector . Normalized ( ) * SharedInteractionSystem . InteractionRange ;
target = new MapCoordinates ( origin . Position + dropVector , target . MapId ) ;
}
2022-03-17 20:13:31 +13:00
2024-08-18 15:24:28 -07:00
dropLength = _interactionSystem . UnobstructedDistance ( origin , target , predicate : e = > e = = user | | e = = held ) ;
2024-01-04 00:31:58 -05:00
}
2022-03-17 20:13:31 +13:00
if ( dropLength < requestedDropDistance )
2023-07-08 14:08:32 +10:00
return origin . Position + dropVector . Normalized ( ) * dropLength ;
2022-03-17 20:13:31 +13:00
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>
2025-06-25 09:13:03 -04:00
public virtual void DoDrop ( Entity < HandsComponent ? > ent ,
string handId ,
bool doDropInteraction = true ,
bool log = true )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2022-03-17 20:13:31 +13:00
return ;
2025-06-25 09:13:03 -04:00
if ( ! ContainerSystem . TryGetContainer ( ent , handId , out var container ) )
2022-03-17 20:13:31 +13:00
return ;
2025-06-25 09:13:03 -04:00
if ( ! TryGetHeldItem ( ent , handId , out var entity ) )
return ;
2022-03-17 20:13:31 +13:00
2025-06-25 09:13:03 -04:00
if ( TerminatingOrDeleted ( ent ) | | TerminatingOrDeleted ( entity ) )
2023-12-09 20:00:53 -05:00
return ;
2025-06-25 09:13:03 -04:00
if ( ! ContainerSystem . Remove ( entity . Value , container ) )
2022-03-17 20:13:31 +13:00
{
2025-06-25 09:13:03 -04:00
Log . Error ( $"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(ent)}. Hand: {handId}." ) ;
2022-03-17 20:13:31 +13:00
return ;
}
2025-06-25 09:13:03 -04:00
Dirty ( ent ) ;
2022-03-17 20:13:31 +13:00
if ( doDropInteraction )
2025-06-25 09:13:03 -04:00
_interactionSystem . DroppedInteraction ( ent , entity . Value ) ;
2022-03-17 20:13:31 +13:00
2024-10-14 17:05:40 +13:00
if ( log )
2025-06-25 09:13:03 -04:00
_adminLogger . Add ( LogType . Drop , LogImpact . Low , $"{ToPrettyString(ent):user} dropped {ToPrettyString(entity):entity}" ) ;
2024-10-14 17:05:40 +13:00
2025-06-25 09:13:03 -04:00
if ( handId = = ent . Comp . ActiveHandId )
RaiseLocalEvent ( entity . Value , new HandDeselectedEvent ( ent ) ) ;
2022-03-17 20:13:31 +13:00
}
}