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 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 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 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 { 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.UtcNowruntime.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();} }