using System.Numerics; using Content.Client.Eye; using Content.Shared.Administration; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.Timing; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; namespace Content.Client.Administration.UI.AdminCamera; [GenerateTypedNameReferences] public sealed partial class AdminCameraControl : Control { [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IClientGameTiming _timing = default!; public event Action? OnFollow; public event Action? OnPopoutControl; private readonly EyeLerpingSystem _eyeLerpingSystem; private readonly FixedEye _defaultEye = new(); private AdminCameraEuiState? _nextState; private const float MinimumZoom = 0.1f; private const float MaximumZoom = 2.0f; public EntityUid? CurrentCamera; public float Zoom = 1.0f; public bool IsPoppedOut; public AdminCameraControl() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _eyeLerpingSystem = _entManager.System(); CameraView.Eye = _defaultEye; FollowButton.OnPressed += _ => OnFollow?.Invoke(); PopControl.OnPressed += _ => OnPopoutControl?.Invoke(); CameraView.OnResized += OnResized; } private new void OnResized() { var width = Math.Max(CameraView.PixelWidth, (int)Math.Floor(CameraView.MinWidth)); var height = Math.Max(CameraView.PixelHeight, (int)Math.Floor(CameraView.MinHeight)); CameraView.ViewportSize = new Vector2i(width, height); } protected override void MouseWheel(GUIMouseWheelEventArgs args) { base.MouseWheel(args); if (CameraView.Eye == null) return; Zoom = Math.Clamp(Zoom - args.Delta.Y * 0.15f * Zoom, MinimumZoom, MaximumZoom); CameraView.Eye.Zoom = new Vector2(Zoom, Zoom); args.Handle(); } public void SetState(AdminCameraEuiState state) { _nextState = state; } // I know that this is awful, but I copied this from the solution editor anyways. // This is needed because EUIs update before the gamestate is applied, which means it will fail to get the uid from the net entity. // The suggestion from the comment in the solution editor saying to use a BUI is not ideal either: // - We would need to bind the UI to an entity, but with how BUIs currently work we cannot open it in the same tick as we spawn that entity on the server. // - We want the UI opened by the user session, not by their currently attached entity. Otherwise it would close in cases where admins move from one entity to another, for example when ghosting. protected override void FrameUpdate(FrameEventArgs args) { if (_nextState == null || _timing.LastRealTick < _nextState.Tick) // make sure the last gamestate has been applied return; if (!_entManager.TryGetEntity(_nextState.Camera, out var cameraUid)) return; if (CurrentCamera == null) { _eyeLerpingSystem.AddEye(cameraUid.Value); CurrentCamera = cameraUid; } else if (CurrentCamera != cameraUid) { _eyeLerpingSystem.RemoveEye(CurrentCamera.Value); _eyeLerpingSystem.AddEye(cameraUid.Value); CurrentCamera = cameraUid; } if (_entManager.TryGetComponent(CurrentCamera, out var eye)) CameraView.Eye = eye.Eye ?? _defaultEye; } }