Remove ignore-inside-blocker (#6692)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2022-02-17 15:40:03 +13:00
committed by GitHub
parent 00c3a181d3
commit 4a00d01ced
60 changed files with 297 additions and 872 deletions

View File

@@ -1,5 +1,6 @@
using System;
using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -34,7 +35,7 @@ namespace Content.Shared.Buckle.Components
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(eventArgs.Dragged, out SharedBuckleComponent? buckleComponent)) return false;
bool Ignored(EntityUid entity) => entity == eventArgs.User || entity == eventArgs.Dragged || entity == eventArgs.Target;
return eventArgs.Target.InRangeUnobstructed(eventArgs.Dragged, buckleComponent.Range, predicate: Ignored);
return EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(eventArgs.Target, eventArgs.Dragged, buckleComponent.Range, predicate: Ignored);
}
public abstract bool DragDropOn(DragDropEvent eventArgs);

View File

@@ -1,13 +1,29 @@
using System;
using Robust.Shared.GameObjects;
using System.Linq;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using static Content.Shared.Interaction.SharedInteractionSystem;
namespace Content.Shared.Construction
{
public abstract class SharedConstructionSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
/// <summary>
/// Get predicate for construction obstruction checks.
/// </summary>
public Ignored? GetPredicate(bool canBuildInImpassable, MapCoordinates coords)
{
if (!canBuildInImpassable)
return null;
if (!_mapManager.TryFindGridAt(coords, out var grid))
return null;
var ignored = grid.GetAnchoredEntities(coords).ToHashSet();
return e => ignored.Contains(e);
}
/// <summary>
/// Sent client -> server to to tell the server that we started building
/// a structure-construction.

View File

@@ -65,7 +65,7 @@ namespace Content.Shared.Examine
if (EntityManager.TryGetComponent(examiner, out MobStateComponent mobState) && mobState.IsIncapacitated())
return false;
if (!_interactionSystem.InRangeUnobstructed(examiner, entity, ExamineDetailsRange, ignoreInsideBlocker: true))
if (!_interactionSystem.InRangeUnobstructed(examiner, entity, ExamineDetailsRange))
return false;
// Is the target hidden in a opaque locker or something? Currently this check allows players to examine

View File

@@ -387,7 +387,8 @@ namespace Content.Shared.Hands.Components
target = new MapCoordinates(origin.Position + dropVector, target.MapId);
}
var dropLength = EntitySystem.Get<SharedInteractionSystem>().UnobstructedDistance(origin, target, ignoredEnt: Owner);
var dropLength = EntitySystem.Get<SharedInteractionSystem>().UnobstructedDistance(origin, target, predicate: e => e == Owner);
if (dropLength < requestedDropDistance)
return origin.Position + dropVector.Normalized * dropLength;

View File

