Refactor InRangeUnobstructed and add extension methods (#1925)

* Sort out InRangeUnobstructed and add extension methods

* Rename client RangeChecks to RangeExtensions

* Add container extension methods and test

* Add missing component methods

Component to container
Grid coordinates to container
Map coordinates to container
Local player to container

* Actually use the field

* Merge fixes

* Add popup argument to local player extension methods

* Reduce code repetition for client range extensions
This commit is contained in:
DrSmugleaf
2020-08-30 11:37:06 +02:00
committed by GitHub
parent 9ec3ddf368
commit 9d6c394f6b
39 changed files with 1287 additions and 359 deletions

View File

@@ -142,8 +142,7 @@ namespace Content.Client.GameObjects.EntitySystems
if (_entityManager.TryGetEntity(args.EntityUid, out var entity))
{
// check if the entity is reachable
if (_interactionSystem.InRangeUnobstructed(dragger.Transform.MapPosition,
entity.Transform.MapPosition, ignoredEnt: dragger) == false)
if (!_interactionSystem.InRangeUnobstructed(dragger, entity))
{
return false;
}
@@ -193,8 +192,8 @@ namespace Content.Client.GameObjects.EntitySystems
// tell the server we are dropping if we are over a valid drop target in range.
// We don't use args.EntityUid here because drag interactions generally should
// work even if there's something "on top" of the drop target
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
args.Coordinates.ToMap(_mapManager), ignoredEnt: _dragger, ignoreInsideBlocker: true) == false)
if (!_interactionSystem.InRangeUnobstructed(_dragger,
args.Coordinates, ignoreInsideBlocker: true))
{
CancelDrag(false, null);
return false;
@@ -288,8 +287,7 @@ namespace Content.Client.GameObjects.EntitySystems
if (anyValidDraggable)
{
// highlight depending on whether its in or out of range
var inRange = _interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
pvsEntity.Transform.MapPosition, ignoredEnt: _dragger);
var inRange = _interactionSystem.InRangeUnobstructed(_dragger, pvsEntity);
inRangeSprite.PostShader = inRange ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
highlightedSprites.Add(inRangeSprite);
@@ -377,8 +375,7 @@ namespace Content.Client.GameObjects.EntitySystems
return;
}
// still in range of the thing we are dragging?
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
_draggedEntity.Transform.MapPosition, ignoredEnt: _dragger) == false)
if (!_interactionSystem.InRangeUnobstructed(_dragger, _draggedEntity))
{
CancelDrag(false, null);
return;

View File

@@ -1,6 +1,7 @@
using Content.Client.GameObjects.Components.Instruments;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.GameObjects.EntitySystems;
using Content.Client.Utility;
using Content.Shared.Utility;
using Robust.Client.Audio.Midi;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.UserInterface;
@@ -9,7 +10,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
@@ -197,9 +197,7 @@ namespace Content.Client.Instruments
|| conMan.Owner != localPlayer.ControlledEntity))) return;
// We check that we're in range unobstructed just in case.
if(!EntitySystem.Get<SharedInteractionSystem>()
.InRangeUnobstructed(localPlayer.ControlledEntity.Transform.MapPosition,
instrumentEnt.Transform.MapPosition, ignoredEnt:instrumentEnt)) return;
if (!localPlayer.InRangeUnobstructed(instrumentEnt)) return;
if (!_midiManager.IsMidiFile(filename))
{

View File

@@ -2,7 +2,8 @@
using System.Collections.Immutable;
using System.Linq;
using Content.Client.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems;
using Content.Client.Utility;
using Content.Shared.Utility;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Interfaces.GameObjects;
using Robust.Client.Interfaces.Graphics.ClientEye;
@@ -67,13 +68,7 @@ namespace Content.Client.State
var inRange = false;
if (localPlayer.ControlledEntity != null && entityToClick != null)
{
var playerPos = localPlayer.ControlledEntity.Transform.MapPosition;
var entityPos = entityToClick.Transform.MapPosition;
inRange = EntitySystemManager.GetEntitySystem<SharedInteractionSystem>()
.InRangeUnobstructed(playerPos, entityPos,
predicate: entity =>
entity == localPlayer.ControlledEntity || entity == entityToClick,
ignoreInsideBlocker: true);
inRange = localPlayer.InRangeUnobstructed(entityToClick, ignoreInsideBlocker: true);
}
InteractionOutlineComponent outline;

View File

@@ -0,0 +1,93 @@
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem;
namespace Content.Client.Utility
{
public static class RangeExtensions
{
private static SharedInteractionSystem SharedInteractionSystem => EntitySystem.Get<SharedInteractionSystem>();
public static bool InRangeUnobstructed(
this LocalPlayer origin,
IEntity other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var otherPosition = other.Transform.MapPosition;
return origin.InRangeUnobstructed(otherPosition, range, collisionMask, predicate, ignoreInsideBlocker,
popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return origin.InRangeUnobstructed(other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return origin.InRangeUnobstructed(other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
GridCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var otherPosition = other.ToMap(mapManager);
return origin.InRangeUnobstructed(otherPosition, range, collisionMask, predicate, ignoreInsideBlocker,
popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.ControlledEntity;
if (originEntity == null)
{
// TODO: Take into account the player's camera position?
return false;
}
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
}
}

View File

@@ -0,0 +1,159 @@
using System.Threading.Tasks;
using Content.Client.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Utility;
using NUnit.Framework;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.Interaction
{
[TestFixture]
[TestOf(typeof(SharedInteractionSystem))]
[TestOf(typeof(SharedRangeExtensions))]
[TestOf(typeof(RangeExtensions))]
public class InRangeUnobstructed : ContentIntegrationTest
{
private const string HumanId = "BaseHumanMob_Content";
private const float InteractionRange = SharedInteractionSystem.InteractionRange;
private const float InteractionRangeDivided15 = InteractionRange / 1.5f;
private readonly (float, float) _interactionRangeDivided15X = (InteractionRangeDivided15, 0f);
private const float InteractionRangeDivided15Times3 = InteractionRangeDivided15 * 3;
[Test]
public async Task EntityEntityTest()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var entityManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
IEntity origin = null;
IEntity other = null;
IContainer container = null;
IComponent component = null;
GridCoordinates gridCoordinates = default;
MapCoordinates mapCoordinates = default;
server.Assert(() =>
{
mapManager.CreateNewMapEntity(MapId.Nullspace);
var coordinates = MapCoordinates.Nullspace;
origin = entityManager.SpawnEntity(HumanId, coordinates);
other = entityManager.SpawnEntity(HumanId, coordinates);
container = ContainerManagerComponent.Ensure<Container>("InRangeUnobstructedTestOtherContainer", other);
component = other.Transform;
gridCoordinates = other.Transform.GridPosition;
mapCoordinates = other.Transform.MapPosition;
});
await server.WaitIdleAsync();
server.Assert(() =>
{
// Entity <-> Entity
Assert.True(origin.InRangeUnobstructed(other));
Assert.True(other.InRangeUnobstructed(origin));
// Entity <-> Component
Assert.True(origin.InRangeUnobstructed(component));
Assert.True(component.InRangeUnobstructed(origin));
// Entity <-> Container
Assert.True(origin.InRangeUnobstructed(container));
Assert.True(container.InRangeUnobstructed(origin));
// Entity <-> GridCoordinates
Assert.True(origin.InRangeUnobstructed(gridCoordinates));
Assert.True(gridCoordinates.InRangeUnobstructed(origin));
// Entity <-> MapCoordinates
Assert.True(origin.InRangeUnobstructed(mapCoordinates));
Assert.True(mapCoordinates.InRangeUnobstructed(origin));
// Move them slightly apart
origin.Transform.LocalPosition += _interactionRangeDivided15X;
// Entity <-> Entity
Assert.True(origin.InRangeUnobstructed(other));
Assert.True(other.InRangeUnobstructed(origin));
// Entity <-> Component
Assert.True(origin.InRangeUnobstructed(component));
Assert.True(component.InRangeUnobstructed(origin));
// Entity <-> Container
Assert.True(origin.InRangeUnobstructed(container));
Assert.True(container.InRangeUnobstructed(origin));
// Entity <-> GridCoordinates
Assert.True(origin.InRangeUnobstructed(gridCoordinates));
Assert.True(gridCoordinates.InRangeUnobstructed(origin));
// Entity <-> MapCoordinates
Assert.True(origin.InRangeUnobstructed(mapCoordinates));
Assert.True(mapCoordinates.InRangeUnobstructed(origin));
// Move them out of range
origin.Transform.LocalPosition += _interactionRangeDivided15X;
// Entity <-> Entity
Assert.False(origin.InRangeUnobstructed(other));
Assert.False(other.InRangeUnobstructed(origin));
// Entity <-> Component
Assert.False(origin.InRangeUnobstructed(component));
Assert.False(component.InRangeUnobstructed(origin));
// Entity <-> Container
Assert.False(origin.InRangeUnobstructed(container));
Assert.False(container.InRangeUnobstructed(origin));
// Entity <-> GridCoordinates
Assert.False(origin.InRangeUnobstructed(gridCoordinates));
Assert.False(gridCoordinates.InRangeUnobstructed(origin));
// Entity <-> MapCoordinates
Assert.False(origin.InRangeUnobstructed(mapCoordinates));
Assert.False(mapCoordinates.InRangeUnobstructed(origin));
// Checks with increased range
// Entity <-> Entity
Assert.True(origin.InRangeUnobstructed(other, InteractionRangeDivided15Times3));
Assert.True(other.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> Component
Assert.True(origin.InRangeUnobstructed(component, InteractionRangeDivided15Times3));
Assert.True(component.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> Container
Assert.True(origin.InRangeUnobstructed(container, InteractionRangeDivided15Times3));
Assert.True(container.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> GridCoordinates
Assert.True(origin.InRangeUnobstructed(gridCoordinates, InteractionRangeDivided15Times3));
Assert.True(gridCoordinates.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> MapCoordinates
Assert.True(origin.InRangeUnobstructed(mapCoordinates, InteractionRangeDivided15Times3));
Assert.True(mapCoordinates.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
});
await server.WaitIdleAsync();
}
}
}

View File

@@ -3,6 +3,7 @@ using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Utility;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Operators.Inventory
@@ -50,7 +51,7 @@ namespace Content.Server.AI.Operators.Inventory
public override Outcome Execute(float frameTime)
{
if (!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
if (!_owner.InRangeUnobstructed(_target, popup: true))
{
return Outcome.Failed;
}

View File

@@ -1,6 +1,7 @@
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Utility;
using Content.Shared.Utility;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
@@ -28,7 +29,7 @@ namespace Content.Server.AI.Operators.Inventory
return Outcome.Failed;
}
if (!InteractionChecks.InRangeUnobstructed(_owner, _useTarget.Transform.MapPosition))
if (!_owner.InRangeUnobstructed(_useTarget, popup: true))
{
return Outcome.Failed;
}

View File

@@ -4,6 +4,7 @@ using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Utility;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects;
@@ -30,7 +31,7 @@ namespace Content.Server.AI.Operators.Inventory
return Outcome.Success;
}
if (!InteractionChecks.InRangeUnobstructed(_owner, container.Owner.Transform.MapPosition, ignoredEnt: container.Owner))
if (!_owner.InRangeUnobstructed(container, popup: true))
{
return Outcome.Failed;
}

View File

@@ -3,6 +3,7 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Utility;
using Content.Shared.Utility;
using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
@@ -26,7 +27,7 @@ namespace Content.Server.AI.Operators.Inventory
if (_target.Deleted ||
!_target.HasComponent<ItemComponent>() ||
ContainerHelpers.IsInContainer(_target) ||
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
!_owner.InRangeUnobstructed(_target, popup: true))
{
return Outcome.Failed;
}

View File

@@ -1,5 +1,4 @@

using Robust.Server.GameObjects;
using Robust.Server.GameObjects;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
@@ -22,6 +21,8 @@ using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using Content.Shared.Utility;
using Serilog;
using Content.Server.GameObjects.Components.GUI;
namespace Content.Server.GameObjects.Components.ActionBlocking
@@ -109,11 +110,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
handcuff.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
if (!handcuff.InRangeUnobstructed(Owner, _interactRange))
{
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
return;
@@ -238,22 +235,13 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return;
}
if (!isOwner &&
!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
user.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
if (!isOwner && user.InRangeUnobstructed(Owner, _interactRange))
{
user.PopupMessage(user, Loc.GetString("You are too far away to remove the cuffs."));
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
cuffsToRemove.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
if (!cuffsToRemove.InRangeUnobstructed(Owner, _interactRange))
{
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
return;

View File

@@ -16,6 +16,7 @@ using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Server.GameObjects.Components.Mobs;
using Robust.Shared.Maths;
using System;
using Content.Shared.Utility;
namespace Content.Server.GameObjects.Components.ActionBlocking
{
@@ -182,11 +183,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are too far away to use the cuffs!"));
return;

View File

@@ -7,13 +7,13 @@ using Content.Server.GameObjects.Components.Mobs.State;
using Content.Server.GameObjects.Components.Strap;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Buckle;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Strap;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystemMessages;
using Robust.Server.GameObjects.EntitySystems;
@@ -190,13 +190,10 @@ namespace Content.Server.GameObjects.Components.Buckle
return false;
}
var ownerPosition = Owner.Transform.MapPosition;
var strapPosition = strap.Owner.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
var component = strap;
bool Ignored(IEntity entity) => entity == Owner || entity == user || entity == component.Owner;
if (!interaction.InRangeUnobstructed(ownerPosition, strapPosition, _range, predicate: Ignored))
if (!Owner.InRangeUnobstructed(strap, _range, predicate: Ignored, popup: true))
{
_notifyManager.PopupMessage(strap.Owner, user,
Loc.GetString("You can't reach there!"));
@@ -346,9 +343,7 @@ namespace Content.Server.GameObjects.Components.Buckle
return false;
}
var strapPosition = Owner.Transform.MapPosition;
if (!InteractionChecks.InRangeUnobstructed(user, strapPosition, _range))
if (!user.InRangeUnobstructed(oldBuckledTo, _range, popup: true))
{
return false;
}

View File

@@ -2,10 +2,10 @@
using System;
using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
@@ -111,7 +111,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="eventArgs"></param>
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
//Make sure we have the attacking entity
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector)

View File

@@ -5,6 +5,7 @@ using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -80,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return false;
}
if (!InteractionChecks.InRangeUnobstructed(user, trueTarget.Transform.MapPosition))
if (!user.InRangeUnobstructed(trueTarget, popup: true))
{
return false;
}

View File

@@ -4,6 +4,7 @@ using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
@@ -68,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Fluids
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!Owner.TryGetComponent(out SolutionComponent? contents)) return;
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
if (CurrentVolume <= 0)
{

View File

@@ -2,6 +2,7 @@
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Maps;
using Content.Shared.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
@@ -42,7 +43,7 @@ namespace Content.Server.GameObjects.Components.Interactable
var coordinates = mapGrid.GridTileToLocal(tile.GridIndices);
if (!_entitySystemManager.GetEntitySystem<InteractionSystem>().InRangeUnobstructed(user.Transform.MapPosition, coordinates.ToMap(_mapManager), ignoredEnt:user))
if (!user.InRangeUnobstructed(coordinates, popup: true))
return;
var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId];

View File

@@ -2,6 +2,7 @@
using Content.Server.Utility;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Maps;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
@@ -35,7 +36,7 @@ namespace Content.Server.GameObjects.Components.Items
public void AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
if (!Owner.TryGetComponent(out StackComponent stack)) return;
var attacked = eventArgs.Target;

View File

@@ -6,6 +6,7 @@ using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Maps;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
@@ -168,7 +169,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD
}
var coordinates = mapGrid.GridTileToLocal(tile.GridIndices);
if (coordinates == GridCoordinates.InvalidGrid || !InteractionChecks.InRangeUnobstructed(eventArgs))
if (coordinates == GridCoordinates.InvalidGrid || !eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
{
return false;
}

View File

@@ -8,6 +8,7 @@ using Content.Shared.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
@@ -96,9 +97,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
return false;
}
var itemPos = Owner.Transform.MapPosition;
return InteractionChecks.InRangeUnobstructed(user, itemPos, ignoredEnt: Owner, ignoreInsideBlocker:true);
return user.InRangeUnobstructed(Owner, ignoreInsideBlocker: true, popup: true);
}
public bool InteractHand(InteractHandEventArgs eventArgs)

View File

@@ -11,6 +11,7 @@ using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.EntitySystemMessages;
@@ -405,9 +406,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
break;
}
var storagePosition = Owner.Transform.MapPosition;
if (!InteractionChecks.InRangeUnobstructed(player, storagePosition))
if (!player.InRangeUnobstructed(Owner, popup: true))
{
break;
}

View File

@@ -5,6 +5,7 @@ using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
@@ -43,19 +44,11 @@ namespace Content.Server.GameObjects.Components.Medical
return;
}
if (eventArgs.User != eventArgs.Target)
{
var interactionSystem = EntitySystem.Get<SharedInteractionSystem>();
var from = eventArgs.User.Transform.MapPosition;
var to = eventArgs.Target.Transform.MapPosition;
bool Ignored(IEntity entity) => entity == eventArgs.User || entity == eventArgs.Target;
var inRange = interactionSystem.InRangeUnobstructed(from, to, predicate: Ignored);
if (!inRange)
if (eventArgs.User != eventArgs.Target &&
!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
{
return;
}
}
if (Owner.TryGetComponent(out StackComponent stack) &&
!stack.Use(1))

View File

@@ -1,4 +1,4 @@
using Content.Server.GameObjects.Components.Body;
using Content.Server.GameObjects.Components.Body;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems;
@@ -18,6 +18,7 @@ using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
using Content.Server.Utility;
using Content.Shared.Utility;
namespace Content.Server.GameObjects.Components.Movement
{
@@ -107,12 +108,7 @@ namespace Content.Server.GameObjects.Components.Movement
return false;
}
var userPosition = user.Transform.MapPosition;
var climbablePosition = target.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => (entity == target || entity == user);
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored))
if (!user.InRangeUnobstructed(target, _range))
{
reason = Loc.GetString("You can't reach there!");
return false;
@@ -144,14 +140,10 @@ namespace Content.Server.GameObjects.Components.Movement
return false;
}
var userPosition = user.Transform.MapPosition;
var otherUserPosition = dragged.Transform.MapPosition;
var climbablePosition = target.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => (entity == target || entity == user || entity == dragged);
bool Ignored(IEntity entity) => entity == target || entity == user || entity == dragged;
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) ||
!interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored))
if (!user.InRangeUnobstructed(target, _range, predicate: Ignored) ||
!user.InRangeUnobstructed(dragged, _range, predicate: Ignored))
{
reason = Loc.GetString("You can't reach there!");
return false;

View File

@@ -11,6 +11,7 @@ using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Utensil;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -162,7 +163,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
}
}
if (!InteractionChecks.InRangeUnobstructed(user, trueTarget.Transform.MapPosition))
if (!user.InRangeUnobstructed(trueTarget, popup: true))
{
return false;
}

View File

@@ -3,7 +3,6 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Server.Interfaces;
@@ -12,7 +11,6 @@ using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Power.AME;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Microsoft.EntityFrameworkCore.Internal;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -20,17 +18,12 @@ using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Content.Server.GameObjects.Components.Power.AME
@@ -87,20 +80,27 @@ namespace Content.Server.GameObjects.Components.Power.AME
return;
}
var group = GetAMENodeGroup();
if (group == null)
{
return;
}
_jarSlot.ContainedEntity.TryGetComponent<AMEFuelContainerComponent>(out var fuelJar);
if(fuelJar != null && _powerSupplier != null && fuelJar.FuelAmount > InjectionAmount)
{
_powerSupplier.SupplyRate = GetAMENodeGroup().InjectFuel(InjectionAmount);
_powerSupplier.SupplyRate = group.InjectFuel(InjectionAmount);
fuelJar.FuelAmount -= InjectionAmount;
InjectSound();
UpdateUserInterface();
}
_stability = GetAMENodeGroup().GetTotalStability();
_stability = group.GetTotalStability();
UpdateDisplay(_stability);
if(_stability <= 0) { GetAMENodeGroup().ExplodeCores(); }
if(_stability <= 0) { group.ExplodeCores(); }
}
@@ -213,7 +213,7 @@ namespace Content.Server.GameObjects.Components.Power.AME
break;
}
GetAMENodeGroup().UpdateCoreVisuals(InjectionAmount, _injecting);
GetAMENodeGroup()?.UpdateCoreVisuals(InjectionAmount, _injecting);
UpdateUserInterface();
ClickSound();
@@ -272,11 +272,11 @@ namespace Content.Server.GameObjects.Components.Power.AME
private void RefreshParts()
{
GetAMENodeGroup().RefreshAMENodes(this);
GetAMENodeGroup()?.RefreshAMENodes(this);
UpdateUserInterface();
}
private AMENodeGroup GetAMENodeGroup()
private AMENodeGroup? GetAMENodeGroup()
{
Owner.TryGetComponent(out NodeContainerComponent? nodeContainer);
@@ -285,12 +285,12 @@ namespace Content.Server.GameObjects.Components.Power.AME
.OfType<AMENodeGroup>()
.First();
return engineNodeGroup ?? default!;
return engineNodeGroup;
}
private bool IsMasterController()
{
if(GetAMENodeGroup().MasterController == this)
if(GetAMENodeGroup()?.MasterController == this)
{
return true;
}
@@ -301,10 +301,11 @@ namespace Content.Server.GameObjects.Components.Power.AME
private int GetCoreCount()
{
var coreCount = 0;
var group = GetAMENodeGroup();
if(GetAMENodeGroup() != null)
if (group != null)
{
coreCount = GetAMENodeGroup().CoreCount;
coreCount = group.CoreCount;
}
return coreCount;

View File

@@ -1,6 +1,7 @@
using Content.Server.GameObjects.Components.Stack;
using Content.Server.Utility;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
@@ -36,7 +37,7 @@ namespace Content.Server.GameObjects.Components.Power
/// <inheritdoc />
public void AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
if(!_mapManager.TryGetGrid(eventArgs.ClickLocation.GridID, out var grid))
return;
var snapPos = grid.SnapGridCellFor(eventArgs.ClickLocation, SnapGridOffset.Center);

View File

@@ -4,6 +4,7 @@ using Content.Shared.GameObjects.Components.Strap;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
@@ -202,14 +203,9 @@ namespace Content.Server.GameObjects.Components.Strap
parent = parent.Parent;
}
var userPosition = user.Transform.MapPosition;
var strapPosition = component.Owner.Transform.MapPosition;
var range = SharedInteractionSystem.InteractionRange / 2;
var inRange = EntitySystem.Get<SharedInteractionSystem>()
.InRangeUnobstructed(userPosition, strapPosition, range,
predicate: entity => entity == user || entity == component.Owner);
if (!inRange)
if (!user.InRangeUnobstructed(component, range))
{
return;
}

View File

@@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Utensil;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -121,7 +122,7 @@ namespace Content.Server.GameObjects.Components.Utensil
return;
}
if (!InteractionChecks.InRangeUnobstructed(user, target.Transform.MapPosition))
if (!user.InRangeUnobstructed(target, popup: true))
{
return;
}

