2021-11-22 09:40:09 +13:00
using System ;
2021-12-03 11:42:24 +01:00
using System.Collections.Generic ;
2020-10-29 09:55:27 +01:00
using System.Linq ;
2021-06-09 22:19:39 +02:00
using Content.Shared.DragDrop ;
using Content.Shared.Interaction ;
using Content.Shared.Interaction.Helpers ;
2021-11-22 09:40:09 +13:00
using Content.Shared.MobState.Components ;
2019-07-19 10:45:04 +02:00
using JetBrains.Annotations ;
2020-08-15 20:38:37 +02:00
using Robust.Shared.Containers ;
2020-10-28 13:04:29 +01:00
using Robust.Shared.GameObjects ;
2021-12-03 11:11:52 +01:00
using Robust.Shared.IoC ;
2022-02-09 07:08:07 +13:00
using Robust.Shared.Log ;
2020-09-24 18:18:50 +02:00
using Robust.Shared.Map ;
2020-08-01 17:37:12 +02:00
using Robust.Shared.Maths ;
2021-03-05 01:08:38 +01:00
using Robust.Shared.Physics ;
2020-08-01 17:37:12 +02:00
using Robust.Shared.Utility ;
2021-06-09 22:19:39 +02:00
using static Content . Shared . Interaction . SharedInteractionSystem ;
2019-07-19 10:45:04 +02:00
2021-06-09 22:19:39 +02:00
namespace Content.Shared.Examine
2019-07-19 10:45:04 +02:00
{
2021-05-26 10:23:07 +02:00
[Obsolete("Use ExaminedEvent instead.")]
2020-08-01 17:37:12 +02:00
public interface IExamine
{
/// <summary>
/// Returns a status examine value for components appended to the end of the description of the entity
/// </summary>
/// <param name="message">The message to append to which will be displayed.</param>
/// <param name="inDetailsRange">Whether the examiner is within the 'Details' range, allowing you to show information logically only availabe when close to the examined entity.</param>
2021-12-20 12:42:42 +01:00
void Examine ( FormattedMessage message , bool inDetailsRange ) ;
2020-08-01 17:37:12 +02:00
}
2020-10-10 15:25:13 +02:00
2019-07-19 10:45:04 +02:00
public abstract class ExamineSystemShared : EntitySystem
{
2022-01-31 20:08:53 +13:00
[Dependency] private readonly SharedContainerSystem _containerSystem = default ! ;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
2022-02-09 07:08:07 +13:00
public const float MaxRaycastRange = 100 ;
2021-11-22 09:40:09 +13:00
/// <summary>
/// Examine range to use when the examiner is in critical condition.
/// </summary>
/// <remarks>
/// Detailed examinations are disabled while incapactiated. Ideally this should just be set equal to the
/// radius of the crit overlay that blackens most of the screen. The actual radius of that is defined
/// in a shader sooo... eh.
/// </remarks>
public const float CritExamineRange = 1.3f ;
/// <summary>
/// Examine range to use when the examiner is dead. See <see cref="CritExamineRange"/>.
/// </summary>
public const float DeadExamineRange = 0.75f ;
2020-04-25 11:37:59 +02:00
public const float ExamineRange = 16f ;
2020-08-01 17:37:12 +02:00
protected const float ExamineDetailsRange = 3f ;
2019-07-19 10:45:04 +02:00
2022-02-13 20:20:58 -07:00
/// <summary>
/// Creates a new examine tooltip with arbitrary info.
/// </summary>
public abstract void SendExamineTooltip ( EntityUid player , EntityUid target , FormattedMessage message , bool getVerbs , bool centerAtCursor ) ;
public bool IsInDetailsRange ( EntityUid examiner , EntityUid entity )
2020-08-15 20:38:37 +02:00
{
2021-11-22 09:40:09 +13:00
// check if the mob is in ciritcal or dead
2021-12-03 15:53:09 +01:00
if ( EntityManager . TryGetComponent ( examiner , out MobStateComponent mobState ) & & mobState . IsIncapacitated ( ) )
2021-11-22 09:40:09 +13:00
return false ;
2022-02-17 15:40:03 +13:00
if ( ! _interactionSystem . InRangeUnobstructed ( examiner , entity , ExamineDetailsRange ) )
2022-01-31 20:08:53 +13:00
return false ;
// Is the target hidden in a opaque locker or something? Currently this check allows players to examine
// their organs, if they can somehow target them. Really this should be with userSeeInsideSelf: false, and a
// separate check for if the item is in their inventory or hands.
if ( _containerSystem . IsInSameOrTransparentContainer ( examiner , entity , userSeeInsideSelf : true ) )
2021-10-13 23:04:16 +02:00
return true ;
2022-01-31 20:08:53 +13:00
// is it inside of an open storage (e.g., an open backpack)?
return _interactionSystem . CanAccessViaStorage ( examiner , entity ) ;
2020-08-15 20:38:37 +02:00
}
2019-07-19 10:45:04 +02:00
[Pure]
2021-12-04 12:47:09 +01:00
public bool CanExamine ( EntityUid examiner , EntityUid examined )
2021-11-22 09:40:09 +13:00
{
2021-12-22 16:08:58 +11:00
return ! Deleted ( examined ) & & CanExamine ( examiner , EntityManager . GetComponent < TransformComponent > ( examined ) . MapPosition ,
2021-11-22 09:40:09 +13:00
entity = > entity = = examiner | | entity = = examined ) ;
}
[Pure]
2021-12-04 12:47:09 +01:00
public virtual bool CanExamine ( EntityUid examiner , MapCoordinates target , Ignored ? predicate = null )
2019-07-19 10:45:04 +02:00
{
2021-12-08 13:00:43 +01:00
if ( ! EntityManager . TryGetComponent ( examiner , out ExaminerComponent ? examinerComponent ) )
2019-07-19 10:45:04 +02:00
return false ;
if ( ! examinerComponent . DoRangeCheck )
return true ;
2021-12-08 13:00:43 +01:00
if ( EntityManager . GetComponent < TransformComponent > ( examiner ) . MapID ! = target . MapId )
2019-07-19 10:45:04 +02:00
return false ;
2020-08-15 20:38:37 +02:00
2020-09-24 18:18:50 +02:00
return InRangeUnOccluded (
2021-12-08 13:00:43 +01:00
EntityManager . GetComponent < TransformComponent > ( examiner ) . MapPosition ,
2021-11-22 09:40:09 +13:00
target ,
2021-12-03 15:53:09 +01:00
GetExaminerRange ( examiner ) ,
2020-09-24 18:18:50 +02:00
predicate : predicate ,
ignoreInsideBlocker : true ) ;
}
2021-11-22 09:40:09 +13:00
/// <summary>
/// Check if a given examiner is incapacitated. If yes, return a reduced examine range. Otherwise, return the deault range.
/// </summary>
public float GetExaminerRange ( EntityUid examiner , MobStateComponent ? mobState = null )
{
if ( Resolve ( examiner , ref mobState , logMissing : false ) )
{
if ( mobState . IsDead ( ) )
return DeadExamineRange ;
else if ( mobState . IsCritical ( ) )
return CritExamineRange ;
}
return ExamineRange ;
}
2020-10-29 09:55:27 +01:00
public static bool InRangeUnOccluded ( MapCoordinates origin , MapCoordinates other , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-09-24 18:18:50 +02:00
{
2022-02-09 07:08:07 +13:00
if ( other . MapId ! = origin . MapId | |
2021-07-21 21:15:12 +10:00
other . MapId = = MapId . Nullspace ) return false ;
2020-09-24 18:18:50 +02:00
var dir = other . Position - origin . Position ;
2022-02-09 07:08:07 +13:00
var length = dir . Length ;
// If range specified also check it
if ( range > 0f & & length > range ) return false ;
if ( MathHelper . CloseTo ( length , 0 ) ) return true ;
2020-09-24 18:18:50 +02:00
2022-02-09 07:08:07 +13:00
if ( length > MaxRaycastRange )
{
Logger . Warning ( "InRangeUnOccluded check performed over extreme range. Limiting CollisionRay size." ) ;
length = MaxRaycastRange ;
}
var occluderSystem = Get < OccluderSystem > ( ) ;
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
2020-09-24 18:18:50 +02:00
predicate ? ? = _ = > false ;
var ray = new Ray ( origin . Position , dir . Normalized ) ;
var rayResults = occluderSystem
2022-02-09 07:08:07 +13:00
. IntersectRayWithPredicate ( origin . MapId , ray , length , predicate . Invoke , false ) . ToList ( ) ;
2020-09-24 18:18:50 +02:00
if ( rayResults . Count = = 0 ) return true ;
if ( ! ignoreInsideBlocker ) return false ;
2020-10-28 13:04:29 +01:00
foreach ( var result in rayResults )
{
2021-12-08 13:07:24 +01:00
if ( ! entMan . TryGetComponent ( result . HitEntity , out OccluderComponent ? o ) )
2020-10-28 13:04:29 +01:00
{
continue ;
}
2021-12-08 13:07:24 +01:00
var bBox = o . BoundingBox . Translated ( entMan . GetComponent < TransformComponent > ( o . Owner ) . WorldPosition ) ;
2020-10-28 13:04:29 +01:00
if ( bBox . Contains ( origin . Position ) | | bBox . Contains ( other . Position ) )
{
continue ;
}
return false ;
}
2020-09-24 18:18:50 +02:00
2020-10-28 13:04:29 +01:00
return true ;
2019-07-19 10:45:04 +02:00
}
2020-08-01 17:37:12 +02:00
2021-12-04 12:47:09 +01:00
public static bool InRangeUnOccluded ( EntityUid origin , EntityUid other , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
var originPos = entMan . GetComponent < TransformComponent > ( origin ) . MapPosition ;
var otherPos = entMan . GetComponent < TransformComponent > ( other ) . MapPosition ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , otherPos , range , predicate , ignoreInsideBlocker ) ;
}
2021-12-04 12:47:09 +01:00
public static bool InRangeUnOccluded ( EntityUid origin , IComponent other , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
var originPos = entMan . GetComponent < TransformComponent > ( origin ) . MapPosition ;
var otherPos = entMan . GetComponent < TransformComponent > ( other . Owner ) . MapPosition ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , otherPos , range , predicate , ignoreInsideBlocker ) ;
}
2021-12-04 12:47:09 +01:00
public static bool InRangeUnOccluded ( EntityUid origin , EntityCoordinates other , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
var originPos = entMan . GetComponent < TransformComponent > ( origin ) . MapPosition ;
var otherPos = other . ToMap ( entMan ) ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , otherPos , range , predicate , ignoreInsideBlocker ) ;
}
2021-12-04 12:47:09 +01:00
public static bool InRangeUnOccluded ( EntityUid origin , MapCoordinates other , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
var originPos = entMan . GetComponent < TransformComponent > ( origin ) . MapPosition ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , other , range , predicate , ignoreInsideBlocker ) ;
}
2020-10-29 09:55:27 +01:00
public static bool InRangeUnOccluded ( ITargetedInteractEventArgs args , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
var originPos = entMan . GetComponent < TransformComponent > ( args . User ) . MapPosition ;
var otherPos = entMan . GetComponent < TransformComponent > ( args . Target ) . MapPosition ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , otherPos , range , predicate , ignoreInsideBlocker ) ;
}
2021-05-22 21:06:40 -07:00
public static bool InRangeUnOccluded ( DragDropEvent args , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entMan = IoCManager . Resolve < IEntityManager > ( ) ;
var originPos = entMan . GetComponent < TransformComponent > ( args . User ) . MapPosition ;
var otherPos = args . DropLocation . ToMap ( entMan ) ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , otherPos , range , predicate , ignoreInsideBlocker ) ;
}
2020-10-29 09:55:27 +01:00
public static bool InRangeUnOccluded ( AfterInteractEventArgs args , float range , Ignored ? predicate , bool ignoreInsideBlocker = true )
2020-10-11 13:13:45 +02:00
{
2021-12-08 13:07:24 +01:00
var entityManager = IoCManager . Resolve < IEntityManager > ( ) ; ;
2021-12-04 14:14:22 +01:00
var originPos = entityManager . GetComponent < TransformComponent > ( args . User ) . MapPosition ;
var target = args . Target ;
var otherPos = ( target ! = null ? entityManager . GetComponent < TransformComponent > ( target . Value ) . MapPosition : args . ClickLocation . ToMap ( entityManager ) ) ;
2020-10-11 13:13:45 +02:00
return InRangeUnOccluded ( originPos , otherPos , range , predicate , ignoreInsideBlocker ) ;
}
2021-12-04 12:47:09 +01:00
public FormattedMessage GetExamineText ( EntityUid entity , EntityUid ? examiner )
2020-08-01 17:37:12 +02:00
{
2021-12-20 12:42:42 +01:00
var message = new FormattedMessage ( ) ;
2020-08-01 17:37:12 +02:00
2021-03-10 14:48:29 +01:00
if ( examiner = = null )
{
2021-12-20 12:42:42 +01:00
return message ;
2021-03-10 14:48:29 +01:00
}
2020-08-01 17:37:12 +02:00
var doNewline = false ;
//Add an entity description if one is declared
2021-12-08 13:00:43 +01:00
if ( ! string . IsNullOrEmpty ( EntityManager . GetComponent < MetaDataComponent > ( entity ) . EntityDescription ) )
2020-08-01 17:37:12 +02:00
{
2021-12-08 13:00:43 +01:00
message . AddText ( EntityManager . GetComponent < MetaDataComponent > ( entity ) . EntityDescription ) ;
2020-08-01 17:37:12 +02:00
doNewline = true ;
}
message . PushColor ( Color . DarkGray ) ;
2021-05-26 10:19:14 +02:00
// Raise the event and let things that subscribe to it change the message...
2021-12-04 12:47:09 +01:00
var isInDetailsRange = IsInDetailsRange ( examiner . Value , entity ) ;
var examinedEvent = new ExaminedEvent ( message , entity , examiner . Value , isInDetailsRange , doNewline ) ;
2021-12-03 15:53:09 +01:00
RaiseLocalEvent ( entity , examinedEvent ) ;
2021-05-26 10:19:14 +02:00
2020-08-01 17:37:12 +02:00
//Add component statuses from components that report one
2021-12-08 13:00:43 +01:00
foreach ( var examineComponent in EntityManager . GetComponents < IExamine > ( entity ) )
2020-08-01 17:37:12 +02:00
{
2021-12-20 12:42:42 +01:00
var subMessage = new FormattedMessage ( ) ;
examineComponent . Examine ( subMessage , isInDetailsRange ) ;
if ( subMessage . Tags . Count = = 0 )
continue ;
2020-08-01 17:37:12 +02:00
if ( doNewline )
message . AddText ( "\n" ) ;
2021-12-20 12:42:42 +01:00
message . AddMessage ( subMessage ) ;
2020-08-01 17:37:12 +02:00
doNewline = true ;
}
2021-12-20 12:42:42 +01:00
message . Pop ( ) ;
return message ;
2020-08-01 17:37:12 +02:00
}
2019-07-19 10:45:04 +02:00
}
2021-05-26 10:19:14 +02:00
/// <summary>
/// Raised when an entity is examined.
/// </summary>
2022-02-16 00:23:23 -07:00
public sealed class ExaminedEvent : EntityEventArgs
2021-05-26 10:19:14 +02:00
{
/// <summary>
/// The message that will be displayed as the examine text.
2021-09-15 16:58:15 +02:00
/// For most use cases, you probably want to use <see cref="PushMarkup"/> and similar instead to modify this,
/// since it handles newlines and such correctly.
2021-05-26 10:19:14 +02:00
/// </summary>
2021-09-15 16:58:15 +02:00
/// <seealso cref="PushMessage"/>
/// <seealso cref="PushMarkup"/>
/// <seealso cref="PushText"/>
2021-12-20 12:42:42 +01:00
public FormattedMessage Message { get ; }
2021-05-26 10:19:14 +02:00
/// <summary>
/// The entity performing the examining.
/// </summary>
2021-12-04 12:47:09 +01:00
public EntityUid Examiner { get ; }
2021-05-26 10:19:14 +02:00
2021-05-26 11:11:14 +02:00
/// <summary>
/// Entity being examined, for broadcast event purposes.
/// </summary>
2021-12-04 12:47:09 +01:00
public EntityUid Examined { get ; }
2021-05-26 11:11:14 +02:00
2021-05-26 10:19:14 +02:00
/// <summary>
/// Whether the examiner is in range of the entity to get some extra details.
/// </summary>
public bool IsInDetailsRange { get ; }
2021-09-15 16:58:15 +02:00
private bool _doNewLine ;
2021-12-20 12:42:42 +01:00
public ExaminedEvent ( FormattedMessage message , EntityUid examined , EntityUid examiner , bool isInDetailsRange , bool doNewLine )
2021-05-26 10:19:14 +02:00
{
Message = message ;
2021-05-26 11:11:14 +02:00
Examined = examined ;
2021-05-26 10:19:14 +02:00
Examiner = examiner ;
2021-05-26 10:32:40 +02:00
IsInDetailsRange = isInDetailsRange ;
2021-09-15 16:58:15 +02:00
_doNewLine = doNewLine ;
}
/// <summary>
/// Push another message into this examine result, on its own line.
/// </summary>
/// <seealso cref="PushMarkup"/>
/// <seealso cref="PushText"/>
public void PushMessage ( FormattedMessage message )
{
2021-12-20 12:42:42 +01:00
if ( message . Tags . Count = = 0 )
return ;
2021-09-15 16:58:15 +02:00
if ( _doNewLine )
Message . AddText ( "\n" ) ;
Message . AddMessage ( message ) ;
_doNewLine = true ;
}
/// <summary>
/// Push another message parsed from markup into this examine result, on its own line.
/// </summary>
/// <seealso cref="PushText"/>
/// <seealso cref="PushMessage"/>
public void PushMarkup ( string markup )
{
2021-12-20 12:42:42 +01:00
PushMessage ( FormattedMessage . FromMarkup ( markup ) ) ;
2021-09-15 16:58:15 +02:00
}
/// <summary>
/// Push another message containing raw text into this examine result, on its own line.
/// </summary>
/// <seealso cref="PushMarkup"/>
/// <seealso cref="PushMessage"/>
2021-12-20 12:42:42 +01:00
public void PushText ( string text )
{
var msg = new FormattedMessage ( ) ;
msg . AddText ( text ) ;
PushMessage ( msg ) ;
}
2021-05-26 10:19:14 +02:00
}
2019-07-19 10:45:04 +02:00
}