2020-07-27 00:54:32 +02:00
using System ;
2021-06-21 02:21:20 -07:00
using System.Collections.Generic ;
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
using Content.Shared.ActionBlocker ;
2021-11-24 16:52:31 -06:00
using Content.Shared.Administration.Logs ;
2021-11-28 14:56:53 +01:00
using Content.Shared.Database ;
2021-06-21 02:21:20 -07:00
using Content.Shared.Interaction ;
2021-10-25 20:06:12 +13:00
using Content.Shared.Item ;
2021-06-21 02:21:20 -07:00
using Robust.Shared.Containers ;
2019-11-13 17:37:46 -05:00
using Robust.Shared.GameObjects ;
2021-07-12 01:32:10 -07:00
using Robust.Shared.GameStates ;
2021-06-21 02:21:20 -07:00
using Robust.Shared.IoC ;
using Robust.Shared.Log ;
2021-02-03 22:07:13 +00:00
using Robust.Shared.Map ;
2021-06-21 02:21:20 -07:00
using Robust.Shared.Maths ;
2019-11-13 17:37:46 -05:00
using Robust.Shared.Serialization ;
2021-06-21 02:21:20 -07:00
using Robust.Shared.Serialization.Manager.Attributes ;
using Robust.Shared.ViewVariables ;
2017-09-26 21:27:48 +02:00
2021-06-09 22:19:39 +02:00
namespace Content.Shared.Hands.Components
2017-09-26 21:27:48 +02:00
{
2021-11-08 15:08:24 +01:00
[NetworkedComponent]
public abstract class SharedHandsComponent : Component
2017-09-26 21:27:48 +02:00
{
2021-12-08 17:32:32 +01:00
[Dependency] private readonly IEntityManager _entMan = default ! ;
2017-09-26 21:27:48 +02:00
public sealed override string Name = > "Hands" ;
2021-06-21 02:21:20 -07:00
public event Action ? OnItemChanged ; //TODO: Try to replace C# event
/// <summary>
/// The name of the currently active hand.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string? ActiveHand
{
get = > _activeHand ;
set
{
if ( value ! = null & & ! HasHand ( value ) )
{
Logger . Warning ( $"{nameof(SharedHandsComponent)} on {Owner} tried to set its active hand to {value}, which was not a hand." ) ;
return ;
}
if ( value = = null & & Hands . Count ! = 0 )
{
Logger . Error ( $"{nameof(SharedHandsComponent)} on {Owner} tried to set its active hand to null, when it still had another hand." ) ;
_activeHand = Hands [ 0 ] . Name ;
return ;
}
if ( value ! = ActiveHand )
{
DeselectActiveHeldEntity ( ) ;
_activeHand = value ;
SelectActiveHeldEntity ( ) ;
HandsModified ( ) ;
}
}
}
2021-07-31 03:14:00 +02:00
2021-06-21 02:21:20 -07:00
private string? _activeHand ;
[ViewVariables]
2021-07-31 03:14:00 +02:00
public readonly List < Hand > Hands = new ( ) ;
2021-06-21 02:21:20 -07:00
/// <summary>
/// The amount of throw impulse per distance the player is from the throw target.
/// </summary>
[DataField("throwForceMultiplier")]
[ViewVariables(VVAccess.ReadWrite)]
2021-07-25 16:58:02 +10:00
public float ThrowForceMultiplier { get ; set ; } = 10f ; //should be tuned so that a thrown item lands about under the player's cursor
2021-06-21 02:21:20 -07:00
/// <summary>
/// Distance after which longer throw targets stop increasing throw impulse.
/// </summary>
[DataField("throwRange")]
[ViewVariables(VVAccess.ReadWrite)]
public float ThrowRange { get ; set ; } = 8f ;
2021-11-30 15:20:38 +01:00
public override ComponentState GetComponentState ( )
2021-06-21 02:21:20 -07:00
{
var hands = new HandState [ Hands . Count ] ;
for ( var i = 0 ; i < Hands . Count ; i + + )
{
var hand = Hands [ i ] . ToHandState ( ) ;
hands [ i ] = hand ;
}
return new HandsComponentState ( hands , ActiveHand ) ;
}
public virtual void HandsModified ( )
{
2021-07-31 03:14:00 +02:00
// todo axe all this for ECS.
2021-10-29 01:34:59 +13:00
// todo burn it all down.
UpdateHandVisualizer ( ) ;
2021-06-21 02:21:20 -07:00
Dirty ( ) ;
2021-07-31 03:14:00 +02:00
2021-12-08 17:32:32 +01:00
_entMan . EventBus . RaiseEvent ( EventSource . Local , new HandsModifiedMessage { Hands = this } ) ;
2021-06-21 02:21:20 -07:00
}
2021-10-29 01:34:59 +13:00
public void UpdateHandVisualizer ( )
{
2021-12-08 17:32:32 +01:00
var entMan = _entMan ;
2021-12-04 12:35:33 +01:00
if ( ! entMan . TryGetComponent ( Owner , out AppearanceComponent ? appearance ) )
2021-10-29 01:34:59 +13:00
return ;
var hands = new List < HandVisualState > ( ) ;
foreach ( var hand in Hands )
{
2021-12-20 15:20:27 +01:00
if ( hand . HeldEntity = = null )
continue ;
2021-12-06 15:39:46 +11:00
if ( ! entMan . TryGetComponent ( hand . HeldEntity , out SharedItemComponent ? item ) | | item . RsiPath = = null )
2021-10-29 01:34:59 +13:00
continue ;
var handState = new HandVisualState ( item . RsiPath , item . EquippedPrefix , hand . Location , item . Color ) ;
hands . Add ( handState ) ;
}
appearance . SetData ( HandsVisuals . VisualState , new HandsVisualState ( hands ) ) ;
}
2021-06-21 02:21:20 -07:00
public void AddHand ( string handName , HandLocation handLocation )
{
if ( HasHand ( handName ) )
return ;
2021-11-08 15:08:24 +01:00
var container = Owner . CreateContainer < ContainerSlot > ( handName ) ;
2021-06-21 02:21:20 -07:00
container . OccludesLight = false ;
2021-07-31 03:14:00 +02:00
Hands . Add ( new Hand ( handName , handLocation , container ) ) ;
2021-06-21 02:21:20 -07:00
2021-11-08 15:08:24 +01:00
ActiveHand ? ? = handName ;
2021-06-21 02:21:20 -07:00
HandCountChanged ( ) ;
HandsModified ( ) ;
}
public void RemoveHand ( string handName )
{
if ( ! TryGetHand ( handName , out var hand ) )
return ;
RemoveHand ( hand ) ;
}
private void RemoveHand ( Hand hand )
{
2021-11-24 00:38:39 +01:00
DropHeldEntityToFloor ( hand ) ;
2021-06-21 02:21:20 -07:00
hand . Container ? . Shutdown ( ) ;
Hands . Remove ( hand ) ;
if ( ActiveHand = = hand . Name )
2021-07-31 03:14:00 +02:00
ActiveHand = Hands . FirstOrDefault ( ) ? . Name ;
2021-06-21 02:21:20 -07:00
HandCountChanged ( ) ;
HandsModified ( ) ;
}
2021-07-31 03:14:00 +02:00
private Hand ? GetActiveHand ( )
{
if ( ActiveHand = = null )
return null ;
return GetHandOrNull ( ActiveHand ) ;
}
2021-06-21 02:21:20 -07:00
public bool HasHand ( string handName )
{
2021-07-31 03:14:00 +02:00
return TryGetHand ( handName , out _ ) ;
2021-06-21 02:21:20 -07:00
}
2021-07-31 03:14:00 +02:00
public Hand ? GetHandOrNull ( string handName )
2021-06-21 02:21:20 -07:00
{
2021-07-31 03:14:00 +02:00
return TryGetHand ( handName , out var hand ) ? hand : null ;
2021-06-21 02:21:20 -07:00
}
2021-07-31 03:14:00 +02:00
public Hand GetHand ( string handName )
2021-06-21 02:21:20 -07:00
{
2021-07-31 03:14:00 +02:00
if ( ! TryGetHand ( handName , out var hand ) )
throw new KeyNotFoundException ( $"Unable to find hand with name {handName}" ) ;
2021-06-21 02:21:20 -07:00
2021-07-31 03:14:00 +02:00
return hand ;
2021-06-21 02:21:20 -07:00
}
2021-07-31 03:14:00 +02:00
public bool TryGetHand ( string handName , [ NotNullWhen ( true ) ] out Hand ? foundHand )
2021-06-21 02:21:20 -07:00
{
2021-07-31 03:14:00 +02:00
foreach ( var hand in Hands )
{
if ( hand . Name = = handName )
{
foundHand = hand ;
return true ;
} ;
}
foundHand = null ;
return false ;
2021-06-21 02:21:20 -07:00
}
2021-07-31 03:14:00 +02:00
public bool TryGetActiveHand ( [ NotNullWhen ( true ) ] out Hand ? activeHand )
2021-06-21 02:21:20 -07:00
{
activeHand = GetActiveHand ( ) ;
return activeHand ! = null ;
}
#region Held Entities
public bool ActiveHandIsHoldingEntity ( )
{
if ( ! TryGetActiveHand ( out var hand ) )
return false ;
2021-12-05 18:09:01 +01:00
return hand . HeldEntity ! = default ;
2021-06-21 02:21:20 -07:00
}
2021-12-05 18:09:01 +01:00
public bool TryGetHeldEntity ( string handName , out EntityUid heldEntity )
2021-06-21 02:21:20 -07:00
{
2021-12-05 18:09:01 +01:00
heldEntity = default ;
2021-06-21 02:21:20 -07:00
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
heldEntity = hand . HeldEntity ;
2021-12-05 18:09:01 +01:00
return heldEntity ! = default ;
2021-06-21 02:21:20 -07:00
}
2021-12-05 18:09:01 +01:00
public bool TryGetActiveHeldEntity ( out EntityUid heldEntity )
2021-06-21 02:21:20 -07:00
{
2021-12-20 15:20:27 +01:00
heldEntity = GetActiveHand ( ) ? . HeldEntity ? ? default ;
return heldEntity ! = null ;
2021-06-21 02:21:20 -07:00
}
2021-12-04 12:35:33 +01:00
public bool IsHolding ( EntityUid entity )
2021-06-21 02:21:20 -07:00
{
foreach ( var hand in Hands )
{
if ( hand . HeldEntity = = entity )
return true ;
}
return false ;
}
2021-12-04 12:35:33 +01:00
public IEnumerable < EntityUid > GetAllHeldEntities ( )
2021-06-21 02:21:20 -07:00
{
2021-07-31 03:14:00 +02:00
foreach ( var hand in Hands )
2021-06-21 02:21:20 -07:00
{
2021-12-05 18:09:01 +01:00
if ( hand . HeldEntity ! = default )
2021-12-06 15:39:46 +11:00
yield return hand . HeldEntity ;
2021-06-21 02:21:20 -07:00
}
}
2021-09-17 07:16:11 -07:00
/// <summary>
/// Returns the number of hands that have no items in them.
/// </summary>
/// <returns></returns>
public int GetFreeHands ( )
{
int acc = 0 ;
foreach ( var hand in Hands )
{
2021-12-05 18:09:01 +01:00
if ( hand . HeldEntity = = default )
2021-09-17 07:16:11 -07:00
acc + = 1 ;
}
return acc ;
}
2021-12-04 12:35:33 +01:00
public bool TryGetHandHoldingEntity ( EntityUid entity , [ NotNullWhen ( true ) ] out Hand ? handFound )
2021-06-21 02:21:20 -07:00
{
handFound = null ;
foreach ( var hand in Hands )
{
if ( hand . HeldEntity = = entity )
{
handFound = hand ;
return true ;
}
}
return false ;
}
#endregion
#region Dropping
/// <summary>
/// Checks all the conditions relevant to a player being able to drop an item.
/// </summary>
public bool CanDrop ( string handName , bool checkActionBlocker = true )
{
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
if ( ! CanRemoveHeldEntityFromHand ( hand ) )
return false ;
if ( checkActionBlocker & & ! PlayerCanDrop ( ) )
return false ;
return true ;
}
/// <summary>
/// Tries to drop the contents of the active hand to the target location.
/// </summary>
2021-11-24 00:38:39 +01:00
public bool TryDropActiveHand ( EntityCoordinates targetDropLocation , bool doMobChecks = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetActiveHand ( out var hand ) )
return false ;
2021-11-24 00:38:39 +01:00
return TryDropHeldEntity ( hand , targetDropLocation , doMobChecks ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Tries to drop the contents of a hand to the target location.
/// </summary>
2021-11-24 00:38:39 +01:00
public bool TryDropHand ( string handName , EntityCoordinates targetDropLocation , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
2021-11-24 00:38:39 +01:00
return TryDropHeldEntity ( hand , targetDropLocation , checkActionBlocker ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Tries to drop a held entity to the target location.
/// </summary>
2021-12-04 12:35:33 +01:00
public bool TryDropEntity ( EntityUid entity , EntityCoordinates coords , bool doMobChecks = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHandHoldingEntity ( entity , out var hand ) )
return false ;
2021-11-24 00:38:39 +01:00
return TryDropHeldEntity ( hand , coords , doMobChecks ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Attempts to move the contents of a hand into a container that is not another hand, without dropping it on the floor inbetween.
/// </summary>
public bool TryPutHandIntoContainer ( string handName , BaseContainer targetContainer , bool checkActionBlocker = true )
{
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
if ( ! CanPutHeldEntityIntoContainer ( hand , targetContainer , checkActionBlocker ) )
return false ;
PutHeldEntityIntoContainer ( hand , targetContainer ) ;
return true ;
}
/// <summary>
2021-11-08 15:08:24 +01:00
/// 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.
2021-06-21 02:21:20 -07:00
/// </summary>
2021-12-04 12:35:33 +01:00
public bool Drop ( EntityUid entity , BaseContainer targetContainer , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHandHoldingEntity ( entity , out var hand ) )
return false ;
if ( ! CanPutHeldEntityIntoContainer ( hand , targetContainer , checkActionBlocker ) )
return false ;
PutHeldEntityIntoContainer ( hand , targetContainer ) ;
return true ;
}
/// <summary>
/// Tries to drop the contents of a hand directly under the player.
/// </summary>
2021-11-24 00:38:39 +01:00
public bool Drop ( string handName , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
2021-12-08 17:32:32 +01:00
return TryDropHeldEntity ( hand , _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates , checkActionBlocker ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Tries to drop a held entity directly under the player.
/// </summary>
2021-12-04 12:35:33 +01:00
public bool Drop ( EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHandHoldingEntity ( entity , out var hand ) )
return false ;
2021-12-08 17:32:32 +01:00
return TryDropHeldEntity ( hand , _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates , checkActionBlocker ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Tries to remove the item in the active hand, without dropping it.
2021-11-08 15:08:24 +01:00
/// For transferring the held item to another location, like an inventory slot,
/// which shouldn't trigger the drop interaction
/// </summary>
2021-06-21 02:21:20 -07:00
public bool TryDropNoInteraction ( )
{
if ( ! TryGetActiveHand ( out var hand ) )
return false ;
if ( ! CanRemoveHeldEntityFromHand ( hand ) )
return false ;
RemoveHeldEntityFromHand ( hand ) ;
return true ;
}
/// <summary>
/// Checks if the contents of a hand is able to be removed from its container.
/// </summary>
private bool CanRemoveHeldEntityFromHand ( Hand hand )
{
2021-12-20 15:20:27 +01:00
if ( hand . HeldEntity = = null )
2021-06-21 02:21:20 -07:00
return false ;
2021-12-06 15:39:46 +11:00
var heldEntity = hand . HeldEntity ;
2021-12-04 12:35:33 +01:00
2021-06-21 02:21:20 -07:00
var handContainer = hand . Container ;
if ( handContainer = = null )
return false ;
if ( ! handContainer . CanRemove ( heldEntity ) )
return false ;
return true ;
}
/// <summary>
/// Checks if the player is allowed to perform drops.
/// </summary>
private bool PlayerCanDrop ( )
{
2021-12-07 21:54:00 +11:00
if ( ! IoCManager . Resolve < IEntitySystemManager > ( ) . GetEntitySystem < ActionBlockerSystem > ( ) . CanDrop ( Owner ) )
2021-06-21 02:21:20 -07:00
return false ;
return true ;
}
/// <summary>
/// Removes the contents of a hand from its container. Assumes that the removal is allowed.
/// </summary>
private void RemoveHeldEntityFromHand ( Hand hand )
{
2021-12-20 15:20:27 +01:00
if ( hand . HeldEntity = = null )
2021-06-21 02:21:20 -07:00
return ;
2021-12-06 15:39:46 +11:00
var heldEntity = hand . HeldEntity ;
2021-12-04 12:35:33 +01:00
2021-06-21 02:21:20 -07:00
var handContainer = hand . Container ;
if ( handContainer = = null )
return ;
if ( hand . Name = = ActiveHand )
DeselectActiveHeldEntity ( ) ;
if ( ! handContainer . Remove ( heldEntity ) )
{
Logger . Error ( $"{nameof(SharedHandsComponent)} on {Owner} could not remove {heldEntity} from {handContainer}." ) ;
return ;
}
2021-09-17 07:16:11 -07:00
2021-06-21 02:21:20 -07:00
OnHeldEntityRemovedFromHand ( heldEntity , hand . ToHandState ( ) ) ;
HandsModified ( ) ;
}
/// <summary>
/// Drops a hands contents to the target location.
/// </summary>
2021-11-24 00:38:39 +01:00
public void DropHeldEntity ( Hand hand , EntityCoordinates targetDropLocation )
2021-06-21 02:21:20 -07:00
{
2021-12-19 21:55:23 -08:00
if ( hand . IsEmpty )
2021-06-21 02:21:20 -07:00
return ;
2021-12-06 15:39:46 +11:00
var heldEntity = hand . HeldEntity ;
2021-12-04 12:35:33 +01:00
2021-06-21 02:21:20 -07:00
RemoveHeldEntityFromHand ( hand ) ;
2021-11-24 00:38:39 +01:00
EntitySystem . Get < SharedInteractionSystem > ( ) . DroppedInteraction ( Owner , heldEntity ) ;
2021-06-21 02:21:20 -07:00
2021-12-08 17:32:32 +01:00
_entMan . GetComponent < TransformComponent > ( heldEntity ) . WorldPosition = GetFinalDropCoordinates ( targetDropLocation ) ;
2021-06-21 02:21:20 -07:00
OnItemChanged ? . Invoke ( ) ;
}
/// <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>
2021-08-25 03:35:37 -07:00
private Vector2 GetFinalDropCoordinates ( EntityCoordinates targetCoords )
2021-06-21 02:21:20 -07:00
{
2021-12-08 17:32:32 +01:00
var origin = _entMan . GetComponent < TransformComponent > ( Owner ) . MapPosition ;
var target = targetCoords . ToMap ( _entMan ) ;
2021-06-21 02:21:20 -07:00
2021-08-25 03:35:37 -07:00
var dropVector = target . Position - origin . Position ;
var requestedDropDistance = dropVector . Length ;
2021-07-13 18:21:09 +10:00
if ( dropVector . Length > SharedInteractionSystem . InteractionRange )
{
dropVector = dropVector . Normalized * SharedInteractionSystem . InteractionRange ;
2021-08-25 03:35:37 -07:00
target = new MapCoordinates ( origin . Position + dropVector , target . MapId ) ;
2021-07-13 18:21:09 +10:00
}
2021-08-25 03:35:37 -07:00
var dropLength = EntitySystem . Get < SharedInteractionSystem > ( ) . UnobstructedDistance ( origin , target , ignoredEnt : Owner ) ;
2021-06-21 02:21:20 -07:00
2021-08-25 03:35:37 -07:00
if ( dropLength < requestedDropDistance )
return origin . Position + dropVector . Normalized * dropLength ;
return target . Position ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Tries to drop a hands contents to the target location.
/// </summary>
2021-11-24 00:38:39 +01:00
private bool TryDropHeldEntity ( Hand hand , EntityCoordinates location , bool checkActionBlocker )
2021-06-21 02:21:20 -07:00
{
if ( ! CanRemoveHeldEntityFromHand ( hand ) )
return false ;
if ( checkActionBlocker & & ! PlayerCanDrop ( ) )
return false ;
2021-11-24 00:38:39 +01:00
DropHeldEntity ( hand , location ) ;
2021-06-21 02:21:20 -07:00
return true ;
}
/// <summary>
/// Drops the contents of a hand directly under the player.
/// </summary>
2021-11-24 00:38:39 +01:00
private void DropHeldEntityToFloor ( Hand hand )
2021-06-21 02:21:20 -07:00
{
2021-12-08 17:32:32 +01:00
DropHeldEntity ( hand , _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates ) ;
2021-06-21 02:21:20 -07:00
}
private bool CanPutHeldEntityIntoContainer ( Hand hand , IContainer targetContainer , bool checkActionBlocker )
{
2021-12-20 15:20:27 +01:00
if ( hand . HeldEntity = = null )
2021-06-21 02:21:20 -07:00
return false ;
2021-12-06 15:39:46 +11:00
var heldEntity = hand . HeldEntity ;
2021-12-04 12:35:33 +01:00
2021-06-21 02:21:20 -07:00
if ( checkActionBlocker & & ! PlayerCanDrop ( ) )
return false ;
if ( ! targetContainer . CanInsert ( heldEntity ) )
return false ;
return true ;
}
/// <summary>
/// For putting the contents of a hand into a container that is not another hand.
/// </summary>
private void PutHeldEntityIntoContainer ( Hand hand , IContainer targetContainer )
{
2021-12-20 15:20:27 +01:00
if ( hand . HeldEntity = = null )
2021-06-21 02:21:20 -07:00
return ;
2021-12-06 15:39:46 +11:00
var heldEntity = hand . HeldEntity ;
2021-12-04 12:35:33 +01:00
2021-06-21 02:21:20 -07:00
RemoveHeldEntityFromHand ( hand ) ;
if ( ! targetContainer . Insert ( heldEntity ) )
{
Logger . Error ( $"{nameof(SharedHandsComponent)} on {Owner} could not insert {heldEntity} into {targetContainer}." ) ;
return ;
}
}
#endregion
#region Pickup
2021-12-04 12:35:33 +01:00
public bool CanPickupEntity ( string handName , EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
if ( checkActionBlocker & & ! PlayerCanPickup ( ) )
return false ;
if ( ! CanInsertEntityIntoHand ( hand , entity ) )
return false ;
return true ;
}
2021-12-04 12:35:33 +01:00
public bool CanPickupEntityToActiveHand ( EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
2021-07-31 03:14:00 +02:00
return ActiveHand ! = null & & CanPickupEntity ( ActiveHand , entity , checkActionBlocker ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Tries to pick up an entity to a specific hand.
/// </summary>
2021-12-04 12:35:33 +01:00
public bool TryPickupEntity ( string handName , EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetHand ( handName , out var hand ) )
return false ;
return TryPickupEntity ( hand , entity , checkActionBlocker ) ;
}
2021-12-04 12:35:33 +01:00
public bool TryPickupEntityToActiveHand ( EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
2021-07-31 03:14:00 +02:00
return ActiveHand ! = null & & TryPickupEntity ( ActiveHand , entity , checkActionBlocker ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Checks if an entity can be put into a hand's container.
/// </summary>
2021-12-04 12:35:33 +01:00
protected bool CanInsertEntityIntoHand ( Hand hand , EntityUid entity )
2021-06-21 02:21:20 -07:00
{
var handContainer = hand . Container ;
if ( handContainer = = null )
return false ;
if ( ! handContainer . CanInsert ( entity ) )
return false ;
return true ;
}
/// <summary>
/// Checks if the player is allowed to perform pickup actions.
/// </summary>
/// <returns></returns>
protected bool PlayerCanPickup ( )
{
2021-12-03 15:53:09 +01:00
if ( ! EntitySystem . Get < ActionBlockerSystem > ( ) . CanPickup ( Owner ) )
2021-06-21 02:21:20 -07:00
return false ;
return true ;
}
/// <summary>
/// Puts an entity into the player's hand, assumes that the insertion is allowed.
/// </summary>
2021-12-04 12:35:33 +01:00
public void PutEntityIntoHand ( Hand hand , EntityUid entity )
2021-06-21 02:21:20 -07:00
{
var handContainer = hand . Container ;
if ( handContainer = = null )
return ;
if ( ! handContainer . Insert ( entity ) )
{
Logger . Error ( $"{nameof(SharedHandsComponent)} on {Owner} could not insert {entity} into {handContainer}." ) ;
return ;
}
2021-10-25 20:06:12 +13:00
EntitySystem . Get < SharedInteractionSystem > ( ) . EquippedHandInteraction ( Owner , entity , hand . ToHandState ( ) ) ;
2021-06-21 02:21:20 -07:00
if ( hand . Name = = ActiveHand )
SelectActiveHeldEntity ( ) ;
2021-12-08 17:32:32 +01:00
_entMan . GetComponent < TransformComponent > ( entity ) . LocalPosition = Vector2 . Zero ;
2021-06-21 02:21:20 -07:00
OnItemChanged ? . Invoke ( ) ;
HandsModified ( ) ;
}
2021-12-04 12:35:33 +01:00
private bool TryPickupEntity ( Hand hand , EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( ! CanInsertEntityIntoHand ( hand , entity ) )
return false ;
if ( checkActionBlocker & & ! PlayerCanPickup ( ) )
return false ;
HandlePickupAnimation ( entity ) ;
PutEntityIntoHand ( hand , entity ) ;
2021-12-14 00:22:58 +13:00
EntitySystem . Get < SharedAdminLogSystem > ( ) . Add ( LogType . Pickup , LogImpact . Low , $"{_entMan.ToPrettyString(Owner):user} picked up {_entMan.ToPrettyString(entity):entity}" ) ;
2021-06-21 02:21:20 -07:00
return true ;
}
#endregion
#region Hand Interactions
/// <summary>
/// Get the name of the hand that a swap hands would result in.
/// </summary>
public bool TryGetSwapHandsResult ( [ NotNullWhen ( true ) ] out string? nextHand )
{
nextHand = null ;
if ( ! TryGetActiveHand ( out var activeHand ) | | Hands . Count = = 1 )
return false ;
var newActiveIndex = Hands . IndexOf ( activeHand ) + 1 ;
var finalHandIndex = Hands . Count - 1 ;
if ( newActiveIndex > finalHandIndex )
newActiveIndex = 0 ;
2021-07-31 03:14:00 +02:00
nextHand = Hands [ newActiveIndex ] . Name ;
2021-06-21 02:21:20 -07:00
return true ;
}
/// <summary>
/// Attempts to interact with the item in a hand using the active held item.
/// </summary>
2021-10-25 20:06:12 +13:00
public async void InteractHandWithActiveHand ( string handName )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetActiveHeldEntity ( out var activeHeldEntity ) )
return ;
if ( ! TryGetHeldEntity ( handName , out var heldEntity ) )
return ;
if ( activeHeldEntity = = heldEntity )
return ;
2021-10-25 20:06:12 +13:00
await EntitySystem . Get < SharedInteractionSystem > ( )
2021-12-06 15:39:46 +11:00
. InteractUsing ( Owner , activeHeldEntity , heldEntity , EntityCoordinates . Invalid ) ;
2021-06-21 02:21:20 -07:00
}
2021-10-25 20:06:12 +13:00
public void ActivateItem ( bool altInteract = false )
2021-06-21 02:21:20 -07:00
{
if ( ! TryGetActiveHeldEntity ( out var heldEntity ) )
return ;
2021-10-25 20:06:12 +13:00
EntitySystem . Get < SharedInteractionSystem > ( )
2021-12-06 15:39:46 +11:00
. TryUseInteraction ( Owner , heldEntity , altInteract ) ;
2021-06-21 02:21:20 -07:00
}
public void ActivateHeldEntity ( string handName )
{
if ( ! TryGetHeldEntity ( handName , out var heldEntity ) )
return ;
2021-10-25 20:06:12 +13:00
EntitySystem . Get < SharedInteractionSystem > ( )
. TryInteractionActivate ( Owner , heldEntity ) ;
2021-06-21 02:21:20 -07:00
}
/// <summary>
/// Moves an entity from one hand to the active hand.
/// </summary>
public bool TryMoveHeldEntityToActiveHand ( string handName , bool checkActionBlocker = true )
{
if ( ! TryGetHand ( handName , out var hand ) | | ! TryGetActiveHand ( out var activeHand ) )
return false ;
if ( ! TryGetHeldEntity ( handName , out var heldEntity ) )
return false ;
2021-12-06 15:39:46 +11:00
if ( ! CanInsertEntityIntoHand ( activeHand , heldEntity ) | | ! CanRemoveHeldEntityFromHand ( hand ) )
2021-06-21 02:21:20 -07:00
return false ;
if ( checkActionBlocker & & ( ! PlayerCanDrop ( ) | | ! PlayerCanPickup ( ) ) )
return false ;
RemoveHeldEntityFromHand ( hand ) ;
2021-12-06 15:39:46 +11:00
PutEntityIntoHand ( activeHand , heldEntity ) ;
2021-06-21 02:21:20 -07:00
return true ;
}
#endregion
private void DeselectActiveHeldEntity ( )
{
if ( TryGetActiveHeldEntity ( out var entity ) )
2021-12-06 15:39:46 +11:00
EntitySystem . Get < SharedInteractionSystem > ( ) . HandDeselectedInteraction ( Owner , entity ) ;
2021-06-21 02:21:20 -07:00
}
private void SelectActiveHeldEntity ( )
{
if ( TryGetActiveHeldEntity ( out var entity ) )
2021-12-06 15:39:46 +11:00
EntitySystem . Get < SharedInteractionSystem > ( ) . HandSelectedInteraction ( Owner , entity ) ;
2021-06-21 02:21:20 -07:00
}
private void HandCountChanged ( )
{
2021-12-08 17:32:32 +01:00
_entMan . EventBus . RaiseEvent ( EventSource . Local , new HandCountChangedEvent ( Owner ) ) ;
2021-06-21 02:21:20 -07:00
}
2021-10-25 20:06:12 +13:00
/// <summary>
/// Tries to pick up an entity into the active hand. If it cannot, tries to pick up the entity into each other hand.
/// </summary>
public bool PutInHand ( SharedItemComponent item , bool checkActionBlocker = true )
{
return TryPutInActiveHandOrAny ( item . Owner , checkActionBlocker ) ;
}
/// <summary>
/// Puts an item any hand, prefering the active hand, or puts it on the floor under the player.
/// </summary>
public void PutInHandOrDrop ( SharedItemComponent item , bool checkActionBlocker = true )
{
var entity = item . Owner ;
if ( ! TryPutInActiveHandOrAny ( entity , checkActionBlocker ) )
2021-12-08 17:32:32 +01:00
_entMan . GetComponent < TransformComponent > ( entity ) . Coordinates = _entMan . GetComponent < TransformComponent > ( Owner ) . Coordinates ;
2021-10-25 20:06:12 +13:00
}
2021-06-21 02:21:20 -07:00
/// <summary>
/// Tries to pick up an entity into the active hand. If it cannot, tries to pick up the entity into each other hand.
/// </summary>
2021-12-04 12:35:33 +01:00
public bool TryPutInActiveHandOrAny ( EntityUid entity , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
return TryPutInAnyHand ( entity , GetActiveHand ( ) , checkActionBlocker ) ;
}
/// <summary>
/// Tries to pick up an entity into the priority hand, if provided. If it cannot, tries to pick up the entity into each other hand.
/// </summary>
2021-12-04 12:35:33 +01:00
public bool TryPutInAnyHand ( EntityUid entity , string? priorityHandName = null , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
Hand ? priorityHand = null ;
if ( priorityHandName ! = null )
2021-07-31 03:14:00 +02:00
priorityHand = GetHandOrNull ( priorityHandName ) ;
2021-06-21 02:21:20 -07:00
return TryPutInAnyHand ( entity , priorityHand , checkActionBlocker ) ;
}
/// <summary>
/// Tries to pick up an entity into the priority hand, if provided. If it cannot, tries to pick up the entity into each other hand.
/// </summary>
2021-12-04 12:35:33 +01:00
private bool TryPutInAnyHand ( EntityUid entity , Hand ? priorityHand = null , bool checkActionBlocker = true )
2021-06-21 02:21:20 -07:00
{
if ( priorityHand ! = null )
{
if ( TryPickupEntity ( priorityHand , entity , checkActionBlocker ) )
return true ;
}
foreach ( var hand in Hands )
{
if ( TryPickupEntity ( hand , entity , checkActionBlocker ) )
return true ;
}
return false ;
}
2021-12-04 12:35:33 +01:00
protected virtual void OnHeldEntityRemovedFromHand ( EntityUid heldEntity , HandState handState ) { }
2021-06-21 02:21:20 -07:00
2021-12-04 12:35:33 +01:00
protected virtual void HandlePickupAnimation ( EntityUid entity ) { }
2021-06-21 02:21:20 -07:00
}
2021-10-29 01:34:59 +13:00
#region visualizerData
[Serializable, NetSerializable]
public enum HandsVisuals : byte
{
VisualState
}
[Serializable, NetSerializable]
public class HandsVisualState
{
public List < HandVisualState > Hands { get ; } = new ( ) ;
public HandsVisualState ( List < HandVisualState > hands )
{
Hands = hands ;
}
}
[Serializable, NetSerializable]
public class HandVisualState
{
public string RsiPath { get ; }
public string? EquippedPrefix { get ; }
public HandLocation Location { get ; }
public Color Color { get ; }
public HandVisualState ( string rsiPath , string? equippedPrefix , HandLocation location , Color color )
{
RsiPath = rsiPath ;
EquippedPrefix = equippedPrefix ;
Location = location ;
Color = color ;
}
}
#endregion
2021-06-21 02:21:20 -07:00
2021-07-31 03:14:00 +02:00
public class Hand
2021-06-21 02:21:20 -07:00
{
[ViewVariables]
2021-07-31 03:14:00 +02:00
public string Name { get ; }
2021-06-21 02:21:20 -07:00
[ViewVariables]
2021-07-31 03:14:00 +02:00
public HandLocation Location { get ; }
2021-06-21 02:21:20 -07:00
/// <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]
public IContainer ? Container { get ; set ; }
2021-12-14 06:17:18 +01:00
// TODO: Make this a nullable EntityUid...
2021-06-21 02:21:20 -07:00
[ViewVariables]
2021-12-05 18:09:01 +01:00
public EntityUid HeldEntity = > Container ? . ContainedEntities . FirstOrDefault ( ) ? ? EntityUid . Invalid ;
2021-06-21 02:21:20 -07:00
2021-12-05 18:09:01 +01:00
public bool IsEmpty = > HeldEntity = = default ;
2021-07-31 03:14:00 +02:00
public Hand ( string name , HandLocation location , IContainer ? container = null )
2021-06-21 02:21:20 -07:00
{
Name = name ;
Location = location ;
Container = container ;
}
public HandState ToHandState ( )
{
2021-07-31 03:14:00 +02:00
return new ( Name , Location ) ;
2021-06-21 02:21:20 -07:00
}
2017-09-26 21:27:48 +02:00
}
2020-07-25 15:11:16 +02:00
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public struct HandState
2020-07-25 15:11:16 +02:00
{
2021-06-21 02:21:20 -07:00
public string Name { get ; }
public HandLocation Location { get ; }
2020-07-25 15:11:16 +02:00
2021-07-31 03:14:00 +02:00
public HandState ( string name , HandLocation location )
2020-07-25 15:11:16 +02:00
{
Name = name ;
Location = location ;
}
}
2018-05-27 10:13:33 +02:00
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public sealed class HandsComponentState : ComponentState
2017-09-26 21:27:48 +02:00
{
2021-06-21 02:21:20 -07:00
public HandState [ ] Hands { get ; }
public string? ActiveHand { get ; }
2017-09-26 21:27:48 +02:00
2021-07-12 01:32:10 -07:00
public HandsComponentState ( HandState [ ] hands , string? activeHand = null )
2017-09-26 21:27:48 +02:00
{
Hands = hands ;
2021-06-21 02:21:20 -07:00
ActiveHand = activeHand ;
2017-09-26 21:27:48 +02:00
}
}
2018-04-22 06:11:38 -05:00
/// <summary>
2020-01-17 18:41:47 -08:00
/// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand.
2018-04-22 06:11:38 -05:00
/// </summary>
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public sealed class UseInHandMsg : EntityEventArgs
2018-04-22 06:11:38 -05:00
{
}
2018-11-21 20:58:11 +01:00
2020-01-17 18:41:47 -08:00
/// <summary>
2021-06-21 02:21:20 -07:00
/// A message that calls the activate interaction on the item in the specified hand.
2020-01-17 18:41:47 -08:00
/// </summary>
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public class ActivateInHandMsg : EntityEventArgs
2020-01-17 18:41:47 -08:00
{
2021-06-21 02:21:20 -07:00
public string HandName { get ; }
2020-01-17 18:41:47 -08:00
2021-06-21 02:21:20 -07:00
public ActivateInHandMsg ( string handName )
2020-01-17 18:41:47 -08:00
{
2021-06-21 02:21:20 -07:00
HandName = handName ;
2020-01-17 18:41:47 -08:00
}
}
2021-06-21 02:21:20 -07:00
/// <summary>
/// Uses the item in the active hand on the item in the specified hand.
/// </summary>
2018-11-21 20:58:11 +01:00
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public class ClientInteractUsingInHandMsg : EntityEventArgs
2018-11-21 20:58:11 +01:00
{
2021-06-21 02:21:20 -07:00
public string HandName { get ; }
2018-11-21 20:58:11 +01:00
2021-07-31 03:14:00 +02:00
public ClientInteractUsingInHandMsg ( string handName )
2018-11-21 20:58:11 +01:00
{
2021-06-21 02:21:20 -07:00
HandName = handName ;
2018-11-21 20:58:11 +01:00
}
}
2019-08-31 17:49:18 -07:00
2021-06-21 02:21:20 -07:00
/// <summary>
/// Moves an item from one hand to the active hand.
/// </summary>
2019-08-31 17:49:18 -07:00
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public class MoveItemFromHandMsg : EntityEventArgs
2019-08-31 17:49:18 -07:00
{
2021-06-21 02:21:20 -07:00
public string HandName { get ; }
2019-08-31 17:49:18 -07:00
2021-06-21 02:21:20 -07:00
public MoveItemFromHandMsg ( string handName )
2019-08-31 17:49:18 -07:00
{
2021-06-21 02:21:20 -07:00
HandName = handName ;
2020-10-28 10:16:40 +01:00
}
}
2021-06-21 02:21:20 -07:00
/// <summary>
/// What side of the body this hand is on.
/// </summary>
2020-07-25 15:11:16 +02:00
public enum HandLocation : byte
{
Left ,
Middle ,
Right
}
2021-02-03 22:07:13 +00:00
2021-06-21 02:21:20 -07:00
public class HandCountChangedEvent : EntityEventArgs
{
2021-12-04 12:35:33 +01:00
public HandCountChangedEvent ( EntityUid sender )
2021-06-21 02:21:20 -07:00
{
Sender = sender ;
}
2021-12-04 12:35:33 +01:00
public EntityUid Sender { get ; }
2021-06-21 02:21:20 -07:00
}
2021-02-03 22:07:13 +00:00
[Serializable, NetSerializable]
2021-07-31 03:14:00 +02:00
public class PickupAnimationMessage : EntityEventArgs
2021-02-03 22:07:13 +00:00
{
2021-06-21 02:21:20 -07:00
public EntityUid EntityUid { get ; }
2021-08-06 19:20:27 +02:00
public EntityCoordinates InitialPosition { get ; }
public Vector2 FinalPosition { get ; }
2021-06-21 02:21:20 -07:00
2021-08-06 19:20:27 +02:00
public PickupAnimationMessage ( EntityUid entityUid , Vector2 finalPosition , EntityCoordinates initialPosition )
2021-02-03 22:07:13 +00:00
{
2021-06-21 02:21:20 -07:00
EntityUid = entityUid ;
2021-08-06 19:20:27 +02:00
FinalPosition = finalPosition ;
2021-06-21 02:21:20 -07:00
InitialPosition = initialPosition ;
2021-02-03 22:07:13 +00:00
}
}
2021-07-31 03:14:00 +02:00
[Serializable, NetSerializable]
public struct HandsModifiedMessage
{
public SharedHandsComponent Hands ;
}
2017-09-26 21:27:48 +02:00
}