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 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<SystemLocationLiveRow,SystemLocation,string> 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?"自动驾驶已停止,原始诊断已保留。":"飞行数据已恢复,继续原航程。");
    });
}