XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using XFEEDToolBox.Core.Models;

namespace XFEEDToolBox.Core.Navigation;

public interface ISystemFlightControlDriver
{
    Task<SystemNavigationCapabilities> InspectAsync(CancellationToken token);
    Task SelectAsync(SystemNavigationTarget target,CancellationToken token);
    Task PrepareAsync(CancellationToken token);
    Task<SystemFlightFrame> ReadSystemFlightAsync(CancellationToken token);
    Task ApplySystemFlightAsync(SystemFlightDecision decision,SystemFlightFrame reference,SystemNavigationTarget target,DateTimeOffset expiresAt,CancellationToken token);
    Task ReleaseSystemFlightAsync();
    Task SuspendSystemFlightAsync()=>ReleaseSystemFlightAsync();
}
public sealed record SystemNavigationProgress(NavigationRunState State,SystemNavigationTarget Target,SystemNavigationPhase Phase,string Message)
{public string? Diagnostic{get;init;}}
public sealed class DockingHandoffException(string message,long marketId,Exception? inner=null):InvalidOperationException(message,inner)
{public long MarketId{get;}=marketId;}
public sealed class DockingRequestNotAppliedException():IOException("游戏尚未确认空管请求已执行。");
public sealed class DockingResetObservationPendingException(string requestId,long marketId,DateTimeOffset requestedAt,Exception inner)
    :Exception("归零尚未确认,正在只读核对游戏自动降落接管,不重发请求。",inner)
{public string RequestId{get;}=requestId;public long MarketId{get;}=marketId;public DateTimeOffset RequestedAt{get;}=requestedAt;}

