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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Globalization;
using XFEEDToolBox.Core.Outfitting;
namespace XFEEDToolBox.Core.Presentation;

public sealed record ShipPreviewFlightState(double? Fuel=null,double? Reserve=null,double? Cargo=null,int? EnginePips=null);
public sealed record ShipOverviewMetric(string Label,string Value);

/// <summary>Selected editor loadout, with the same mass curves and pip scaling as the pinned EDSY reference.</summary>
public sealed record ShipOverviewValues
{
    public static ShipOverviewValues Empty {get;}=new();
    public double? Speed{get;init;}
    public double? Boost{get;init;}
    public double? Pitch{get;init;}
    public double? Roll{get;init;}
    public double? Yaw{get;init;}
    public double? JumpMinimum{get;init;}
    public double? JumpCurrent{get;init;}
    public double? JumpMaximum{get;init;}
    public double? Capacity{get;init;}
    public double? Retracted{get;init;}
    public double? Deployed{get;init;}
    public double? Hull{get;init;}
    public double? Shield{get;init;}
    public double? BurstDps{get;init;}
    public double? SustainedDps{get;init;}
    public double? CurrentMass{get;init;}
    public double? MaximumMass{get;init;}
    public double? Fuel{get;init;}
    public double? Cargo{get;init;}
    public IReadOnlyDictionary<string,double> Additional{get;init;}=new Dictionary<string,double>();
    public IReadOnlyDictionary<string,double> Distributor{get;init;}=new Dictionary<string,double>();
    public double? PlantEfficiency{get;init;}
    public string Context{get;init;}="等待完整模拟装配";
    private static string Number(double? value,string format)=>value is {} n&&double.IsFinite(n)?n.ToString(format,CultureInfo.InvariantCulture):"—";
    public string SpeedText=>$"{Number(Speed,"N0")} | {Number(Boost,"N0")} m/s";
    public string RotationText=>$"{Number(Pitch,"F2")} | {Number(Roll,"F2")} | {Number(Yaw,"F2")}°/s";
    public string JumpText=>$"{Number(JumpMinimum,"F2")} | {Number(JumpCurrent,"F2")} | {Number(JumpMaximum,"F2")} ly";
    public string CapacityText=>Number(Capacity,"F2")+" MW";
    public string RetractedText=>Number(Retracted,"F2")+" MW";
    public string DeployedText=>Number(Deployed,"F2")+" MW";
    public string MarginText=>Number(Capacity is {} c&&Deployed is {} d?c-d:null,"F2")+" MW";
    private double? Extra(string key)=>Additional.TryGetValue(key,out var n)&&double.IsFinite(n)?n:null;
    private double? Distribution(string key)=>Distributor.TryGetValue(key,out var n)&&double.IsFinite(n)?n:null;
    private string ExtraText(string key,string format="N1",string unit="")=>Number(Extra(key),format)+unit;
    private string Resistances(string prefix)=>string.Join(" | ",new[]{"kinres","thmres","expres"}.Select(key=>Number(Extra(prefix+key),"F1")))+" %";
    public string WeaponPipsText=>"WEP "+Number(Extra("_weapon_pips")/2,"0.#")+" 格";
    public IReadOnlyList<ShipOverviewMetric> DefenceHull=>[
        new("船体完整度",Number(Hull,"N0")+" HP"),
        new("动能 / 热能 / 爆炸抗性",Resistances("_a")),
        new("装甲硬度",ExtraText("_hull_hardness","N0")),
        new("模块保护 / 完整度",Number(Extra("dmgprot") is {} protection?(1-protection)*100:null,"F0")+" % | "+ExtraText("integ_imrp","N0"," HP"))];
    public IReadOnlyList<ShipOverviewMetric> DefenceShield=>[
        new("护盾强度",Number(Shield,"N1")+" MJ"),
        new("动能 / 热能 / 爆炸抗性",Resistances("_s")),
        new("护盾电池总回复",ExtraText("shieldrnfps_ammomax","N1"," MJ")),
        new("系统电容 / 回充",Number(Distribution("syscap"),"F1")+" MJ | "+Number(Distribution("syschg"),"F2")+" MW")];
    public IReadOnlyList<ShipOverviewMetric> WeaponOutput=>[
        new("总爆发 DPS",Number(BurstDps,"F2")),
        new(Extra("_weapon_pips") is {} wep?$"持续 DPS({wep/2:0.#} 格 WEP)":"持续 DPS",Number(SustainedDps,"F2")),
        new("持续 DPS(4 格 WEP)",ExtraText("_sustained_dps_max","F2")),
        new("武器电容续航",BurstDps is not >0?"—":Extra("_wepcap_unlimited")==1?"∞":ExtraText("wepcap_burst_cur","F1"," s"))];
    public IReadOnlyList<ShipOverviewMetric> WeaponDamage=>[
        new("动能 / 热能 DPS",ExtraText("dps_kin","F2")+" | "+ExtraText("dps_thm","F2")),
        new("爆炸 / 绝对 DPS",ExtraText("dps_exp","F2")+" | "+ExtraText("dps_abs","F2")),
        new("反异种 / 腐蚀 DPS",ExtraText("dps_axe","F2")+" | "+ExtraText("dps_cau","F2")),
        new("武器电容 / 回充",Number(Distribution("wepcap"),"F1")+" MJ | "+Number(Distribution("wepchg"),"F2")+" MW")];
    public IReadOnlyList<ShipOverviewMetric> Capacities=>[
        new("质量 当前 / 满载",Number(CurrentMass,"N1")+" | "+Number(MaximumMass,"N1")+" t"),
        new("货舱 当前 / 容量",Number(Cargo,"N0")+" | "+ExtraText("cargocap","N0"," t")),
        new("燃料 当前 / 容量",Number(Fuel,"F2")+" | "+ExtraText("fuelcap","F2"," t")),
        new("乘客容量",ExtraText("cabincap","N0"," 人"))];
    public IReadOnlyList<ShipOverviewMetric> ThermalAndCost=>[
        new("热容量",ExtraText("_hull_heatcap","N1")),
        new("电厂热效率",Number(PlantEfficiency,"F2")),
        new("整船造价",ExtraText("cost","N0"," Cr")),
        new("标准保险重购(5%)",Number(Extra("cost")*.05,"N0")+" Cr")];
    public bool RetractedOverloaded=>Capacity is {} c&&Retracted>c;
    public bool DeployedOverloaded=>Capacity is {} c&&Deployed>c;
    private double PowerScale=>Math.Max(Capacity??0,Math.Max(Retracted??0,Deployed??0));
    public double CapacityFraction=>PowerScale>0?Math.Clamp((Capacity??0)/PowerScale,0,1):0;
    private double Within(double? draw)=>PowerScale>0&&Capacity is >0?Math.Clamp(Math.Min(draw??0,Capacity.Value)/PowerScale,0,1):0;
    private double Excess(double? draw)=>PowerScale>0&&Capacity is >0?Math.Clamp(((draw??0)-Capacity.Value)/PowerScale,0,1):0;
    public double RetractedNormalFraction=>Within(Retracted);
    public double RetractedExcessFraction=>Excess(Retracted);
    public double RetractedEmptyFraction=>Math.Max(0,1-RetractedNormalFraction-RetractedExcessFraction);
    public double DeployedNormalFraction=>Within(Deployed);
    public double DeployedExcessFraction=>Excess(Deployed);
    public double DeployedEmptyFraction=>Math.Max(0,1-DeployedNormalFraction-DeployedExcessFraction);

