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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Presentation;
using XFEEDToolBox.Core.Galaxy;
using System.Text.Json;

namespace XFEEDToolBox.ViewModels;

public partial class ToolboxViewModel
{
    [ObservableProperty] public partial string NavigationDestination { get; set; }="";
    [ObservableProperty] public partial double NavigationFuelReserve { get; set; }=.5;
    [ObservableProperty] public partial GameRoutePlotOptions NavigationRouteOptions {get;set;}=new();
    [ObservableProperty] public partial string NavigationRouteSummary { get; set; }="选择目标后,长按星图中的恒星,由游戏计算完整航路。";
    [ObservableProperty] public partial string NavigationNext { get; set; }="—";
    [ObservableProperty] public partial string NavigationFuel { get; set; }="—";
    [ObservableProperty] public partial string NavigationFuelBudget { get; set; }="等待航线与完整飞船配置";
    [ObservableProperty] public partial string NavigationShip { get; set; }="等待当前飞船配置";
    [ObservableProperty] public partial string NavigationScoop { get; set; }="油勺状态未知";
    [ObservableProperty] public partial string NavigationStatus { get; set; }="待命 · 设定目标后核对航行计划";
    [ObservableProperty] public partial string NavigationStartHint { get; set; }="正在读取目标与飞船配置…";
    [ObservableProperty] public partial InfoBarSeverity NavigationStatusSeverity { get; set; }=InfoBarSeverity.Informational;
    [ObservableProperty] public partial string NavigationNativeStatus { get; set; }="安装游戏插件并进入飞行状态后,连接原生导航驱动。";
    [ObservableProperty] public partial bool NavigationActive { get; set; }
    [ObservableProperty] public partial bool NavigationPlanning { get; set; }
    [ObservableProperty] public partial bool NavigationCancelling { get; set; }
    [ObservableProperty] public partial long NavigationPlottedDestinationAddress { get; set; }
    [ObservableProperty] public partial string NavigationPlottedDestination { get; set; }="";
    public bool NavigationRouteBusy=>NavigationPlanning||NavigationCancelling;
    public bool CanCancelPlottedRoute=>NavigationPlottedDestinationAddress>0&&!NavigationRouteBusy&&!RunActive&&!SessionBusy;
    public string NavigationHoldHint=>NavigationPlottedDestinationAddress>0?"已同步游戏航线 · 长按目的地取消航线\n点击开始即可导航;Esc 或空白处取消选择":"点击选择 · 双击放大 · 长按恒星规划航线\nEsc 或点击空白处取消选择";
    [ObservableProperty] public partial IReadOnlyList<NavWaypoint> NavigationMapRoute { get; set; }=[];
    [ObservableProperty] public partial NavigationFuelOverlay NavigationFuelMap { get; set; }=NavigationFuelOverlay.Empty;
    public bool NavigationTargetLocked=>NavigationActive||NavigationRouteBusy;
    public bool NavigationTargetEditable=>!NavigationTargetLocked;
    public event Action? NavigationPlanned;
    [ObservableProperty] public partial bool NavigationRouteMatches { get; set; }
    [ObservableProperty] public partial bool PluginNavigationReady { get; set; }
    public LiveCollection<NavigationLegLiveRow,NavigationLegPreview,int> NavigationLegs { get; }=new(r=>r.Number,r=>new(r));
    [ObservableProperty] public partial bool HasNoNavigationLegs {get;set;}=true;
    private readonly NavigationItinerary navigationItinerary=new();
    private string navigationItineraryOwner="",navigationItineraryDestination="";
    private IReadOnlyList<NavWaypoint> navigationDisplayRoute=[];
    private readonly GameRouteMirror gameRouteMirror=new();
    private bool synchronizingGameRoute;
    partial void OnNavigationStatusChanged(string value)=>RefreshNavigationLegPresentation();
    partial void OnNavigationStatusSeverityChanged(InfoBarSeverity value)=>RefreshNavigationLegPresentation();
    private void RefreshNavigationLegPresentation()
    {
        var state=NavigationActive?NavigationRunState.Running:NavigationStatusSeverity switch{InfoBarSeverity.Error=>NavigationRunState.Failed,InfoBarSeverity.Warning=>NavigationRunState.Stopped,_=>NavigationRunState.Idle};
        NavigationLegs.Apply(navigationItinerary.Present(state,NavigationStatus,NavigationNext));HasNoNavigationLegs=NavigationLegs.Count==0;
    }
    private CancellationTokenSource? navigationStartStop;
    internal GameRoutePlan? LastConfirmedGameRoute { get; private set; }
    private DateTimeOffset nextNavigationUiUpdate;
    private string lastNavigationUiState="";
    private async Task ResolveNavigationDestinationAsync(CancellationToken token=default)
    {
        using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,token);
        stop.Token.ThrowIfCancellationRequested();
        var name=NavigationDestination.Trim();
        if(name.Length is <1 or >128||galaxyCache.GetValueOrDefault(name)?.SystemAddress is >0)return;
        if(CurrentPlayer.Navigation.Route.FirstOrDefault(w=>w.Name.Equals(name,StringComparison.OrdinalIgnoreCase)) is {} waypoint)
        {galaxyCache[name]=new(waypoint.Name,waypoint.Position,waypoint.SystemAddress,waypoint.StarClass);return;}
        NavigationStatus="正在读取目标星系的真实坐标与地址…";
        var found=await FindGalacticSystemAsync(name,stop.Token);
        if(!found.Name.Equals(name,StringComparison.OrdinalIgnoreCase)||found.SystemAddress is not >0)throw new InvalidOperationException("目标星系目录缺少有效的游戏地址。");
        galaxyCache[found.Name]=found;PublishGalaxy();
        await File.WriteAllTextAsync(Path.Combine(runtime.DataDirectory,"galaxy-cache.json"),JsonSerializer.Serialize(galaxyCache.Values,ToolboxSettings.JsonOptions),stop.Token);
    }
    private void InitializeNavigation()
    {
        NavigationDestination=runtime.Settings.NavigationDestination;NavigationFuelReserve=runtime.Settings.NavigationFuelReserve;
        runtime.Navigator.Progress+=OnNavigationProgress;
        runtime.FlightAutomation.Progress+=OnFlightAutomationProgress;
    }
    private void OnFlightAutomationProgress(FlightAutomationProgress progress)=>dispatcher.TryEnqueue(()=>
    {
        var running=progress.Stage is not (FlightAutomationStage.Completed or FlightAutomationStage.Stopped or FlightAutomationStage.Failed);
        if(progress.Entry==FlightAutomationEntry.Galaxy)
        {
            NavigationActive=running;NavigationStatus=progress.Message;
            NavigationStatusSeverity=progress.Stage==FlightAutomationStage.Failed?InfoBarSeverity.Error:progress.Stage==FlightAutomationStage.Completed?InfoBarSeverity.Success:InfoBarSeverity.Informational;
        }
        else if(progress.Entry==FlightAutomationEntry.CurrentSystem){SystemFlightActive=running;SystemFlightStatus=progress.Message;}
        RefreshNavigationStartHint();StartNavigationCommand.NotifyCanExecuteChanged();StopNavigationCommand.NotifyCanExecuteChanged();
        StartRemoteJourneyCommand.NotifyCanExecuteChanged();
        StartSystemFlightCommand.NotifyCanExecuteChanged();RefreshTradePlanCommands();
        StartGameCommand.NotifyCanExecuteChanged();RelogGameCommand.NotifyCanExecuteChanged();RestartGameCommand.NotifyCanExecuteChanged();
    });
    private void OnNavigationProgress(NavigationProgress progress)
    {
        if(runtime.FlightAutomation.ActiveEntry is {} entry&&entry!=FlightAutomationEntry.Galaxy)return;
        var state=progress.Decision?.State??progress.Message;var now=DateTimeOffset.UtcNow;
        if(progress.State==NavigationRunState.Running&&state==lastNavigationUiState&&now<nextNavigationUiUpdate)return;
        lastNavigationUiState=state;nextNavigationUiUpdate=now.AddMilliseconds(200);
        dispatcher.TryEnqueue(()=>
        {
            NavigationActive=runtime.FlightAutomation.ActiveEntry==FlightAutomationEntry.Galaxy||progress.State==NavigationRunState.Running;NavigationStatus=progress.Message;NavigationNext=progress.NextSystem;
            NavigationStatusSeverity=progress.State switch{NavigationRunState.Failed=>InfoBarSeverity.Error,NavigationRunState.Completed=>InfoBarSeverity.Success,NavigationRunState.Stopped=>InfoBarSeverity.Warning,_=>InfoBarSeverity.Informational};
            if(progress.State!=NavigationRunState.Running)LogNavigation("导航结果",progress.Message);
            RefreshNavigationStartHint();
            StartNavigationCommand.NotifyCanExecuteChanged();StopNavigationCommand.NotifyCanExecuteChanged();
            StartGameCommand.NotifyCanExecuteChanged();RelogGameCommand.NotifyCanExecuteChanged();RestartGameCommand.NotifyCanExecuteChanged();
            RefreshTradePlanCommands();
        });
    }
    partial void OnNavigationDestinationChanged(string value){if(initialized&&!synchronizingGameRoute)RefreshNavigation(CurrentPlayer);}
    partial void OnNavigationFuelReserveChanged(double value){if(initialized)RefreshNavigation(CurrentPlayer);}
    private void RefreshNavigation(PlayerState player)
    {
        var mirrored=gameRouteMirror.Observe(player,NavigationRouteBusy);
        var plotted=player.Navigation.Route.Count>=2&&player.Navigation.Route[^1].SystemAddress!=player.Navigation.CurrentSystemAddress?player.Navigation.Route[^1]:null;
        var oldPlotted=NavigationPlottedDestinationAddress;
        NavigationPlottedDestinationAddress=plotted?.SystemAddress??0;NavigationPlottedDestination=plotted?.Name??"";
        OnPropertyChanged(nameof(CanCancelPlottedRoute));OnPropertyChanged(nameof(NavigationHoldHint));
        if(mirrored.Changed)
        {
            if(mirrored.Cleared)
            {
                if(NavigationActive)StopForObservedRouteChange("游戏航线已清空,已释放原导航输入。");
                navigationItinerary.Reset();navigationDisplayRoute=[];LastConfirmedGameRoute=null;
                synchronizingGameRoute=true;try{NavigationDestination="";}finally{synchronizingGameRoute=false;}
                NavigationStatus="游戏航线已取消。";NavigationStatusSeverity=InfoBarSeverity.Informational;
            }
            else if(mirrored.Destination is {} gameDestination)
            {
                if(mirrored.Replaced&&NavigationActive)StopForObservedRouteChange("游戏航线终点已改变,已释放原导航输入。");
                synchronizingGameRoute=true;try{NavigationDestination=gameDestination.Name;}finally{synchronizingGameRoute=false;}
                var system=new GalacticSystem(gameDestination.Name,gameDestination.Position,gameDestination.SystemAddress,gameDestination.StarClass,"游戏航线");
                galaxyCache[system.Name]=system;
                if(oldPlotted!=gameDestination.SystemAddress){GalaxySelection=system;NavigationPlanned?.Invoke();}
            }
        }
        var destination=(plotted?.Name??NavigationDestination).Trim();
        if(navigationItineraryOwner!=player.CommanderId||!navigationItineraryDestination.Equals(destination,StringComparison.OrdinalIgnoreCase))
        {navigationItinerary.Reset();navigationDisplayRoute=[];navigationItineraryOwner=player.CommanderId;navigationItineraryDestination=destination;}
        ObserveNativeSession(player);
        var ship=player.Ships.FirstOrDefault(s=>s.ShipId==player.CurrentShipId);
        NavigationShip=ship?.DisplayName??"等待当前飞船配置";
        NavigationScoop=ship?.ConfigurationAt is null?"装配未读取":ship.ConfigurationChanged?"装配变化,等待新快照":NavigationPreviewBuilder.HasScoop(ship)?"已安装并启用油勺":"未检测到启用的油勺";
        var current=player.Navigation.FuelAt>player.StatusAt?player.Navigation.MainFuel:player.Status?.FuelMain??player.Navigation.MainFuel;
        NavigationFuel=current is {} main?$"{main:F2} t":"等待燃油状态";
        try
        {
            var preview=NavigationPreviewBuilder.Build(player,plotted?.Name??NavigationDestination,NavigationFuelReserve);
            navigationItinerary.UpdateRoute(preview.Legs);NavigationNext=preview.NextSystem;NavigationFuelBudget=preview.Message;
            navigationDisplayRoute=player.Navigation.Route;
            NavigationRouteSummary=preview.RemainingJumps==0?"已抵达目标星系":$"{player.SystemName} → {plotted?.Name??NavigationDestination.Trim()} · 剩余 {preview.RemainingJumps} 跳";
            NavigationRouteMatches=preview.RemainingJumps>0;
        }
        catch(InvalidOperationException e)
        {
            NavigationRouteMatches=false;NavigationRouteSummary=e.Message;NavigationNext=player.Navigation.TargetSystem.Length>0?player.Navigation.TargetSystem:"—";
            NavigationFuelBudget="等待游戏生成航路后,按当前装配、货重和燃油估算。";
            if(player.Navigation.Route.Count==0)NavigationRouteSummary="游戏尚未生成航路,请长按星图中的目标恒星。";
        }
        if(player.Navigation.CurrentSystemAddress is >0&&destination.Length>0
            &&player.SystemName.Equals(destination,StringComparison.OrdinalIgnoreCase))
        {
            NavigationRouteMatches=false;NavigationNext="已抵达";
            NavigationRouteSummary=$"已抵达 {player.SystemName}";NavigationFuelBudget="当前已在目标星系。";
            if(!NavigationActive){NavigationStatus=NavigationRouteSummary;NavigationStatusSeverity=InfoBarSeverity.Success;}
        }
        navigationItinerary.ConfirmPosition(player.Navigation.CurrentSystemAddress??0);RefreshNavigationLegPresentation();
        NavigationMapRoute=StableSequence(NavigationMapRoute,navigationDisplayRoute);
        var fuelMap=NavigationRouteMatches?NavigationFuelOverlay.Build(player,NavigationFuelReserve):NavigationFuelOverlay.Empty;
        if(!NavigationFuelMap.DashedLegs.SequenceEqual(fuelMap.DashedLegs)||!NavigationFuelMap.SupplySites.SequenceEqual(fuelMap.SupplySites)||NavigationFuelMap.RecommendedSupply!=fuelMap.RecommendedSupply)NavigationFuelMap=fuelMap;
        RefreshNavigationStartHint();StartNavigationCommand.NotifyCanExecuteChanged();
        RefreshDashboardRoute();
    }
    private bool CanPlanNavigation()=>!NavigationActive&&!NavigationRouteBusy&&!RunActive&&!SessionBusy;
    [RelayCommand(CanExecute=nameof(CanPlanNavigation))] private async Task PlanNavigationAsync(CancellationToken cancellationToken=default)
    {
        using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
        if(!CanPlanNavigation())return;
        var destination=NavigationDestination.Trim();var reserve=NavigationFuelReserve;
        void RequireSameTarget()
        {
            token.ThrowIfCancellationRequested();
            if(!destination.Equals(NavigationDestination.Trim(),StringComparison.OrdinalIgnoreCase))
                throw new InvalidOperationException("规划目标已改变,请长按所选目标重新规划。");
        }
        NavigationPlanning=true;
        navigationItinerary.Reset();navigationDisplayRoute=[];RefreshNavigationLegPresentation();
        LastConfirmedGameRoute=null;
        try
        {
            if(NavigationActive)throw new InvalidOperationException("请先停止导航再更改目标。");
            if(NavigationDestination.Trim().Length is <1 or >128)throw new InvalidOperationException("请输入有效的目标星系名称。");
            if(!double.IsFinite(NavigationFuelReserve)||NavigationFuelReserve<0||NavigationFuelReserve>100)throw new InvalidOperationException("燃油余量需在 0–100 t 之间。");
            await ResolveNavigationDestinationAsync(token);
            RequireSameTarget();
            var target=galaxyCache.GetValueOrDefault(destination);
            if(target?.SystemAddress is not >0||!target.Name.Equals(destination,StringComparison.OrdinalIgnoreCase))throw new InvalidOperationException("目标星系缺少匹配的游戏地址。");
            var routeOptions=NavigationRouteOptions;routeOptions.Validate(CurrentPlayer.CargoCapacity);
            await runtime.SaveSettingsAsync(runtime.Settings with{NavigationDestination=destination,NavigationFuelReserve=reserve,NavigationRouteOptions=routeOptions});
            await ReadPluginStatusAsync();
            RequireSameTarget();
            if(runtime.FlightDriver is not IGameRoutePlanningDriver planner)throw new InvalidOperationException("请连接支持游戏航线规划的原生插件。");
            NavigationStatus="正在将目标提交给游戏航线规划器…";NavigationStatusSeverity=InfoBarSeverity.Informational;
            LogNavigation("规划航线",$"提交游戏规划:{target.Name}");
            var result=await new GameRoutePlanner(()=>runtime.Player).PlanAsync(new(target.SystemAddress.Value,target.Name,target.Position,target.StarClass),planner,message=>NavigationStatus=message,token,routeOptions);
            RequireSameTarget();
            LastConfirmedGameRoute=result;
            RefreshNavigation(CurrentPlayer);
            NavigationStatus=$"游戏航线已确认,共 {result.Route.Count-1} 跳;下一跳 {result.Next.Name} 已锁定。";
            NavigationStatusSeverity=InfoBarSeverity.Success;LogNavigation("航线确认",NavigationStatus);NavigationPlanned?.Invoke();
        }
        catch(OperationCanceledException){NavigationStatus="航线规划已取消。";NavigationStatusSeverity=InfoBarSeverity.Informational;}
        catch(Exception e){NavigationStatus=e.Message;NavigationStatusSeverity=InfoBarSeverity.Error;}
        finally{NavigationPlanning=false;PlanNavigationCommand.NotifyCanExecuteChanged();RefreshNavigation(CurrentPlayer);}
    }
    internal Task ChooseNavigationTargetAsync(GalacticSystem target)
    {
        if(NavigationTargetLocked)return Task.CompletedTask;
        galaxyCache[target.Name]=target;GalaxySelection=target;NavigationDestination=target.Name;
        NavigationStatus=$"已选择 {target.Name},长按星图中的恒星规划航线。";NavigationStatusSeverity=InfoBarSeverity.Informational;
        return Task.CompletedTask;
    }
    partial void OnNavigationPlanningChanged(bool value)
    {
        RefreshRelogAvailability();
        OnPropertyChanged(nameof(NavigationRouteBusy));OnPropertyChanged(nameof(CanCancelPlottedRoute));
        OnPropertyChanged(nameof(NavigationTargetLocked));RefreshNavigationStartHint();StartNavigationCommand.NotifyCanExecuteChanged();PlanNavigationCommand.NotifyCanExecuteChanged();
        OnPropertyChanged(nameof(NavigationTargetEditable));
    }
    partial void OnNavigationCancellingChanged(bool value)
    {
        RefreshRelogAvailability();
        OnPropertyChanged(nameof(NavigationRouteBusy));OnPropertyChanged(nameof(NavigationTargetLocked));OnPropertyChanged(nameof(NavigationTargetEditable));OnPropertyChanged(nameof(CanCancelPlottedRoute));
        StartNavigationCommand.NotifyCanExecuteChanged();PlanNavigationCommand.NotifyCanExecuteChanged();
    }
    internal bool IsPlottedDestination(GalacticSystem target)=>NavigationPlottedDestinationAddress>0&&target.SystemAddress==NavigationPlottedDestinationAddress;
    [RelayCommand] private async Task CancelPlottedRouteAsync(CancellationToken token=default)
    {
        if(!CanCancelPlottedRoute)return;
        var expected=NavigationPlottedDestinationAddress;NavigationCancelling=true;
        try
        {
            if(NavigationActive){StopNavigation();var until=DateTimeOffset.UtcNow.AddSeconds(5);while(NavigationActive&&DateTimeOffset.UtcNow<until)await Task.Delay(50,token);if(NavigationActive)throw new InvalidOperationException("正在停止导航,请稍后再次长按取消航线。");}
            if(runtime.FlightDriver is not IGameRouteCancellationDriver driver)throw new InvalidOperationException("请等待原生导航连接完成后取消游戏航线。");
            await new GameRouteCancellation(()=>runtime.Player).CancelAsync(expected,driver,message=>NavigationStatus=message,token);
            NavigationStatus="游戏航线已取消。";NavigationStatusSeverity=InfoBarSeverity.Success;
        }
        catch(OperationCanceledException){NavigationStatus="未收到游戏的完整取消确认,正在继续同步实际航线。";NavigationStatusSeverity=InfoBarSeverity.Warning;}
        catch(Exception error){NavigationStatus=error.Message;NavigationStatusSeverity=InfoBarSeverity.Error;}
        finally{NavigationCancelling=false;RefreshNavigation(CurrentPlayer);}
    }
    [RelayCommand] private async Task CheckNavigationAsync()
    {
        if(NavigationActive)return;
        try{await ValidateNavigationStartAsync();}
        catch(Exception e){NavigationStatus=e.Message;NavigationStatusSeverity=InfoBarSeverity.Error;}
    }
    private string NavigationStartBlocker()
    {
        if(RunActive)return "请先停止正在执行的贸易路线。";
        if(SessionBusy)return "正在处理游戏会话,请等待完成。";
        if(NavigationDestination.Trim().Length is <1 or >128)return "请输入有效的目标星系名称。";
        if(!double.IsFinite(NavigationFuelReserve)||NavigationFuelReserve<0||NavigationFuelReserve>100)return "燃油余量需在 0–100 t 之间。";
        var player=CurrentPlayer;
        if(ProtectionBlocksTesting||NavigationSafety.UnhealthyHull(player))return "飞船安全保护正在等待稳定健康确认,请先处理实际受损或失效状态。";
        if(!player.InGame)return "请先进入游戏中的飞船驾驶舱。";
        if(player.Docked&&!NavigationAssists.From(player).AdvancedDocking)return "自动离站需要已启用的高级自动起降模块。";
        if(runtime.FlightAutomation.IsActive&&runtime.FlightAutomation.ActiveEntry!=FlightAutomationEntry.Galaxy)return "已有自动驾驶正在运行,请先停止当前航程。";
        if(NavigationSafety.MustStop(player,(runtime.FlightDriver as NativeFlightControlDriver)?.LastObservation?.Frame,DateTimeOffset.UtcNow))return "检测到战斗、拦截或未确认的危险状态,请先解除危险。";
        if(!NavigationRouteMatches)return NavigationRouteSummary;
        if(!PluginNavigationReady||runtime.FlightDriver is null)return NavigationNativeStatus;
        return "";
    }
    private void RefreshNavigationStartHint()
    {
        var blocker=NavigationStartBlocker();
        NavigationStartHint=NavigationActive?"自动导航正在核对或执行,可随时停止。":NavigationPlanning?"正在规划航线…":blocker.Length>0?blocker:CurrentPlayer.Docked?"已就绪 · 开始后自动离站并接续航路。":"已就绪 · 点击开始即可自动飞行。";
    }
    // Shared by the start button and the read-only diagnostic; this method sends no flight inputs.
    internal async Task ValidateNavigationStartAsync(CancellationToken token=default)
    {
        NavigationStatus="正在核对目标、飞船与原生导航驱动…";NavigationStatusSeverity=InfoBarSeverity.Informational;
        if(CurrentPlayer.Navigation.Route.Count>=2&&CurrentPlayer.Navigation.Route[^1] is {} gameDestination)NavigationDestination=gameDestination.Name;
        if(NavigationDestination.Trim().Length is <1 or >128)throw new InvalidOperationException("请输入有效的目标星系名称。");
        if(!double.IsFinite(NavigationFuelReserve)||NavigationFuelReserve<0||NavigationFuelReserve>100)throw new InvalidOperationException("燃油余量需在 0–100 t 之间。");
        await ResolveNavigationDestinationAsync(token);
        await ReadPluginStatusAsync();
        token.ThrowIfCancellationRequested();
        RefreshNavigation(CurrentPlayer);
        var driver=runtime.FlightDriver??throw new InvalidOperationException(NavigationNativeStatus);
        await driver.BeginNavigationSessionAsync(token);
        void Recovering()
        {
            if(!CurrentPlayer.InGame||NavigationSafety.UnhealthyHull(CurrentPlayer)||NavigationSafety.HasExternalThreat(CurrentPlayer))
                throw new InvalidOperationException("游戏会话或飞船安全状态已改变,已停止导航连接恢复。");
            NavigationStatus="正在恢复导航连接,原航线保留并自动继续…";
        }
        await NavigationRecovery.PreflightWithRecoveryAsync(driver,Recovering,token);
        if(CurrentPlayer.Docked&&driver is NativeFlightControlDriver dockedDriver)
        {
            // Docked ships need a live, identified cockpit observation, not a
            // target vector for a flight phase that has not started yet.
            await NavigationRecovery.ReadWithRecoveryAsync(dockedDriver.ReadShipTelemetryAsync,Recovering,token);
        }
        else await NavigationRecovery.ReadWithRecoveryAsync(async readToken=>
        {
            try{return await driver.ReadAsync(readToken);}
            catch(FlightTelemetryUnavailableException error)when(error.Recovery==FlightRecovery.RefreshDriver)
            {
                await NavigationRecovery.PreflightWithRecoveryAsync(driver,Recovering,readToken);
                return await driver.ReadAsync(readToken);
            }
        },Recovering,token);
        var blocker=NavigationStartBlocker();
        if(blocker.Length>0)throw new InvalidOperationException(blocker);
        // Validate current reference geometry before the start button can report
        // readiness. This performs read/describe only, with no target or inputs.
        NavigationStatus="航线已就绪,等待启动。";NavigationStatusSeverity=InfoBarSeverity.Success;
    }
    private void LogNavigation(string kind,string message)
    {
        runtime.Log(kind,message);
        try
        {
            var path=Path.Combine(runtime.DataDirectory,"navigation.log");
            if(File.Exists(path)&&new FileInfo(path).Length>1024*1024)File.Move(path,path+".previous",true);
            File.AppendAllText(path,$"{DateTimeOffset.Now:o}\t{kind}\t{message.Replace('\r',' ').Replace('\n',' ')}{Environment.NewLine}");
        }
        catch(Exception e)when(e is IOException or UnauthorizedAccessException){runtime.Log("导航日志",e.Message);}
    }
    private bool CanStartNavigation()=>!NavigationActive&&!NavigationRouteBusy&&!RunActive&&!SessionBusy&&!runtime.FlightAutomation.IsActive;
    [RelayCommand(CanExecute=nameof(CanStartNavigation))] private async Task StartNavigationAsync()
    {
        if(!CanStartNavigation())return;
        using var startStop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);navigationStartStop=startStop;
        NavigationActive=true;RefreshNavigationStartHint();StopNavigationCommand.NotifyCanExecuteChanged();
        StartGameCommand.NotifyCanExecuteChanged();RefreshTradePlanCommands();RelogGameCommand.NotifyCanExecuteChanged();RestartGameCommand.NotifyCanExecuteChanged();
        LogNavigation("启动导航",$"收到启动请求:{NavigationDestination.Trim()}");
        try
        {
            await ValidateNavigationStartAsync(startStop.Token);
            if(runtime.Settings.NavigationDestination!=NavigationDestination.Trim()||runtime.Settings.NavigationFuelReserve!=NavigationFuelReserve)
                await runtime.SaveSettingsAsync(runtime.Settings with{NavigationDestination=NavigationDestination.Trim(),NavigationFuelReserve=NavigationFuelReserve});
            var destination=NavigationDestination;var driver=runtime.FlightDriver as NativeFlightControlDriver??throw new InvalidOperationException("原生导航驱动尚未就绪。");
            var departure=new NativeFlightJourneyDriver(driver.ProcessId,Core.Native.NativeSystemLocationReader.GameSha256,()=>runtime.Player,
                XFEEDToolBox.NativeSupport.NativeSupportPackage.Directory,()=>NavigationFuelReserve,
                cancellation=>EnsureNativeFlightProtectionAsync(driver.ProcessId,Core.Native.NativeSystemLocationReader.GameSha256,cancellation));
            startStop.Token.ThrowIfCancellationRequested();
            var result=await Task.Run(()=>runtime.FlightAutomation.NavigateGalaxyAsync(destination,driver,departure,startStop.Token),startStop.Token);
            if(result.Failure is {} failure)throw failure;
        }
        catch(OperationCanceledException)when(lifetime.IsCancellationRequested){}
        catch(OperationCanceledException)when(startStop.IsCancellationRequested){NavigationStatus="自动导航已停止。";NavigationStatusSeverity=InfoBarSeverity.Warning;LogNavigation("导航取消",NavigationStatus);}
        catch(Exception e){NavigationStatus=e.Message;NavigationStatusSeverity=InfoBarSeverity.Error;LogNavigation("导航未启动",e.Message);}
        finally{navigationStartStop=null;NavigationActive=false;RefreshNavigationStartHint();StartGameCommand.NotifyCanExecuteChanged();StartNavigationCommand.NotifyCanExecuteChanged();StopNavigationCommand.NotifyCanExecuteChanged();RefreshTradePlanCommands();RelogGameCommand.NotifyCanExecuteChanged();RestartGameCommand.NotifyCanExecuteChanged();}
    }
    private bool CanStopNavigation()=>NavigationActive;
    private int navigationUserStopVersion;
    internal int NavigationUserStopVersion=>Volatile.Read(ref navigationUserStopVersion);
    private void StopOwnedNavigation(){navigationStartStop?.Cancel();runtime.FlightAutomation.Stop();runtime.Navigator.Stop();}
    private void StopForObservedRouteChange(string reason)
    {
        runtime.FlightAutomation.PauseForMonitoring(new InvalidOperationException(reason));
        runtime.Navigator.Stop();
    }
    internal void StopNavigationForScoAcceptance()
    {
        if(!Environment.GetCommandLineArgs().Contains("--accept-sco"))throw new InvalidOperationException("SCO QA stop requires its explicit launch entry.");
        StopOwnedNavigation();
    }
    [RelayCommand(CanExecute=nameof(CanStopNavigation))] private void StopNavigation(){Interlocked.Increment(ref navigationUserStopVersion);StopOwnedNavigation();}
    partial void OnPluginNavigationReadyChanged(bool value){RefreshNavigationStartHint();StartNavigationCommand.NotifyCanExecuteChanged();}
}