View File

@@ -1,6 +1,6 @@
using System;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Weapons;
using Content.Shared.Utility;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
@@ -32,7 +32,7 @@ namespace Content.Server.GameObjects.Components.Weapon
{
foreach (var entity in IoCManager.Resolve<IEntityManager>().GetEntitiesInRange(source.Transform.GridPosition, range))
{
if (!InteractionChecks.InRangeUnobstructed(source, entity.Transform.MapPosition, range, ignoredEnt:entity))
if (!source.InRangeUnobstructed(entity, range, popup: true))
continue;
if(entity.TryGetComponent(out FlashableComponent flashable))

View File

@@ -13,6 +13,7 @@ using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -388,7 +389,7 @@ namespace Content.Server.GameObjects.Components
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(player.Transform.MapPosition, Owner.Transform.MapPosition, ignoredEnt: Owner))
if (!player.InRangeUnobstructed(Owner))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
Loc.GetString("You can't reach there!"));

View File

@@ -7,8 +7,8 @@ using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
using Content.Server.GameObjects.EntitySystems.JobQueues;
using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Utility;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
@@ -281,7 +281,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
if (targetDistance <= steeringRequest.ArrivalDistance && steeringRequest.TimeUntilInteractionCheck <= 0.0f)
{
if (!steeringRequest.RequiresInRangeUnobstructed ||
InteractionChecks.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, ignoredEnt: entity))
entity.InRangeUnobstructed(steeringRequest.TargetMap, steeringRequest.ArrivalDistance, popup: true))
{
// TODO: Need cruder LOS checks for ranged weaps
controller.VelocityDir = Vector2.Zero;

View File

@@ -12,8 +12,8 @@ using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Physics;
using Content.Shared.Physics.Pull;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.Player;
@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
var interactionArgs = new DragDropEventArgs(performer, msg.DropLocation, dropped, target);
// must be in range of both the target and the object they are drag / dropping
if (!InteractionChecks.InRangeUnobstructed(interactionArgs)) return;
if (!interactionArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
// trigger dragdrops on the dropped entity
foreach (var dragDrop in dropped.GetAllComponents<IDragDrop>())
@@ -145,7 +145,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
// all activates should only fire when in range / unbostructed
var activateEventArgs = new ActivateEventArgs {User = user, Target = used};
if (InteractionChecks.InRangeUnobstructed(activateEventArgs))
if (activateEventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
{
activateComp.Activate(activateEventArgs);
}
@@ -453,7 +453,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click
};
// all AttackBys should only happen when in range / unobstructed, so no range check is needed
if (InteractionChecks.InRangeUnobstructed(attackByEventArgs))
if (attackByEventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
{
foreach (var attackBy in attackBys)
{
@@ -501,8 +501,8 @@ namespace Content.Server.GameObjects.EntitySystems.Click
var attackHands = attacked.GetAllComponents<IInteractHand>().ToList();
var attackHandEventArgs = new InteractHandEventArgs {User = user, Target = attacked};
// all attackHands should only fire when in range / unbostructed
if (InteractionChecks.InRangeUnobstructed(attackHandEventArgs))
// all attackHands should only fire when in range / unobstructed
if (attackHandEventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
{
foreach (var attackHand in attackHands)
{

View File

@@ -6,19 +6,18 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Utility;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
@@ -91,8 +90,7 @@ namespace Content.Server.GameObjects.EntitySystems
if(targetEnt is null || handEnt is null)
return;
var interaction = Get<InteractionSystem>();
if(!interaction.InRangeUnobstructed(handEnt.Transform.MapPosition, targetEnt.Transform.MapPosition, ignoredEnt: targetEnt, ignoreInsideBlocker: true))
if (!handEnt.InRangeUnobstructed(targetEnt, ignoreInsideBlocker: true))
return;
// Cannot deconstruct an entity with no prototype.
@@ -246,8 +244,7 @@ namespace Content.Server.GameObjects.EntitySystems
{
var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);
if (!InteractionChecks.InRangeUnobstructed(placingEnt, loc.ToMap(_mapManager),
ignoredEnt: placingEnt, ignoreInsideBlocker: prototype.CanBuildInImpassable))
if (!placingEnt.InRangeUnobstructed(loc, ignoreInsideBlocker: prototype.CanBuildInImpassable, popup: true))
{
return false;
}
@@ -377,7 +374,7 @@ namespace Content.Server.GameObjects.EntitySystems
var constructPrototype = constructionComponent.Prototype;
if (constructPrototype.CanBuildInImpassable == false)
{
if (!InteractionChecks.InRangeUnobstructed(user, constructEntity.Transform.MapPosition))
if (!user.InRangeUnobstructed(constructEntity, popup: true))
return false;
}

View File

@@ -3,6 +3,7 @@ using System;
using System.Threading;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Physics;
using Content.Shared.Utility;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
@@ -13,15 +14,15 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
public sealed class DoAfterEventArgs
{
// Premade checks
public Func<bool> GetInRangeUnobstructed(int collisionMask = (int) CollisionGroup.MobMask)
public Func<bool> GetInRangeUnobstructed(CollisionGroup collisionMask = CollisionGroup.MobMask)
{
if (Target == null)
{
throw new InvalidOperationException("Can't supply a null target to DoAfterEventArgs.GetInRangeUnobstructed");
}
var interactionSystem = EntitySystem.Get<SharedInteractionSystem>();
Func<IEntity, bool> ignored = entity => entity == User || entity == Target;
return () => interactionSystem.InRangeUnobstructed(User.Transform.MapPosition, Target.Transform.MapPosition, collisionMask: collisionMask, predicate: ignored);
bool Ignored(IEntity entity) => entity == User || entity == Target;
return () => User.InRangeUnobstructed(Target, collisionMask: collisionMask, predicate: Ignored);
}
/// <summary>

View File

@@ -125,7 +125,7 @@ namespace Content.Server.GameObjects.EntitySystems
var entToDesiredDropCoords = coords.Position - entCoords;
var targetLength = Math.Min(entToDesiredDropCoords.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error
var newCoords = new GridCoordinates((entToDesiredDropCoords.Normalized * targetLength) + entCoords, coords.GridID);
var rayLength = Get<SharedInteractionSystem>().UnobstructedRayLength(ent.Transform.MapPosition, newCoords.ToMap(_mapManager), ignoredEnt: ent);
var rayLength = Get<SharedInteractionSystem>().UnobstructedDistance(ent.Transform.MapPosition, newCoords.ToMap(_mapManager), ignoredEnt: ent);
handsComp.Drop(handsComp.ActiveHand, new GridCoordinates(entCoords + (entToDesiredDropCoords.Normalized * rayLength), coords.GridID));

View File

@@ -1,124 +0,0 @@
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Physics;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
namespace Content.Server.Utility
{
/// <summary>
/// Convenient methods for checking for various conditions commonly needed
/// for interactions.
/// </summary>
public static class InteractionChecks
{
/// <summary>
/// Default interaction check for targeted attack interaction types.
/// Same as <see cref="SharedInteractionSystem.InRangeUnobstructed"/>, but defaults to ignore inside blockers
/// (making the check less restrictive).
/// Validates that attacker is in range of the attacked entity. Additionally shows a popup if
/// validation fails.
/// </summary>
public static bool InRangeUnobstructed(ITargetedInteractEventArgs eventArgs, bool ignoreInsideBlocker = true)
{
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition, ignoredEnt: eventArgs.Target, ignoreInsideBlocker: ignoreInsideBlocker))
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.Target.PopupMessage(eventArgs.User, localizationManager.GetString("You can't reach there!"));
return false;
}
return true;
}
/// <summary>
/// Default interaction check for Drag drop interaction types.
/// Same as <see cref="SharedInteractionSystem.InRangeUnobstructed"/>, but defaults to ignore inside blockers
/// (making the check less restrictive) and checks reachability of both the target and the dragged / dropped object.
/// Additionally shows a popup if validation fails.
/// </summary>
public static bool InRangeUnobstructed(DragDropEventArgs eventArgs, bool ignoreInsideBlocker = true)
{
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition, ignoredEnt: eventArgs.Target, ignoreInsideBlocker: ignoreInsideBlocker))
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.Target.PopupMessage(eventArgs.User, localizationManager.GetString("You can't reach there!"));
return false;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(eventArgs.User.Transform.MapPosition,
eventArgs.Dropped.Transform.MapPosition, ignoredEnt: eventArgs.Dropped, ignoreInsideBlocker: ignoreInsideBlocker))
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.Dropped.PopupMessage(eventArgs.User, localizationManager.GetString("You can't reach there!"));
return false;
}
return true;
}
/// <summary>
/// Default interaction check for after attack interaction types.
/// Same as <see cref="SharedInteractionSystem.InRangeUnobstructed"/>, but defaults to ignore inside blockers
/// (making the check less restrictive).
/// Validates that attacker is in range of the attacked entity, if there is such an entity.
/// If there is no attacked entity, validates that they are in range of the clicked position.
/// Additionally shows a popup if validation fails.
/// </summary>
public static bool InRangeUnobstructed(AfterInteractEventArgs eventArgs, bool ignoreInsideBlocker = true)
{
if (eventArgs.Target != null)
{
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition, ignoredEnt: eventArgs.Target, ignoreInsideBlocker: ignoreInsideBlocker))
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.Target.PopupMessage(eventArgs.User, localizationManager.GetString("You can't reach there!"));
return false;
}
}
else
{
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(eventArgs.User.Transform.MapPosition,
eventArgs.ClickLocation.ToMap(IoCManager.Resolve<IMapManager>()), ignoredEnt: eventArgs.User, ignoreInsideBlocker: ignoreInsideBlocker))
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
eventArgs.User.PopupMessage(eventArgs.User, localizationManager.GetString("You can't reach there!"));
return false;
}
}
return true;
}
/// <summary>
/// Convenient static alternative to <see cref="SharedInteractionSystem.InRangeUnobstructed"/>, which also
/// shows a popup message if not in range.
/// </summary>
public static bool InRangeUnobstructed(IEntity user, MapCoordinates otherCoords,
float range = SharedInteractionSystem.InteractionRange,
int collisionMask = (int) CollisionGroup.Impassable, IEntity ignoredEnt = null, bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var interactionSystem = EntitySystem.Get<SharedInteractionSystem>();
if (!interactionSystem.InRangeUnobstructed(user.Transform.MapPosition, otherCoords, range, collisionMask,
ignoredEnt, ignoreInsideBlocker))
{
var localizationManager = IoCManager.Resolve<ILocalizationManager>();
user.PopupMessage(user, localizationManager.GetString("You can't reach there!"));
return false;
}
return true;
}
}
}

