using System; using System.Collections.Generic; using System.Linq; using Content.Client.Clickable; using Robust.Client.GameObjects; using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; using Robust.Client.UserInterface; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Timing; namespace Content.Client.Viewport { // OH GOD. // Ok actually it's fine. // Instantiated dynamically through the StateManager, Dependencies will be resolved. [Virtual] public class GameScreenBase : State, IEntityEventSubscriber { [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] protected readonly IUserInterfaceManager UserInterfaceManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; private ClickableEntityComparer _comparer = default!; public override void Startup() { _inputManager.KeyBindStateChanged += OnKeyBindStateChanged; _comparer = new ClickableEntityComparer(_entityManager); } public override void Shutdown() { _inputManager.KeyBindStateChanged -= OnKeyBindStateChanged; } public EntityUid? GetEntityUnderPosition(MapCoordinates coordinates) { var entitiesUnderPosition = GetEntitiesUnderPosition(coordinates); return entitiesUnderPosition.Count > 0 ? entitiesUnderPosition[0] : null; } public IList GetEntitiesUnderPosition(EntityCoordinates coordinates) { return GetEntitiesUnderPosition(coordinates.ToMap(_entityManager)); } public IList GetEntitiesUnderPosition(MapCoordinates coordinates) { // Find all the entities intersecting our click var entities = EntitySystem.Get().GetEntitiesIntersecting(coordinates.MapId, Box2.CenteredAround(coordinates.Position, (1, 1))); var containerSystem = _entitySystemManager.GetEntitySystem(); // Check the entities against whether or not we can click them var foundEntities = new List<(EntityUid clicked, int drawDepth, uint renderOrder)>(); foreach (var entity in entities) { if (_entityManager.TryGetComponent(entity, out var component) && !containerSystem.IsEntityInContainer(entity) && component.CheckClick(coordinates.Position, out var drawDepthClicked, out var renderOrder)) { foundEntities.Add((entity, drawDepthClicked, renderOrder)); } } if (foundEntities.Count == 0) return Array.Empty(); 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 readonly IEntityManager _entities; public ClickableEntityComparer(IEntityManager entities) { _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) { return val; } // 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; } */ var transX = _entities.GetComponent(x.clicked); var transY = _entities.GetComponent(y.clicked); val = transX.Coordinates.Y.CompareTo(transY.Coordinates.Y); if (val != 0) { return val; } return x.clicked.CompareTo(y.clicked); } } /// /// Converts a state change event from outside the simulation to inside the simulation. /// /// Event data values for a bound key state change. protected virtual void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args) { // If there is no InputSystem, then there is nothing to forward to, and nothing to do here. if(!_entitySystemManager.TryGetEntitySystem(out InputSystem? inputSys)) return; var kArgs = args.KeyEventArgs; var func = kArgs.Function; var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func); EntityCoordinates coordinates = default; EntityUid? entityToClick = null; if (args.Viewport is IViewportControl vp) { var mousePosWorld = vp.ScreenToMap(kArgs.PointerLocation.Position); entityToClick = GetEntityUnderPosition(mousePosWorld); coordinates = _mapManager.TryFindGridAt(mousePosWorld, out var grid) ? grid.MapToGrid(mousePosWorld) : EntityCoordinates.FromMap(_mapManager, mousePosWorld); } var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, kArgs.State, coordinates , kArgs.PointerLocation, entityToClick ?? default); // TODO make entityUid nullable // client side command handlers will always be sent the local player session. var session = _playerManager.LocalPlayer?.Session; if (inputSys.HandleInputCommand(session, func, message)) { kArgs.Handle(); } } } }