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.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;

internal static class TradeJourneyAcceptance
{
    public static async Task RunAsync(int pid,string directory)
    {
        directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
        var statePath=Path.Combine(directory,"journey.json");var statusPath=Path.Combine(directory,"status.json");
        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;
        if(!initial.InGame||initial.CurrentShipId is null||!initial.CargoReliable)throw new InvalidOperationException("Reliable live ship required.");
        var state=File.Exists(statePath)?TradeJourneyCheckpoints.Load(statePath):new TradeJourneyCheckpoint(initial.CommanderId,initial.CurrentShipId.Value,DateTimeOffset.UtcNow);
        if(state.Commander!=initial.CommanderId||state.ShipId!=initial.CurrentShipId)throw new InvalidOperationException("Acceptance commander or ship changed.");
        using var stop=new CancellationTokenSource(TimeSpan.FromMinutes(35));
        using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop")))stop.Cancel();},null,100,100);
        using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));
        var statusLock=new object();string phase="准备 1t 低价跑商验收";
        void Publish(string message,bool? passed=null)
        {
            lock(statusLock)
            {
                phase=message;
                var temporary=statusPath+".tmp";
                File.WriteAllText(temporary,JsonSerializer.Serialize(new{pid,phase,passed,at=DateTimeOffset.UtcNow,profit=ledger.Snapshot,
                    state.StartedAt,shipId=state.ShipId,initial.CargoCapacity,destination="AD Leonis / Homer Platform",
                    quantity=1,commodity="biowaste",screenUse=false},ToolboxSettings.JsonOptions));
                File.Move(temporary,statusPath,true);Console.WriteLine(message);
            }
        }
        Task Save(){TradeJourneyCheckpoints.Save(statePath,state);return Task.CompletedTask;}
        var channel=new NativeCommerceChannel(pid);NativeWalletObservation? wallet=null;
        PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
        async Task Check(CancellationToken token)
        {
            token.ThrowIfCancellationRequested();var current=Player();
            var maintenance=Path.Combine(Directory.GetParent(directory)!.FullName,"maintenance-hold.json");
            if(current.Docked&&File.Exists(maintenance))
            {
                using var notice=JsonDocument.Parse(await File.ReadAllTextAsync(maintenance,token));
                if(notice.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("服务器维护等待中,保持停靠;维护完成后自动继续同一笔 1t 货物验收。");
            }
            if(!current.InGame||current.CurrentShipId!=state.ShipId||current.CommanderId!=state.Commander||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current))
                throw new InvalidOperationException("Acceptance session or ship safety changed.");
            var health=await channel.CallAsync(new{op="health.read"},token);
            if(!health.GetProperty("localPlayerShipVerified").GetBoolean()||!health.GetProperty("shipHealth").GetProperty("verified").GetBoolean()
                ||health.GetProperty("shipHealth").GetProperty("hullFraction").GetDouble()<.5)throw new InvalidOperationException("Independent ship health unavailable.");
        }
        async Task Travel(Station station,Func<CancellationToken,Task> beforeInput,CancellationToken token)
        {
            await beforeInput(token);
            if(Player().Docked)
            {
                if(Player().MarketId==station.MarketId)return;
                Publish("自动起飞并解除质量锁定");
                await LocalDepartureAcceptance.RunAsync(pid,"Quimper Dock",Path.Combine(directory,"departure-"+Guid.NewGuid().ToString("N")+".json"),token);
            }
            await Check(token);
            if(!Player().SystemName.Equals(station.SystemName,StringComparison.OrdinalIgnoreCase))
            {
                Publish("正在按游戏内航线前往 "+station.SystemName);
                await InterstellarTravelAcceptance.RunAsync(pid,station.SystemName,Path.Combine(directory,"interstellar-"+Guid.NewGuid().ToString("N")+".json"),token);
            }
            await Check(token);Publish("正在超巡、呼叫空管并自动停靠 "+station.Name);
            await LocalNavigationAcceptance.RunAsync(pid,station.Name,Path.Combine(directory,"arrival-"+Guid.NewGuid().ToString("N")+".json"),requireCruise:false,useNativeCruise:true,allowObscuration:true,externalToken:token);
            if(!Player().Docked||Player().MarketId!=station.MarketId)throw new InvalidOperationException("Original docking did not confirm the expected destination.");
        }
        var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),Travel,Check);
        driver.WalletObserved+=observation=>wallet=observation;
        driver.ServiceConfirmed+=receipt=>{ledger.Record(receipt.ToCashEntry());Publish(phase);};
        driver.Status+=message=>Publish(message);
        var from=new Station{MarketId=128096504,Name="Leiber Installation",SystemName="Phanes"};
        var to=new Station{MarketId=4210924803,Name="Homer Platform",SystemName="AD Leonis"};
        // A small acceptance expense is allowed; this is not the production route.
        // Sale uses a floor of 1 Cr and records the actual server price and net loss/profit.
        var order=new TradeOrder("biowaste","Biowaste",1,40,1);
        var leg=new TradeLeg{Number=1,From=from,To=to,Orders=[order]};
        var policy=new TradeAcceptancePolicy();
        var healthLog=Path.Combine(directory,"continuous-health-"+Guid.NewGuid().ToString("N")+".json");
        var start=new ProcessStartInfo(Environment.ProcessPath!){UseShellExecute=false,CreateNoWindow=true,WindowStyle=ProcessWindowStyle.Hidden,
            RedirectStandardOutput=true,RedirectStandardError=true};
        foreach(var argument in new[]{"observe-health",pid.ToString(),healthLog,"2100"})start.ArgumentList.Add(argument);
        using var monitor=Process.Start(start)??throw new IOException("Cannot start independent continuous ship protection.");
        var monitorReady=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        monitor.OutputDataReceived+=(_,e)=>{if(e.Data?.Contains("health protection armed",StringComparison.Ordinal)==true)monitorReady.TrySetResult();};
        monitor.BeginOutputReadLine();monitor.BeginErrorReadLine();
        string? failure=null;Task? dockedService=null;
        try
        {
            await monitorReady.Task.WaitAsync(TimeSpan.FromSeconds(12),stop.Token);
            await Save();await driver.PreflightAsync(stop.Token);await Check(stop.Token);
            if(state.Bought is null)
            {
                if(!Player().Docked||Player().MarketId!=from.MarketId||Player().CargoCount!=0)throw new InvalidOperationException("First acceptance purchase requires the confirmed empty hold at Leiber Installation.");
                Publish("加油维修补给与 1t 低价买入同时执行");
                var buy=new TradeAction(TradeActionKind.Buy,leg,order);policy.ValidateAction(buy,Player());
                dockedService=driver.ServiceDockedAsync(new(from.MarketId,1),Check,stop.Token);
                var purchase=driver.ExecuteTradeAsync(buy,Check,stop.Token);
                var receipt=await purchase;receipt.Validate(buy,Player(),state.StartedAt);ledger.RecordTrade(receipt);
                state=state with{Bought=receipt};await Save();Publish("已确认买入 1t 低价货物");
                await dockedService;dockedService=null;
            }
            else ledger.RecordTrade(state.Bought);
            if(state.Sold is null)
            {
                var travel=new TradeAction(TradeActionKind.Travel,leg,null);policy.ValidateAction(travel,Player());
                if(!Player().Docked||Player().MarketId!=to.MarketId)
                {
                    // Driver travel starts at the original departure station. A
                    // resumed in-flight acceptance continues its same destination.
                    await Travel(to,Check,stop.Token);
                }
                var sale=new TradeAction(TradeActionKind.Sell,leg,order);policy.ValidateAction(sale,Player());
                Publish("到站维护与实际卖出同时执行");
                dockedService=driver.ServiceDockedAsync(new(to.MarketId,1),Check,stop.Token);
                var receipt=await driver.ExecuteTradeAsync(sale,Check,stop.Token);
                receipt.Validate(sale,Player(),state.StartedAt);ledger.RecordTrade(receipt);state=state with{Sold=receipt};await Save();
                await dockedService;dockedService=null;
            }
            else ledger.RecordTrade(state.Sold);
            if(Player().CargoCount!=0||!Player().Docked||Player().MarketId!=to.MarketId||monitor.HasExited)
                throw new InvalidOperationException("Final cargo, docking or continuous health protection not confirmed.");
            Publish("1t 完整跑商实测完成",true);
        }
        catch(Exception error){failure=error.ToString();Publish("正在处理验收问题:"+error.Message,false);}
        finally
        {
            stop.Cancel();
            if(dockedService is not null)try{await dockedService;}catch(Exception error){failure??=error.ToString();}
            await driver.ReleaseAllAsync();
            await File.WriteAllTextAsync(healthLog+".stop","stop",CancellationToken.None);
            try{await monitor.WaitForExitAsync().WaitAsync(TimeSpan.FromSeconds(4));}catch(TimeoutException){}
            await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),JsonSerializer.Serialize(new{passed=failure is null,failure,state,profit=ledger.Snapshot,
                finalSystem=Player().SystemName,finalStation=Player().StationName,Player().Docked,Player().CargoCount,screenUse=false},ToolboxSettings.JsonOptions));
        }
        if(failure is not null)throw new InvalidOperationException("1t journey is awaiting automatic repair; see its persisted receipts and result.");
    }
}