diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs index 60ae6065b6..3d566050ed 100644 --- a/Content.Client/Construction/ConstructionSystem.cs +++ b/Content.Client/Construction/ConstructionSystem.cs @@ -1,20 +1,14 @@ -using System; -using System.Collections.Generic; using Content.Shared.Construction; using Content.Shared.Construction.Prototypes; using Content.Shared.Examine; using Content.Shared.Input; -using Content.Shared.Interaction.Helpers; +using Content.Shared.Interaction; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; -using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.Input.Binding; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; namespace Content.Client.Construction @@ -27,6 +21,7 @@ namespace Content.Client.Construction { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; private readonly Dictionary _ghosts = new(); private readonly Dictionary _guideCache = new(); @@ -162,8 +157,12 @@ namespace Content.Client.Construction return; } + if (GhostPresent(loc)) return; + // This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?" - if (GhostPresent(loc) || !user.InRangeUnobstructed(loc, 20f, ignoreInsideBlocker: prototype.CanBuildInImpassable)) return; + var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager)); + if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate)) + return; foreach (var condition in prototype.Conditions) { diff --git a/Content.Client/DragDrop/DragDropSystem.cs b/Content.Client/DragDrop/DragDropSystem.cs index 07b8a0d7bc..bf7f9b8911 100644 --- a/Content.Client/DragDrop/DragDropSystem.cs +++ b/Content.Client/DragDrop/DragDropSystem.cs @@ -297,19 +297,6 @@ namespace Content.Client.DragDrop return false; } - // now when ending the drag, we will not replay the click because - // by this time we've determined the input was actually a drag attempt - var range = (args.Coordinates.ToMapPos(EntityManager) - EntityManager.GetComponent(_dragger).MapPosition.Position).Length + 0.01f; - // 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, - args.Coordinates, range, ignoreInsideBlocker: true)) - { - _dragDropHelper.EndDrag(); - return false; - } - IList entities; if (_stateManager.CurrentState is GameScreen screen) @@ -333,7 +320,8 @@ namespace Content.Client.DragDrop // TODO: Cache valid CanDragDrops if (ValidDragDrop(dropArgs) != true) continue; - if (!dropArgs.InRangeUnobstructed(ignoreInsideBlocker: true)) + if (!_interactionSystem.InRangeUnobstructed(dropArgs.User, dropArgs.Target) + || !_interactionSystem.InRangeUnobstructed(dropArgs.User, dropArgs.Dragged)) { outOfRange = true; continue; @@ -401,7 +389,8 @@ namespace Content.Client.DragDrop // We'll do a final check given server-side does this before any dragdrop can take place. if (valid.Value) { - valid = dropArgs.InRangeUnobstructed(ignoreInsideBlocker: true); + valid = _interactionSystem.InRangeUnobstructed(dropArgs.Target, dropArgs.Dragged) + && _interactionSystem.InRangeUnobstructed(dropArgs.Target, dropArgs.Target); } // highlight depending on whether its in or out of range diff --git a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs index 7d2e8b4b9c..6762b3450c 100644 --- a/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs +++ b/Content.Client/Instruments/UI/InstrumentMenu.xaml.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Threading.Tasks; using Content.Client.Interactable; +using Content.Shared.Interaction; using Robust.Client.Audio.Midi; using Robust.Client.AutoGenerated; using Robust.Client.Player; @@ -151,8 +152,7 @@ namespace Content.Client.Instruments.UI || conMan.Owner != localPlayer.ControlledEntity))) return false; // We check that we're in range unobstructed just in case. - return localPlayer.InRangeUnobstructed(instrumentEnt.Value, - predicate: (e) => e == instrumentEnt || e == localPlayer.ControlledEntity); + return EntitySystem.Get().InRangeUnobstructed(localPlayer.ControlledEntity.Value, instrumentEnt.Value); } private void MidiStopButtonOnPressed(ButtonEventArgs? obj) diff --git a/Content.Client/Interactable/UnobstructedExtensions.cs b/Content.Client/Interactable/UnobstructedExtensions.cs deleted file mode 100644 index 9d320c276a..0000000000 --- a/Content.Client/Interactable/UnobstructedExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Content.Shared.Interaction; -using Content.Shared.Physics; -using Robust.Client.Player; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using static Content.Shared.Interaction.SharedInteractionSystem; - -namespace Content.Client.Interactable -{ - public static class UnobstructedExtensions - { - private static SharedInteractionSystem SharedInteractionSystem => EntitySystem.Get(); - - public static bool InRangeUnobstructed( - this LocalPlayer origin, - EntityUid other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var otherPosition = IoCManager.Resolve().GetComponent(other).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, - EntityCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var entityManager = IoCManager.Resolve(); - var otherPosition = other.ToMap(entityManager); - - 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.Value, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - } -} diff --git a/Content.Client/Movement/Components/ClimbableComponent.cs b/Content.Client/Movement/Components/ClimbableComponent.cs index 7a8563271f..9d92566a0a 100644 --- a/Content.Client/Movement/Components/ClimbableComponent.cs +++ b/Content.Client/Movement/Components/ClimbableComponent.cs @@ -1,5 +1,6 @@ -using Content.Shared.Climbing; +using Content.Shared.Climbing; using Content.Shared.DragDrop; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Robust.Shared.GameObjects; @@ -19,7 +20,10 @@ namespace Content.Client.Movement.Components var dragged = eventArgs.Dragged; bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged; - return user.InRangeUnobstructed(target, Range, predicate: Ignored) && user.InRangeUnobstructed(dragged, Range, predicate: Ignored); + var sys = EntitySystem.Get(); + + return sys.InRangeUnobstructed(user, target, Range, predicate: Ignored) + && sys.InRangeUnobstructed(user, dragged, Range, predicate: Ignored); } public override bool DragDropOn(DragDropEvent eventArgs) diff --git a/Content.Client/Outline/InteractionOutlineSystem.cs b/Content.Client/Outline/InteractionOutlineSystem.cs index 08b511e45b..a9847377b2 100644 --- a/Content.Client/Outline/InteractionOutlineSystem.cs +++ b/Content.Client/Outline/InteractionOutlineSystem.cs @@ -3,6 +3,7 @@ using Content.Client.Interactable; using Content.Client.Interactable.Components; using Content.Client.Viewport; using Content.Shared.CCVar; +using Content.Shared.Interaction; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; @@ -10,8 +11,6 @@ using Robust.Client.State; using Robust.Client.UserInterface; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Configuration; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Client.Outline; @@ -23,6 +22,7 @@ public sealed class InteractionOutlineSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; public bool Enabled = true; @@ -76,7 +76,7 @@ public sealed class InteractionOutlineSystem : EntitySystem var inRange = false; if (localPlayer.ControlledEntity != null && entityToClick != null) { - inRange = localPlayer.InRangeUnobstructed(entityToClick.Value, ignoreInsideBlocker: true); + inRange = _interactionSystem.InRangeUnobstructed(localPlayer.ControlledEntity.Value, entityToClick.Value); } InteractionOutlineComponent? outline; diff --git a/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs b/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs index c106b1a57f..449e58a128 100644 --- a/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs +++ b/Content.IntegrationTests/Tests/Interaction/InRangeUnobstructed.cs @@ -1,11 +1,8 @@ using System.Threading.Tasks; -using Content.Client.Interactable; using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; using NUnit.Framework; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -13,8 +10,6 @@ namespace Content.IntegrationTests.Tests.Interaction { [TestFixture] [TestOf(typeof(SharedInteractionSystem))] - [TestOf(typeof(SharedUnobstructedExtensions))] - [TestOf(typeof(UnobstructedExtensions))] public sealed class InRangeUnobstructed : ContentIntegrationTest { private const string HumanId = "MobHumanBase"; @@ -59,98 +54,50 @@ namespace Content.IntegrationTests.Tests.Interaction await server.WaitIdleAsync(); + var interactionSys = server.ResolveDependency().GetEntitySystem(); + 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 <-> EntityCoordinates - Assert.True(origin.InRangeUnobstructed(entityCoordinates)); - Assert.True(entityCoordinates.InRangeUnobstructed(origin)); + Assert.True(interactionSys.InRangeUnobstructed(origin, other)); + Assert.True(interactionSys.InRangeUnobstructed(other, origin)); // Entity <-> MapCoordinates - Assert.True(origin.InRangeUnobstructed(mapCoordinates)); - Assert.True(mapCoordinates.InRangeUnobstructed(origin)); - + Assert.True(interactionSys.InRangeUnobstructed(origin, mapCoordinates)); + Assert.True(interactionSys.InRangeUnobstructed(mapCoordinates, origin)); // Move them slightly apart sEntities.GetComponent(origin).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 <-> EntityCoordinates - Assert.True(origin.InRangeUnobstructed(entityCoordinates)); - Assert.True(entityCoordinates.InRangeUnobstructed(origin)); + // Entity <-> Entity + Assert.True(interactionSys.InRangeUnobstructed(origin, other)); + Assert.True(interactionSys.InRangeUnobstructed(other, origin)); // Entity <-> MapCoordinates - Assert.True(origin.InRangeUnobstructed(mapCoordinates)); - Assert.True(mapCoordinates.InRangeUnobstructed(origin)); - + Assert.True(interactionSys.InRangeUnobstructed(origin, mapCoordinates)); + Assert.True(interactionSys.InRangeUnobstructed(mapCoordinates, origin)); // Move them out of range sEntities.GetComponent(origin).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 <-> EntityCoordinates - Assert.False(origin.InRangeUnobstructed(entityCoordinates)); - Assert.False(entityCoordinates.InRangeUnobstructed(origin)); + Assert.False(interactionSys.InRangeUnobstructed(origin, other)); + Assert.False(interactionSys.InRangeUnobstructed(other, origin)); // Entity <-> MapCoordinates - Assert.False(origin.InRangeUnobstructed(mapCoordinates)); - Assert.False(mapCoordinates.InRangeUnobstructed(origin)); - + Assert.False(interactionSys.InRangeUnobstructed(origin, mapCoordinates)); + Assert.False(interactionSys.InRangeUnobstructed(mapCoordinates, 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 <-> EntityCoordinates - Assert.True(origin.InRangeUnobstructed(entityCoordinates, InteractionRangeDivided15Times3)); - Assert.True(entityCoordinates.InRangeUnobstructed(origin, InteractionRangeDivided15Times3)); + Assert.True(interactionSys.InRangeUnobstructed(origin, other, InteractionRangeDivided15Times3)); + Assert.True(interactionSys.InRangeUnobstructed(other, origin, InteractionRangeDivided15Times3)); // Entity <-> MapCoordinates - Assert.True(origin.InRangeUnobstructed(mapCoordinates, InteractionRangeDivided15Times3)); - Assert.True(mapCoordinates.InRangeUnobstructed(origin, InteractionRangeDivided15Times3)); + Assert.True(interactionSys.InRangeUnobstructed(origin, mapCoordinates, InteractionRangeDivided15Times3)); + Assert.True(interactionSys.InRangeUnobstructed(mapCoordinates, origin, InteractionRangeDivided15Times3)); }); await server.WaitIdleAsync(); diff --git a/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs b/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs index 5812ba66d7..5c851ea2f4 100644 --- a/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs +++ b/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs @@ -1,4 +1,4 @@ -using Content.Server.AI.Utility; +using Content.Server.AI.Utility; using Content.Server.AI.WorldState.States.Inventory; using Content.Server.Storage.Components; using Content.Shared.Interaction; @@ -54,7 +54,7 @@ namespace Content.Server.AI.Operators.Inventory public override Outcome Execute(float frameTime) { - if (_target == default || !_owner.InRangeUnobstructed(_target, popup: true)) + if (_target == default || !EntitySystem.Get().InRangeUnobstructed(_owner, _target, popup: true)) { return Outcome.Failed; } diff --git a/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs b/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs index 9feb90159f..80dd03e812 100644 --- a/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs @@ -1,5 +1,6 @@ using Content.Server.CombatMode; using Content.Server.Interaction; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -34,7 +35,9 @@ namespace Content.Server.AI.Operators.Inventory return Outcome.Failed; } - if (!_owner.InRangeUnobstructed(_useTarget, popup: true)) + var interactionSystem = EntitySystem.Get(); + + if (!interactionSystem.InRangeUnobstructed(_owner, _useTarget, popup: true)) { return Outcome.Failed; } @@ -45,7 +48,6 @@ namespace Content.Server.AI.Operators.Inventory } // Click on da thing - var interactionSystem = EntitySystem.Get(); interactionSystem.AiUseInteraction(_owner, targetTransform.Coordinates, _useTarget); return Outcome.Success; diff --git a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs index fbd207ae7e..2d123aea43 100644 --- a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs +++ b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs @@ -1,4 +1,4 @@ -using Content.Server.AI.Utility; +using Content.Server.AI.Utility; using Content.Server.AI.WorldState.States.Inventory; using Content.Server.Storage.Components; using Content.Shared.Interaction; @@ -30,7 +30,7 @@ namespace Content.Server.AI.Operators.Inventory return Outcome.Success; } - if (!_owner.InRangeUnobstructed(container, popup: true)) + if (!EntitySystem.Get().InRangeUnobstructed(_owner, container.Owner, popup: true)) { return Outcome.Failed; } diff --git a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs index 8148d4308f..f5c7c101f6 100644 --- a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs @@ -1,5 +1,6 @@ using Content.Server.Hands.Components; using Content.Server.Interaction; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Item; using Robust.Shared.Containers; @@ -27,7 +28,7 @@ namespace Content.Server.AI.Operators.Inventory if (entMan.Deleted(_target) || !entMan.HasComponent(_target) || _target.IsInContainer() - || !_owner.InRangeUnobstructed(_target, popup: true)) + || !EntitySystem.Get().InRangeUnobstructed(_owner, _target, popup: true)) { return Outcome.Failed; } diff --git a/Content.Server/AI/Steering/AiSteeringSystem.cs b/Content.Server/AI/Steering/AiSteeringSystem.cs index f1539cf0c4..29c9e8420e 100644 --- a/Content.Server/AI/Steering/AiSteeringSystem.cs +++ b/Content.Server/AI/Steering/AiSteeringSystem.cs @@ -9,6 +9,7 @@ using Content.Server.AI.Pathfinding.Pathfinders; using Content.Server.CPUJob.JobQueues; using Content.Shared.Access.Systems; using Content.Shared.ActionBlocker; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -28,6 +29,7 @@ namespace Content.Server.AI.Steering [Dependency] private readonly IPauseManager _pauseManager = default!; [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; /// /// Whether we try to avoid non-blocking physics objects @@ -283,7 +285,7 @@ namespace Content.Server.AI.Steering if (targetDistance <= steeringRequest.ArrivalDistance && steeringRequest.TimeUntilInteractionCheck <= 0.0f) { if (!steeringRequest.RequiresInRangeUnobstructed || - entity.InRangeUnobstructed(steeringRequest.TargetMap, steeringRequest.ArrivalDistance, popup: true)) + _interactionSystem.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, popup: true)) { // TODO: Need cruder LOS checks for ranged weaps controller.VelocityDir = Vector2.Zero; diff --git a/Content.Server/Actions/Actions/DisarmAction.cs b/Content.Server/Actions/Actions/DisarmAction.cs index 98ebca505b..dd49625da2 100644 --- a/Content.Server/Actions/Actions/DisarmAction.cs +++ b/Content.Server/Actions/Actions/DisarmAction.cs @@ -13,7 +13,6 @@ using Content.Shared.Actions.Components; using Content.Shared.Audio; using Content.Shared.Cooldown; using Content.Shared.Database; -using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; using Content.Shared.Sound; using JetBrains.Annotations; @@ -56,7 +55,9 @@ namespace Content.Server.Actions.Actions if (attemptEvent.Cancelled) return; - if (!args.Performer.InRangeUnobstructed(args.Target)) return; + var sys = EntitySystem.Get(); + + if (!sys.InRangeUnobstructed(args.Performer, args.Target)) return; if (disarmedActs.Length == 0) { @@ -66,7 +67,7 @@ namespace Content.Server.Actions.Actions var player = actor.PlayerSession; var coordinates = entMan.GetComponent(args.Target).Coordinates; var target = args.Target; - EntitySystem.Get().HandleUseInteraction(player, coordinates, target); + sys.HandleUseInteraction(player, coordinates, target); return; } diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index 87144b4c55..240cf54a62 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -252,9 +252,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands)) return; - if (!args.User.InRangeUnobstructed(canister, SharedInteractionSystem.InteractionRange, popup: true)) - return; - if (!hands.Drop(args.Used, container)) return; diff --git a/Content.Server/Buckle/Components/BuckleComponent.cs b/Content.Server/Buckle/Components/BuckleComponent.cs index 8276cb7e97..499acb2f19 100644 --- a/Content.Server/Buckle/Components/BuckleComponent.cs +++ b/Content.Server/Buckle/Components/BuckleComponent.cs @@ -1,11 +1,10 @@ -using System; using System.Diagnostics.CodeAnalysis; using Content.Server.Hands.Components; using Content.Server.Pulling; using Content.Shared.ActionBlocker; using Content.Shared.Alert; using Content.Shared.Buckle.Components; -using Content.Shared.Interaction.Helpers; +using Content.Shared.Interaction; using Content.Shared.MobState.Components; using Content.Shared.Popups; using Content.Shared.Pulling.Components; @@ -14,14 +13,8 @@ using Content.Shared.Stunnable; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; using Robust.Shared.Player; -using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; -using Robust.Shared.ViewVariables; namespace Content.Server.Buckle.Components { @@ -146,7 +139,7 @@ namespace Content.Server.Buckle.Components var strapUid = strap.Owner; bool Ignored(EntityUid entity) => entity == Owner || entity == user || entity == strapUid; - if (!Owner.InRangeUnobstructed(strapUid, Range, predicate: Ignored, popup: true)) + if (!EntitySystem.Get().InRangeUnobstructed(Owner, strapUid, Range, predicate: Ignored, popup: true)) { return false; } @@ -288,7 +281,7 @@ namespace Content.Server.Buckle.Components return false; } - if (!user.InRangeUnobstructed(oldBuckledTo.Owner, Range, popup: true)) + if (!EntitySystem.Get().InRangeUnobstructed(user, oldBuckledTo.Owner, Range, popup: true)) { return false; } diff --git a/Content.Server/Climbing/Components/ClimbableComponent.cs b/Content.Server/Climbing/Components/ClimbableComponent.cs index 4f7b2c7526..1246c36c18 100644 --- a/Content.Server/Climbing/Components/ClimbableComponent.cs +++ b/Content.Server/Climbing/Components/ClimbableComponent.cs @@ -1,4 +1,3 @@ -using System; using Content.Server.DoAfter; using Content.Server.Popups; using Content.Shared.ActionBlocker; @@ -6,17 +5,10 @@ using Content.Shared.Body.Components; using Content.Shared.Body.Part; using Content.Shared.Climbing; using Content.Shared.DragDrop; +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; -using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; -using Robust.Shared.Maths; using Robust.Shared.Physics; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; namespace Content.Server.Climbing.Components { @@ -91,7 +83,7 @@ namespace Content.Server.Climbing.Components return false; } - if (!user.InRangeUnobstructed(target, Range)) + if (!EntitySystem.Get().InRangeUnobstructed(user, target, Range)) { reason = Loc.GetString("comp-climbable-cant-reach"); return false; @@ -135,8 +127,9 @@ namespace Content.Server.Climbing.Components bool Ignored(EntityUid entity) => entity == target || entity == user || entity == dragged; - if (!user.InRangeUnobstructed(target, Range, predicate: Ignored) || - !user.InRangeUnobstructed(dragged, Range, predicate: Ignored)) + var sys = EntitySystem.Get(); + if (!sys.InRangeUnobstructed(user, target, Range, predicate: Ignored) || + !sys.InRangeUnobstructed(user, dragged, Range, predicate: Ignored)) { reason = Loc.GetString("comp-climbable-cant-reach"); return false; diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 2a8d8c9809..ff59aa518e 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,15 +10,10 @@ using Content.Shared.Construction; using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Steps; using Content.Shared.Coordinates; -using Content.Shared.Interaction.Helpers; +using Content.Shared.Interaction; using Content.Shared.Inventory; -using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; using Robust.Shared.Players; using Robust.Shared.Timing; @@ -29,6 +23,8 @@ namespace Content.Server.Construction { [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; // --- WARNING! LEGACY CODE AHEAD! --- // This entire file contains the legacy code for initial construction. @@ -399,15 +395,23 @@ namespace Content.Server.Construction _beingBuilt[args.SenderSession].Remove(ev.Ack); } - if (!Get().CanInteract(user, null) - || !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.GetActiveHandItem == null - || !user.InRangeUnobstructed(ev.Location, ignoreInsideBlocker:constructionPrototype.CanBuildInImpassable)) + if (!_actionBlocker.CanInteract(user, null) + || !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.GetActiveHandItem == null) { Cleanup(); return; } - if(pathFind == null) + var mapPos = ev.Location.ToMap(EntityManager); + var predicate = GetPredicate(constructionPrototype.CanBuildInImpassable, mapPos); + + if (!_interactionSystem.InRangeUnobstructed(user, mapPos, predicate: predicate)) + { + Cleanup(); + return; + } + + if (pathFind == null) throw new InvalidDataException($"Can't find path from starting node to target node in construction! Recipe: {ev.PrototypeName}"); var edge = startNode.GetEdge(pathFind[0].Name); diff --git a/Content.Server/Cuffs/Components/CuffableComponent.cs b/Content.Server/Cuffs/Components/CuffableComponent.cs index c0d958701f..ec5de0ac55 100644 --- a/Content.Server/Cuffs/Components/CuffableComponent.cs +++ b/Content.Server/Cuffs/Components/CuffableComponent.cs @@ -5,6 +5,7 @@ using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Shared.Alert; using Content.Shared.Cuffs.Components; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; using Robust.Server.GameObjects; @@ -96,7 +97,7 @@ namespace Content.Server.Cuffs.Components return false; } - if (!handcuff.InRangeUnobstructed(Owner)) + if (!EntitySystem.Get().InRangeUnobstructed(handcuff, Owner)) { Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!"); return true; @@ -210,7 +211,7 @@ namespace Content.Server.Cuffs.Components return; } - if (!isOwner && !user.InRangeUnobstructed(Owner)) + if (!isOwner && !EntitySystem.Get().InRangeUnobstructed(user, Owner)) { user.PopupMessage(Loc.GetString("cuffable-component-cannot-remove-cuffs-too-far-message")); return; diff --git a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs index eb27670d1f..2c2d8aa3cb 100644 --- a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs +++ b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs @@ -1,6 +1,7 @@ using Content.Server.DoAfter; using Content.Server.Engineering.Components; using Content.Server.Hands.Components; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Item; using Content.Shared.Verbs; @@ -10,6 +11,8 @@ namespace Content.Server.Engineering.EntitySystems [UsedImplicitly] public sealed class DisassembleOnAltVerbSystem : EntitySystem { + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + public override void Initialize() { base.Initialize(); @@ -18,7 +21,7 @@ namespace Content.Server.Engineering.EntitySystems } private void AddDisassembleVerb(EntityUid uid, DisassembleOnAltVerbComponent component, GetVerbsEvent args) { - if (!args.CanInteract) + if (!args.CanInteract || !args.CanAccess) return; AlternativeVerb verb = new() @@ -39,8 +42,6 @@ namespace Content.Server.Engineering.EntitySystems return; if (string.IsNullOrEmpty(component.Prototype)) return; - if (!user.InRangeUnobstructed(target)) - return; if (component.DoAfterTime > 0 && TryGet(out var doAfterSystem)) { diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index ee0dc17e10..618bf7f96f 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -37,7 +37,7 @@ namespace Content.Server.Engineering.EntitySystems bool IsTileClear() { - return tileRef.Tile.IsEmpty == false && args.User.InRangeUnobstructed(args.ClickLocation, popup: true); + return tileRef.Tile.IsEmpty == false; } if (!IsTileClear()) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index a37725516c..8ecf1d6993 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Explosion.Components; using Content.Shared.Acts; using Content.Shared.Camera; using Content.Shared.Database; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Maps; using Content.Shared.Physics; @@ -27,6 +28,8 @@ namespace Content.Server.Explosion.EntitySystems { public sealed class ExplosionSystem : EntitySystem { + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + /// /// Distance used for camera shake when distance from explosion is (0.0, 0.0). /// Avoids getting NaN values down the line from doing math on (0.0, 0.0). @@ -174,7 +177,7 @@ namespace Content.Server.Explosion.EntitySystems var epicenterMapPos = epicenter.ToMap(EntityManager); foreach (var (entity, distance) in impassableEntities) { - if (!entity.InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: true, predicate: IgnoreExplosivePassable)) + if (!_interactionSystem.InRangeUnobstructed(epicenterMapPos, entity, maxRange, predicate: IgnoreExplosivePassable)) { continue; } @@ -186,7 +189,7 @@ namespace Content.Server.Explosion.EntitySystems // there are probably more ExplosivePassable entities around foreach (var (entity, distance) in nonImpassableEntities) { - if (!entity.InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: true, predicate: IgnoreExplosivePassable)) + if (!_interactionSystem.InRangeUnobstructed(epicenterMapPos, entity, maxRange, predicate: IgnoreExplosivePassable)) { continue; } @@ -235,7 +238,7 @@ namespace Content.Server.Explosion.EntitySystems continue; } - if (!tileLoc.ToMap(EntityManager).InRangeUnobstructed(epicenterMapPos, maxRange, ignoreInsideBlocker: false, predicate: IgnoreExplosivePassable)) + if (!_interactionSystem.InRangeUnobstructed(tileLoc.ToMap(EntityManager), epicenterMapPos, maxRange, predicate: IgnoreExplosivePassable)) { continue; } diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index 53bf9c0c77..d893d59bab 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -28,6 +28,7 @@ namespace Content.Server.Flash [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; public override void Initialize() { @@ -153,7 +154,7 @@ namespace Content.Server.Flash foreach (var entity in flashableEntities) { // Check for unobstructed entities while ignoring the mobs with flashable components. - if (!transform.InRangeUnobstructed(entity, range, CollisionGroup.Opaque, (e) => flashableEntities.Contains(e))) + if (!_interactionSystem.InRangeUnobstructed(entity, transform.MapPosition, range, CollisionGroup.Opaque, (e) => flashableEntities.Contains(e))) continue; Flash(entity, user, source, duration, slowTo, displayPopup); diff --git a/Content.Server/Fluids/Components/BucketComponent.cs b/Content.Server/Fluids/Components/BucketComponent.cs index 72979e46de..66e463e28a 100644 --- a/Content.Server/Fluids/Components/BucketComponent.cs +++ b/Content.Server/Fluids/Components/BucketComponent.cs @@ -85,7 +85,7 @@ namespace Content.Server.Fluids.Components _currentlyUsing.Remove(eventArgs.Using); if (result == DoAfterStatus.Cancelled || _entMan.Deleted(Owner) || mopComponent.Deleted || - CurrentVolume <= 0 || !Owner.InRangeUnobstructed(mopComponent.Owner)) + CurrentVolume <= 0 || !EntitySystem.Get().InRangeUnobstructed(Owner, mopComponent.Owner)) return false; //Checks if the mop is empty diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 34f752d893..575d5f7545 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -110,7 +110,8 @@ namespace Content.Server.Interaction // must be in range of both the target and the object they are drag / dropping // Client also does this check but ya know we gotta validate it. - if (!interactionArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) + if (!InRangeUnobstructed(interactionArgs.User, interactionArgs.Dragged, popup: true) + || !InRangeUnobstructed(interactionArgs.User, interactionArgs.Target, popup: true)) return; // trigger dragdrops on the dropped entity @@ -311,7 +312,11 @@ namespace Content.Server.Interaction } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. - if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true)) + var unobstructed = (target == null) + ? InRangeUnobstructed(user, coordinates) + : InRangeUnobstructed(user, target.Value); + + if (!unobstructed) return; } diff --git a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs index 328c2ac08e..0bdc60c085 100644 --- a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs +++ b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Content.Server.Hands.Components; using Content.Server.Interaction; @@ -125,7 +125,6 @@ namespace Content.Server.MachineLinking.System !EntityManager.TryGetComponent(msg.Session.AttachedEntity, out HandsComponent? hands) || !hands.TryGetActiveHeldEntity(out var heldEntity) || !EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent) || - !_interaction.InRangeUnobstructed(attached, component.Owner, ignoreInsideBlocker: true) || !signalLinkerComponent.Port.HasValue || !signalLinkerComponent.Port.Value.transmitter.Outputs.ContainsPort(signalLinkerComponent.Port .Value.port) || !component.Inputs.ContainsPort(portSelected.Port)) @@ -167,8 +166,7 @@ namespace Content.Server.MachineLinking.System if (msg.Session.AttachedEntity == default || !EntityManager.TryGetComponent(msg.Session.AttachedEntity, out HandsComponent? hands) || !hands.TryGetActiveHeldEntity(out var heldEntity) || - !EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent) || - !_interaction.InRangeUnobstructed(attached, component.Owner, ignoreInsideBlocker: true)) + !EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent)) return; LinkerSaveInteraction(attached, signalLinkerComponent, component, portSelected.Port); diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index fe973645c9..3c0d7c1c2f 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -19,6 +19,7 @@ public sealed class HealingSystem : EntitySystem [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly StackSystem _stacks = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; public override void Initialize() { @@ -87,7 +88,7 @@ public sealed class HealingSystem : EntitySystem return; if (user != target && - !user.InRangeUnobstructed(target, ignoreInsideBlocker: true, popup: true)) + !_interactionSystem.InRangeUnobstructed(user, target, popup: true)) { return; } diff --git a/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs b/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs index 065b6cf844..2fa6d0ac6c 100644 --- a/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs +++ b/Content.Server/Morgue/Components/MorgueEntityStorageComponent.cs @@ -79,7 +79,7 @@ namespace Content.Server.Morgue.Components public override bool CanOpen(EntityUid user, bool silent = false) { - if (!Owner.InRangeUnobstructed( + if (!EntitySystem.Get().InRangeUnobstructed(Owner, _entMan.GetComponent(Owner).Coordinates.Offset(_entMan.GetComponent(Owner).LocalRotation.GetCardinalDir()), collisionMask: CollisionGroup.Impassable | CollisionGroup.VaultImpassable )) diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 9387b712ac..13bfe95f6d 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -41,6 +41,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedAdminLogSystem _logSystem = default!; [Dependency] private readonly SpillableSystem _spillableSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; public override void Initialize() { @@ -212,7 +213,7 @@ namespace Content.Server.Nutrition.EntitySystems if (_foodSystem.IsMouthBlocked(target, user)) return true; - if (!user.InRangeUnobstructed(drink.Owner, popup: true)) + if (!_interactionSystem.InRangeUnobstructed(user, drink.Owner, popup: true)) return true; var forceDrink = user != target; diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 1c62e00a5d..ed63c333b4 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -14,13 +14,9 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; using Content.Shared.MobState.Components; using Content.Shared.Verbs; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Player; using Robust.Shared.Utility; using Content.Shared.Inventory; @@ -41,6 +37,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedAdminLogSystem _logSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; public override void Initialize() { @@ -111,7 +108,7 @@ namespace Content.Server.Nutrition.EntitySystems if (!TryGetRequiredUtensils(user, food, out var utensils)) return false; - if (!user.InRangeUnobstructed(food.Owner, popup: true)) + if (!_interactionSystem.InRangeUnobstructed(user, food.Owner, popup: true)) return true; var forceFeed = user != target; diff --git a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs index 6a0d0f6d80..fd9177542e 100644 --- a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs @@ -19,6 +19,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly FoodSystem _foodSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; public override void Initialize() { @@ -51,7 +52,7 @@ namespace Content.Server.Nutrition.EntitySystems return false; } - if (!user.InRangeUnobstructed(target, popup: true)) + if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true)) return false; return _foodSystem.TryFeed(user, target, food); diff --git a/Content.Server/RCD/Systems/RCDSystem.cs b/Content.Server/RCD/Systems/RCDSystem.cs index 31026bca73..1ba8471ea1 100644 --- a/Content.Server/RCD/Systems/RCDSystem.cs +++ b/Content.Server/RCD/Systems/RCDSystem.cs @@ -162,7 +162,7 @@ namespace Content.Server.RCD.Systems var coordinates = mapGrid.ToCoordinates(tile.GridIndices); if (coordinates == EntityCoordinates.Invalid || - !_interactionSystem.InRangeUnobstructed(eventArgs.User, coordinates, ignoreInsideBlocker: true, popup: true)) + !_interactionSystem.InRangeUnobstructed(eventArgs.User, coordinates, popup: true)) { return false; } diff --git a/Content.Server/Radio/Components/HandheldRadioComponent.cs b/Content.Server/Radio/Components/HandheldRadioComponent.cs index 4818e1c5b0..d249b248c4 100644 --- a/Content.Server/Radio/Components/HandheldRadioComponent.cs +++ b/Content.Server/Radio/Components/HandheldRadioComponent.cs @@ -77,7 +77,7 @@ namespace Content.Server.Radio.Components public bool CanListen(string message, EntityUid source) { return RadioOn && - Owner.InRangeUnobstructed(IoCManager.Resolve().GetComponent(source).Coordinates, range: ListenRange); + EntitySystem.Get().InRangeUnobstructed(Owner, source, range: ListenRange); } public void Receive(string message, int channel, EntityUid speaker) diff --git a/Content.Server/Storage/Components/ServerStorageComponent.cs b/Content.Server/Storage/Components/ServerStorageComponent.cs index 424084c0c1..af70f6e286 100644 --- a/Content.Server/Storage/Components/ServerStorageComponent.cs +++ b/Content.Server/Storage/Components/ServerStorageComponent.cs @@ -498,7 +498,7 @@ namespace Content.Server.Storage.Components break; } - if (!player.InRangeUnobstructed(Owner, popup: true)) + if (!EntitySystem.Get().InRangeUnobstructed(player, Owner, popup: true)) { break; } diff --git a/Content.Server/Tools/ToolSystem.TilePrying.cs b/Content.Server/Tools/ToolSystem.TilePrying.cs index 83509d7e9e..7240ac4ba9 100644 --- a/Content.Server/Tools/ToolSystem.TilePrying.cs +++ b/Content.Server/Tools/ToolSystem.TilePrying.cs @@ -11,6 +11,8 @@ namespace Content.Server.Tools; public sealed partial class ToolSystem { + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + private void InitializeTilePrying() { SubscribeLocalEvent(OnTilePryingAfterInteract); @@ -50,7 +52,7 @@ public sealed partial class ToolSystem var coordinates = mapGrid.GridTileToLocal(tile.GridIndices); - if (!user.InRangeUnobstructed(coordinates, popup: false)) + if (!_interactionSystem.InRangeUnobstructed(user, coordinates, popup: false)) return false; var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId]; diff --git a/Content.Server/WireHacking/WiresComponent.cs b/Content.Server/WireHacking/WiresComponent.cs index 54af7dbd26..7e8021420c 100644 --- a/Content.Server/WireHacking/WiresComponent.cs +++ b/Content.Server/WireHacking/WiresComponent.cs @@ -332,7 +332,7 @@ namespace Content.Server.WireHacking return false; } - if (!user.InRangeUnobstructed(Owner)) + if (!EntitySystem.Get().InRangeUnobstructed(user, Owner)) { Owner.PopupMessage(user, Loc.GetString("wires-component-ui-on-receive-message-cannot-reach")); return false; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs index 6cdabf7b15..b640a9c9c6 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs @@ -1,9 +1,5 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; -using Content.Shared.ActionBlocker; using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; @@ -21,8 +17,6 @@ public sealed class ArtifactInteractionTriggerSystem : EntitySystem { if (args.Handled) return; - if (!args.InRangeUnobstructed()) - return; args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User); } diff --git a/Content.Shared/Buckle/Components/SharedStrapComponent.cs b/Content.Shared/Buckle/Components/SharedStrapComponent.cs index 9f2433aaab..d19ea58961 100644 --- a/Content.Shared/Buckle/Components/SharedStrapComponent.cs +++ b/Content.Shared/Buckle/Components/SharedStrapComponent.cs @@ -1,5 +1,6 @@ using System; using Content.Shared.DragDrop; +using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; @@ -34,7 +35,7 @@ namespace Content.Shared.Buckle.Components if (!IoCManager.Resolve().TryGetComponent(eventArgs.Dragged, out SharedBuckleComponent? buckleComponent)) return false; bool Ignored(EntityUid entity) => entity == eventArgs.User || entity == eventArgs.Dragged || entity == eventArgs.Target; - return eventArgs.Target.InRangeUnobstructed(eventArgs.Dragged, buckleComponent.Range, predicate: Ignored); + return EntitySystem.Get().InRangeUnobstructed(eventArgs.Target, eventArgs.Dragged, buckleComponent.Range, predicate: Ignored); } public abstract bool DragDropOn(DragDropEvent eventArgs); diff --git a/Content.Shared/Construction/SharedConstructionSystem.cs b/Content.Shared/Construction/SharedConstructionSystem.cs index 8301bb2292..150ce79a01 100644 --- a/Content.Shared/Construction/SharedConstructionSystem.cs +++ b/Content.Shared/Construction/SharedConstructionSystem.cs @@ -1,13 +1,29 @@ -using System; -using Robust.Shared.GameObjects; +using System.Linq; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Serialization; +using static Content.Shared.Interaction.SharedInteractionSystem; namespace Content.Shared.Construction { public abstract class SharedConstructionSystem : EntitySystem { + [Dependency] private readonly IMapManager _mapManager = default!; + + /// + /// Get predicate for construction obstruction checks. + /// + public Ignored? GetPredicate(bool canBuildInImpassable, MapCoordinates coords) + { + if (!canBuildInImpassable) + return null; + + if (!_mapManager.TryFindGridAt(coords, out var grid)) + return null; + + var ignored = grid.GetAnchoredEntities(coords).ToHashSet(); + return e => ignored.Contains(e); + } + /// /// Sent client -> server to to tell the server that we started building /// a structure-construction. diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index 11f3fdf13d..5ddc08a2b9 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -65,7 +65,7 @@ namespace Content.Shared.Examine if (EntityManager.TryGetComponent(examiner, out MobStateComponent mobState) && mobState.IsIncapacitated()) return false; - if (!_interactionSystem.InRangeUnobstructed(examiner, entity, ExamineDetailsRange, ignoreInsideBlocker: true)) + if (!_interactionSystem.InRangeUnobstructed(examiner, entity, ExamineDetailsRange)) return false; // Is the target hidden in a opaque locker or something? Currently this check allows players to examine diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index d1de5c9d52..a18f0e9a18 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -387,7 +387,8 @@ namespace Content.Shared.Hands.Components target = new MapCoordinates(origin.Position + dropVector, target.MapId); } - var dropLength = EntitySystem.Get().UnobstructedDistance(origin, target, ignoredEnt: Owner); + + var dropLength = EntitySystem.Get().UnobstructedDistance(origin, target, predicate: e => e == Owner); if (dropLength < requestedDropDistance) return origin.Position + dropVector.Normalized * dropLength; diff --git a/Content.Shared/Interaction/Helpers/SharedUnobstructedExtensions.cs b/Content.Shared/Interaction/Helpers/SharedUnobstructedExtensions.cs deleted file mode 100644 index b226e49aaf..0000000000 --- a/Content.Shared/Interaction/Helpers/SharedUnobstructedExtensions.cs +++ /dev/null @@ -1,431 +0,0 @@ -using Content.Shared.DragDrop; -using Content.Shared.Physics; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using static Content.Shared.Interaction.SharedInteractionSystem; - -namespace Content.Shared.Interaction.Helpers -{ - // TODO: Kill these with fire. - public static class SharedUnobstructedExtensions - { - private static SharedInteractionSystem SharedInteractionSystem => EntitySystem.Get(); - - #region Entities - public static bool InRangeUnobstructed( - this EntityUid origin, - EntityUid other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false, - IEntityManager? entityManager = null) - { - return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate, ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this EntityUid origin, - IComponent other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this EntityUid origin, - IContainer other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var otherEntity = other.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(origin, otherEntity, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this EntityUid origin, - EntityCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this EntityUid origin, - MapCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - #endregion - - #region Components - public static bool InRangeUnobstructed( - this IComponent origin, - EntityUid other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IComponent origin, - IComponent other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IComponent origin, - IContainer other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - var otherEntity = other.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, otherEntity, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IComponent origin, - EntityCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IComponent origin, - MapCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - #endregion - - #region Containers - public static bool InRangeUnobstructed( - this IContainer origin, - EntityUid other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this IContainer origin, - IComponent other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IContainer origin, - IContainer other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - var otherEntity = other.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, otherEntity, range, collisionMask, - predicate, ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IContainer origin, - EntityCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - - public static bool InRangeUnobstructed( - this IContainer origin, - MapCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var originEntity = origin.Owner; - - return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate, - ignoreInsideBlocker, popup); - } - #endregion - - #region EntityCoordinates - public static bool InRangeUnobstructed( - this EntityCoordinates origin, - EntityUid other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var originPosition = origin.ToMap(IoCManager.Resolve()); - var otherPosition = IoCManager.Resolve().GetComponent(other).MapPosition; - - return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, - predicate, ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this EntityCoordinates origin, - IComponent other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var originPosition = origin.ToMap(IoCManager.Resolve()); - var otherPosition = IoCManager.Resolve().GetComponent(other.Owner).MapPosition; - - return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, - predicate, ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this EntityCoordinates origin, - IContainer other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var originPosition = origin.ToMap(IoCManager.Resolve()); - var otherPosition = IoCManager.Resolve().GetComponent(other.Owner).MapPosition; - - return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, - predicate, ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this EntityCoordinates origin, - EntityCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - IEntityManager? entityManager = null) - { - entityManager ??= IoCManager.Resolve(); - - var originPosition = origin.ToMap(entityManager); - var otherPosition = other.ToMap(entityManager); - - return SharedInteractionSystem.InRangeUnobstructed(originPosition, otherPosition, range, collisionMask, - predicate, ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this EntityCoordinates origin, - MapCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - IEntityManager? entityManager = null) - { - entityManager ??= IoCManager.Resolve(); - - var originPosition = origin.ToMap(entityManager); - - return SharedInteractionSystem.InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, - ignoreInsideBlocker); - } - #endregion - - #region MapCoordinates - public static bool InRangeUnobstructed( - this MapCoordinates origin, - EntityUid other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var otherPosition = IoCManager.Resolve().GetComponent(other).MapPosition; - - return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate, - ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this MapCoordinates origin, - IComponent other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var otherPosition = IoCManager.Resolve().GetComponent(other.Owner).MapPosition; - - return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate, - ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this MapCoordinates origin, - IContainer other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - var otherPosition = IoCManager.Resolve().GetComponent(other.Owner).MapPosition; - - return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate, - ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this MapCoordinates origin, - EntityCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - IEntityManager? entityManager = null) - { - entityManager ??= IoCManager.Resolve(); - - var otherPosition = other.ToMap(entityManager); - - return SharedInteractionSystem.InRangeUnobstructed(origin, otherPosition, range, collisionMask, predicate, - ignoreInsideBlocker); - } - - public static bool InRangeUnobstructed( - this MapCoordinates origin, - MapCoordinates other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) - { - return SharedInteractionSystem.InRangeUnobstructed(origin, other, range, collisionMask, predicate, - ignoreInsideBlocker); - } - #endregion - - #region EventArgs - public static bool InRangeUnobstructed( - this ITargetedInteractEventArgs args, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - return SharedInteractionSystem.InRangeUnobstructed(args.User, args.Target, range, collisionMask, predicate, ignoreInsideBlocker, popup); - } - #endregion - - #region EntityEventArgs - public static bool InRangeUnobstructed( - this DragDropEvent args, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) - { - var user = args.User; - var dropped = args.Dragged; - var target = args.Target; - - if (!SharedInteractionSystem.InRangeUnobstructed(user, target, range, collisionMask, predicate, ignoreInsideBlocker, popup)) - return false; - - if (!SharedInteractionSystem.InRangeUnobstructed(user, dropped, range, collisionMask, predicate, ignoreInsideBlocker, popup)) - return false; - - return true; - } - #endregion - } -} diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 3fdb55a867..b4354af026 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.CombatMode; using Content.Shared.Database; using Content.Shared.Hands.Components; using Content.Shared.Input; -using Content.Shared.Interaction.Helpers; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Throwing; @@ -16,16 +15,13 @@ using Content.Shared.Timing; using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; using Robust.Shared.Input.Binding; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Players; using Robust.Shared.Serialization; +using Content.Shared.Wall; +using Content.Shared.Item; using Robust.Shared.Player; #pragma warning disable 618 @@ -38,6 +34,7 @@ namespace Content.Shared.Interaction [UsedImplicitly] public abstract class SharedInteractionSystem : EntitySystem { + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!; @@ -87,7 +84,7 @@ namespace Content.Shared.Interaction return; } - if (!InRangeUnobstructed(user, ev.Target, ignoreInsideBlocker: true)) + if (!InRangeUnobstructed(user, ev.Target)) { ev.Cancel(); return; @@ -190,12 +187,9 @@ namespace Content.Shared.Interaction if (!TryComp(user, out SharedHandsComponent? hands) || !hands.TryGetActiveHand(out hand)) return; - // ^ In future, things like looking at a UI & opening doors (i.e., Activate interactions) shouldn't neccesarily require hands. - // But that would first involve some work with BUIs & making sure other activate-interactions check hands if they are required. - - // Check range - // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something. - var inRangeUnobstructed = !checkAccess || user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true); + var inRangeUnobstructed = target == null + ? !checkAccess || InRangeUnobstructed(user, coordinates) + : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // empty-hand interactions if (hand.HeldEntity == null) @@ -289,30 +283,6 @@ namespace Content.Shared.Interaction return (rayResults[0].HitPos - origin.Position).Length; } - /// - /// Traces a ray from coords to otherCoords and returns the length - /// of the vector between coords and the ray's first hit. - /// - /// Set of coordinates to use. - /// Other set of coordinates to use. - /// The mask to check for collisions - /// - /// The entity to be ignored when checking for collisions. - /// - /// Length of resulting ray. - public float UnobstructedDistance( - MapCoordinates origin, - MapCoordinates other, - int collisionMask = (int) CollisionGroup.Impassable, - EntityUid? ignoredEnt = null) - { - var predicate = ignoredEnt == null - ? null - : (Ignored) (e => e == ignoredEnt); - - return UnobstructedDistance(origin, other, collisionMask, predicate); - } - /// /// Checks that these coordinates are within a certain distance without any /// entity that matches the collision mask obstructing them. @@ -330,14 +300,6 @@ namespace Content.Shared.Interaction /// A predicate to check whether to ignore an entity or not. /// If it returns true, it will be ignored. /// - /// - /// If true and or 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. - /// /// /// True if the two points are within a given range without being obstructed. /// @@ -346,8 +308,7 @@ namespace Content.Shared.Interaction MapCoordinates other, float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false) + Ignored? predicate = null) { // Have to be on same map regardless. if (other.MapId != origin.MapId) return false; @@ -371,29 +332,7 @@ namespace Content.Shared.Interaction var ray = new CollisionRay(origin.Position, dir.Normalized, (int) collisionMask); var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); - if (rayResults.Count == 0) return true; - - // TODO: Wot? This should just be in the predicate. - if (!ignoreInsideBlocker) return false; - - foreach (var result in rayResults) - { - if (!TryComp(result.HitEntity, out IPhysBody? p)) - { - continue; - } - - var bBox = p.GetWorldAABB(); - - if (bBox.Contains(other.Position)) - { - continue; - } - - return false; - } - - return true; + return rayResults.Count == 0; } /// @@ -401,6 +340,8 @@ namespace Content.Shared.Interaction /// entity that matches the collision mask obstructing them. /// If the is zero or negative, /// this method will only check if nothing obstructs the two entities. + /// This function will also check whether the other entity is a wall-mounted entity. If it is, it will + /// automatically ignore some obstructions. /// /// The first entity to use. /// Other entity to use. @@ -412,14 +353,6 @@ namespace Content.Shared.Interaction /// A predicate to check whether to ignore an entity or not. /// If it returns true, it will be ignored. /// - /// - /// If true and or 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. - /// /// /// Whether or not to popup a feedback message on the origin entity for /// it to see. @@ -433,55 +366,92 @@ namespace Content.Shared.Interaction float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, Ignored? predicate = null, - bool ignoreInsideBlocker = false, bool popup = false) { - predicate ??= e => e == origin || e == other; - return InRangeUnobstructed(origin, Transform(other).MapPosition, range, collisionMask, predicate, ignoreInsideBlocker, popup); + var originPosition = Transform(origin).MapPosition; + var transform = Transform(other); + var (position, rotation) = transform.GetWorldPositionRotation(); + var mapPos = new MapCoordinates(position, transform.MapID); + var wallPredicate = AddAnchoredPredicate(other, mapPos, rotation, originPosition); + + Ignored combinedPredicate = e => + { + return e == origin + || e == other + || (predicate?.Invoke(e) ?? false) + || (wallPredicate?.Invoke(e) ?? false); + }; + + var inRange = InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate, popup); + + if (!inRange && popup) + { + var message = Loc.GetString("interaction-system-user-interaction-cannot-reach"); + _popupSystem.PopupEntity(message, origin, Filter.Entities(origin)); + } + + return inRange; + } + + public bool InRangeUnobstructed( + MapCoordinates origin, + EntityUid target, + float range = InteractionRange, + CollisionGroup collisionMask = CollisionGroup.Impassable, + Ignored? predicate = null) + { + var transform = Transform(target); + var (position, rotation) = transform.GetWorldPositionRotation(); + var mapPos = new MapCoordinates(position, transform.MapID); + + var wallPredicate = AddAnchoredPredicate(target, mapPos, rotation, origin); + Ignored combinedPredicate = e => + { + return e == target + || (predicate?.Invoke(e) ?? false) + || (wallPredicate?.Invoke(e) ?? false); + }; + + return InRangeUnobstructed(origin, mapPos, range, collisionMask, combinedPredicate); } /// - /// Checks that an entity and a component are within a certain - /// distance without any entity that matches the collision mask - /// obstructing them. - /// If the is zero or negative, - /// this method will only check if nothing obstructs the entity and component. + /// If the target entity is either an item or a wall-mounted object, this will add a predicate to ignore any + /// anchored entities on that tile. /// - /// The entity to use. - /// The component to use. - /// - /// Maximum distance between the entity and component. - /// - /// The mask to check for collisions. - /// - /// A predicate to check whether to ignore an entity or not. - /// If it returns true, it will be ignored. - /// - /// - /// If true and or 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. - /// - /// - /// Whether or not to popup a feedback message on the origin entity for - /// it to see. - /// - /// - /// True if the two points are within a given range without being obstructed. - /// - public bool InRangeUnobstructed( - EntityUid origin, - IComponent other, - float range = InteractionRange, - CollisionGroup collisionMask = CollisionGroup.Impassable, - Ignored? predicate = null, - bool ignoreInsideBlocker = false, - bool popup = false) + public Ignored? AddAnchoredPredicate( + EntityUid target, + MapCoordinates targetPosition, + Angle targetRotation, + MapCoordinates origin) { - return InRangeUnobstructed(origin, other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup); + if (!_mapManager.TryFindGridAt(targetPosition, out var grid)) + return null; + + if (HasComp(target)) + { + // Ignore anchored entities on that tile. + var colliding = new HashSet(grid.GetAnchoredEntities(targetPosition)); + return e => colliding.Contains(e); + } + + if (!TryComp(target, out WallMountComponent? wallMount)) + return null; + + // wall-mount exemptions may be restricted to a specific angle range. + if (wallMount.Arc < 360) + { + var angle = Angle.FromWorldVec(origin.Position - targetPosition.Position); + var angleDelta = (wallMount.Direction + targetRotation - angle).Reduced().FlipPositive(); + var inArc = angleDelta < wallMount.Arc / 2 || Math.Tau - angleDelta < wallMount.Arc / 2; + + if (!inArc) + return null; + } + + // Ignore anchored entities on that tile. + var ignored = new HashSet(grid.GetAnchoredEntities(targetPosition)); + return e => ignored.Contains(e); } /// @@ -501,14 +471,6 @@ namespace Content.Shared.Interaction /// A predicate to check whether to ignore an entity or not. /// If it returns true, it will be ignored. /// - /// - /// If true and or 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. - /// /// /// Whether or not to popup a feedback message on the origin entity for /// it to see. @@ -522,10 +484,9 @@ namespace Content.Shared.Interaction float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, Ignored? predicate = null, - bool ignoreInsideBlocker = false, bool popup = false) { - return InRangeUnobstructed(origin, other.ToMap(EntityManager), range, collisionMask, predicate, ignoreInsideBlocker, popup); + return InRangeUnobstructed(origin, other.ToMap(EntityManager), range, collisionMask, predicate, popup); } /// @@ -545,14 +506,6 @@ namespace Content.Shared.Interaction /// A predicate to check whether to ignore an entity or not. /// If it returns true, it will be ignored. /// - /// - /// If true and or 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. - /// /// /// Whether or not to popup a feedback message on the origin entity for /// it to see. @@ -566,18 +519,16 @@ namespace Content.Shared.Interaction float range = InteractionRange, CollisionGroup collisionMask = CollisionGroup.Impassable, Ignored? predicate = null, - bool ignoreInsideBlocker = false, bool popup = false) { + Ignored combinedPredicatre = e => e == origin || (predicate?.Invoke(e) ?? false); var originPosition = Transform(origin).MapPosition; - predicate ??= e => e == origin; - - var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, predicate, ignoreInsideBlocker); + var inRange = InRangeUnobstructed(originPosition, other, range, collisionMask, combinedPredicatre); if (!inRange && popup) { var message = Loc.GetString("interaction-system-user-interaction-cannot-reach"); - origin.PopupMessage(message); + _popupSystem.PopupEntity(message, origin, Filter.Entities(origin)); } return inRange; @@ -690,9 +641,7 @@ namespace Content.Shared.Interaction if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) return false; - - // all activates should only fire when in range / unobstructed - if (checkAccess && !InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true)) + if (checkAccess && !InRangeUnobstructed(user, used, popup: true)) return false; // Check if interacted entity is in the same container, the direct child, or direct parent of the user. diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 2b25abda1e..6a8d97131c 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -163,7 +163,7 @@ public abstract partial class InventorySystem public bool CanAccess(EntityUid actor, EntityUid target, EntityUid itemUid) { // Can the actor reach the target? - if (actor != target && !( actor.InRangeUnobstructed(target) && _containerSystem.IsInSameOrParentContainer(actor, target))) + if (actor != target && !(_interactionSystem.InRangeUnobstructed(actor, target) && _containerSystem.IsInSameOrParentContainer(actor, target))) return false; // Can the actor reach the item? diff --git a/Content.Shared/Item/SharedItemComponent.cs b/Content.Shared/Item/SharedItemComponent.cs index 145a5f35dc..44a7352708 100644 --- a/Content.Shared/Item/SharedItemComponent.cs +++ b/Content.Shared/Item/SharedItemComponent.cs @@ -101,9 +101,6 @@ namespace Content.Shared.Item { var user = eventArgs.User; - if (!user.InRangeUnobstructed(Owner, ignoreInsideBlocker: true)) - return false; - if (!_entMan.TryGetComponent(user, out SharedHandsComponent hands)) return false; diff --git a/Content.Shared/Tabletop/SharedTabletopSystem.cs b/Content.Shared/Tabletop/SharedTabletopSystem.cs index e3e9807286..7903e999b0 100644 --- a/Content.Shared/Tabletop/SharedTabletopSystem.cs +++ b/Content.Shared/Tabletop/SharedTabletopSystem.cs @@ -1,12 +1,7 @@ -using System; using Content.Shared.ActionBlocker; -using Content.Shared.Ghost; using Content.Shared.Hands.Components; -using Content.Shared.Interaction.Helpers; -using Content.Shared.MobState.Components; +using Content.Shared.Interaction; using Content.Shared.Stunnable; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -15,6 +10,7 @@ namespace Content.Shared.Tabletop public abstract class SharedTabletopSystem : EntitySystem { [Dependency] protected readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Serializable, NetSerializable] public sealed class TabletopDraggableComponentState : ComponentState @@ -49,7 +45,7 @@ namespace Content.Shared.Tabletop return false; } - return playerEntity.InRangeUnobstructed(table.Value) && _actionBlockerSystem.CanInteract(playerEntity, table); + return _interactionSystem.InRangeUnobstructed(playerEntity, table.Value) && _actionBlockerSystem.CanInteract(playerEntity, table); } protected bool StunnedOrNoHands(EntityUid playerEntity) diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 6950f1c576..2f7626a370 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -65,7 +65,7 @@ namespace Content.Shared.Verbs bool canAccess = false; if (force || target == user) canAccess = true; - else if (EntityManager.EntityExists(target) && _interactionSystem.InRangeUnobstructed(user, target, ignoreInsideBlocker: true)) + else if (EntityManager.EntityExists(target) && _interactionSystem.InRangeUnobstructed(user, target)) { if (ContainerSystem.IsInSameOrParentContainer(user, target)) canAccess = true; diff --git a/Content.Shared/Wall/WallMountComponent.cs b/Content.Shared/Wall/WallMountComponent.cs new file mode 100644 index 0000000000..b1ac696f63 --- /dev/null +++ b/Content.Shared/Wall/WallMountComponent.cs @@ -0,0 +1,26 @@ +namespace Content.Shared.Wall; + +/// +/// This component enables an entity to ignore some obstructions for interaction checks. +/// +/// +/// This will only exempt anchored entities that intersect the wall-mount. Additionally, this exemption will apply +/// in a limited arc, providing basic functionality for directional wall mounts. +/// +[RegisterComponent] +public sealed class WallMountComponent : Component +{ + /// + /// Range of angles in which the exemption applies. Bigger is more permissive. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("arc")] + public float Arc = MathF.PI; + + /// + /// The direction in which the exemption arc is facing, relative to the entity's rotation. Defaults to south. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("direction")] + public Angle Direction = Angle.Zero; +} diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index 9bea43559f..ad723b8594 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -59,6 +59,7 @@ - type: Icon sprite: Structures/Power/wall_recharger.rsi state: empty + - type: WallMount - type: Charger transferEfficiency: 0.95 chargerSlot: diff --git a/Resources/Prototypes/Entities/Structures/Power/saltern_power.yml b/Resources/Prototypes/Entities/Structures/Power/saltern_power.yml index 2bd7f4b94d..19a00c8f88 100644 --- a/Resources/Prototypes/Entities/Structures/Power/saltern_power.yml +++ b/Resources/Prototypes/Entities/Structures/Power/saltern_power.yml @@ -25,6 +25,7 @@ parent: BaseAPC id: SalternAPC components: + - type: WallMount - type: Battery maxCharge: 12000 startingCharge: 2000 diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/atmos_plaque.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/atmos_plaque.yml index d7ed55e77d..3927613afd 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/atmos_plaque.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/atmos_plaque.yml @@ -3,6 +3,7 @@ id: PlaqueAtmos name: atmos plaque components: + - type: WallMount - type: Physics - type: Fixtures fixtures: diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml index 7be976678d..21f5cf0241 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/bar_sign.yml @@ -3,6 +3,7 @@ parent: BaseStructure name: bar sign components: + - type: WallMount - type: Physics - type: Fixtures fixtures: diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/base_structuresigns.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/base_structuresigns.yml index 5a4511e3f0..5eecb71569 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/base_structuresigns.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/base_structuresigns.yml @@ -5,6 +5,8 @@ placement: mode: SnapgridCenter components: + - type: WallMount + arc: 360 - type: Clickable - type: InteractionOutline - type: Rotatable diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml index 9e165e9990..7a6924b2de 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml @@ -3,6 +3,8 @@ id: PosterBase abstract: true components: + - type: WallMount + arc: 360 - type: Sprite drawdepth: WallTops sprite: Structures/Wallmounts/posters.rsi diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml index 0cd27481f1..6dcee6b489 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml @@ -3,6 +3,7 @@ name: air alarm description: An air alarm. Alarms... air? components: + - type: WallMount - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DeviceNetworkComponent diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/extinguisher_cabinet.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/extinguisher_cabinet.yml index 13f264300d..d3c32f3b8b 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/extinguisher_cabinet.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/extinguisher_cabinet.yml @@ -3,6 +3,8 @@ name: extinguisher cabinet description: A small wall mounted cabinet designed to hold a fire extinguisher. components: + - type: WallMount + arc: 360 - type: Transform anchored: true - type: Clickable diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml index 0996102d5f..20922ec2fb 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml @@ -3,6 +3,7 @@ name: fire alarm description: A fire alarm. Spicy! components: + - type: WallMount - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DeviceNetworkComponent diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/fireaxe_cabinet.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/fireaxe_cabinet.yml index 69e1562549..eee66ff269 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/fireaxe_cabinet.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/fireaxe_cabinet.yml @@ -3,6 +3,7 @@ name: fire axe cabinet description: There is a small label that reads \"For Emergency use only\" along with details for safe use of the axe. As if. components: + - type: WallMount - type: Clickable - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml index 82dd590bae..f2df3fc89b 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml @@ -4,6 +4,7 @@ name: mirror description: 'Mirror mirror on the wall , who''s the most robust of them all?' components: + - type: WallMount - type: Sprite sprite: Structures/Wallmounts/mirror.rsi state: mirror diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml index 6d84bbeb86..f59ece4535 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml @@ -3,6 +3,8 @@ name: "signal switch" description: "Its a switch for toggling power to things." components: + - type: WallMount + arc: 360 - type: Clickable - type: InteractionOutline - type: Physics @@ -30,6 +32,8 @@ name: "signal button" description: "It's a button for activating something." components: + - type: WallMount + arc: 360 - type: Clickable - type: InteractionOutline - type: Physics @@ -51,6 +55,8 @@ name: apc net switch description: Its a switch for toggling lights that are connected to the same apc. components: + - type: WallMount + arc: 360 - type: Clickable - type: InteractionOutline - type: Physics diff --git a/Resources/Prototypes/Entities/Structures/Windows/window.yml b/Resources/Prototypes/Entities/Structures/Windows/window.yml index 2f44af65b2..fde55af0c2 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/window.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/window.yml @@ -8,6 +8,8 @@ snap: - Window components: + - type: WallMount + arc: 360 # interact despite grilles - type: Tag tags: - RCDDeconstructWhitelist @@ -92,6 +94,8 @@ snap: - Window components: + - type: WallMount + arc: 360 # interact despite grilles # Attention! If adding tags here: # Keep WindowTintedDirectional in mind - type: Tag