diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs index 231972e0a7..2c09798c1c 100644 --- a/Content.Client/Clickable/ClickableComponent.cs +++ b/Content.Client/Clickable/ClickableComponent.cs @@ -9,8 +9,6 @@ namespace Content.Client.Clickable public sealed class ClickableComponent : Component { [Dependency] private readonly IClickMapManager _clickMapManager = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; - [Dependency] private readonly IEntityManager _entMan = default!; [DataField("bounds")] public DirBoundData? Bounds; @@ -23,30 +21,31 @@ namespace Content.Client.Clickable /// The draw depth for the sprite that captured the click. /// /// True if the click worked, false otherwise. - public bool CheckClick(Vector2 worldPos, out int drawDepth, out uint renderOrder) + public bool CheckClick(SpriteComponent sprite, EntityQuery xformQuery, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom) { - if (!_entMan.TryGetComponent(Owner, out SpriteComponent? sprite) || !sprite.Visible) + if (!sprite.Visible || !xformQuery.TryGetComponent(sprite.Owner, out var transform)) { drawDepth = default; renderOrder = default; + bottom = default; return false; } drawDepth = sprite.DrawDepth; renderOrder = sprite.RenderOrder; - - var transform = _entMan.GetComponent(Owner); - var worldRot = transform.WorldRotation; + var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery); + var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye); + bottom = spriteBB.CalcBoundingBox().Bottom; var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix()); // This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites. - var relativeRotation = (worldRot + _eyeManager.CurrentEye.Rotation).Reduced().FlipPositive(); + var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive(); Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero; // First we get `localPos`, the clicked location in the sprite-coordinate frame. - var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -_eyeManager.CurrentEye.Rotation : worldRot - cardinalSnapping); + var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos)); // Check explicitly defined click-able bounds @@ -70,10 +69,10 @@ namespace Content.Client.Clickable } // Either we weren't clicking on the texture, or there wasn't one. In which case: check the RSI next - if (layer.State == null || layer.ActualRsi is not RSI rsi || !rsi.TryGetState(layer.State, out var rsiState)) + if (layer.ActualRsi is not { } rsi || !rsi.TryGetState(layer.State, out var rsiState)) continue; - var dir = SpriteComponent.Layer.GetDirection(rsiState.Directions, relativeRotation); + var dir = Layer.GetDirection(rsiState.Directions, relativeRotation); // convert to layer-local coordinates layer.GetLayerDrawMatrix(dir, out var matrix); @@ -95,6 +94,7 @@ namespace Content.Client.Clickable drawDepth = default; renderOrder = default; + bottom = default; return false; } diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs index cb65ece0fc..727eb2b299 100644 --- a/Content.Client/Gameplay/GameplayStateBase.cs +++ b/Content.Client/Gameplay/GameplayStateBase.cs @@ -4,6 +4,7 @@ using System.Linq; using Content.Client.Clickable; using Content.Client.ContextMenu.UI; using Robust.Client.GameObjects; +using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; @@ -22,6 +23,7 @@ namespace Content.Client.Gameplay [Virtual] public class GameplayStateBase : State, IEntityEventSubscriber { + [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; @@ -63,7 +65,7 @@ namespace Content.Client.Gameplay { _vvm.RegisterDomain("enthover", ResolveVVHoverObject, ListVVHoverPaths); _inputManager.KeyBindStateChanged += OnKeyBindStateChanged; - _comparer = new ClickableEntityComparer(_entityManager); + _comparer = new ClickableEntityComparer(); } protected override void Shutdown() @@ -90,13 +92,21 @@ namespace Content.Client.Gameplay Box2.CenteredAround(coordinates.Position, (1, 1)), LookupFlags.Uncontained | LookupFlags.Approximate); // Check the entities against whether or not we can click them - var foundEntities = new List<(EntityUid clicked, int drawDepth, uint renderOrder)>(); + var foundEntities = new List<(EntityUid clicked, int drawDepth, uint renderOrder, float bottom)>(); + var clickQuery = _entityManager.GetEntityQuery(); + var metaQuery = _entityManager.GetEntityQuery(); + var spriteQuery = _entityManager.GetEntityQuery(); + var xformQuery = _entityManager.GetEntityQuery(); + // TODO: Smelly + var eye = _eyeManager.CurrentEye; + foreach (var entity in entities) { - if (_entityManager.TryGetComponent(entity, out var component) - && component.CheckClick(coordinates.Position, out var drawDepthClicked, out var renderOrder)) + if (clickQuery.TryGetComponent(entity, out var component) && + spriteQuery.TryGetComponent(entity, out var sprite) && + component.CheckClick(sprite, xformQuery, coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom)) { - foundEntities.Add((entity, drawDepthClicked, renderOrder)); + foundEntities.Add((entity, drawDepthClicked, renderOrder, bottom)); } } @@ -105,46 +115,35 @@ namespace Content.Client.Gameplay foundEntities.Sort(_comparer); // 0 is the top element. - foundEntities.Reverse(); return foundEntities.Select(a => a.clicked).ToList(); } - private sealed class ClickableEntityComparer : IComparer<(EntityUid clicked, int depth, uint renderOrder)> + private sealed class ClickableEntityComparer : IComparer<(EntityUid clicked, int depth, uint renderOrder, float bottom)> { - private readonly IEntityManager _entities; - - public ClickableEntityComparer(IEntityManager entities) + public int Compare((EntityUid clicked, int depth, uint renderOrder, float bottom) x, + (EntityUid clicked, int depth, uint renderOrder, float bottom) y) { - _entities = entities; - } - - public int Compare((EntityUid clicked, int depth, uint renderOrder) x, - (EntityUid clicked, int depth, uint renderOrder) y) - { - var val = x.depth.CompareTo(y.depth); - if (val != 0) + var cmp = y.depth.CompareTo(x.depth); + if (cmp != 0) { - return val; + return cmp; } - // Turning this off it can make picking stuff out of lockers and such up a bit annoying. - /* - val = x.renderOrder.CompareTo(y.renderOrder); - if (val != 0) - { - return val; - } - */ + cmp = y.renderOrder.CompareTo(x.renderOrder); - var transX = _entities.GetComponent(x.clicked); - var transY = _entities.GetComponent(y.clicked); - val = transX.Coordinates.Y.CompareTo(transY.Coordinates.Y); - if (val != 0) + if (cmp != 0) { - return val; + return cmp; } - return x.clicked.CompareTo(y.clicked); + cmp = y.bottom.CompareTo(x.bottom); + + if (cmp != 0) + { + return cmp; + } + + return y.clicked.CompareTo(x.clicked); } } diff --git a/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs b/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs index 67e23930ca..9c3e214e19 100644 --- a/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/TetherGunSystem.cs @@ -1,8 +1,10 @@ using Content.Client.Clickable; +using Content.Client.Gameplay; using Content.Shared.Weapons.Ranged.Systems; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; +using Robust.Client.State; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Physics.Components; @@ -79,27 +81,23 @@ public sealed class TetherGunSystem : SharedTetherGunSystem if (_dragging == null) { - var bodyQuery = GetEntityQuery(); - var lowest = new List<(int DrawDepth, uint RenderOrder, EntityUid Entity)>(); + var gameState = IoCManager.Resolve().CurrentState; - foreach (var ent in _lookup.GetEntitiesIntersecting(mousePos, LookupFlags.Approximate | LookupFlags.Static)) + if (gameState is GameplayState game) { - if (!bodyQuery.HasComponent(ent) || - !TryComp(ent, out var clickable) || - !clickable.CheckClick(mousePos.Position, out var drawDepth, out var renderOrder)) continue; + EntityUid? uid; - lowest.Add((drawDepth, renderOrder, ent)); + foreach (var ent in _lookup.GetEntitiesIntersecting(mousePos, LookupFlags.Approximate | LookupFlags.Static)) + { + uid = game.GetEntityUnderPosition(mousePos); + + if (uid != null) + StartDragging(uid.Value, mousePos); + } } - lowest.Sort((x, y) => y.DrawDepth == x.DrawDepth ? y.RenderOrder.CompareTo(x.RenderOrder) : y.DrawDepth.CompareTo(x.DrawDepth)); - - foreach (var ent in lowest) - { - StartDragging(ent.Entity, mousePos); - break; - } - - if (_dragging == null) return; + if (_dragging == null) + return; } if (!TryComp(_dragging!.Value, out var xform) || diff --git a/Content.IntegrationTests/Tests/ClickableTest.cs b/Content.IntegrationTests/Tests/ClickableTest.cs index 670f1313ea..390771ce8f 100644 --- a/Content.IntegrationTests/Tests/ClickableTest.cs +++ b/Content.IntegrationTests/Tests/ClickableTest.cs @@ -53,6 +53,9 @@ namespace Content.IntegrationTests.Tests var clientEntManager = client.ResolveDependency(); var serverEntManager = server.ResolveDependency(); var eyeManager = client.ResolveDependency(); + var spriteQuery = clientEntManager.GetEntityQuery(); + var xformQuery = clientEntManager.GetEntityQuery(); + var eye = client.ResolveDependency().CurrentEye; var testMap = await PoolManager.CreateTestMap(pairTracker); await server.WaitPost(() => @@ -69,7 +72,8 @@ namespace Content.IntegrationTests.Tests await client.WaitPost(() => { - clientEntManager.GetComponent(entity).Scale = (scale, scale); + var sprite = spriteQuery.GetComponent(entity); + sprite.Scale = (scale, scale); // these tests currently all assume player eye is 0 eyeManager.CurrentEye.Rotation = 0; @@ -77,7 +81,7 @@ namespace Content.IntegrationTests.Tests var pos = clientEntManager.GetComponent(entity).WorldPosition; var clickable = clientEntManager.GetComponent(entity); - hit = clickable.CheckClick((clickPosX, clickPosY) + pos, out _, out _); + hit = clickable.CheckClick(sprite, xformQuery, (clickPosX, clickPosY) + pos, eye, out _, out _, out _); }); await server.WaitPost(() =>