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); /// Selected editor loadout, with the same mass curves and pip scaling as the pinned EDSY reference. 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 Additional{get;init;}=new Dictionary(); public IReadOnlyDictionary Distributor{get;init;}=new Dictionary(); 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 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 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 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 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 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 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 Module(string slot)=>stats.Modules.GetValueOrDefault(slot)??new Dictionary(); var engine=Module("MainEngines");var fsd=Module("FrameShiftDrive");var distributor=Module("PowerDistributor"); static double? Attr(IReadOnlyDictionary 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.#} 格"}; } }