View File

@@ -1,11 +1,13 @@
using System;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem;
namespace Content.Shared.GameObjects.EntitySystems
{
@@ -26,10 +28,7 @@ namespace Content.Shared.GameObjects.EntitySystems
private static bool IsInDetailsRange(IEntity examiner, IEntity entity)
{
return Get<SharedInteractionSystem>()
.InRangeUnobstructed(examiner.Transform.MapPosition, entity.Transform.MapPosition,
ExamineDetailsRange, predicate: entity0 => entity0 == examiner || entity0 == entity,
ignoreInsideBlocker: true) &&
return examiner.InRangeUnobstructed(entity, ExamineDetailsRange, ignoreInsideBlocker: true) &&
examiner.IsInSameOrNoContainer(entity);
}
@@ -51,16 +50,14 @@ namespace Content.Shared.GameObjects.EntitySystems
return false;
}
Func<IEntity, bool> predicate = entity => entity == examiner || entity == examined;
Ignored predicate = entity => entity == examiner || entity == examined;
if (ContainerHelpers.TryGetContainer(examiner, out var container))
{
predicate += entity => entity == container.Owner;
}
return Get<SharedInteractionSystem>()
.InRangeUnobstructed(examiner.Transform.MapPosition, examined.Transform.MapPosition,
ExamineRange, predicate: predicate, ignoreInsideBlocker:true);
return examiner.InRangeUnobstructed(examined, ExamineRange, predicate: predicate, ignoreInsideBlocker: true);
}
public static FormattedMessage GetExamineText(IEntity entity, IEntity examiner)

