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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFEEDToolBox.Core.Galaxy;

public sealed record GalaxyCurrentTradeRoute(long FromSystemAddress,long ToSystemAddress,int OriginalCategoryId,float Amount,
    IReadOnlyList<GalaxyCurrentTradeFlow> Contributions);
public sealed record GalaxyCurrentTradeRenderFlow(GalaxyCurrentTradeRoute Route,GalacticSystem From,GalacticSystem To);
public sealed record GalaxyCurrentTradeProjection(IReadOnlyList<GalaxyCurrentTradeRoute> Routes,IReadOnlyList<GalaxyCurrentTradeRenderFlow> Flows,
    IReadOnlyList<long> MissingSystemAddresses);

/// <summary>Original current-cache direction, category aggregation and strongest-route selection. No historical fallback.</summary>
public static class GalaxyCurrentTradeRoutes
{
    private static readonly Lazy<IReadOnlyDictionary<string,int>> categories=new(()=>GalaxyOriginalTradeCategories.Commodities
        .ToDictionary(c=>GalaxyMapFilter.Normalize(c.Name),c=>c.CategoryId,StringComparer.Ordinal));
    public static int? CategoryFor(string? originalSymbol)=>originalSymbol is not null&&categories.Value.TryGetValue(GalaxyMapFilter.Normalize(originalSymbol),out var value)?value:null;
    public static IReadOnlyList<GalaxyCommodityGroup> Choices(IEnumerable<GalaxyCurrentTradeFlow> flows)
    {
        return flows.Where(f=>f.CommodityName is not null).DistinctBy(f=>GalaxyMapFilter.Normalize(f.CommodityName!))
            .Select(f=>(Flow:f,Category:CategoryFor(f.CommodityName))).GroupBy(f=>f.Category).OrderBy(g=>g.Key??int.MaxValue)
            .Select(g=>new GalaxyCommodityGroup(g.Key is {} id?"originaltrade:"+id:"__unknownoriginaltradecategory",
                g.Key is {} category?GalaxyOriginalTradeCategories.Categories.Single(c=>c.Id==category).Label:"原贸易分类尚未确认",
                g.Select(f=>new GalaxyFilterChoice(GalaxyMapFilter.Normalize(f.Flow.CommodityName!),f.Flow.CommodityName!)).OrderBy(c=>c.Label,StringComparer.Ordinal).ToArray(),g.Key is not null)).ToArray();
    }
    public static IReadOnlyList<GalaxyCurrentTradeRoute> Select(GalaxyCurrentTradeFlowsSnapshot snapshot,GalaxyTradeFlowSource source,IReadOnlySet<string>? commodities,IReadOnlySet<int>? categories=null)
    {
        if(!snapshot.Available)return [];
        var cutoff=unchecked(snapshot.OriginalClock-snapshot.LifetimeSeconds);
        var latest=snapshot.Flows.Where(f=>f.Timestamp>=cutoff&&f.Timestamp<=snapshot.OriginalClock)
            .GroupBy(f=>(f.FromSystemAddress,f.ToSystemAddress,f.CommodityToken)).ToDictionary(g=>g.Key,g=>g.OrderByDescending(f=>f.Timestamp).First());
        float Amount(GalaxyCurrentTradeFlow row)=>source switch{GalaxyTradeFlowSource.Npc=>row.NpcAmount,GalaxyTradeFlowSource.Players=>row.PlayerAmount,_=>row.NpcAmount+row.PlayerAmount};
        var selected=new List<(GalaxyCurrentTradeFlow Flow,int Category)>();
        foreach(var row in latest.Values)
        {
            if(CategoryFor(row.CommodityName) is not {} category||categories is not null&&!categories.Contains(category)||commodities is not null&&!commodities.Contains(GalaxyMapFilter.Normalize(row.CommodityName!)))continue;
            var amount=Amount(row);var opposite=latest.TryGetValue((row.ToSystemAddress,row.FromSystemAddress,row.CommodityToken),out var reverse)?Amount(reverse):0;
            // 3B92120 uses the positive net amount only as its direction test;
            // the per-category route accumulates the original gross amount.
            if(float.IsFinite(amount)&&float.IsFinite(opposite)&&amount-opposite>0)selected.Add((row,category));
        }
        return selected.GroupBy(row=>(row.Flow.FromSystemAddress,row.Flow.ToSystemAddress,row.Category)).Select(group=>
        {
            float amount=0;foreach(var item in group)amount+=Amount(item.Flow);
            return new GalaxyCurrentTradeRoute(group.Key.FromSystemAddress,group.Key.ToSystemAddress,group.Key.Category,amount,group.Select(row=>row.Flow).ToArray());
        }).Where(r=>float.IsFinite(r.Amount)&&r.Amount>0).GroupBy(r=>r.FromSystemAddress)
            // The original default is four strongest category routes per origin.
            // Equal-strength ordering is deterministic here; original hash-table
            // tie ordering is not asserted to be identical.
            .SelectMany(g=>g.OrderByDescending(r=>r.Amount).ThenBy(r=>r.ToSystemAddress).ThenBy(r=>r.OriginalCategoryId).Take(4))
            .OrderBy(r=>r.FromSystemAddress).ThenBy(r=>r.ToSystemAddress).ThenBy(r=>r.OriginalCategoryId).ToArray();
    }
    public static GalaxyCurrentTradeProjection Project(IReadOnlyList<GalaxyCurrentTradeRoute> routes,IReadOnlyDictionary<long,GalacticSystem> systems)
    {
        var resolved=new List<GalaxyCurrentTradeRenderFlow>();var missing=new HashSet<long>();
        foreach(var route in routes)
        {
            bool Try(long address,out GalacticSystem? system)
            {system=null;if(systems.TryGetValue(address,out var found)&&found.SystemAddress==address&&found.Position.IsValid&&!string.IsNullOrWhiteSpace(found.Name)){system=found;return true;}missing.Add(address);return false;}
            var fromKnown=Try(route.FromSystemAddress,out var from);var toKnown=Try(route.ToSystemAddress,out var to);
            if(fromKnown&&toKnown)resolved.Add(new(route,from!,to!));
        }
        return new(routes,resolved,missing.Order().ToArray());
    }
}