using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Text.Json; using XFEEDToolBox.Core.Data; using XFEEDToolBox.Core.Galaxy; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Native; using XFEEDToolBox.Core.Navigation; using XFEEDToolBox.Core.Planning; using XFEEDToolBox.Core.Presentation; using XFEEDToolBox.Core.Session; using XFEEDToolBox.NativeSupport; using XFEEDToolBox.Views; namespace XFEEDToolBox.ViewModels; public partial class ToolboxViewModel { public LiveCollection RemoteMapLocations{get;}=new(x=>x.Identity,x=>new(x)); [ObservableProperty] public partial string RemoteMapTitle{get;set;}="本地星系地图"; [ObservableProperty] public partial string RemoteMapStatus{get;set;}="选择星系后载入地点目录。"; [ObservableProperty] public partial string RemoteMapSearch{get;set;}=""; [ObservableProperty] public partial bool RemoteMapLoading{get;set;} [ObservableProperty] public partial SystemLocationLiveRow? RemoteMapSelection{get;set;} private SystemMapCatalogue? remoteMap; private CancellationTokenSource? remoteMapLoadStop; internal CurrentSystemSnapshot? RemoteMapSnapshot=>remoteMap?.Preview; internal event Action? RemoteMapChanged; public string RemoteMapSelectionName=>RemoteMapSelection?.Name??"选择空间站"; public string RemoteMapSelectionDetails=>RemoteMapSelection?.Record is not {} row?"在 3D 场景或左侧目录中选择目的站。": row.KindText+" · 距主恒星 "+row.DistanceText; public string RemoteMapActionHint=>RemoteMapSelection?.Record is not {} selected?"选择目的站后可一次启动跃迁与星系内导航。": remoteMap?.Stations.TryGetValue(selected.Identity,out var station)!=true?"此地点没有已确认的停靠身份,仅供浏览。": station!.IsPlanetary?"地表进近尚未完成验证,暂不能自动停靠。": $"停机坪 {station.PadDisplay} · 开始后自动离站、跃迁并停靠;抵达后重新核对游戏中的站点。"; partial void OnRemoteMapSearchChanged(string value)=>FilterRemoteMap(); partial void OnRemoteMapSelectionChanged(SystemLocationLiveRow? value) { OnPropertyChanged(nameof(RemoteMapSelectionName));OnPropertyChanged(nameof(RemoteMapSelectionDetails));OnPropertyChanged(nameof(RemoteMapActionHint)); StartRemoteJourneyCommand.NotifyCanExecuteChanged(); } private void FilterRemoteMap() { if(remoteMap is not {} map)return;var selected=RemoteMapSelection?.Record.Identity; RemoteMapLocations.ApplyLocations(new SystemLocationFilter(Search:RemoteMapSearch).Apply(map.Preview)); RemoteMapSelection=RemoteMapLocations.FirstOrDefault(row=>row.Record.Identity==selected);RemoteMapChanged?.Invoke(); } [RelayCommand] private async Task OpenSelectedSystemMapAsync() { if(GalaxySelection is not {} selected)return; await OpenSystemMapAsync(selected); } internal async Task OpenSystemMapAsync(GalacticSystem selected) { remoteMapLoadStop?.Cancel();remoteMapLoadStop?.Dispose();remoteMapLoadStop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token); var stop=remoteMapLoadStop;RemoteMapTitle=selected.Name;RemoteMapSearch="";RemoteMapSelection=null;remoteMap=null; RemoteMapLocations.Apply([]);RemoteMapChanged?.Invoke();RemoteMapLoading=true;RemoteMapStatus="正在载入所选星系的天体与空间站目录。"; AppShellPage.Current?.Navigate(typeof(SystemMapCataloguePage)); try { var map=await new SystemMapCatalogueClient(galaxyHttp).ReadAsync(selected,stop.Token);stop.Token.ThrowIfCancellationRequested(); if(!ReferenceEquals(stop,remoteMapLoadStop))return; remoteMap=map;FilterRemoteMap();RemoteMapStatus=$"{SystemMapCatalogue.PreviewDescription} · {map.Stations.Count} 个空间站"; } catch(OperationCanceledException)when(stop.IsCancellationRequested){} catch(Exception error){if(ReferenceEquals(stop,remoteMapLoadStop))RemoteMapStatus="本地目录载入未完成:"+error.Message;} finally{if(ReferenceEquals(stop,remoteMapLoadStop))RemoteMapLoading=false;} } private bool CanStartRemoteJourney()=>!OfflinePreview&&!DemoMode&&!NavigationActive&&!SystemFlightActive&&!RunActive&&!SessionBusy&&RemoteMapSelection is {} row &&remoteMap?.Stations.TryGetValue(row.Record.Identity,out var station)==true&&!station.IsPlanetary; [RelayCommand(CanExecute=nameof(CanStartRemoteJourney))] private async Task StartRemoteJourneyAsync() { if(!CanStartRemoteJourney()||RemoteMapSelection is not {} selected||remoteMap?.Stations.TryGetValue(selected.Record.Identity,out var station)!=true)return; await RunStationJourneyAsync(station!); } internal void SetSystemMapCatalogueForProbe(SystemMapCatalogue catalogue) { if(!OfflinePreview||!Environment.GetCommandLineArgs().Contains("--system-atlas-only"))throw new InvalidOperationException("仅允许隔离的目录界面核验。"); remoteMap=catalogue;RemoteMapTitle=catalogue.System.Name;RemoteMapStatus=SystemMapCatalogue.PreviewDescription;FilterRemoteMap(); } internal async Task RunStationJourneyAsync(Station station) { if(OfflinePreview||DemoMode||!sessionAutomationAllowed)throw new InvalidOperationException("界面预览不能启动驾驶。"); if(runtime.FlightAutomation.IsActive||RunActive||SystemFlightActive||NavigationActive||SessionBusy)throw new InvalidOperationException("请先停止当前自动驾驶或任务。"); using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);navigationStartStop=stop;NavigationActive=true; StopNavigationCommand.NotifyCanExecuteChanged(); try { if(station.MarketId<=0||string.IsNullOrWhiteSpace(station.Name)||string.IsNullOrWhiteSpace(station.SystemName))throw new InvalidDataException("目的站身份不完整。"); if(station.IsPlanetary)throw new NotSupportedException("地表进近尚未完成验证,暂不能自动停靠。"); ShipPadPolicy.RequireFits(station,runtime.Player.Ship); shipProtection?.RequireHealthyShip(); NavigationDestination=station.SystemName;LogNavigation("开始完整航程",station.DisplayName); for(var recoveries=0;;recoveries++) { await ReadEnvironmentAsync(stop.Token);stop.Token.ThrowIfCancellationRequested(); await ReadPluginStatusAsync(); var requiresEntry=gameEnvironment?.ProcessId is null; if(gameEnvironment?.ProcessId is {} existingPid) { using var existing=System.Diagnostics.Process.GetProcessById(existingPid); var started=new DateTimeOffset(existing.StartTime.ToUniversalTime()); var menu=await new NativeRelogChannel(existingPid,NativeRelogChannel.SupportedGameSha256,entryOnly:true).ReadEntryStatusAsync(stop.Token); var navigation=await new NavigationPipeClient(existingPid).CallAsync(new{op="describe"},stop.Token); stop.Token.ThrowIfCancellationRequested(); requiresEntry=NativeSessionStartupReadiness.JourneyEntryRequired(runtime.Player,menu,navigation,existingPid, gameEnvironment.ImageSha256??"",started,DateTimeOffset.UtcNow); } if(requiresEntry) { var launch=new Services.WindowsTradingBootstrap(()=>runtime.Player,PluginGameExecutable,NativeSupportPackage.Directory){ResumeExistingCargo=true}; launch.Progress+=text=>dispatcher.TryEnqueue(()=>{NavigationStatus=text;LogNavigation("航程会话恢复",text);}); await Task.Run(()=>launch.UpgradeAndEnterAsync(stop.Token),stop.Token); await ReadEnvironmentAsync(stop.Token);stop.Token.ThrowIfCancellationRequested(); await ReadPluginStatusAsync(); } var pid=gameEnvironment?.ProcessId??throw new InvalidOperationException("当前游戏实例尚未连接。"); var hash=gameEnvironment?.ImageSha256??""; var adapter=new NativeFlightJourneyDriver(pid,hash,()=>runtime.Player,NativeSupportPackage.Directory,()=>NavigationFuelReserve, token=>EnsureNativeFlightProtectionAsync(pid,hash,token),cruiseDiagnostics:runtime.RecordScoDiagnostic); adapter.Status+=text=>dispatcher.TryEnqueue(()=>NavigationStatus=text); var result=await Task.Run(()=>runtime.FlightAutomation.NavigateStationAsync(station,adapter,stop.Token),stop.Token); stop.Token.ThrowIfCancellationRequested(); if(result.Failure is StationDepartureRecoveryException&&recoveries<2) { await Task.Run(()=>Services.WindowsFlightRecovery.RecoverAsync(pid,hash,()=>runtime.Player,PluginGameExecutable,NativeSupportPackage.Directory, ReleaseProtectionForControlledExitAsync,text=>dispatcher.TryEnqueue(()=>{NavigationStatus=text;LogNavigation("离场恢复",text);}),stop.Token),stop.Token); continue; } if(result.Failure is {} sceneError&&SavingSceneRecoveryRequiredException.Present(sceneError)&&recoveries<2) { var bootstrap=new Services.WindowsTradingBootstrap(()=>runtime.Player,PluginGameExecutable,NativeSupportPackage.Directory){ResumeExistingCargo=true}; bootstrap.Progress+=text=>dispatcher.TryEnqueue(()=>{NavigationStatus=text;LogNavigation("场景恢复",text);}); await Task.Run(()=>bootstrap.UpgradeAndEnterAsync(stop.Token),stop.Token); continue; } if(result.Failure is {} failure)throw failure; NavigationStatus=result.Message??(result.State==NavigationRunState.Completed?"已停靠 "+station.DisplayName:"自动驾驶已停止。"); LogNavigation("完整航程结果",NavigationStatus); break; } } catch(OperationCanceledException)when(stop.IsCancellationRequested){NavigationStatus="自动驾驶已停止。";} catch(Exception error){NavigationStatus=error.Message;NavigationStatusSeverity=InfoBarSeverity.Error;runtime.Log("完整航程未完成",error.ToString());} finally{navigationStartStop=null;NavigationActive=false;StopNavigationCommand.NotifyCanExecuteChanged();StartRemoteJourneyCommand.NotifyCanExecuteChanged();} } }