using System.Text.Json; using XFEEDToolBox.Core.Data; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Native; using XFEEDToolBox.Core.Navigation; internal static class LocalDepartureAcceptance { public static async Task RunAsync(int pid,string exactName,string reportPath,CancellationToken externalToken=default,bool stopAfterDeparture=true) { 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 initial=journal.State;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 catalogue=await source.ReadLocationsAsync();var row=catalogue.Locations.Single(x=>x.Name==exactName); if(!initial.Docked||initial.MarketId==row.MarketId||!NavigationAssists.From(initial).AdvancedDocking)throw new InvalidOperationException("Departure acceptance requires a different destination and an enabled advanced docking computer."); var target=new SystemNavigationTarget(catalogue.SystemAddress,row.WorldAddress,row.Name,SystemTargetKind.Station,row.MarketId){NativeKey=row.Key,NativeKind=row.NativeKind,NativeIdentity=row.Identity}; await using var protection=await FlightTestProtectionScope.ArmAsync(pid); var driver=new NativeSystemFlightControlDriver(pid,hash,()=>journal.State,new NativeGameInputChannel(pid,hash,Path.GetFullPath("XFEEDToolBox/PluginPackage"))); using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(7));var start=DateTimeOffset.UtcNow;var engaged=false;var prepared=false;var released=false;double maxHeat=0;string? failure=null; Task? preparation=null;SystemFlightFrame? final=null; await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read)); try { await driver.SelectAsync(target,stop.Token);preparation=driver.PrepareAsync(stop.Token); while(!preparation.IsCompleted) { stop.Token.ThrowIfCancellationRequested();var state=journal.State; if(state.CurrentShipId!=initial.CurrentShipId||state.CommanderId!=initial.CommanderId||state.LoadGameAt!=initial.LoadGameAt||state.CargoCount!=initial.CargoCount||NavigationSafety.HasExternalThreat(state)) throw new InvalidOperationException("Departure session, ship or reserved cargo changed."); try { var raw=await pipe.CallAsync(new{op="read"},stop.Token);var local=await pipe.CallAsync(new{op="local.read"},stop.Token); maxHeat=Math.Max(maxHeat,raw.GetProperty("heatPercent").GetDouble());if(maxHeat>=75)throw new InvalidOperationException("Departure heat bound exceeded."); engaged|=local.GetProperty("docking").GetProperty("nativeLaunchActive").GetBoolean(); await log.WriteLineAsync(JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,state.Docked,raw,local,engaged}));await log.FlushAsync(); } catch(Exception error)when(NavigationRecovery.CanRetry(error,stop.Token)) {await log.WriteLineAsync(JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,recovering=error.Message}));await log.FlushAsync();} await Task.Delay(80,stop.Token); } await preparation;prepared=true; while(true) { final=await driver.ReadSystemFlightAsync(stop.Token); if(final.PlayerOverride||final.ExternalThreat||!target.SameIdentity(final.SelectedTarget!))throw new InvalidOperationException("Departure did not retain the requested destination."); if(!stopAfterDeparture)break; await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"完成离场并停车",null,0),final,target,final.ObservedAt.AddMilliseconds(250),stop.Token); if(final.Speed<1)break;await Task.Delay(80,stop.Token); } if(!engaged||journal.State.Docked||journal.State.Navigation.UndockedAt