2021-08-22 03:20:18 +10:00
using System ;
2021-12-03 11:42:24 +01:00
using System.Collections.Generic ;
2021-04-01 00:04:56 -07:00
using System.Linq ;
2021-10-25 20:06:12 +13:00
using System.Threading.Tasks ;
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-10-25 20:06:12 +13:00
using Content.Shared.Hands ;
using Content.Shared.Hands.Components ;
using Content.Shared.Inventory ;
2020-04-25 11:37:59 +02:00
using Content.Shared.Physics ;
2021-09-26 15:18:45 +02:00
using Content.Shared.Popups ;
2021-10-25 20:06:12 +13:00
using Content.Shared.Throwing ;
using Content.Shared.Timing ;
using Content.Shared.Verbs ;
2020-04-22 00:58:31 +10:00
using JetBrains.Annotations ;
2021-11-29 12:25:22 +13:00
using Robust.Shared.Containers ;
2021-02-11 01:13:03 -08:00
using Robust.Shared.GameObjects ;
2021-07-26 12:58:17 +02:00
using Robust.Shared.IoC ;
2021-06-21 02:13:54 +02:00
using Robust.Shared.Localization ;
2020-04-22 00:58:31 +10:00
using Robust.Shared.Map ;
2021-10-25 20:06:12 +13:00
using Robust.Shared.Maths ;
2021-02-11 01:13:03 -08:00
using Robust.Shared.Physics ;
2021-11-29 12:25:22 +13:00
using Robust.Shared.Players ;
2021-10-25 20:06:12 +13:00
using Robust.Shared.Random ;
2021-08-22 03:20:18 +10:00
using Robust.Shared.Serialization ;
2020-04-22 00:58:31 +10:00
2021-10-27 18:10:40 +02:00
#pragma warning disable 618
2021-06-09 22:19:39 +02:00
namespace Content.Shared.Interaction
2020-04-22 00:58:31 +10:00
{
/// <summary>
/// Governs interactions during clicking on entities
/// </summary>
[UsedImplicitly]
2021-09-16 13:02:10 +10:00
public abstract class SharedInteractionSystem : EntitySystem
2020-04-22 00:58:31 +10:00
{
2021-10-10 14:18:19 +11:00
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default ! ;
2021-10-25 20:06:12 +13:00
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default ! ;
[Dependency] private readonly SharedVerbSystem _verbSystem = default ! ;
2021-11-24 16:52:31 -06:00
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default ! ;
2021-07-26 12:58:17 +02:00
2020-04-22 00:58:31 +10:00
public const float InteractionRange = 2 ;
public const float InteractionRangeSquared = InteractionRange * InteractionRange ;
2021-12-03 11:15:41 -08:00
public delegate bool Ignored ( EntityUid entity ) ;
2020-08-30 11:37:06 +02:00
2020-05-28 13:23:50 +02:00
/// <summary>
/// Traces a ray from coords to otherCoords and returns the length
/// of the vector between coords and the ray's first hit.
/// </summary>
2020-08-30 11:37:06 +02:00
/// <param name="origin">Set of coordinates to use.</param>
/// <param name="other">Other set of coordinates to use.</param>
2020-05-28 13:23:50 +02:00
/// <param name="collisionMask">the mask to check for collisions</param>
2020-08-30 11:37:06 +02:00
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
2020-05-28 13:23:50 +02:00
/// <returns>Length of resulting ray.</returns>
2020-08-30 11:37:06 +02:00
public float UnobstructedDistance (
MapCoordinates origin ,
MapCoordinates other ,
int collisionMask = ( int ) CollisionGroup . Impassable ,
2021-03-15 21:55:49 +01:00
Ignored ? predicate = null )
2020-05-28 13:23:50 +02:00
{
2020-08-30 11:37:06 +02:00
var dir = other . Position - origin . Position ;
2020-05-28 13:23:50 +02:00
if ( dir . LengthSquared . Equals ( 0f ) ) return 0f ;
2020-08-30 11:37:06 +02:00
predicate ? ? = _ = > false ;
var ray = new CollisionRay ( origin . Position , dir . Normalized , collisionMask ) ;
2021-07-26 12:58:17 +02:00
var rayResults = _sharedBroadphaseSystem . IntersectRayWithPredicate ( origin . MapId , ray , dir . Length , predicate . Invoke , false ) . ToList ( ) ;
2020-05-28 13:23:50 +02:00
if ( rayResults . Count = = 0 ) return dir . Length ;
2020-08-30 11:37:06 +02:00
return ( rayResults [ 0 ] . HitPos - origin . Position ) . Length ;
2020-05-28 13:23:50 +02:00
}
/// <summary>
/// Traces a ray from coords to otherCoords and returns the length
/// of the vector between coords and the ray's first hit.
/// </summary>
2020-08-30 11:37:06 +02:00
/// <param name="origin">Set of coordinates to use.</param>
/// <param name="other">Other set of coordinates to use.</param>
/// <param name="collisionMask">The mask to check for collisions</param>
/// <param name="ignoredEnt">
/// The entity to be ignored when checking for collisions.
/// </param>
2020-05-28 13:23:50 +02:00
/// <returns>Length of resulting ray.</returns>
2020-08-30 11:37:06 +02:00
public float UnobstructedDistance (
MapCoordinates origin ,
MapCoordinates other ,
int collisionMask = ( int ) CollisionGroup . Impassable ,
2021-12-04 12:35:33 +01:00
EntityUid ? ignoredEnt = null )
2020-08-30 11:37:06 +02:00
{
var predicate = ignoredEnt = = null
? null
2021-12-04 12:35:33 +01:00
: ( Ignored ) ( e = > e = = ignoredEnt ) ;
2020-08-30 11:37:06 +02:00
return UnobstructedDistance ( origin , other , collisionMask , predicate ) ;
}
2020-05-28 13:23:50 +02:00
2020-04-22 00:58:31 +10:00
/// <summary>
/// Checks that these coordinates are within a certain distance without any
/// entity that matches the collision mask obstructing them.
/// If the <paramref name="range"/> is zero or negative,
2020-08-30 11:37:06 +02:00
/// this method will only check if nothing obstructs the two sets
/// of coordinates.
2020-04-22 00:58:31 +10:00
/// </summary>
2020-08-30 11:37:06 +02:00
/// <param name="origin">Set of coordinates to use.</param>
/// <param name="other">Other set of coordinates to use.</param>
/// <param name="range">
/// Maximum distance between the two sets of coordinates.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="ignoreInsideBlocker">
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
/// the obstruction, ignores the obstruction and considers the interaction
/// unobstructed.
/// Therefore, setting this to true makes this check more permissive,
/// such as allowing an interaction to occur inside something impassable
/// (like a wall). The default, false, makes the check more restrictive.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed (
MapCoordinates origin ,
MapCoordinates other ,
float range = InteractionRange ,
CollisionGroup collisionMask = CollisionGroup . Impassable ,
2021-03-15 21:55:49 +01:00
Ignored ? predicate = null ,
2020-08-30 11:37:06 +02:00
bool ignoreInsideBlocker = false )
2020-04-22 00:58:31 +10:00
{
2020-08-30 11:37:06 +02:00
if ( ! origin . InRange ( other , range ) ) return false ;
2020-05-26 14:23:25 +02:00
2020-08-30 11:37:06 +02:00
var dir = other . Position - origin . Position ;
2020-04-22 00:58:31 +10:00
if ( dir . LengthSquared . Equals ( 0f ) ) return true ;
if ( range > 0f & & ! ( dir . LengthSquared < = range * range ) ) return false ;
2020-08-30 11:37:06 +02:00
predicate ? ? = _ = > false ;
var ray = new CollisionRay ( origin . Position , dir . Normalized , ( int ) collisionMask ) ;
2021-07-26 12:58:17 +02:00
var rayResults = _sharedBroadphaseSystem . IntersectRayWithPredicate ( origin . MapId , ray , dir . Length , predicate . Invoke , false ) . ToList ( ) ;
2020-08-30 11:37:06 +02:00
if ( rayResults . Count = = 0 ) return true ;
2021-09-16 13:02:10 +10:00
// TODO: Wot? This should just be in the predicate.
2020-08-30 11:37:06 +02:00
if ( ! ignoreInsideBlocker ) return false ;
2020-10-28 13:04:29 +01:00
foreach ( var result in rayResults )
{
2021-12-05 18:09:01 +01:00
if ( ! EntityManager . TryGetComponent ( result . HitEntity , out IPhysBody ? p ) )
2020-10-28 13:04:29 +01:00
{
continue ;
}
2020-08-30 11:37:06 +02:00
2021-03-08 04:09:59 +11:00
var bBox = p . GetWorldAABB ( ) ;
2020-10-28 13:04:29 +01:00
if ( bBox . Contains ( origin . Position ) | | bBox . Contains ( other . Position ) )
{
continue ;
}
return false ;
}
return true ;
2020-04-22 00:58:31 +10:00
}
2020-04-25 11:37:59 +02:00
/// <summary>
2020-08-30 11:37:06 +02:00
/// Checks that two entities are within a certain distance without any
2020-04-25 11:37:59 +02:00
/// entity that matches the collision mask obstructing them.
/// If the <paramref name="range"/> is zero or negative,
2020-08-30 11:37:06 +02:00
/// this method will only check if nothing obstructs the two entities.
2020-04-25 11:37:59 +02:00
/// </summary>
2020-08-30 11:37:06 +02:00
/// <param name="origin">The first entity to use.</param>
/// <param name="other">Other entity to use.</param>
/// <param name="range">
/// Maximum distance between the two entities.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="ignoreInsideBlocker">
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
/// the obstruction, ignores the obstruction and considers the interaction
/// unobstructed.
/// Therefore, setting this to true makes this check more permissive,
/// such as allowing an interaction to occur inside something impassable
/// (like a wall). The default, false, makes the check more restrictive.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed (
2021-12-04 12:35:33 +01:00
EntityUid origin ,
EntityUid other ,
2020-08-30 11:37:06 +02:00
float range = InteractionRange ,
CollisionGroup collisionMask = CollisionGroup . Impassable ,
2021-03-15 21:55:49 +01:00
Ignored ? predicate = null ,
2020-08-30 11:37:06 +02:00
bool ignoreInsideBlocker = false ,
bool popup = false )
{
2021-12-04 12:35:33 +01:00
predicate ? ? = e = > e = = origin | | e = = other ;
2021-12-05 18:09:01 +01:00
return InRangeUnobstructed ( origin , EntityManager . GetComponent < TransformComponent > ( other ) . MapPosition , range , collisionMask , predicate , ignoreInsideBlocker , popup ) ;
2020-08-30 11:37:06 +02:00
}
/// <summary>
/// Checks that an entity and a component are within a certain
/// distance without any entity that matches the collision mask
/// obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the entity and component.
/// </summary>
/// <param name="origin">The entity to use.</param>
/// <param name="other">The component to use.</param>
/// <param name="range">
/// Maximum distance between the entity and component.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="ignoreInsideBlocker">
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
/// the obstruction, ignores the obstruction and considers the interaction
/// unobstructed.
/// Therefore, setting this to true makes this check more permissive,
/// such as allowing an interaction to occur inside something impassable
/// (like a wall). The default, false, makes the check more restrictive.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed (
2021-12-04 12:35:33 +01:00
EntityUid origin ,
2020-08-30 11:37:06 +02:00
IComponent other ,
float range = InteractionRange ,
CollisionGroup collisionMask = CollisionGroup . Impassable ,
2021-03-15 21:55:49 +01:00
Ignored ? predicate = null ,
2020-08-30 11:37:06 +02:00
bool ignoreInsideBlocker = false ,
bool popup = false )
{
2021-06-07 05:49:43 -07:00
return InRangeUnobstructed ( origin , other . Owner , range , collisionMask , predicate , ignoreInsideBlocker , popup ) ;
2020-08-30 11:37:06 +02:00
}
/// <summary>
/// Checks that an entity and a set of grid coordinates are within a certain
/// distance without any entity that matches the collision mask
/// obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the entity and component.
/// </summary>
/// <param name="origin">The entity to use.</param>
/// <param name="other">The grid coordinates to use.</param>
/// <param name="range">
/// Maximum distance between the two entity and set of grid coordinates.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="ignoreInsideBlocker">
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
/// the obstruction, ignores the obstruction and considers the interaction
/// unobstructed.
/// Therefore, setting this to true makes this check more permissive,
/// such as allowing an interaction to occur inside something impassable
/// (like a wall). The default, false, makes the check more restrictive.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed (
2021-12-04 12:35:33 +01:00
EntityUid origin ,
2020-09-06 16:11:53 +02:00
EntityCoordinates other ,
2020-08-30 11:37:06 +02:00
float range = InteractionRange ,
CollisionGroup collisionMask = CollisionGroup . Impassable ,
2021-03-15 21:55:49 +01:00
Ignored ? predicate = null ,
2020-08-30 11:37:06 +02:00
bool ignoreInsideBlocker = false ,
bool popup = false )
{
2021-06-07 05:49:43 -07:00
return InRangeUnobstructed ( origin , other . ToMap ( EntityManager ) , range , collisionMask , predicate , ignoreInsideBlocker , popup ) ;
2020-08-30 11:37:06 +02:00
}
/// <summary>
/// Checks that an entity and a set of map coordinates are within a certain
/// distance without any entity that matches the collision mask
/// obstructing them.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the entity and component.
/// </summary>
/// <param name="origin">The entity to use.</param>
/// <param name="other">The map coordinates to use.</param>
/// <param name="range">
/// Maximum distance between the two entity and set of map coordinates.
/// </param>
/// <param name="collisionMask">The mask to check for collisions.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <param name="ignoreInsideBlocker">
/// If true and <see cref="origin"/> or <see cref="other"/> are inside
/// the obstruction, ignores the obstruction and considers the interaction
/// unobstructed.
/// Therefore, setting this to true makes this check more permissive,
/// such as allowing an interaction to occur inside something impassable
/// (like a wall). The default, false, makes the check more restrictive.
/// </param>
/// <param name="popup">
/// Whether or not to popup a feedback message on the origin entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed (
2021-12-04 12:35:33 +01:00
EntityUid origin ,
2020-08-30 11:37:06 +02:00
MapCoordinates other ,
float range = InteractionRange ,
CollisionGroup collisionMask = CollisionGroup . Impassable ,
2021-03-15 21:55:49 +01:00
Ignored ? predicate = null ,
2020-08-30 11:37:06 +02:00
bool ignoreInsideBlocker = false ,
bool popup = false )
{
2021-12-05 18:09:01 +01:00
var originPosition = EntityManager . GetComponent < TransformComponent > ( origin ) . MapPosition ;
2021-12-04 12:35:33 +01:00
predicate ? ? = e = > e = = origin ;
2020-08-30 11:37:06 +02:00
var inRange = InRangeUnobstructed ( originPosition , other , range , collisionMask , predicate , ignoreInsideBlocker ) ;
if ( ! inRange & & popup )
{
2021-06-21 02:13:54 +02:00
var message = Loc . GetString ( "shared-interaction-system-in-range-unobstructed-cannot-reach" ) ;
2020-09-01 12:34:53 +02:00
origin . PopupMessage ( message ) ;
2020-08-30 11:37:06 +02:00
}
return inRange ;
}
2021-10-25 20:06:12 +13:00
2021-10-28 13:19:38 +02:00
public bool InteractDoBefore (
2021-12-04 12:35:33 +01:00
EntityUid user ,
EntityUid used ,
EntityUid ? target ,
2021-10-25 20:06:12 +13:00
EntityCoordinates clickLocation ,
bool canReach )
{
var ev = new BeforeInteractEvent ( user , used , target , clickLocation , canReach ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( used , ev , false ) ;
2021-10-25 20:06:12 +13:00
return ev . Handled ;
}
/// <summary>
/// Uses a item/object on an entity
/// Finds components with the InteractUsing interface and calls their function
/// NOTE: Does not have an InRangeUnobstructed check
/// </summary>
2021-12-04 12:35:33 +01:00
public async Task InteractUsing ( EntityUid user , EntityUid used , EntityUid target , EntityCoordinates clickLocation )
2021-10-25 20:06:12 +13:00
{
2021-12-03 15:53:09 +01:00
if ( ! _actionBlockerSystem . CanInteract ( user ) )
2021-10-25 20:06:12 +13:00
return ;
2021-10-28 13:19:38 +02:00
if ( InteractDoBefore ( user , used , target , clickLocation , true ) )
2021-10-25 20:06:12 +13:00
return ;
// all interactions should only happen when in range / unobstructed, so no range check is needed
var interactUsingEvent = new InteractUsingEvent ( user , used , target , clickLocation ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( target , interactUsingEvent ) ;
2021-10-25 20:06:12 +13:00
if ( interactUsingEvent . Handled )
return ;
var interactUsingEventArgs = new InteractUsingEventArgs ( user , clickLocation , used , target ) ;
2021-12-05 18:09:01 +01:00
var interactUsings = EntityManager . GetComponents < IInteractUsing > ( target ) . OrderByDescending ( x = > x . Priority ) ;
2021-10-25 20:06:12 +13:00
foreach ( var interactUsing in interactUsings )
{
// If an InteractUsing returns a status completion we finish our interaction
if ( await interactUsing . InteractUsing ( interactUsingEventArgs ) )
return ;
}
// If we aren't directly interacting with the nearby object, lets see if our item has an after interact we can do
await InteractDoAfter ( user , used , target , clickLocation , true ) ;
}
/// <summary>
/// We didn't click on any entity, try doing an AfterInteract on the click location
/// </summary>
2021-12-04 12:35:33 +01:00
public async Task < bool > InteractDoAfter ( EntityUid user , EntityUid used , EntityUid ? target , EntityCoordinates clickLocation , bool canReach )
2021-10-25 20:06:12 +13:00
{
2021-12-11 15:36:50 +01:00
if ( target is { Valid : false } )
target = null ;
2021-10-25 20:06:12 +13:00
var afterInteractEvent = new AfterInteractEvent ( user , used , target , clickLocation , canReach ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( used , afterInteractEvent , false ) ;
2021-10-25 20:06:12 +13:00
if ( afterInteractEvent . Handled )
return true ;
var afterInteractEventArgs = new AfterInteractEventArgs ( user , clickLocation , target , canReach ) ;
2021-12-05 18:09:01 +01:00
var afterInteracts = EntityManager . GetComponents < IAfterInteract > ( used ) . OrderByDescending ( x = > x . Priority ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
foreach ( var afterInteract in afterInteracts )
{
if ( await afterInteract . AfterInteract ( afterInteractEventArgs ) )
return true ;
}
return false ;
}
#region ActivateItemInWorld
/// <summary>
/// Activates the IActivate behavior of an object
/// Verifies that the user is capable of doing the use interaction first
/// </summary>
2021-12-04 12:35:33 +01:00
public void TryInteractionActivate ( EntityUid ? user , EntityUid ? used )
2021-10-25 20:06:12 +13:00
{
if ( user = = null | | used = = null )
return ;
2021-12-04 12:35:33 +01:00
InteractionActivate ( user . Value , used . Value ) ;
2021-10-25 20:06:12 +13:00
}
2021-12-04 12:35:33 +01:00
protected void InteractionActivate ( EntityUid user , EntityUid used )
2021-10-25 20:06:12 +13:00
{
2021-12-05 18:09:01 +01:00
if ( EntityManager . TryGetComponent < UseDelayComponent ? > ( used , out var delayComponent ) )
2021-10-25 20:06:12 +13:00
{
if ( delayComponent . ActiveDelay )
return ;
delayComponent . BeginDelay ( ) ;
}
2021-12-03 15:53:09 +01:00
if ( ! _actionBlockerSystem . CanInteract ( user ) | | ! _actionBlockerSystem . CanUse ( user ) )
2021-10-25 20:06:12 +13:00
return ;
// all activates should only fire when in range / unobstructed
if ( ! InRangeUnobstructed ( user , used , ignoreInsideBlocker : true , popup : true ) )
return ;
2021-11-29 12:25:22 +13:00
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
2021-12-03 15:53:09 +01:00
if ( ! user . IsInSameOrParentContainer ( used ) & & ! CanAccessViaStorage ( user , used ) )
2021-11-29 12:25:22 +13:00
return ;
2021-10-25 20:06:12 +13:00
var activateMsg = new ActivateInWorldEvent ( user , used ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( used , activateMsg ) ;
2021-10-25 20:06:12 +13:00
if ( activateMsg . Handled )
2021-11-24 16:52:31 -06:00
{
2021-12-14 00:22:58 +13:00
_adminLogSystem . Add ( LogType . InteractActivate , LogImpact . Low , $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}" ) ;
2021-10-25 20:06:12 +13:00
return ;
2021-11-24 16:52:31 -06:00
}
2021-10-25 20:06:12 +13:00
2021-12-05 18:09:01 +01:00
if ( ! EntityManager . TryGetComponent ( used , out IActivate ? activateComp ) )
2021-10-25 20:06:12 +13:00
return ;
var activateEventArgs = new ActivateEventArgs ( user , used ) ;
activateComp . Activate ( activateEventArgs ) ;
2021-12-14 00:22:58 +13:00
_adminLogSystem . Add ( LogType . InteractActivate , LogImpact . Low , $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}" ) ; // No way to check success.
2021-10-25 20:06:12 +13:00
}
#endregion
#region Hands
#region Use
/// <summary>
/// Activates the IUse behaviors of an entity
/// Verifies that the user is capable of doing the use interaction first
/// </summary>
/// <param name="user"></param>
/// <param name="used"></param>
2021-12-04 12:35:33 +01:00
public void TryUseInteraction ( EntityUid user , EntityUid used , bool altInteract = false )
2021-10-25 20:06:12 +13:00
{
2021-12-03 15:53:09 +01:00
if ( user ! = null & & used ! = null & & _actionBlockerSystem . CanUse ( user ) )
2021-10-25 20:06:12 +13:00
{
if ( altInteract )
AltInteract ( user , used ) ;
else
UseInteraction ( user , used ) ;
}
}
/// <summary>
/// Activates the IUse behaviors of an entity without first checking
/// if the user is capable of doing the use interaction.
/// </summary>
2021-12-04 12:35:33 +01:00
public void UseInteraction ( EntityUid user , EntityUid used )
2021-10-25 20:06:12 +13:00
{
2021-12-05 18:09:01 +01:00
if ( EntityManager . TryGetComponent < UseDelayComponent ? > ( used , out var delayComponent ) )
2021-10-25 20:06:12 +13:00
{
if ( delayComponent . ActiveDelay )
return ;
delayComponent . BeginDelay ( ) ;
}
var useMsg = new UseInHandEvent ( user , used ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( used , useMsg ) ;
2021-10-25 20:06:12 +13:00
if ( useMsg . Handled )
return ;
2021-12-05 18:09:01 +01:00
var uses = EntityManager . GetComponents < IUse > ( used ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
// Try to use item on any components which have the interface
foreach ( var use in uses )
{
// If a Use returns a status completion we finish our interaction
if ( use . UseEntity ( new UseEntityEventArgs ( user ) ) )
return ;
}
}
/// <summary>
/// Alternative interactions on an entity.
/// </summary>
/// <remarks>
/// Uses the context menu verb list, and acts out the highest priority alternative interaction verb.
/// </remarks>
2021-12-04 12:35:33 +01:00
public void AltInteract ( EntityUid user , EntityUid target )
2021-10-25 20:06:12 +13:00
{
// Get list of alt-interact verbs
2021-11-29 12:25:22 +13:00
var verbs = _verbSystem . GetLocalVerbs ( target , user , VerbType . Alternative ) [ VerbType . Alternative ] ;
if ( verbs . Any ( ) )
2021-12-03 15:53:09 +01:00
_verbSystem . ExecuteVerb ( verbs . First ( ) , user , target ) ;
2021-10-25 20:06:12 +13:00
}
#endregion
#region Throw
/// <summary>
/// Calls Thrown on all components that implement the IThrown interface
/// on an entity that has been thrown.
/// </summary>
2021-12-04 12:35:33 +01:00
public void ThrownInteraction ( EntityUid user , EntityUid thrown )
2021-10-25 20:06:12 +13:00
{
var throwMsg = new ThrownEvent ( user , thrown ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( thrown , throwMsg ) ;
2021-10-25 20:06:12 +13:00
if ( throwMsg . Handled )
2021-11-24 16:52:31 -06:00
{
2021-12-14 00:22:58 +13:00
_adminLogSystem . Add ( LogType . Throw , LogImpact . Low , $"{ToPrettyString(user):user} threw {ToPrettyString(thrown):entity}" ) ;
2021-10-25 20:06:12 +13:00
return ;
2021-11-24 16:52:31 -06:00
}
2021-10-25 20:06:12 +13:00
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IThrown > ( thrown ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
var args = new ThrownEventArgs ( user ) ;
// Call Thrown on all components that implement the interface
foreach ( var comp in comps )
{
comp . Thrown ( args ) ;
}
2021-12-14 00:22:58 +13:00
_adminLogSystem . Add ( LogType . Throw , LogImpact . Low , $"{ToPrettyString(user):user} threw {ToPrettyString(thrown):entity}" ) ;
2021-10-25 20:06:12 +13:00
}
#endregion
#region Equip
/// <summary>
/// Calls Equipped on all components that implement the IEquipped interface
/// on an entity that has been equipped.
/// </summary>
2021-12-04 12:35:33 +01:00
public void EquippedInteraction ( EntityUid user , EntityUid equipped , EquipmentSlotDefines . Slots slot )
2021-10-25 20:06:12 +13:00
{
var equipMsg = new EquippedEvent ( user , equipped , slot ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( equipped , equipMsg ) ;
2021-10-25 20:06:12 +13:00
if ( equipMsg . Handled )
return ;
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IEquipped > ( equipped ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
// Call Thrown on all components that implement the interface
foreach ( var comp in comps )
{
comp . Equipped ( new EquippedEventArgs ( user , slot ) ) ;
}
}
/// <summary>
/// Calls Unequipped on all components that implement the IUnequipped interface
/// on an entity that has been equipped.
/// </summary>
2021-12-04 12:35:33 +01:00
public void UnequippedInteraction ( EntityUid user , EntityUid equipped , EquipmentSlotDefines . Slots slot )
2021-10-25 20:06:12 +13:00
{
var unequipMsg = new UnequippedEvent ( user , equipped , slot ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( equipped , unequipMsg ) ;
2021-10-25 20:06:12 +13:00
if ( unequipMsg . Handled )
return ;
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IUnequipped > ( equipped ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
// Call Thrown on all components that implement the interface
foreach ( var comp in comps )
{
comp . Unequipped ( new UnequippedEventArgs ( user , slot ) ) ;
}
}
#region Equip Hand
/// <summary>
/// Calls EquippedHand on all components that implement the IEquippedHand interface
/// on an item.
/// </summary>
2021-12-04 12:35:33 +01:00
public void EquippedHandInteraction ( EntityUid user , EntityUid item , HandState hand )
2021-10-25 20:06:12 +13:00
{
var equippedHandMessage = new EquippedHandEvent ( user , item , hand ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( item , equippedHandMessage ) ;
2021-10-25 20:06:12 +13:00
if ( equippedHandMessage . Handled )
return ;
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IEquippedHand > ( item ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
foreach ( var comp in comps )
{
comp . EquippedHand ( new EquippedHandEventArgs ( user , hand ) ) ;
}
}
/// <summary>
/// Calls UnequippedHand on all components that implement the IUnequippedHand interface
/// on an item.
/// </summary>
2021-12-04 12:35:33 +01:00
public void UnequippedHandInteraction ( EntityUid user , EntityUid item , HandState hand )
2021-10-25 20:06:12 +13:00
{
var unequippedHandMessage = new UnequippedHandEvent ( user , item , hand ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( item , unequippedHandMessage ) ;
2021-10-25 20:06:12 +13:00
if ( unequippedHandMessage . Handled )
return ;
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IUnequippedHand > ( item ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
foreach ( var comp in comps )
{
comp . UnequippedHand ( new UnequippedHandEventArgs ( user , hand ) ) ;
}
}
#endregion
#endregion
#region Drop
/// <summary>
/// Activates the Dropped behavior of an object
/// Verifies that the user is capable of doing the drop interaction first
/// </summary>
2021-12-04 12:35:33 +01:00
public bool TryDroppedInteraction ( EntityUid user , EntityUid item )
2021-10-25 20:06:12 +13:00
{
2021-12-03 15:53:09 +01:00
if ( user = = null | | item = = null | | ! _actionBlockerSystem . CanDrop ( user ) ) return false ;
2021-10-25 20:06:12 +13:00
2021-11-24 00:38:39 +01:00
DroppedInteraction ( user , item ) ;
2021-10-25 20:06:12 +13:00
return true ;
}
/// <summary>
/// Calls Dropped on all components that implement the IDropped interface
/// on an entity that has been dropped.
/// </summary>
2021-12-04 12:35:33 +01:00
public void DroppedInteraction ( EntityUid user , EntityUid item )
2021-10-25 20:06:12 +13:00
{
2021-12-03 15:53:09 +01:00
var dropMsg = new DroppedEvent ( user , item ) ;
RaiseLocalEvent ( item , dropMsg ) ;
2021-10-25 20:06:12 +13:00
if ( dropMsg . Handled )
2021-11-24 16:52:31 -06:00
{
2021-12-14 00:22:58 +13:00
_adminLogSystem . Add ( LogType . Drop , LogImpact . Low , $"{ToPrettyString(user):user} dropped {ToPrettyString(item):entity}" ) ;
2021-10-25 20:06:12 +13:00
return ;
2021-11-24 16:52:31 -06:00
}
2021-10-25 20:06:12 +13:00
2021-12-05 18:09:01 +01:00
EntityManager . GetComponent < TransformComponent > ( item ) . LocalRotation = Angle . Zero ;
2021-10-25 20:06:12 +13:00
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IDropped > ( item ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
// Call Land on all components that implement the interface
foreach ( var comp in comps )
{
2021-11-24 00:38:39 +01:00
comp . Dropped ( new DroppedEventArgs ( user ) ) ;
2021-10-25 20:06:12 +13:00
}
2021-12-14 00:22:58 +13:00
_adminLogSystem . Add ( LogType . Drop , LogImpact . Low , $"{ToPrettyString(user):user} dropped {ToPrettyString(item):entity}" ) ;
2021-10-25 20:06:12 +13:00
}
#endregion
#region Hand Selected
/// <summary>
/// Calls HandSelected on all components that implement the IHandSelected interface
/// on an item entity on a hand that has just been selected.
/// </summary>
2021-12-04 12:35:33 +01:00
public void HandSelectedInteraction ( EntityUid user , EntityUid item )
2021-10-25 20:06:12 +13:00
{
var handSelectedMsg = new HandSelectedEvent ( user , item ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( item , handSelectedMsg ) ;
2021-10-25 20:06:12 +13:00
if ( handSelectedMsg . Handled )
return ;
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IHandSelected > ( item ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
// Call Land on all components that implement the interface
foreach ( var comp in comps )
{
comp . HandSelected ( new HandSelectedEventArgs ( user ) ) ;
}
}
/// <summary>
/// Calls HandDeselected on all components that implement the IHandDeselected interface
/// on an item entity on a hand that has just been deselected.
/// </summary>
2021-12-04 12:35:33 +01:00
public void HandDeselectedInteraction ( EntityUid user , EntityUid item )
2021-10-25 20:06:12 +13:00
{
var handDeselectedMsg = new HandDeselectedEvent ( user , item ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( item , handDeselectedMsg ) ;
2021-10-25 20:06:12 +13:00
if ( handDeselectedMsg . Handled )
return ;
2021-12-05 18:09:01 +01:00
var comps = EntityManager . GetComponents < IHandDeselected > ( item ) . ToList ( ) ;
2021-10-25 20:06:12 +13:00
// Call Land on all components that implement the interface
foreach ( var comp in comps )
{
comp . HandDeselected ( new HandDeselectedEventArgs ( user ) ) ;
}
}
#endregion
2021-11-29 12:25:22 +13:00
/// <summary>
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This
/// checks if the user can access the item in these situations.
/// </summary>
public abstract bool CanAccessViaStorage ( EntityUid user , EntityUid target ) ;
2021-10-25 20:06:12 +13:00
#endregion
2020-04-22 00:58:31 +10:00
}
2021-08-22 03:20:18 +10:00
/// <summary>
/// Raised when a player attempts to activate an item in an inventory slot or hand slot
/// </summary>
[Serializable, NetSerializable]
public class InteractInventorySlotEvent : EntityEventArgs
{
/// <summary>
/// Entity that was interacted with.
/// </summary>
public EntityUid ItemUid { get ; }
/// <summary>
/// Whether the interaction used the alt-modifier to trigger alternative interactions.
/// </summary>
public bool AltInteract { get ; }
public InteractInventorySlotEvent ( EntityUid itemUid , bool altInteract = false )
{
ItemUid = itemUid ;
AltInteract = altInteract ;
}
}
2020-04-22 00:58:31 +10:00
}