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.Data;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Engineering;

public sealed class EngineerProjector
{
    private readonly Dictionary<long, EngineerAccess> engineers = [];
    private readonly EngineeringRules rules = EngineeringRules.Captured;
    public IReadOnlyList<EngineerAccess> Engineers => engineers.Values.OrderByDescending(x => x.Unlocked).ThenBy(x => x.Name).ToArray();
    public DateTimeOffset? UpdatedAt { get; private set; }
    public int VerifiedQualityRolls { get; private set; }
    public int MismatchedQualityRolls { get; private set; }
    public void Reset() { engineers.Clear(); UpdatedAt = null; VerifiedQualityRolls = MismatchedQualityRolls = 0; }
    public void Apply(JsonElement e, IReadOnlyList<ShipRecord> ships, long? activeShip, long? marketId = null)
    {
        var time = e.Time() ?? DateTimeOffset.MinValue;
        var kind = e.Text("event");
        if (kind is "RedeemVoucher" or "SellExplorationData" or "MultiSellExplorationData" or "MarketSell")
        {
            // These activities can raise reputation at particular engineer workshops. Their gain formula
            // is not part of the crafting configuration; keep an interval until an authoritative update.
            foreach (var (id, market) in EngineeringCatalog.Default.EngineerMarkets)
                if (market == marketId && engineers.TryGetValue(id, out var access) && access.Unlocked && access.Rank < 5 && access.ObservedAt <= time)
                {
                    engineers[id] = access with { ReputationMin = access.ReputationMin ?? rules.ReputationThresholds[access.Rank], ReputationMax = 100, RankProgress = null, ObservedAt = time };
                    UpdatedAt = time;
                }
        }
        if (kind == "EngineerProgress")
        {
            if (UpdatedAt > time) return;
            var rows = e.Get("Engineers").ValueKind == JsonValueKind.Array ? e.Get("Engineers").Items() : [e];
            // Startup is authoritative; single-engineer events merge missing fields.
            if (e.Get("Engineers").ValueKind == JsonValueKind.Array) engineers.Clear();
            foreach (var x in rows)
            {
                var id = x.Long("EngineerID"); if (id <= 0) continue;
                var prior = engineers.GetValueOrDefault(id) ?? new EngineerAccess { Id = id };
                var rank = (int)x.Long("Rank", prior.Rank);
                var access = x.Text("Progress", rank > 0 ? "Unlocked" : prior.Progress);
                double? lo = prior.ReputationMin, hi = prior.ReputationMax, progress = x.Number("RankProgress");
                if (rank is >= 1 and <= 5)
                {
                    if (progress is { } p) lo = hi = rules.Absolute(rank, p);
                    else
                    {
                        var floor = rules.ReputationThresholds[rank]; var ceiling = rank == 5 ? floor : rules.ReputationThresholds[rank + 1] - 1e-7;
                        if (lo is null || hi is null || hi < floor || lo > ceiling) { lo = floor; hi = ceiling; }
                        else { lo = Math.Max(floor, lo.Value); hi = Math.Min(ceiling, hi.Value); }
                        if (lo == hi) progress = rules.WithinRank(lo!.Value);
                    }
                }
                engineers[id] = prior with { Name = x.Text("Engineer", prior.Name), Progress = access, Rank = rank, RankProgress = progress, ReputationMin = lo, ReputationMax = hi, ObservedAt = time };
            }
            UpdatedAt = time;
        }
        if (kind != "EngineerCraft") return;
        var engineerId = e.Long("EngineerID");
        if (!engineers.TryGetValue(engineerId, out var old) || old.ObservedAt > time) return;
        var grade = (int)e.Long("Level");
        var experiment = e.Text("ApplyExperimentalEffect").Length > 0;
        var remote = e.Flag("IsRemote") || e.Flag("Remote");
        if (!experiment && grade is >= 1 and <= 5)
        {
            var module = ships.FirstOrDefault(s => s.ShipId == activeShip)?.Modules.FirstOrDefault(m => m.Slot == e.Text("Slot") && m.Item.Equals(e.Text("Module"), StringComparison.OrdinalIgnoreCase));
            if (module is not null && old.Rank >= grade && e.Number("Quality") is { } actual)
            {
                var before = module.Engineering is { } eng && eng.Blueprint == e.Text("BlueprintName") && eng.Level == grade ? eng.Quality : 0;
                if (before is { } q && q < .999)
                {
                    var minRank = old.ReputationMin is { } min ? rules.Rank(min) : old.Rank;
                    var maxRank = old.ReputationMax is { } max ? rules.Rank(max) : old.Rank;
                    if (Enumerable.Range(Math.Max(grade, minRank), Math.Max(0, maxRank - Math.Max(grade, minRank) + 1))
                        .Any(rank => Math.Abs(Math.Min(1, q + EngineeringRules.QualityGain(grade, rank)) - actual) < .002)) VerifiedQualityRolls++;
                    else MismatchedQualityRolls++;
                }
            }
            if (!remote && old.ReputationMin is { } lo && old.ReputationMax is { } hi)
            {
                var gain = rules.Reward(grade); lo = Math.Min(100, lo + gain); hi = Math.Min(100, hi + gain);
                var newRank = rules.Rank(lo);
                engineers[engineerId] = old with { ReputationMin = lo, ReputationMax = hi, Rank = newRank,
                    RankProgress = Math.Abs(lo - hi) < 1e-6 ? rules.WithinRank(lo) : null, ObservedAt = time };
            }
        }
        UpdatedAt = time;
    }
}