Remove ignore-inside-blocker (#6692)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user