View File

@@ -1,11 +1,14 @@
using System;
using System.Linq;
using System.Linq;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Physics;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -18,98 +21,516 @@ namespace Content.Shared.GameObjects.EntitySystems
public class SharedInteractionSystem : EntitySystem
{
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public const float InteractionRange = 2;
public const float InteractionRangeSquared = InteractionRange * InteractionRange;
public delegate bool Ignored(IEntity entity);
/// <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="coords">Set of coordinates to use.</param>
/// <param name="otherCoords">Other set of coordinates to use.</param>
/// <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="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
/// <param name="predicate">
/// A predicate to check whether to ignore an entity or not.
/// If it returns true, it will be ignored.
/// </param>
/// <returns>Length of resulting ray.</returns>
public float UnobstructedRayLength(MapCoordinates coords, MapCoordinates otherCoords,
int collisionMask = (int) CollisionGroup.Impassable, Func<IEntity, bool> predicate = null)
public float UnobstructedDistance(
MapCoordinates origin,
MapCoordinates other,
int collisionMask = (int) CollisionGroup.Impassable,
Ignored predicate = null)
{
var dir = otherCoords.Position - coords.Position;
var dir = other.Position - origin.Position;
if (dir.LengthSquared.Equals(0f)) return 0f;
var ray = new CollisionRay(coords.Position, dir.Normalized, collisionMask);
var rayResults = _physicsManager.IntersectRayWithPredicate(coords.MapId, ray, dir.Length, predicate, returnOnFirstHit: false).ToList();
predicate ??= _ => false;
var ray = new CollisionRay(origin.Position, dir.Normalized, collisionMask);
var rayResults = _physicsManager.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
if (rayResults.Count == 0) return dir.Length;
return (rayResults[0].HitPos - coords.Position).Length;
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="coords">Set of coordinates to use.</param>
/// <param name="otherCoords">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>
/// <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 UnobstructedRayLength(MapCoordinates coords, MapCoordinates otherCoords,
int collisionMask = (int) CollisionGroup.Impassable, IEntity ignoredEnt = null) =>
UnobstructedRayLength(coords, otherCoords, collisionMask,
ignoredEnt == null ? null : (Func<IEntity, bool>) (entity => ignoredEnt == entity));
public float UnobstructedDistance(
MapCoordinates origin,
MapCoordinates other,
int collisionMask = (int) CollisionGroup.Impassable,
IEntity 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.
/// If the <paramref name="range"/> is zero or negative,
/// this method will only check if nothing obstructs the two sets of coordinates..
/// this method will only check if nothing obstructs the two sets
/// of coordinates.
/// </summary>
/// <param name="coords">Set of coordinates to use.</param>
/// <param name="otherCoords">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 the coordinates 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 coords, MapCoordinates otherCoords, float range = InteractionRange,
int collisionMask = (int)CollisionGroup.Impassable, Func<IEntity, bool> predicate = null, bool ignoreInsideBlocker = false)
/// <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,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
if (!coords.InRange(otherCoords, range))
return false;
if (!origin.InRange(other, range)) return false;
var dir = otherCoords.Position - coords.Position;
var dir = other.Position - origin.Position;
if (dir.LengthSquared.Equals(0f)) return true;
if (range > 0f && !(dir.LengthSquared <= range * range)) return false;
var ray = new CollisionRay(coords.Position, dir.Normalized, collisionMask);
var rayResults = _physicsManager.IntersectRayWithPredicate(coords.MapId, ray, dir.Length, predicate, returnOnFirstHit: false).ToList();
return rayResults.Count == 0 || (ignoreInsideBlocker && rayResults.Count > 0 && (rayResults[0].HitPos - otherCoords.Position).Length < 1f);
predicate ??= _ => false;
var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask);
var rayResults = _physicsManager.IntersectRayWithPredicate(origin.MapId, ray, dir.Length, predicate.Invoke, false).ToList();
if (rayResults.Count == 0) return true;
if (!ignoreInsideBlocker) return false;
if (rayResults.Count <= 0) return false;
return (rayResults[0].HitPos - other.Position).Length < 1f;
}
/// <summary>
/// Checks that these coordinates are within a certain distance without any
/// Checks that two entities 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 two sets of coordinates..
/// this method will only check if nothing obstructs the two entities.
/// </summary>
/// <param name="coords">Set of coordinates to use.</param>
/// <param name="otherCoords">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="ignoredEnt">the entity to be ignored when checking for collisions.</param>
/// <param name="ignoreInsideBlocker">if true and the coordinates 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 coords, MapCoordinates otherCoords, float range = InteractionRange,
int collisionMask = (int)CollisionGroup.Impassable, IEntity ignoredEnt = null, bool ignoreInsideBlocker = false) =>
InRangeUnobstructed(coords, otherCoords, range, collisionMask,
ignoredEnt == null ? null : (Func<IEntity, bool>)(entity => ignoredEnt == entity), ignoreInsideBlocker);
/// <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(
IEntity origin,
IEntity other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originPosition = origin.Transform.MapPosition;
var otherPosition = other.Transform.MapPosition;
predicate ??= e => e == origin || e == other;
var inRange = InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker);
if (!inRange && popup)
{
var message = Loc.GetString("You can't reach there!");
origin.PopupMessage(origin, message);
}
return inRange;
}
/// <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(
IEntity origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originPosition = origin.Transform.MapPosition;
var otherPosition = other.Owner.Transform.MapPosition;
predicate ??= e => e == origin || e == other.Owner;
var inRange = InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker);
if (!inRange && popup)
{
var message = Loc.GetString("You can't reach there!");
origin.PopupMessage(origin, message);
}
return inRange;
}
/// <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(
IEntity origin,
GridCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originPosition = origin.Transform.MapPosition;
var otherPosition = other.ToMap(_mapManager);
predicate ??= e => e == origin;
var inRange = InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker);
if (!inRange && popup)
{
var message = Loc.GetString("You can't reach there!");
origin.PopupMessage(origin, message);
}
return inRange;
}
/// <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(
IEntity origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originPosition = origin.Transform.MapPosition;
predicate ??= e => e == origin;
var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker);
if (!inRange && popup)
{
var message = Loc.GetString("You can't reach there!");
origin.PopupMessage(origin, message);
}
return inRange;
}
/// <summary>
/// Checks that the user and target of a
/// <see cref="ITargetedInteractEventArgs"/> 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="args">The event args 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 both the user and target 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 user entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
ITargetedInteractEventArgs args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var origin = args.User;
var other = args.Target;
return InRangeUnobstructed(origin, other, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
/// <summary>
/// Checks that the user of a <see cref="DragDropEventArgs"/> is within a
/// certain distance of the target and dropped entities 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="args">The event args 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 both the user and target 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 user entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
DragDropEventArgs args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var user = args.User;
var dropped = args.Dropped;
var target = args.Target;
if (!InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker))
{
if (popup)
{
var message = Loc.GetString("You can't reach there!");
target.PopupMessage(user, message);
}
return false;
}
if (!InRangeUnobstructed(user, dropped, range, collisionMask, predicate, ignoreInsideBlocker))
{
if (popup)
{
var message = Loc.GetString("You can't reach there!");
dropped.PopupMessage(user, message);
}
return false;
}
return true;
}
/// <summary>
/// Checks that the user and target of a
/// <see cref="AfterInteractEventArgs"/> 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="args">The event args 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 both the user and target 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 user entity for
/// it to see.
/// </param>
/// <returns>
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
AfterInteractEventArgs args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var user = args.User;
var target = args.Target;
predicate ??= e => e == user;
MapCoordinates otherPosition;
if (target == null)
{
otherPosition = args.ClickLocation.ToMap(_mapManager);
}
else
{
otherPosition = target.Transform.MapPosition;
predicate += e => e == target;
}
return InRangeUnobstructed(user, otherPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
}
}

