using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.UI.Dispatching; using XFEEDToolBox.Core.Native; using XFEEDToolBox.Core.Navigation; using XFEEDToolBox.Core.Presentation; using XFEEDToolBox.Core.Services; namespace XFEEDToolBox.ViewModels; public partial class ToolboxViewModel { public LiveCollection CurrentSystemLocations{get;}=new(x=>x.Identity,x=>new(x)); [ObservableProperty] public partial string CurrentSystemTitle{get;set;}="当前星系"; [ObservableProperty] public partial string CurrentSystemSearch{get;set;}=""; [ObservableProperty] public partial string CurrentSystemStatus{get;set;}="等待读取游戏中的导航地点。"; [ObservableProperty] public partial string SystemFlightStatus{get;set;}="选择地点后检查可用驾驶方式。"; [ObservableProperty] public partial string SystemSceneStatus{get;set;}="等待读取真实地点坐标"; [ObservableProperty] public partial string SystemControlAvailability{get;set;}="等待驾驶接口。"; [ObservableProperty] public partial bool CurrentSystemReading{get;set;} [ObservableProperty] public partial bool SystemFlightActive{get;set;} [ObservableProperty] public partial bool SystemFlightReady{get;set;} [ObservableProperty,NotifyCanExecuteChangedFor(nameof(StartSystemFlightCommand))] public partial SystemLocationLiveRow? SelectedSystemLocation{get;set;} [ObservableProperty] public partial bool ShowSystemStars{get;set;} [ObservableProperty] public partial bool ShowSystemAsteroids{get;set;} [ObservableProperty] public partial bool ShowSystemPlanets{get;set;} [ObservableProperty] public partial bool ShowSystemLandablePlanets{get;set;} [ObservableProperty] public partial bool ShowSystemSettlements{get;set;} [ObservableProperty] public partial bool ShowSystemStations{get;set;} [ObservableProperty] public partial bool ShowSystemCarriers{get;set;} [ObservableProperty] public partial bool ShowSystemPoints{get;set;} [ObservableProperty] public partial bool ShowSystemSignals{get;set;} [ObservableProperty] public partial bool ShowSystemOther{get;set;} private NativeCurrentSystemSource? currentSystemSource; private CurrentSystemSnapshot? currentSystemSnapshot; private DispatcherQueueTimer? currentSystemTimer; private int currentSystemViewCount,currentSystemPid; private bool currentSystemReadInProgress; private int currentSystemReadFailures; private string currentSystemGameHash=""; private ISystemFlightControlDriver? currentSystemCapabilityDriver; private SystemNavigationCapabilities? currentSystemCapabilities; private DateTimeOffset currentSystemCapabilitiesReadAt; private readonly SystemNavigationRunner systemNavigator; private CancellationTokenSource? systemFlightStop; internal CurrentSystemSnapshot? CurrentSystemSceneSnapshot=>currentSystemSnapshot; internal event Action? CurrentSystemSceneChanged; public string SelectedSystemTitle=>SelectedSystemLocation?.Name??"选择目的地"; public string SelectedSystemKind=>SelectedSystemLocation?.Record.KindText??"在场景或地点列表中选择"; public string SelectedSystemDistance=>SelectedSystemLocation?.Distance??"—"; public string SelectedSystemPositionStatus=>SelectedSystemLocation is null?"选中地点后查看驾驶方式":SelectedSystemLocation.Record.Position is {Finite:true}?"游戏坐标已读取":"坐标待确认 · 仍保留在地点列表"; partial void OnSelectedSystemLocationChanged(SystemLocationLiveRow? value) { OnPropertyChanged(nameof(SelectedSystemTitle));OnPropertyChanged(nameof(SelectedSystemKind));OnPropertyChanged(nameof(SelectedSystemDistance));OnPropertyChanged(nameof(SelectedSystemPositionStatus)); UpdateSystemControlAvailability(); } private static bool CurrentGameProcessAlive(int pid) { try{using var process=System.Diagnostics.Process.GetProcessById(pid);return !process.HasExited&&process.ProcessName.Equals("EliteDangerous64",StringComparison.OrdinalIgnoreCase);} catch(Exception error)when(error is ArgumentException or InvalidOperationException or System.ComponentModel.Win32Exception){return false;} } partial void OnCurrentSystemSearchChanged(string value)=>FilterSystemLocations(); partial void OnShowSystemStarsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemAsteroidsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemPlanetsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemLandablePlanetsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemSettlementsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemStationsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemCarriersChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemPointsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemSignalsChanged(bool value)=>FilterSystemLocations(); partial void OnShowSystemOtherChanged(bool value)=>FilterSystemLocations(); partial void OnSystemFlightReadyChanged(bool value)=>StartSystemFlightCommand.NotifyCanExecuteChanged(); private void FilterSystemLocations(bool keepOrder=false) { if(currentSystemSnapshot is not {} snapshot)return; var categories=SystemLocationCategory.None; void Add(bool show,SystemLocationCategory category){if(show)categories|=category;} Add(ShowSystemStars,SystemLocationCategory.Star);Add(ShowSystemAsteroids,SystemLocationCategory.Asteroid);Add(ShowSystemPlanets,SystemLocationCategory.Planet); Add(ShowSystemLandablePlanets,SystemLocationCategory.LandablePlanet);Add(ShowSystemSettlements,SystemLocationCategory.Settlement);Add(ShowSystemStations,SystemLocationCategory.Station); Add(ShowSystemCarriers,SystemLocationCategory.Carrier);Add(ShowSystemPoints,SystemLocationCategory.PointOfInterest);Add(ShowSystemSignals,SystemLocationCategory.Signal);Add(ShowSystemOther,SystemLocationCategory.Other); var selected=SelectedSystemLocation?.Record.Identity; CurrentSystemLocations.ApplyLocations(new SystemLocationFilter(categories,CurrentSystemSearch).Apply(snapshot),sortExisting:!keepOrder); SelectedSystemLocation=CurrentSystemLocations.FirstOrDefault(r=>r.Record.Identity==selected); CurrentSystemStatus=$"{CurrentSystemLocations.Count} / {snapshot.Locations.Count} 个地点"; OnPropertyChanged(nameof(SelectedSystemDistance));OnPropertyChanged(nameof(SelectedSystemPositionStatus)); CurrentSystemSceneChanged?.Invoke(); } internal void ShowCurrentSystem() { if(OfflinePreview)return; currentSystemViewCount++; if(currentSystemTimer is null){currentSystemTimer=dispatcher.CreateTimer();currentSystemTimer.Interval=TimeSpan.FromSeconds(3);currentSystemTimer.Tick+=async(_,_)=>await RefreshCurrentSystemAsync();} currentSystemTimer.Start();_=RefreshCurrentSystemAsync(); } internal void HideCurrentSystem(){currentSystemViewCount=Math.Max(0,currentSystemViewCount-1);if(currentSystemViewCount==0)currentSystemTimer?.Stop();} [RelayCommand] private async Task RefreshCurrentSystemAsync() { if(OfflinePreview)return; if(currentSystemReadInProgress)return; currentSystemReadInProgress=true; CurrentSystemReading=currentSystemSnapshot is null; try { if(DemoMode||!runtime.Player.InGame) { CurrentSystemStatus="请进入游戏后查看当前星系地点。";CurrentSystemLocations.Apply([]);currentSystemSnapshot=null;SelectedSystemLocation=null;SystemFlightReady=false; CurrentSystemSceneChanged?.Invoke();UpdateSystemControlAvailability();runtime.ReportStatus("current-system.wait","当前星系","等待进入驾驶场景。");return; } // The driver may still belong to a game process that has exited. // Refresh the environment independently and bind reads to its live PID. // Checking the PID is cheap; hashing the game image every thirty // seconds is not. Re-discover only on process loss or read failure. if(gameEnvironment?.ProcessId is not >0||!CurrentGameProcessAlive(gameEnvironment.ProcessId.Value)) await ReadEnvironmentAsync(); var pid=gameEnvironment?.ProcessId??0; var hash=gameEnvironment?.ImageSha256??""; if(pid<=0||hash.Length!=64){await ReadEnvironmentAsync();pid=gameEnvironment?.ProcessId??0;hash=gameEnvironment?.ImageSha256??"";} if(pid<=0)throw new FlightTelemetryUnavailableException("等待当前游戏实例。"); if(currentSystemSource is null||currentSystemPid!=pid||currentSystemGameHash!=hash) {currentSystemSource?.Dispose();currentSystemSource=new(pid,hash,()=>runtime.Player,snapshot=>runtime.Journal.SystemOrbits.Bind(snapshot));currentSystemPid=pid;currentSystemGameHash=hash;currentSystemCapabilityDriver=null;currentSystemCapabilities=null;} var next=await currentSystemSource.ReadLocationsAsync(lifetime.Token); if(runtime.Player.Navigation.CurrentSystemAddress!=next.SystemAddress)return; ApplyCurrentSystemSnapshot(next); currentSystemReadFailures=0; await RefreshCurrentSystemControlAsync(pid,hash,lifetime.Token); runtime.ResolveDiagnostic("current-system.read","当前星系","地点读取已恢复。"); runtime.ResolveDiagnostic("current-system.read-stalled","当前星系","地点遥测已恢复。"); runtime.ReportStatus("current-system.wait","当前星系",$"已读取 {next.Locations.Count} 个真实地点。"); } catch(OperationCanceledException)when(lifetime.IsCancellationRequested){} catch(Exception error)when(NavigationRecovery.CanRetry(error,lifetime.Token)) { HandleCurrentSystemReadFailure(); runtime.ReportStatus("current-system.wait","当前星系","正在等待驾驶场景和最新地点遥测,恢复后自动刷新。"); if(currentSystemReadFailures>=10)runtime.ReportDiagnostic("current-system.read-stalled","当前星系",error,ActivitySeverity.Warning,"地点遥测持续未就绪,保留最近目录,暂不允许启动驾驶。"); } catch(Exception error) { HandleCurrentSystemReadFailure();CurrentSystemStatus="地点接口暂不可用:"+error.Message; runtime.ReportDiagnostic("current-system.read","当前星系",error,error is NotSupportedException?ActivitySeverity.Warning:ActivitySeverity.Error,"地点读取失败:"+error.Message); } finally{currentSystemReadInProgress=false;CurrentSystemReading=false;} } internal void ApplyCurrentSystemSnapshot(CurrentSystemSnapshot snapshot) { if(currentSystemSnapshot?.SystemAddress!=snapshot.SystemAddress)SelectedSystemLocation=null; currentSystemSnapshot=snapshot;CurrentSystemTitle=snapshot.SystemName;FilterSystemLocations(keepOrder:true); } private void HandleCurrentSystemReadFailure() { SystemFlightReady=false;currentSystemReadFailures++;CurrentSystemStatus="正在重新读取当前星系地点…"; if(currentSystemSnapshot?.SystemAddress!=runtime.Player.Navigation.CurrentSystemAddress) {currentSystemSnapshot=null;CurrentSystemLocations.Apply([]);SelectedSystemLocation=null;CurrentSystemSceneChanged?.Invoke();} UpdateSystemControlAvailability(); } private bool CanStartSystemFlight()=>SystemFlightReady&&CurrentSystemTargetIssue() is null&&!SystemFlightActive&&!NavigationActive&&!RunActive&&!SessionBusy&&!runtime.FlightAutomation.IsActive; [RelayCommand(CanExecute=nameof(CanStartSystemFlight))] private async Task StartSystemFlightAsync() { if(!CanStartSystemFlight()||SelectedSystemLocation is not {} selected||currentSystemSnapshot is not {} snapshot)return; if(currentSystemCapabilityDriver is not {} driver)return; var row=selected.Record; var target=SystemTarget(snapshot,row); using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);systemFlightStop=stop;SystemFlightActive=true;StartSystemFlightCommand.NotifyCanExecuteChanged(); try { shipProtection?.RequireHealthyShip();var result=await Task.Run(()=>runtime.FlightAutomation.NavigateLocalAsync(target,driver,stop.Token),stop.Token); if(result.Failure is {} failure) runtime.ReportDiagnostic("current-system.flight","星系内驾驶",failure,ActivitySeverity.Error,"驾驶已停止:"+failure.Message); else if(result.State==NavigationRunState.Completed) runtime.ResolveDiagnostic("current-system.flight","星系内驾驶","已确认到达所选目的地。"); } catch(OperationCanceledException){SystemFlightStatus="星系内导航已停止。";} catch(Exception error){SystemFlightStatus=error.Message;runtime.ReportDiagnostic("current-system.flight","星系内驾驶",error,ActivitySeverity.Error,"驾驶未能启动:"+error.Message);} finally{systemFlightStop=null;SystemFlightActive=false;StartSystemFlightCommand.NotifyCanExecuteChanged();} } [RelayCommand] private void StopSystemFlight(){systemFlightStop?.Cancel();runtime.FlightAutomation.Stop();systemNavigator.Stop();} private void OnSystemFlightProgress(SystemNavigationProgress progress)=>dispatcher.TryEnqueue(()=> { SystemFlightStatus=progress.Message;if(runtime.FlightAutomation.ActiveEntry==FlightAutomationEntry.Galaxy)NavigationStatus=progress.Message; if(progress.Diagnostic is {} diagnostic) runtime.ReportDiagnostic("flight.telemetry-recovery","驾驶恢复",progress.Message,diagnostic,ActivitySeverity.Status); else if(progress.Phase!=SystemNavigationPhase.Waiting) runtime.ResolveDiagnostic("flight.telemetry-recovery","驾驶恢复",progress.State==NavigationRunState.Stopped?"自动驾驶已停止,原始诊断已保留。":"飞行数据已恢复,继续原航程。"); }); }