using System.Numerics; using AcDream.App.Input; using AcDream.App.Interaction; using AcDream.App.Net; using AcDream.App.Rendering; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.App.UI.Testing; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Runtime; using AcDream.UI.Abstractions; using Silk.NET.Windowing; namespace AcDream.App.Composition; /// /// Early retained-UI projection over Slice J's later current-runtime seam. /// Every call captures the view and command owner together and supplies that /// exact generation, so a displaced session can never receive the command. /// internal sealed class DeferredGameRuntimeStateCommands { private readonly object _gate = new(); private IGameRuntimeView? _view; private IGameRuntimeCommands? _commands; private bool _deactivated; public bool IsInWorld { get { lock (_gate) return !_deactivated && _view?.Lifecycle.State == RuntimeLifecycleState.InWorld; } } public IDisposable Bind( IGameRuntimeView view, IGameRuntimeCommands commands) { ArgumentNullException.ThrowIfNull(view); ArgumentNullException.ThrowIfNull(commands); lock (_gate) { ObjectDisposedException.ThrowIf(_deactivated, this); if (_view is not null || _commands is not null) { throw new InvalidOperationException( "The retained-UI game-runtime command seam is already bound."); } _view = view; _commands = commands; } return new ExpectedRuntimeBinding(this, view, commands); } public RuntimeCommandResult AddShortcut(ShortcutEntry entry) => Invoke((commands, generation) => commands.InventoryState.AddShortcut( generation, new RuntimeShortcutCommand( entry.Index, entry.ObjectId, entry.SpellId))); public RuntimeCommandResult RemoveShortcut(uint index) { if (index > int.MaxValue) return CurrentResult(RuntimeCommandStatus.Rejected); return Invoke((commands, generation) => commands.InventoryState.RemoveShortcut( generation, (int)index)); } public RuntimeCommandResult AddFavorite( int tab, int position, uint spellId) => Invoke((commands, generation) => commands.Spellbook.AddFavorite( generation, tab, position, spellId)); public RuntimeCommandResult RemoveFavorite(int tab, uint spellId) => Invoke((commands, generation) => commands.Spellbook.RemoveFavorite( generation, tab, spellId)); public RuntimeCommandResult SetSpellbookFilter(uint filters) => Invoke((commands, generation) => commands.Spellbook.SetFilter( generation, filters)); public RuntimeCommandResult ForgetSpell(uint spellId) => Invoke((commands, generation) => commands.Spellbook.ForgetSpell( generation, spellId)); public RuntimeCommandResult SetDesiredComponent( uint componentId, uint amount) => Invoke((commands, generation) => commands.Spellbook.SetDesiredComponent( generation, componentId, amount)); public RuntimeCommandResult Advance( RuntimeAdvancementKind kind, uint statId, ulong cost) => Invoke((commands, generation) => commands.Character.Advance( generation, new RuntimeAdvancementCommand(kind, statId, cost))); public void Deactivate() { lock (_gate) { _deactivated = true; _view = null; _commands = null; } } private RuntimeCommandResult Invoke( Func< IGameRuntimeCommands, RuntimeGenerationToken, RuntimeCommandResult> invoke) { IGameRuntimeCommands commands; RuntimeGenerationToken generation; lock (_gate) { if (_deactivated || _view is null || _commands is null) { return new RuntimeCommandResult( RuntimeCommandStatus.Inactive, _view?.Generation ?? default); } commands = _commands; generation = _view.Generation; } return invoke(commands, generation); } private RuntimeCommandResult CurrentResult(RuntimeCommandStatus status) { lock (_gate) { return new RuntimeCommandResult( status, _view?.Generation ?? default); } } private void Release( IGameRuntimeView expectedView, IGameRuntimeCommands expectedCommands) { lock (_gate) { if (!ReferenceEquals(_view, expectedView) || !ReferenceEquals(_commands, expectedCommands)) { return; } _view = null; _commands = null; } } private sealed class ExpectedRuntimeBinding( DeferredGameRuntimeStateCommands owner, IGameRuntimeView view, IGameRuntimeCommands commands) : IDisposable { private DeferredGameRuntimeStateCommands? _owner = owner; public void Dispose() => Interlocked.Exchange(ref _owner, null)?.Release(view, commands); } } /// /// Early UI view over the later live-session owner. A binding lease clears only /// the exact owner it installed, so rollback cannot withdraw a replacement. /// internal sealed class DeferredLiveSessionUiAuthority : ILiveInWorldSource, ILiveWorldSessionSource { private readonly object _gate = new(); private ILiveUiSessionTarget? _target; private bool _deactivated; public bool IsInWorld { get { lock (_gate) return !_deactivated && _target?.IsInWorld == true; } } public WorldSession? CurrentSession { get { lock (_gate) return !_deactivated ? _target?.CurrentSession : null; } } public ICommandBus Commands { get { lock (_gate) return !_deactivated ? _target?.Commands ?? NullCommandBus.Instance : NullCommandBus.Instance; } } public string? AccountName => CurrentSession?.Characters?.AccountName; public LinkStatusSnapshot LinkStatus => CurrentSession?.LinkStatus ?? LinkStatusSnapshot.Disconnected; public IDisposable Bind(ILiveUiSessionTarget target) { ArgumentNullException.ThrowIfNull(target); lock (_gate) { ObjectDisposedException.ThrowIf(_deactivated, this); if (_target is not null) { throw new InvalidOperationException( "The retained-UI live-session authority is already bound."); } _target = target; } return new ExpectedOwnerBinding( target, Release); } public void Deactivate() { lock (_gate) { _deactivated = true; _target = null; } } public bool TryUseItem(uint guid, Action? log = null) { WorldSession? session = CurrentSession; if (!IsInWorld || session is null) return false; uint sequence = session.NextGameActionSequence(); session.SendGameAction(InteractRequests.BuildUse(sequence, guid)); log?.Invoke($"[D.5.1] toolbar use-item guid=0x{guid:X8} seq={sequence}"); return true; } private void Release(ILiveUiSessionTarget expected) { lock (_gate) { if (ReferenceEquals(_target, expected)) _target = null; } } } /// /// Selection operations mounted by retained UI before the Phase-6 world query /// and interaction controller exist. /// internal sealed class DeferredSelectionUiAuthority { private readonly object _gate = new(); private IRetainedUiSelectionQuery? _query; private SelectionInteractionController? _interactions; private bool _deactivated; public IDisposable Bind( IRetainedUiSelectionQuery query, SelectionInteractionController interactions) { ArgumentNullException.ThrowIfNull(query); ArgumentNullException.ThrowIfNull(interactions); lock (_gate) { ObjectDisposedException.ThrowIf(_deactivated, this); if (_query is not null || _interactions is not null) { throw new InvalidOperationException( "The retained-UI selection authority is already bound."); } _query = query; _interactions = interactions; } return new ExpectedSelectionBinding(this, query, interactions); } public void Deactivate() { lock (_gate) { _deactivated = true; _query = null; _interactions = null; } } public uint? PickAtCursor(bool includeSelf) { SelectionInteractionController? target; lock (_gate) target = !_deactivated ? _interactions : null; return target?.PickAtCursor(includeSelf); } public void SendUse(uint guid) { SelectionInteractionController? target; lock (_gate) target = !_deactivated ? _interactions : null; target?.SendUse(guid); } public void RequestUse(uint guid, ItemUseRequestReservation reservation) { ArgumentNullException.ThrowIfNull(reservation); SelectionInteractionController? target; lock (_gate) target = !_deactivated ? _interactions : null; if (target is null) reservation.CancelBeforeDispatch(); else target.RequestUse(guid, reservation); } public void SendPickup(uint itemGuid, uint destinationContainerId, int placement) { SelectionInteractionController? target; lock (_gate) target = !_deactivated ? _interactions : null; target?.SendPickup(itemGuid, destinationContainerId, placement); } public uint? SelectClosestCombatTarget(bool showToast) { SelectionInteractionController? target; lock (_gate) target = !_deactivated ? _interactions : null; return target?.SelectClosestCombatTarget(showToast); } public bool IsWithinExternalContainerUseRange(uint targetGuid) { IRetainedUiSelectionQuery? query; lock (_gate) query = !_deactivated ? _query : null; return query?.IsWithinExternalContainerUseRange(targetGuid) != false; } public bool ShouldShowHealth(uint guid) { IRetainedUiSelectionQuery? query; lock (_gate) query = !_deactivated ? _query : null; return query?.ShouldShowHealth(guid) == true; } public VividTargetInfo? ResolveVividTargetInfo(uint guid) { IRetainedUiSelectionQuery? query; lock (_gate) query = !_deactivated ? _query : null; return query?.ResolveVividTargetInfo(guid); } private void Release( IRetainedUiSelectionQuery expectedQuery, SelectionInteractionController expectedInteractions) { lock (_gate) { if (ReferenceEquals(_query, expectedQuery) && ReferenceEquals(_interactions, expectedInteractions)) { _query = null; _interactions = null; } } } private sealed class ExpectedSelectionBinding : IDisposable { private DeferredSelectionUiAuthority? _owner; private readonly IRetainedUiSelectionQuery _query; private readonly SelectionInteractionController _interactions; public ExpectedSelectionBinding( DeferredSelectionUiAuthority owner, IRetainedUiSelectionQuery query, SelectionInteractionController interactions) { _owner = owner; _query = query; _interactions = interactions; } public void Dispose() => Interlocked.Exchange(ref _owner, null)?.Release(_query, _interactions); } } /// Identity view plane until Phase 7 binds portal presentation. internal sealed class DeferredSelectionViewPlaneSource { private ISelectionViewPlaneSource? _target; private bool _deactivated; public IDisposable Bind(ISelectionViewPlaneSource target) { ArgumentNullException.ThrowIfNull(target); ObjectDisposedException.ThrowIf(_deactivated, this); if (_target is not null) { throw new InvalidOperationException( "The retained-UI selection view plane is already bound."); } _target = target; return new ExpectedOwnerBinding(target, Release); } public ICamera Apply(ICamera camera) => !_deactivated && _target is { } target ? target.ApplyViewPlane(camera) : camera; public void Deactivate() { _deactivated = true; _target = null; } private void Release(ISelectionViewPlaneSource expected) { if (ReferenceEquals(_target, expected)) _target = null; } } internal sealed class SelectionCameraSource { private readonly CameraController _camera; private readonly IView _window; private readonly DeferredSelectionViewPlaneSource _viewPlane; public SelectionCameraSource( CameraController camera, IView window, DeferredSelectionViewPlaneSource viewPlane) { _camera = camera ?? throw new ArgumentNullException(nameof(camera)); _window = window ?? throw new ArgumentNullException(nameof(window)); _viewPlane = viewPlane ?? throw new ArgumentNullException(nameof(viewPlane)); } public SelectionCameraSnapshot Snapshot() { ICamera camera = _viewPlane.Apply(_camera.Active); return new SelectionCameraSnapshot( camera.View, camera.Projection, new Vector2(_window.Size.X, _window.Size.Y)); } public (Matrix4x4 View, Matrix4x4 Projection, Vector2 Viewport) UiSnapshot() { SelectionCameraSnapshot snapshot = Snapshot(); return (snapshot.View, snapshot.Projection, snapshot.Viewport); } } internal sealed class DeferredRadarSnapshotSource { private RadarSnapshotProvider? _target; private bool _deactivated; public UiRadarSnapshot Snapshot() => !_deactivated && _target is { } target ? target.BuildSnapshot() : UiRadarSnapshot.Empty; public IDisposable Bind(RadarSnapshotProvider target) { ArgumentNullException.ThrowIfNull(target); ObjectDisposedException.ThrowIf(_deactivated, this); if (_target is not null) throw new InvalidOperationException("The retained-UI radar is already bound."); _target = target; return new ExpectedOwnerBinding(target, Release); } public void Deactivate() { _deactivated = true; _target = null; } private void Release(RadarSnapshotProvider expected) { if (ReferenceEquals(_target, expected)) _target = null; } } internal sealed class DeferredInventoryContainerSource { private RetailUiRuntime? _runtime; private bool _deactivated; public uint Current(uint playerGuid) => !_deactivated ? _runtime?.InventoryPanelController?.CurrentOpenContainerId ?? playerGuid : playerGuid; public IDisposable Bind(RetailUiRuntime runtime) { ArgumentNullException.ThrowIfNull(runtime); ObjectDisposedException.ThrowIf(_deactivated, this); if (_runtime is not null) throw new InvalidOperationException("The retained inventory container is already bound."); _runtime = runtime; return new ExpectedOwnerBinding(runtime, Release); } public void Deactivate() { _deactivated = true; _runtime = null; } private void Release(RetailUiRuntime expected) { if (ReferenceEquals(_runtime, expected)) _runtime = null; } } /// /// Probe scripts are mounted in Phase 5; reveal/resource facts become valid in /// Phase 7. Calls remain inert and diagnostic until that exact owner binds. /// internal sealed class DeferredWorldLifecycleAutomationRuntime : IRetailUiAutomationRuntime { private IRetailUiAutomationRuntime? _target; private bool _deactivated; public bool IsWorldReady => !_deactivated && _target?.IsWorldReady == true; public bool IsWorldViewportVisible => !_deactivated && _target?.IsWorldViewportVisible == true; public int PortalMaterializationCount => !_deactivated ? _target?.PortalMaterializationCount ?? 0 : 0; public IDisposable Bind(IRetailUiAutomationRuntime target) { ArgumentNullException.ThrowIfNull(target); ObjectDisposedException.ThrowIf(_deactivated, this); if (_target is not null) throw new InvalidOperationException("World lifecycle automation is already bound."); _target = target; return new ExpectedOwnerBinding(target, Release); } public bool TryRequestCheckpoint( string name, out IRetailUiAutomationCheckpoint? checkpoint, out string error) { if (!_deactivated && _target is { } target) return target.TryRequestCheckpoint(name, out checkpoint, out error); checkpoint = null; error = "world lifecycle automation is not bound"; return false; } public void CancelCheckpoint(IRetailUiAutomationCheckpoint checkpoint) { ArgumentNullException.ThrowIfNull(checkpoint); if (!_deactivated && _target is { } target) target.CancelCheckpoint(checkpoint); } public bool TryRequestScreenshot(string name, out string error) { if (!_deactivated && _target is { } target) return target.TryRequestScreenshot(name, out error); error = "world lifecycle automation is not bound"; return false; } public bool IsScreenshotComplete(string name) => !_deactivated && _target?.IsScreenshotComplete(name) == true; public void Deactivate() { _deactivated = true; _target = null; } private void Release(IRetailUiAutomationRuntime expected) { if (ReferenceEquals(_target, expected)) _target = null; } } internal sealed class ExpectedOwnerBinding : IDisposable where T : class { private Action? _release; private readonly T _expected; public ExpectedOwnerBinding(T expected, Action release) { _expected = expected ?? throw new ArgumentNullException(nameof(expected)); _release = release ?? throw new ArgumentNullException(nameof(release)); } public void Dispose() => Interlocked.Exchange(ref _release, null)?.Invoke(_expected); }