View File

@@ -1,10 +1,9 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Physics;
using Content.Shared.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
@@ -15,14 +14,6 @@ namespace Content.Shared.GameObjects.Verbs
{
public class SharedVerbSystem : EntitySystem
{
private SharedInteractionSystem _interactionSystem = null!;
public override void Initialize()
{
base.Initialize();
_interactionSystem = Get<SharedInteractionSystem>();
}
/// <summary>
/// Get all of the entities relevant for the contextmenu
/// </summary>
@@ -47,17 +38,15 @@ namespace Content.Shared.GameObjects.Verbs
// Check if we have LOS to the clicked-location, otherwise no popup.
var vectorDiff = player.Transform.MapPosition.Position - targetPos.Position;
var distance = vectorDiff.Length + 0.01f;
Func<IEntity, bool> ignored = entity => entities.Contains(entity) ||
bool Ignored(IEntity entity)
{
return entities.Contains(entity) ||
entity == player ||
!entity.TryGetComponent(out OccluderComponent? occluder) ||
!occluder.Enabled;
}
var result = _interactionSystem.InRangeUnobstructed(
player.Transform.MapPosition,
targetPos,
distance,
(int) CollisionGroup.Opaque,
ignored);
var result = player.InRangeUnobstructed(targetPos, distance, CollisionGroup.Opaque, Ignored);
if (!result)
{

View File

@@ -0,0 +1,432 @@
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Physics;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem;
namespace Content.Shared.Utility
{
public static class SharedRangeExtensions
{
private static SharedInteractionSystem SharedInteractionSystem => EntitySystem.Get<SharedInteractionSystem>();
#region Entities
public static bool InRangeUnobstructed(
this IEntity origin,
IEntity 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 IEntity 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 IEntity 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 IEntity origin,
GridCoordinates 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 IEntity 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,
IEntity 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,
GridCoordinates 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,
IEntity 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,
GridCoordinates 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 GridCoordinates
public static bool InRangeUnobstructed(
this GridCoordinates origin,
IEntity other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var originPosition = origin.ToMap(mapManager);
var otherPosition = other.Transform.MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this GridCoordinates origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var originPosition = origin.ToMap(mapManager);
var otherPosition = other.Owner.Transform.MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this GridCoordinates origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var originPosition = origin.ToMap(mapManager);
var otherPosition = other.Owner.Transform.MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this GridCoordinates origin,
GridCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var originPosition = origin.ToMap(mapManager);
var otherPosition = other.ToMap(mapManager);
return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask,
predicate, ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this GridCoordinates origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var originPosition = origin.ToMap(mapManager);
return SharedInteractionSystem.InRangeUnobstructed(originPosition, other, range, collisionMask, predicate,
ignoreInsideBlocker);
}
#endregion
#region MapCoordinates
public static bool InRangeUnobstructed(
this MapCoordinates origin,
IEntity other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var otherPosition = other.Transform.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 = other.Owner.Transform.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 = other.Owner.Transform.MapPosition;
return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate,
ignoreInsideBlocker);
}
public static bool InRangeUnobstructed(
this MapCoordinates origin,
GridCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var otherPosition = other.ToMap(mapManager);
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, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this DragDropEventArgs args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this AfterInteractEventArgs args,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return SharedInteractionSystem.InRangeUnobstructed(args, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
#endregion
}
}