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 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<SystemLocationLiveRow,SystemLocation,string> 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();}
    }
}