@@ -1,431 +0,0 @@
using Content.Shared.DragDrop;
using Content.Shared.Physics;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.Shared.Interaction.SharedInteractionSystem;
namespace Content.Shared.Interaction.Helpers
{
// TODO: Kill these with fire.
public static class SharedUnobstructedExtensions
{
private static SharedInteractionSystem SharedInteractionSystem => EntitySystem.Get<SharedInteractionSystem>();
#region Entities
public static bool InRangeUnobstructed(
this EntityUid origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false,
IEntityManager? entityManager = null)
{
return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this EntityUid origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this EntityUid origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var otherEntity = other.Owner;
return SharedInteractionSystem.InRangeUnobstructed(origin, otherEntity, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this EntityUid origin,
EntityCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this EntityUid origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
#endregion
#region Components
public static bool InRangeUnobstructed(
this IComponent origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IComponent origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IComponent origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
var otherEntity = other.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, otherEntity, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IComponent origin,
EntityCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IComponent origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
#endregion
#region Containers
public static bool InRangeUnobstructed(
this IContainer origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this IContainer origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IContainer origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
var otherEntity = other.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, otherEntity, range, collisionMask,
predicate, ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IContainer origin,
EntityCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this IContainer origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.Owner;
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
#endregion
#region EntityCoordinates
public static bool InRangeUnobstructed(
this EntityCoordinates origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var originPosition = origin.ToMap(IoCManager.Resolve<IEntityManager>());
var otherPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(other).MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this EntityCoordinates origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var originPosition = origin.ToMap(IoCManager.Resolve<IEntityManager>());
var otherPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(other.Owner).MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this EntityCoordinates origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var originPosition = origin.ToMap(IoCManager.Resolve<IEntityManager>());
var otherPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(other.Owner).MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this EntityCoordinates origin,
EntityCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
IEntityManager? entityManager = null)
{
entityManager ??= IoCManager.Resolve<IEntityManager>();
var originPosition = origin.ToMap(entityManager);
var otherPosition = other.ToMap(entityManager);
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this EntityCoordinates origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
IEntityManager? entityManager = null)
{
entityManager ??= IoCManager.Resolve<IEntityManager>();
var originPosition = origin.ToMap(entityManager);
return SharedInteractionSystem.InRangeUnobstructed(originPosition, other, range, collisionMask, predicate,
ignoreInsideBlocker);
}
#endregion
#region MapCoordinates
public static bool InRangeUnobstructed(
this MapCoordinates origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var otherPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(other).MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate,
ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this MapCoordinates origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var otherPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(other.Owner).MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate,
ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this MapCoordinates origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
var otherPosition = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(other.Owner).MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate,
ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this MapCoordinates origin,
EntityCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
IEntityManager? entityManager = null)
{
entityManager ??= IoCManager.Resolve<IEntityManager>();
var otherPosition = other.ToMap(entityManager);
return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate,
ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this MapCoordinates origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
{
return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate,
ignoreInsideBlocker);
}
#endregion
#region EventArgs
public static bool InRangeUnobstructed(
this ITargetedInteractEventArgs args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return SharedInteractionSystem.InRangeUnobstructed(args.User, args.Target, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
#endregion
#region EntityEventArgs
public static bool InRangeUnobstructed(
this DragDropEvent args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var user = args.User;
var dropped = args.Dragged;
var target = args.Target;
if (!SharedInteractionSystem.InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker, popup))
return false;
if (!SharedInteractionSystem.InRangeUnobstructed(user, dropped, range, collisionMask, predicate, ignoreInsideBlocker, popup))
return false;
return true;
}
#endregion
}
}

View File

@@ -8,7 +8,6 @@ using Content.Shared.CombatMode;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Throwing;
@@ -16,16 +15,13 @@ using Content.Shared.Timing;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Content.Shared.Wall;
using Content.Shared.Item;
using Robust.Shared.Player;
#pragma warning disable 618
@@ -38,6 +34,7 @@ namespace Content.Shared.Interaction
[UsedImplicitly]
public abstract class SharedInteractionSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
@@ -87,7 +84,7 @@ namespace Content.Shared.Interaction
return;
}
if (!InRangeUnobstructed(user, ev.Target, ignoreInsideBlocker: true))
if (!InRangeUnobstructed(user, ev.Target))
{
ev.Cancel();
return;
@@ -190,12 +187,9 @@ namespace Content.Shared.Interaction
if (!TryComp(user, out SharedHandsComponent? hands) || !hands.TryGetActiveHand(out hand))
return;
// ^ In future, things like looking at a UI & opening doors (i.e., Activate interactions) shouldn't neccesarily require hands.
// But that would first involve some work with BUIs & making sure other activate-interactions check hands if they are required.
// Check range
// TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
var inRangeUnobstructed = !checkAccess || user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);
var inRangeUnobstructed = target == null
? !checkAccess || InRangeUnobstructed(user, coordinates)
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
// empty-hand interactions
if (hand.HeldEntity == null)
@@ -289,30 +283,6 @@ namespace Content.Shared.Interaction
return (rayResults[0].HitPos - origin.Position).Length;
}
/// <summary>
/// Traces a ray from coords to otherCoords and returns the length
/// of the vector between coords and the ray's first hit.
/// </summary>
/// <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>
/// <returns>Length of resulting ray.</returns>
public float UnobstructedDistance(
MapCoordinates origin,
MapCoordinates other,
int collisionMask = (int) CollisionGroup.Impassable,
EntityUid? ignoredEnt = null)
{
var predicate = ignoredEnt == null
? null
: (Ignored) (e => e == ignoredEnt);
return UnobstructedDistance(origin, other, collisionMask, predicate);
}
/// <summary>
/// Checks that these coordinates are within a certain distance without any
/// entity that matches the collision mask obstructing them.
@@ -330,14 +300,6 @@ namespace Content.Shared.Interaction
/// 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>
@@ -346,8 +308,7 @@ namespace Content.Shared.Interaction
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false)
Ignored? predicate = null)
{
// Have to be on same map regardless.
if (other.MapId != origin.MapId) return false;
@@ -371,29 +332,7 @@ namespace Content.Shared.Interaction
var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask);
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
if (rayResults.Count == 0) return true;
// TODO: Wot? This should just be in the predicate.
if (!ignoreInsideBlocker) return false;
foreach (var result in rayResults)
{
if (!TryComp(result.HitEntity, out IPhysBody? p))
{
continue;
}
var bBox = p.GetWorldAABB();
if (bBox.Contains(other.Position))
{
continue;
}
return false;
}
return true;
return rayResults.Count == 0;
}
/// <summary>
@@ -401,6 +340,8 @@ namespace Content.Shared.Interaction
/// 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 two entities.
/// This function will also check whether the other entity is a wall-mounted entity. If it is, it will
/// automatically ignore some obstructions.
/// </summary>
/// <param name="origin">The first entity to use.</param>
/// <param name="other">Other entity to use.</param>
@@ -412,14 +353,6 @@ namespace Content.Shared.Interaction
/// 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.
@@ -433,55 +366,92 @@ namespace Content.Shared.Interaction
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
predicate ??= e => e == origin || e == other;
return InRangeUnobstructed(origin, Transform(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
var originPosition = Transform(origin).MapPosition;
var transform = Transform(other);
var (position, rotation) = transform.GetWorldPositionRotation();
var mapPos = new MapCoordinates(position, transform.MapID);
var wallPredicate = AddAnchoredPredicate(other, mapPos, rotation, originPosition);
Ignored combinedPredicate = e =>
{
return e == origin
|| e == other
|| (predicate?.Invoke(e) ?? false)
|| (wallPredicate?.Invoke(e) ?? false);
};
var inRange = InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate, popup);
if (!inRange && popup)
{
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
_popupSystem.PopupEntity(message, origin, Filter.Entities(origin));
}
return inRange;
}
public bool InRangeUnobstructed(
MapCoordinates origin,
EntityUid target,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null)
{
var transform = Transform(target);
var (position, rotation) = transform.GetWorldPositionRotation();
var mapPos = new MapCoordinates(position, transform.MapID);
var wallPredicate = AddAnchoredPredicate(target, mapPos, rotation, origin);
Ignored combinedPredicate = e =>
{
return e == target
|| (predicate?.Invoke(e) ?? false)
|| (wallPredicate?.Invoke(e) ?? false);
};
return InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate);
}
/// <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.
/// If the target entity is either an item or a wall-mounted object, this will add a predicate to ignore any
/// anchored entities on that tile.
/// </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(
EntityUid origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
public Ignored? AddAnchoredPredicate(
EntityUid target,
MapCoordinates targetPosition,
Angle targetRotation,
MapCoordinates origin)
{
return InRangeUnobstructed(origin, other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
if (!_mapManager.TryFindGridAt(targetPosition, out var grid))
return null;
if (HasComp<SharedItemComponent>(target))
{
// Ignore anchored entities on that tile.
var colliding = new HashSet<EntityUid>(grid.GetAnchoredEntities(targetPosition));
return e => colliding.Contains(e);
}
if (!TryComp(target, out WallMountComponent? wallMount))
return null;
// wall-mount exemptions may be restricted to a specific angle range.
if (wallMount.Arc < 360)
{
var angle = Angle.FromWorldVec(origin.Position - targetPosition.Position);
var angleDelta = (wallMount.Direction + targetRotation - angle).Reduced().FlipPositive();
var inArc = angleDelta < wallMount.Arc / 2 || Math.Tau - angleDelta < wallMount.Arc / 2;
if (!inArc)
return null;
}
// Ignore anchored entities on that tile.
var ignored = new HashSet<EntityUid>(grid.GetAnchoredEntities(targetPosition));
return e => ignored.Contains(e);
}
/// <summary>
@@ -501,14 +471,6 @@ namespace Content.Shared.Interaction
/// 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.
@@ -522,10 +484,9 @@ namespace Content.Shared.Interaction
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return InRangeUnobstructed(origin, other.ToMap(EntityManager), range, collisionMask, predicate, ignoreInsideBlocker, popup);
return InRangeUnobstructed(origin, other.ToMap(EntityManager), range, collisionMask, predicate, popup);
}
/// <summary>
@@ -545,14 +506,6 @@ namespace Content.Shared.Interaction
/// 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.
@@ -566,18 +519,16 @@ namespace Content.Shared.Interaction
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored? predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
Ignored combinedPredicatre = e => e == origin || (predicate?.Invoke(e) ?? false);
var originPosition = Transform(origin).MapPosition;
predicate ??= e => e == origin;
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, combinedPredicatre);
if (!inRange && popup)
{
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
origin.PopupMessage(message);
_popupSystem.PopupEntity(message, origin, Filter.Entities(origin));
}
return inRange;
@@ -690,9 +641,7 @@ namespace Content.Shared.Interaction
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
return false;
// all activates should only fire when in range / unobstructed
if (checkAccess && !InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
if (checkAccess && !InRangeUnobstructed(user, used, popup: true))
return false;
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.

View File

@@ -163,7 +163,7 @@ public abstract partial class InventorySystem
public bool CanAccess(EntityUid actor, EntityUid target, EntityUid itemUid)
{
// Can the actor reach the target?
if (actor != target && !( actor.InRangeUnobstructed(target) && _containerSystem.IsInSameOrParentContainer(actor, target)))
if (actor != target && !(_interactionSystem.InRangeUnobstructed(actor, target) && _containerSystem.IsInSameOrParentContainer(actor, target)))
return false;
// Can the actor reach the item?

View File

@@ -101,9 +101,6 @@ namespace Content.Shared.Item
{
var user = eventArgs.User;
if (!user.InRangeUnobstructed(Owner, ignoreInsideBlocker: true))
return false;
if (!_entMan.TryGetComponent(user, out SharedHandsComponent hands))
return false;

View File

@@ -1,12 +1,7 @@
using System;
using Content.Shared.ActionBlocker;
using Content.Shared.Ghost;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction.Helpers;
using Content.Shared.MobState.Components;
using Content.Shared.Interaction;
using Content.Shared.Stunnable;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
@@ -15,6 +10,7 @@ namespace Content.Shared.Tabletop
public abstract class SharedTabletopSystem : EntitySystem
{
[Dependency] protected readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Serializable, NetSerializable]
public sealed class TabletopDraggableComponentState : ComponentState
@@ -49,7 +45,7 @@ namespace Content.Shared.Tabletop
return false;
}
return playerEntity.InRangeUnobstructed(table.Value) && _actionBlockerSystem.CanInteract(playerEntity, table);
return _interactionSystem.InRangeUnobstructed(playerEntity, table.Value) && _actionBlockerSystem.CanInteract(playerEntity, table);
}
protected bool StunnedOrNoHands(EntityUid playerEntity)

View File

@@ -65,7 +65,7 @@ namespace Content.Shared.Verbs
bool canAccess = false;
if (force || target == user)
canAccess = true;
else if (EntityManager.EntityExists(target) && _interactionSystem.InRangeUnobstructed(user, target, ignoreInsideBlocker: true))
else if (EntityManager.EntityExists(target) && _interactionSystem.InRangeUnobstructed(user, target))
{
if (ContainerSystem.IsInSameOrParentContainer(user, target))
canAccess = true;

View File

@@ -0,0 +1,26 @@
namespace Content.Shared.Wall;
/// <summary>
/// This component enables an entity to ignore some obstructions for interaction checks.
/// </summary>
/// <remarks>
/// This will only exempt anchored entities that intersect the wall-mount. Additionally, this exemption will apply
/// in a limited arc, providing basic functionality for directional wall mounts.
/// </remarks>
[RegisterComponent]
public sealed class WallMountComponent : Component
{
/// <summary>
/// Range of angles in which the exemption applies. Bigger is more permissive.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("arc")]
public float Arc = MathF.PI;
/// <summary>
/// The direction in which the exemption arc is facing, relative to the entity's rotation. Defaults to south.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("direction")]
public Angle Direction = Angle.Zero;
}