Use nearest edge for interaction range (#11660)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -22,6 +22,8 @@ namespace Content.IntegrationTests.Tests.Interaction
|
|||||||
|
|
||||||
private const float InteractionRangeDivided15Times3 = InteractionRangeDivided15 * 3;
|
private const float InteractionRangeDivided15Times3 = InteractionRangeDivided15 * 3;
|
||||||
|
|
||||||
|
private const float HumanRadius = 0.35f;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task EntityEntityTest()
|
public async Task EntityEntityTest()
|
||||||
{
|
{
|
||||||
@@ -74,7 +76,7 @@ namespace Content.IntegrationTests.Tests.Interaction
|
|||||||
Assert.True(interactionSys.InRangeUnobstructed(mapCoordinates, origin));
|
Assert.True(interactionSys.InRangeUnobstructed(mapCoordinates, origin));
|
||||||
|
|
||||||
// Move them out of range
|
// Move them out of range
|
||||||
sEntities.GetComponent<TransformComponent>(origin).LocalPosition += _interactionRangeDivided15X;
|
sEntities.GetComponent<TransformComponent>(origin).LocalPosition += new Vector2(InteractionRangeDivided15 + HumanRadius * 2f, 0f);
|
||||||
|
|
||||||
// Entity <-> Entity
|
// Entity <-> Entity
|
||||||
Assert.False(interactionSys.InRangeUnobstructed(origin, other));
|
Assert.False(interactionSys.InRangeUnobstructed(origin, other));
|
||||||
|
|||||||
@@ -122,10 +122,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// InRangeUnobstructed is insufficient rn as it checks the centre of the body rather than the nearest edge.
|
if (!_interaction.InRangeUnobstructed(user, ev.Target.Value, component.Range))
|
||||||
// TODO: Look at fixing it
|
|
||||||
// This is mainly to keep consistency between the wide attack raycast and the click attack raycast.
|
|
||||||
if (!_interaction.InRangeUnobstructed(user, ev.Target.Value, component.Range + 0.35f))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var damage = component.Damage * GetModifier(component, true);
|
var damage = component.Damage * GetModifier(component, true);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Content.Shared.Interaction
|
|||||||
private const CollisionGroup InRangeUnobstructedMask
|
private const CollisionGroup InRangeUnobstructedMask
|
||||||
= CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
|
= CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
|
||||||
|
|
||||||
public const float InteractionRange = 2f;
|
public const float InteractionRange = 1.5f;
|
||||||
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
|
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
|
||||||
|
|
||||||
public const float MaxRaycastRange = 100f;
|
public const float MaxRaycastRange = 100f;
|
||||||
@@ -440,10 +440,63 @@ namespace Content.Shared.Interaction
|
|||||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||||
Ignored? predicate = null,
|
Ignored? predicate = null,
|
||||||
bool popup = false)
|
bool popup = false)
|
||||||
{;
|
{
|
||||||
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
||||||
|
var inRange = true;
|
||||||
|
MapCoordinates originPos = default;
|
||||||
|
MapCoordinates targetPos = default;
|
||||||
|
Angle targetRot = default;
|
||||||
|
|
||||||
var inRange = InRangeUnobstructed(Transform(origin).MapPosition, other, range, collisionMask, combinedPredicate);
|
// Alternatively we could check centre distances first though
|
||||||
|
// that means we wouldn't be able to easily check overlap interactions.
|
||||||
|
if (range > 0f &&
|
||||||
|
TryComp<FixturesComponent>(origin, out var fixtureA) &&
|
||||||
|
TryComp<FixturesComponent>(other, out var fixtureB) &&
|
||||||
|
TryComp<TransformComponent>(origin, out var xformA) &&
|
||||||
|
TryComp<TransformComponent>(other, out var xformB))
|
||||||
|
{
|
||||||
|
// Different map or the likes.
|
||||||
|
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
|
||||||
|
out var pointA, out var pointB, out var distance,
|
||||||
|
xformA, xformB, fixtureA, fixtureB))
|
||||||
|
{
|
||||||
|
inRange = false;
|
||||||
|
}
|
||||||
|
// Overlap, early out and no raycast.
|
||||||
|
else if (distance.Equals(0f))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Out of range so don't raycast.
|
||||||
|
else if (distance > range)
|
||||||
|
{
|
||||||
|
inRange = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
|
||||||
|
originPos = xformA.MapPosition;
|
||||||
|
(var targetWorld, targetRot) = xformB.GetWorldPositionRotation();
|
||||||
|
targetPos = new MapCoordinates(targetWorld, xformB.MapID);
|
||||||
|
|
||||||
|
range = (originPos.Position - targetWorld).Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
originPos = Transform(origin).MapPosition;
|
||||||
|
|
||||||
|
xformB = Transform(other);
|
||||||
|
(var targetWorld, targetRot) = xformB.GetWorldPositionRotation();
|
||||||
|
targetPos = new MapCoordinates(targetWorld, xformB.MapID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a raycast to check if relevant
|
||||||
|
if (inRange)
|
||||||
|
{
|
||||||
|
var rayPredicate = GetPredicate(originPos, other, targetPos, targetRot, collisionMask, combinedPredicate);
|
||||||
|
inRange = InRangeUnobstructed(originPos, targetPos, range, collisionMask, rayPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
if (!inRange && popup && _gameTiming.IsFirstTimePredicted)
|
if (!inRange && popup && _gameTiming.IsFirstTimePredicted)
|
||||||
{
|
{
|
||||||
@@ -464,7 +517,25 @@ namespace Content.Shared.Interaction
|
|||||||
var transform = Transform(target);
|
var transform = Transform(target);
|
||||||
var (position, rotation) = transform.GetWorldPositionRotation();
|
var (position, rotation) = transform.GetWorldPositionRotation();
|
||||||
var mapPos = new MapCoordinates(position, transform.MapID);
|
var mapPos = new MapCoordinates(position, transform.MapID);
|
||||||
|
var combinedPredicate = GetPredicate(origin, target, mapPos, rotation, collisionMask, predicate);
|
||||||
|
|
||||||
|
return InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entities to ignore for an unobstructed raycast
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// if the target entity is a wallmount we ignore all other entities on the tile.
|
||||||
|
/// </example>
|
||||||
|
private Ignored GetPredicate(
|
||||||
|
MapCoordinates origin,
|
||||||
|
EntityUid target,
|
||||||
|
MapCoordinates targetCoords,
|
||||||
|
Angle targetRotation,
|
||||||
|
CollisionGroup collisionMask,
|
||||||
|
Ignored? predicate = null)
|
||||||
|
{
|
||||||
HashSet<EntityUid> ignored = new();
|
HashSet<EntityUid> ignored = new();
|
||||||
|
|
||||||
bool ignoreAnchored = false;
|
bool ignoreAnchored = false;
|
||||||
@@ -483,13 +554,13 @@ namespace Content.Shared.Interaction
|
|||||||
ignoreAnchored = true;
|
ignoreAnchored = true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var angle = Angle.FromWorldVec(origin.Position - position);
|
var angle = Angle.FromWorldVec(origin.Position - targetCoords.Position);
|
||||||
var angleDelta = (wallMount.Direction + rotation - angle).Reduced().FlipPositive();
|
var angleDelta = (wallMount.Direction + targetRotation - angle).Reduced().FlipPositive();
|
||||||
ignoreAnchored = angleDelta < wallMount.Arc / 2 || Math.Tau - angleDelta < wallMount.Arc / 2;
|
ignoreAnchored = angleDelta < wallMount.Arc / 2 || Math.Tau - angleDelta < wallMount.Arc / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ignoreAnchored && _mapManager.TryFindGridAt(mapPos, out var grid))
|
if (ignoreAnchored && _mapManager.TryFindGridAt(targetCoords, out var grid))
|
||||||
ignored.UnionWith(grid.GetAnchoredEntities(mapPos));
|
ignored.UnionWith(grid.GetAnchoredEntities(targetCoords));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ignored combinedPredicate = e =>
|
Ignored combinedPredicate = e =>
|
||||||
@@ -499,7 +570,7 @@ namespace Content.Shared.Interaction
|
|||||||
|| ignored.Contains(e);
|
|| ignored.Contains(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
return InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate);
|
return combinedPredicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -569,9 +640,9 @@ namespace Content.Shared.Interaction
|
|||||||
Ignored? predicate = null,
|
Ignored? predicate = null,
|
||||||
bool popup = false)
|
bool popup = false)
|
||||||
{
|
{
|
||||||
Ignored combinedPredicatre = e => e == origin || (predicate?.Invoke(e) ?? false);
|
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
||||||
var originPosition = Transform(origin).MapPosition;
|
var originPosition = Transform(origin).MapPosition;
|
||||||
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, combinedPredicatre);
|
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, combinedPredicate);
|
||||||
|
|
||||||
if (!inRange && popup && _gameTiming.IsFirstTimePredicted)
|
if (!inRange && popup && _gameTiming.IsFirstTimePredicted)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user