#nullable enable using System; using Robust.Client.Graphics.ClientEye; using Robust.Client.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Serialization; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components { [RegisterComponent] public sealed class ClickableComponent : Component { public override string Name => "Clickable"; [Dependency] private readonly IClickMapManager _clickMapManager = default!; [ViewVariables] private DirBoundData _data = default!; public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataField(ref _data, "bounds", DirBoundData.Default); } /// /// Used to check whether a click worked. /// /// The world position that was clicked. /// /// 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) { if (!Owner.TryGetComponent(out ISpriteComponent sprite) || !sprite.Visible) { drawDepth = default; renderOrder = default; return false; } var localPos = Owner.Transform.InvWorldMatrix.Transform(worldPos); var worldRotation = Owner.Transform.WorldRotation; if (sprite.Directional) { localPos = new Angle(worldRotation).RotateVec(localPos); } else { localPos = new Angle(MathHelper.PiOver2).RotateVec(localPos); } var localOffset = localPos * EyeManager.PixelsPerMeter * (1, -1); var found = false; if (_data.All.Contains(localPos)) { found = true; } else { // TODO: diagonal support? var dir = sprite.Directional ? worldRotation.GetCardinalDir() : Direction.South; var boundsForDir = dir switch { Direction.East => _data.East, Direction.North => _data.North, Direction.South => _data.South, Direction.West => _data.West, _ => throw new InvalidOperationException() }; if (boundsForDir.Contains(localPos)) { found = true; } } if (!found) { foreach (var layer in sprite.AllLayers) { if (layer.Texture != null) { if (_clickMapManager.IsOccluding(layer.Texture, (Vector2i) (localOffset + layer.Texture.Size / 2f))) { found = true; break; } } else if (layer.RsiState != default) { var rsi = layer.ActualRsi; if (rsi == null) { continue; } var (mX, mY) = localOffset + rsi.Size / 2; var dir = layer.EffectiveDirection(worldRotation); if (_clickMapManager.IsOccluding(rsi, layer.RsiState, dir, layer.AnimationFrame, ((int) mX, (int) mY))) { found = true; break; } } } } drawDepth = sprite.DrawDepth; renderOrder = sprite.RenderOrder; return found; } private sealed class DirBoundData : IExposeData { [ViewVariables] public Box2 All; [ViewVariables] public Box2 North; [ViewVariables] public Box2 South; [ViewVariables] public Box2 East; [ViewVariables] public Box2 West; public static DirBoundData Default { get; } = new DirBoundData(); public void ExposeData(ObjectSerializer serializer) { serializer.DataField(ref All, "all", default); serializer.DataField(ref North, "north", default); serializer.DataField(ref South, "south", default); serializer.DataField(ref East, "east", default); serializer.DataField(ref West, "west", default); } } } }