    public static ShipOverviewValues From(EquipmentStatistics? stats,ShipPreviewFlightState? state=null)
    {
        if(stats is null)return Empty;
        double? Value(string key)=>stats.Value(key) is {} n&&double.IsFinite(n)?n:null;
        IReadOnlyDictionary<string,double> Module(string slot)=>stats.Modules.GetValueOrDefault(slot)??new Dictionary<string,double>();
        var engine=Module("MainEngines");var fsd=Module("FrameShiftDrive");var distributor=Module("PowerDistributor");
        static double? Attr(IReadOnlyDictionary<string,double> module,string key)=>module.TryGetValue(key,out var n)&&double.IsFinite(n)?n:null;
        var fuel=Math.Clamp(state?.Fuel??Value("fuelcap")??0,0,Math.Max(0,Value("fuelcap")??0));
        var cargo=Math.Clamp(state?.Cargo??0,0,Math.Max(0,Value("cargocap")??0));
        var reserve=Math.Max(0,state?.Reserve??0);
        var pips=Math.Clamp(state?.EnginePips??Value("_engine_pips")??4,0,8)/8;
        var mass=Value("mass");
        double? Curve(string kind)
        {
            if(mass is null||Attr(engine,"engminmass") is not {} min||Attr(engine,"engoptmass") is not {} opt||Attr(engine,"engmaxmass") is not {} max
                ||Attr(engine,"minmul"+kind) is not {} minMul||Attr(engine,"optmul"+kind) is not {} optMul||Attr(engine,"maxmul"+kind) is not {} maxMul)return null;
            var total=mass.Value+fuel+reserve+cargo;
            if(total>max||max<=min||optMul<=minMul||maxMul<=minMul||max<=opt)return null;
            var exponent=Math.Log((optMul-minMul)/(maxMul-minMul))/(opt==min?1:Math.Log((max-opt)/(max-min)));
            var result=(minMul+Math.Pow(Math.Min(1,(max-total)/(max-min)),exponent)*(maxMul-minMul))/100;
            return double.IsFinite(result)?result:null;
        }
        var speedCurve=Curve("spd");var rotationCurve=Curve("rot");
        double? Rotation(string axis)=>rotationCurve*(Value("_hull_"+axis)*pips+Value("_hull_min"+axis)*(1-pips));
        var speed=speedCurve*Value("_hull_topspd")*(pips+(Value("_hull_minthrust")??0)/100*(1-pips));
        var boost=Attr(distributor,"engcap") is {} cap&&Value("_hull_boostcost") is {} cost&&cap>=cost?speedCurve*Value("_hull_bstspd"):null;
        double? jump=null;
        if(mass is {} dryMass&&Attr(fsd,"maxfuel") is {} maxFuel&&Attr(fsd,"fuelmul") is {} multiplier&&multiplier>0&&Attr(fsd,"fuelpower") is {} power&&power>0&&Attr(fsd,"fsdoptmass") is {} optimal)
        {
            var distance=fuel>0?Math.Pow(Math.Min(fuel,maxFuel)/multiplier,1/power)*optimal/(dryMass+cargo+fuel)+(Value("jumpbst")??0):0;
            if(double.IsFinite(distance))jump=distance;
        }
        return new(){Speed=speed,Boost=boost,Pitch=Rotation("pitch"),Roll=Rotation("roll"),Yaw=Rotation("yaw"),
            JumpMinimum=Value("_jump_laden"),JumpCurrent=jump,JumpMaximum=Value("_jump_max"),
            Capacity=Value("pwrcap"),Retracted=Value("pwrdraw_ret"),Deployed=Value("pwrdraw_dep"),
            Hull=Value("_armour"),Shield=Value("_shields"),BurstDps=Value("dps"),SustainedDps=Value("_sustained_dps_cur"),
            CurrentMass=mass+fuel+reserve+cargo,MaximumMass=mass+Value("fuelcap")+reserve+Value("cargocap"),Fuel=fuel,Cargo=cargo,Additional=stats.Ship,Distributor=distributor,PlantEfficiency=Attr(Module("PowerPlant"),"heateff"),
            Context=$"当前模拟装配 · 燃料 {fuel:F2} t · 货物 {cargo:F0} t · 引擎配电 {pips*4:0.#} 格"};
    }
}