XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;

/// <summary>Bounded SC entry, stationary-throttle observation and ordinary low-speed exit. No trade or docking.</summary>
internal static class LocalCruiseEntryAcceptance
{
    public static async Task RunAsync(int pid,string exactName,string reportPath)
    {
        if(File.Exists(reportPath)||File.Exists(reportPath+".jsonl"))throw new IOException("Use a new report path.");
        var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
        await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var pipe=new NavigationPipeClient(pid);
        var hash=(await pipe.CallAsync(new{op="describe"})).GetProperty("gameSha256").GetString()!;
        using var source=new NativeCurrentSystemSource(pid,hash,()=>journal.State);var initial=journal.State;var catalogue=await source.ReadLocationsAsync();
        var row=catalogue.Locations.Single(x=>x.Name==exactName);var businessMarket=row.MarketId;
        if(businessMarket is not >0||initial.Docked)throw new InvalidOperationException("Entry fixture requires an undocked ship and an exact station target.");
        SystemNavigationTarget MakeTarget(CurrentSystemSnapshot snapshot,SystemLocation item)=>new(snapshot.SystemAddress,item.WorldAddress,item.Name,SystemTargetKind.Station,item.MarketId)
            {NativeKey=item.Key,NativeKind=item.NativeKind,NativeIdentity=item.Identity};
        var target=MakeTarget(catalogue,row);
        FlightTestProtectionScope? protection=null;var start=DateTimeOffset.UtcNow;var watch=Stopwatch.StartNew();var entered=false;var returned=false;var released=false;var rebound=false;
        var inputs=0;var enterRequests=0;var exitRequests=0;double maxHeat=0,maxSpeed=0;string? failure=null;SystemFlightFrame? latest=null;DateTimeOffset? enteredAt=null;
        await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
        async Task Record(object value){await log.WriteLineAsync(JsonSerializer.Serialize(value));await log.FlushAsync();}
        void ValidateSession()
        {
            var current=journal.State;
            if(!current.InGame||current.CurrentShipId!=initial.CurrentShipId||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
                ||current.Navigation.CurrentSystemAddress!=catalogue.SystemAddress||current.CargoCount!=initial.CargoCount
                ||current.Inventory.Any(x=>x.Count!=initial.Inventory.Where(y=>y.Key==x.Key).Sum(y=>y.Count)))
                throw new InvalidOperationException("Acceptance session, ship, system or reserved cargo changed.");
        }
        async Task EnsureProtection(CancellationToken token)
        {
            if(protection?.Stopped!=true)return;
            ValidateSession();var nav=journal.State.Navigation;
            if(!(nav.SupercruiseEnteredAt>=start.AddSeconds(-1)||nav.SupercruiseExitedAt>=start.AddSeconds(-1)))
                throw new InvalidOperationException("Cannot rearm protection without an original SC transition.");
            await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid);rebound=true;
        }
        var driver=new NativeSystemFlightControlDriver(pid,hash,()=>journal.State,new NativeGameInputChannel(pid,hash,Path.GetFullPath("XFEEDToolBox/PluginPackage")),EnsureProtection);
        try
        {
            protection=await FlightTestProtectionScope.ArmAsync(pid);using var timeout=new CancellationTokenSource(TimeSpan.FromMinutes(4));
            await driver.SelectAsync(target,timeout.Token);await driver.PrepareAsync(timeout.Token);
            if((await driver.ReadSystemFlightAsync(timeout.Token)).Mode!=LocalFlightMode.Normal)throw new InvalidOperationException("Entry acceptance must start in normal space.");
            DateTimeOffset? unavailableSince=null;
            while(watch.Elapsed<TimeSpan.FromMinutes(3.5))
            {
                ValidateSession();SystemFlightFrame frame;
                try{frame=await driver.ReadSystemFlightAsync(timeout.Token);}
                catch(Exception error)when(NavigationRecovery.CanRetry(error,timeout.Token))
                {
                    unavailableSince??=DateTimeOffset.UtcNow;await Record(new{at=DateTimeOffset.UtcNow,recovering=error.Message});
                    if(DateTimeOffset.UtcNow-unavailableSince>TimeSpan.FromSeconds(30))throw;
                    await Task.Delay(80,timeout.Token);continue;
                }
                unavailableSince=null;
                latest=frame;maxHeat=Math.Max(maxHeat,frame.HeatPercent??100);maxSpeed=Math.Max(maxSpeed,frame.Speed);
                if(frame.PlayerOverride||frame.ExternalThreat||maxHeat>=75)throw new InvalidOperationException("Acceptance heat, threat or manual-override limit exceeded.");
                try
                {
                if(frame.Mode==LocalFlightMode.Supercruise)
                {
                    if(!entered)
                    {
                        entered=true;enteredAt=DateTimeOffset.UtcNow;
                        await File.WriteAllTextAsync(reportPath+".supercruise.json",JsonSerializer.Serialize(new{pid,at=enteredAt,frame,driver.LastTelemetry},ToolboxSettings.JsonOptions));
                        Console.WriteLine("Original supercruise observed; holding zero throttle for bounded read-only diagnostics.");
                    }
                    if(DateTimeOffset.UtcNow-enteredAt<TimeSpan.FromSeconds(90)||frame.Speed>100000)
                        await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"验证超巡并减速",null,0),frame,target,frame.ObservedAt.AddMilliseconds(250),timeout.Token);
                    else if(exitRequests==0)
                    {await driver.LeaveSupercruiseSlowlyAsync(frame,timeout.Token);exitRequests++;}
                    else await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"等待低速退出",null,0),frame,target,frame.ObservedAt.AddMilliseconds(250),timeout.Token);
                }
                else if(entered&&frame.Mode==LocalFlightMode.Normal)
                {
                    await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"验证正常空间停车",null,0),frame,target,frame.ObservedAt.AddMilliseconds(250),timeout.Token);
                    if(exitRequests>0&&frame.Speed<1){returned=true;break;}
                }
                else if(!entered)
                {
                    if(frame.TargetObscured||frame.Mode==LocalFlightMode.Docked)throw new InvalidOperationException("Entry destination is obscured or ship is docked.");
                    var direction=(frame.TargetPosition-frame.Position).Unit();var aligned=Vector3d.AngleDegrees(frame.Forward,direction)<3;
                    var action=aligned&&frame.FsdReady&&enterRequests==0?SystemNavigationAction.EnterSupercruise:SystemNavigationAction.None;
                    await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.EnteringSupercruise,"验证进入超巡",direction,aligned?1:0,action),frame,target,frame.ObservedAt.AddMilliseconds(250),timeout.Token);
                    if(action!=SystemNavigationAction.None)enterRequests++;
                }
                inputs++;await Record(new{at=DateTimeOffset.UtcNow,entered,returned,rebound,inputs,enterRequests,exitRequests,frame,driver.LastTelemetry});
                }
                catch(Exception error)when(NavigationRecovery.CanRetry(error,timeout.Token))
                {await Record(new{at=DateTimeOffset.UtcNow,recovering=error.Message});await driver.SuspendSystemFlightAsync();}
                await Task.Delay(50,timeout.Token);
            }
            if(!entered||!returned)throw new TimeoutException("Bounded SC entry/exit did not complete.");
        }
        catch(Exception error){failure=error.ToString();}
        finally
        {
            try{await driver.ReleaseSystemFlightAsync();released=true;}catch(Exception error){failure=(failure??"")+" Release: "+error.Message;}
            if(protection is not null)await protection.DisposeAsync();
        }
        var passed=entered&&returned&&released&&failure is null;
        await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new{pid,start,finished=DateTimeOffset.UtcNow,passed,entered,returned,released,rebound,inputs,enterRequests,exitRequests,maxHeat,maxSpeed,latest,failure,
            tradeOrdersSent=0,dockingRequestsSent=0,completeNavigationAccepted=false},ToolboxSettings.JsonOptions));
        Console.WriteLine(JsonSerializer.Serialize(new{passed,entered,returned,released,rebound,inputs,enterRequests,exitRequests,maxHeat,maxSpeed,failure}));
        if(!passed)throw new InvalidOperationException("Local SC entry/exit acceptance failed.");
    }
}