/// <summary>One local leg, with expiring inputs, actual target acknowledgements and bounded telemetry recovery.</summary>
public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTimeOffset>? clock=null)
{
    private readonly Func<DateTimeOffset> now=clock??(()=>DateTimeOffset.UtcNow);
    private readonly object gate=new();
    private CancellationTokenSource? cancellation;
    public NavigationRunState State{get;private set;}
    public bool HandoffFailed{get;private set;}
    public Exception? Failure{get;private set;}
    public string LastMessage{get;private set;}="";
    public event Action<SystemNavigationProgress>? Progress;
    public void Stop(){lock(gate)cancellation?.Cancel();}
    public async Task RunAsync(SystemNavigationTarget target,ISystemFlightControlDriver driver,CancellationToken token=default)
    {
        target.Validate();CancellationTokenSource stop;
        lock(gate)
        {
            if(cancellation is not null)throw new InvalidOperationException("星系内导航已在运行。");
            stop=CancellationTokenSource.CreateLinkedTokenSource(token);cancellation=stop;
        }
        HandoffFailed=false;Failure=null;var phase=SystemNavigationPhase.Preparing;var state=NavigationRunState.Running;var terminal="星系内导航已停止。";string? reportedMessage=null;
        void Report(SystemNavigationPhase next,string message,string? diagnostic=null)
        {
            if(State==NavigationRunState.Running&&phase==next&&reportedMessage==message)return;
            phase=next;reportedMessage=message;State=NavigationRunState.Running;Progress?.Invoke(new(State,target,next,message){Diagnostic=diagnostic});
        }
        try
        {
            stop.Token.ThrowIfCancellationRequested();
            var initial=player();var initialShip=initial.CurrentShipId;var initialCommander=initial.CommanderId;var initialSession=initial.LoadGameAt;
            if(!initial.InGame||initial.CurrentShipId is null||initial.Navigation.CurrentSystemAddress!=target.SystemAddress||NavigationSafety.UnhealthyHull(initial))
                throw new InvalidOperationException("请先进入健康、安全的飞船驾驶状态。");
            if(NavigationSafety.HasExternalThreat(initial))throw new FlightThreatPauseException();
            stop.Token.ThrowIfCancellationRequested();
            if(TravelPreparation.AtDockedDestination(initial,target)){state=NavigationRunState.Completed;terminal=$"已停靠 {target.Name}";return;}
            var capabilities=await driver.InspectAsync(stop.Token);
            if(!capabilities.Supports(target,NavigationAssists.From(initial)))throw new NotSupportedException("当前游戏接口尚未完成此类星系内驾驶的验证。");
            await driver.SelectAsync(target,stop.Token);
            if(!TravelPreparation.AtDockedDestination(player(),target))await driver.PrepareAsync(stop.Token);
            var policy=new SystemFlightNavigator();long previous=0;ulong entity=0;string? loadout=null;DateTimeOffset? missingSince=null;
            DockingResetObservationPendingException? pendingReset=null;var landingObserved=false;var requestFailures=0;
            Report(phase,"正在核对星系内目标和驾驶状态。");
            while(true)
            {
                stop.Token.ThrowIfCancellationRequested();var current=player();
                if(!current.InGame||current.CurrentShipId!=initialShip||current.CommanderId!=initialCommander||current.LoadGameAt!=initialSession
                    ||NavigationSafety.UnhealthyHull(current))throw new InvalidOperationException("飞船、会话或安全状态改变,已停止星系内导航。");
                if(NavigationSafety.HasExternalThreat(current))throw new FlightThreatPauseException();
                if(TravelPreparation.AtDockedDestination(current,target)){state=NavigationRunState.Completed;terminal=$"已停靠 {target.Name}";break;}
                SystemFlightFrame frame;
                try
                {
                    frame=await driver.ReadSystemFlightAsync(stop.Token);
                    if(frame.Sequence<previous||frame.ObservedAt>now()||now()-frame.ObservedAt>TimeSpan.FromMilliseconds(300)||!frame.GeometryVerified)
                        throw new FlightTelemetryUnavailableException("等待新飞行数据。");
                    if(frame.GameSha256!=capabilities.GameSha256||string.IsNullOrWhiteSpace(frame.LoadoutFingerprint))throw new InvalidOperationException("飞行数据构建或装配身份不匹配。");
                    var journalNow=GameJournalClock.Now(current,now());
                    var confirmedTransition=frame.ReplacesShipEntity==entity&&frame.SceneTransitionAt is {} transition&&transition<=journalNow&&journalNow-transition<TimeSpan.FromSeconds(30)
                        &&transition>=initialSession&&(transition==current.Navigation.SupercruiseEnteredAt||transition==current.Navigation.SupercruiseExitedAt);
                    if(entity!=0&&frame.ShipEntity!=entity&&!confirmedTransition||loadout is not null&&frame.LoadoutFingerprint!=loadout)throw new InvalidOperationException("受控飞船或装配改变,已停止导航。");
                    if(frame.Sequence==previous){await Task.Delay(40,stop.Token);continue;}
                    entity=frame.ShipEntity;loadout=frame.LoadoutFingerprint;previous=frame.Sequence;
                    var decision=policy.Decide(frame,target,now());
                    if(pendingReset is not null)
                    {
                        landingObserved|=frame.DockingAssist==NativeAssistState.Engaged;
                        if(!landingObserved&&decision.Phase!=SystemNavigationPhase.Stopped&&!decision.Complete
                            &&DockingHandoffPolicy.RecoveryRequired(frame.TargetDistance,pendingReset.RequestedAt,now()))
                            decision=new(SystemNavigationPhase.Stopped,DockingHandoffPolicy.RecoveryMessage(frame.TargetDistance),null,null,SystemNavigationAction.ExitForFailedHandoff);
                        else if(!landingObserved&&!decision.Complete&&now()-pendingReset.RequestedAt>TimeSpan.FromSeconds(30))
                            throw new InvalidOperationException("自动降落回执和接管状态仍未确认,原请求未重发。",pendingReset);
                        if(decision.Phase!=SystemNavigationPhase.Stopped&&!decision.Complete)
                            decision=decision with{Phase=SystemNavigationPhase.NativeLanding,Message=landingObserved?"自动降落已接管,正在核对实际停靠。":"正在核对归零回执与自动降落状态,不重发请求。"};
                    }
                    Report(decision.Phase,decision.Message);
                    if(decision.Phase==SystemNavigationPhase.Stopped)
                    {
                        if(decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff))
                        {
                            HandoffFailed=true;Failure=pendingReset;
                            await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
                        }
                        state=NavigationRunState.Stopped;terminal=decision.Message;break;
                    }
                    // A lost acknowledgement is not permission for a second
                    // arm or another axis lease. Observe the actual AI/dock;
                    // the same distance/ten-second handoff guard remains above.
                    if(pendingReset is null)
                    {
                        await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
                        if(decision.Actions.HasFlag(SystemNavigationAction.RequestDocking))requestFailures=0;
                    }
                    missingSince=null;
                    if(decision.Complete){state=NavigationRunState.Completed;terminal=decision.Message;break;}
                }
                catch(DockingRequestNotAppliedException)
                {
                    requestFailures++;
                    try{await driver.SuspendSystemFlightAsync();}catch(Exception release)when(NavigationRecovery.CanRetry(release,stop.Token)){}
                    if(requestFailures>=3)
                    {
                        HandoffFailed=true;
                        throw new DockingHandoffException("连续 3 次空管请求未被游戏执行,正在恢复原会话后重试。",target.MarketId!.Value);
                    }
                    Report(SystemNavigationPhase.AwaitingClearance,$"游戏尚未执行空管请求,正在核对后重试({requestFailures}/3)。");
                }
                catch(DockingResetObservationPendingException error)
                {
                    if(error.MarketId!=target.MarketId)throw new InvalidDataException("未确认的归零请求属于其他市场。",error);
                    pendingReset=error;Report(SystemNavigationPhase.NativeLanding,error.Message);
                }
                catch(Exception error)when(NavigationRecovery.CanRetry(error,stop.Token))
                {
                    missingSince??=now();
                    if(pendingReset is null)
                        try{await driver.SuspendSystemFlightAsync();}catch(Exception release)when(NavigationRecovery.CanRetry(release,stop.Token)){}
                    if(now()-missingSince>TimeSpan.FromSeconds(30))throw new InvalidOperationException("星系内飞行数据恢复未成功,已停止并交还控制。",error);
                    Report(SystemNavigationPhase.Waiting,"正在重新连接飞行数据并自动重试。",error.ToString());
                }
                await Task.Delay(80,stop.Token);
            }
        }
        catch(OperationCanceledException)when(stop.IsCancellationRequested){state=NavigationRunState.Stopped;phase=SystemNavigationPhase.Stopped;}
        catch(Exception error){Failure=error;state=NavigationRunState.Failed;phase=SystemNavigationPhase.Stopped;terminal=error.Message;}
        finally
        {
            try{await driver.ReleaseSystemFlightAsync();}
            catch(Exception error){Failure=Failure is null?error:new AggregateException(Failure,error);state=NavigationRunState.Failed;terminal="已停止导航,输入释放未获确认:"+error.Message;}
            lock(gate){cancellation=null;stop.Dispose();State=state;}
            LastMessage=terminal;Progress?.Invoke(new(state,target,phase,terminal));
        }
    }
}