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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

namespace XFEEDToolBox.Core.Execution;

public sealed record KnownStationFine(long MarketId,string Authority,long Amount);
public sealed record StationFineSnapshot(string CommanderId,IReadOnlyDictionary<long,KnownStationFine> Restrictions,int JournalFilesRead)
{
    public long FineAt(long market)=>Restrictions.TryGetValue(market,out var fine)?fine.Amount:0;
}

/// <summary>Waits for known journal-confirmed fines affecting this station, without submitting a trade.</summary>
public static class StationAccessWaiter
{
    public static async Task<long> KnownFineAsync(string journals,string commander,long market,CancellationToken token)
        =>(await ReadKnownFinesAsync(journals,commander,token)).FineAt(market);
    /// <summary>One journal pass resolves all known markets for this FID; no per-candidate file scans or guessed access flags.</summary>
    public static async Task<StationFineSnapshot> ReadKnownFinesAsync(string? journals,string commander,CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        if(journals is null||!Directory.Exists(journals))return new(commander,new Dictionary<long,KnownStationFine>(),0);
        var fines=new Dictionary<string,long>(StringComparer.Ordinal);var authorities=new Dictionary<long,string>();var filesRead=0;
        foreach(var file in Directory.EnumerateFiles(journals,"Journal*.log").Order(StringComparer.Ordinal))
        {
            token.ThrowIfCancellationRequested();filesRead++;
            await using var stream=new FileStream(file,FileMode.Open,FileAccess.Read,FileShare.ReadWrite|FileShare.Delete);using var reader=new StreamReader(stream);var fid="";
            while(await reader.ReadLineAsync(token) is {} line)
            {
                if(!line.Contains("\"LoadGame\"",StringComparison.Ordinal)&&!line.Contains("\"Docked\"",StringComparison.Ordinal)&&!line.Contains("\"Location\"",StringComparison.Ordinal)
                    &&!line.Contains("\"CommitCrime\"",StringComparison.Ordinal)&&!line.Contains("\"PayFines\"",StringComparison.Ordinal))continue;
                JsonDocument document;try{document=JsonDocument.Parse(line);}catch(JsonException){continue;}
                using(document)
                {
                    var row=document.RootElement;string Text(string name)=>row.TryGetProperty(name,out var v)&&v.ValueKind==JsonValueKind.String?v.GetString()!:"";
                    long Number(string name)=>row.TryGetProperty(name,out var v)&&v.ValueKind==JsonValueKind.Number&&v.TryGetInt64(out var n)?n:0;
                    var kind=Text("event");if(kind=="LoadGame")fid=Text("FID");if(fid!=commander)continue;
                    var market=Number("MarketID");
                    if(kind is "Location" or "Docked"&&market>0&&row.TryGetProperty("StationFaction",out var station)
                        &&station.ValueKind==JsonValueKind.Object&&station.TryGetProperty("Name",out var name)&&name.ValueKind==JsonValueKind.String)
                        authorities[market]=name.GetString()??"";
                    var authority=Text("Faction");
                    if(kind=="CommitCrime"&&Number("Fine")>0&&authority.Length>0)fines[authority]=checked(fines.GetValueOrDefault(authority)+Number("Fine"));
                    if(kind=="PayFines")
                    {
                        if(row.TryGetProperty("AllFines",out var all)&&all.ValueKind==JsonValueKind.True)fines.Clear();
                        else if(authority.Length>0)fines[authority]=Math.Max(0,fines.GetValueOrDefault(authority)-Number("Amount"));
                    }
                }
            }
        }
        return new(commander,authorities.Where(pair=>fines.GetValueOrDefault(pair.Value)>0)
            .ToDictionary(pair=>pair.Key,pair=>new KnownStationFine(pair.Key,pair.Value,fines[pair.Value])),filesRead);
    }
    public static async Task WaitAsync(string? journals,Func<PlayerState> player,long market,Action<string>? progress,CancellationToken token)
    {
        if(journals is null)return;
        var initial=player();var schedule=new StationAccessWaitSchedule();
        void RequireCurrent()
        {
            token.ThrowIfCancellationRequested();var current=player();
            if(!current.InGame||current.CommanderId!=initial.CommanderId||current.CurrentShipId!=initial.CurrentShipId||current.LoadGameAt!=initial.LoadGameAt)
                throw new InvalidDataException("等待站点交易资格时飞船或会话发生变化,已停止交易。");
            if(!current.Docked||current.MarketId!=market)throw new InvalidOperationException("已离开等待中的交易站点,未提交订单。");
            if(NavigationSafety.UnhealthyHull(current))throw new FlightHealthPauseException();
            if(NavigationSafety.HasExternalThreat(current))throw new FlightThreatPauseException();
        }
        while(true)
        {
            RequireCurrent();
            var fine=await KnownFineAsync(journals,initial.CommanderId,market,token);RequireCurrent();if(fine<=0)return;
            var wait=schedule.Observe(fine);
            if(wait.Report)progress?.Invoke($"本站有未缴罚款 {fine:N0} Cr,请在行政联系人缴纳;程序保留原货物并等待,缴清后自动继续。");
            RequireCurrent();
            await Task.Delay(wait.Delay,token);
        }
    }
}

/// <summary>Unchanged local fines do not create a one-second log loop or repeated full journal scans.</summary>
public sealed class StationAccessWaitSchedule
{
    private static readonly int[] Delays=[1,2,4,8,15,30];
    private long? previous;private int index;
    public (bool Report,TimeSpan Delay) Observe(long fine)
    {
        if(fine<=0)throw new ArgumentOutOfRangeException(nameof(fine));
        var changed=previous!=fine;if(changed){previous=fine;index=0;}
        var delay=TimeSpan.FromSeconds(Delays[index]);index=Math.Min(index+1,Delays.Length-1);
        return(changed,delay);
    }
}