using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Dispatching; using XFEEDToolBox.Core.Navigation; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Outfitting; using XFEEDToolBox.Core.Presentation; namespace XFEEDToolBox.ViewModels; public partial class ToolboxViewModel { public LiveCollection LiveFlightStatus { get; }=new(x=>x.Key,x=>new(x)); public LiveCollection LiveCockpitStatus { get; }=new(x=>x.Key,x=>new(x)); public LiveCollection LiveShipAmmunition { get; }=new(x=>x.Slot,x=>new(x)); public LiveCollection LiveShipSpecifications { get; }=new(x=>x.Key,x=>new(x)); [ObservableProperty] public partial string ShipPowerCapacityText { get; set; }="—"; [ObservableProperty] public partial string ShipPowerRetractedText { get; set; }="—"; [ObservableProperty] public partial string ShipPowerDeployedText { get; set; }="—"; [ObservableProperty] public partial string ShipPowerCurrentText { get; set; }="—"; [ObservableProperty] public partial string ShipPowerMarginText { get; set; }="—"; [ObservableProperty] public partial string ShipPowerSource { get; set; }="等待完整装配"; [ObservableProperty] public partial double ShipPowerRetractedPercent { get; set; } [ObservableProperty] public partial double ShipPowerDeployedPercent { get; set; } [ObservableProperty] public partial bool ShipPowerRetractedOverloaded { get; set; } [ObservableProperty] public partial bool ShipPowerDeployedOverloaded { get; set; } [ObservableProperty] public partial bool ShipPowerCurrentOverloaded { get; set; } [ObservableProperty] public partial string ShipTelemetryStatus { get; set; }="等待飞船状态"; [ObservableProperty] public partial string ShipTelemetryNote { get; set; }="弹药与船体完整度标明快照时间;未提供的数据保持未知。"; private DispatcherQueueTimer? shipTelemetryTimer; private int shipStatusViews; private bool shipTelemetryReading; private ShipRecord? equipmentShip; private EquipmentStatistics? shipEquipment; internal void ShowShipStatus() { shipStatusViews++; if(shipTelemetryTimer is null) { shipTelemetryTimer=dispatcher.CreateTimer();shipTelemetryTimer.Interval=TimeSpan.FromMilliseconds(200); shipTelemetryTimer.Tick+=async(_,_)=>await RefreshShipStatusAsync(); } shipTelemetryTimer.Start();_ = RefreshShipStatusAsync(); } internal void HideShipStatus(){shipStatusViews=Math.Max(0,shipStatusViews-1);if(shipStatusViews==0)shipTelemetryTimer?.Stop();} private async Task RefreshShipStatusAsync() { if(shipTelemetryReading||shipStatusViews==0)return; shipTelemetryReading=true; try { var p=CurrentPlayer; LiveCockpitStatus.Apply(ShipStatusRows.Cockpit(p));LiveShipAmmunition.Apply(ShipStatusRows.Ammunition(p)); await RefreshShipEquipmentAsync(p); if(!DemoMode&&p.InGame&&runtime.FlightDriver is NativeFlightControlDriver driver) { var raw=NavigationActive&&driver.LastRawTelemetry is {} cached?cached:await driver.ReadShipTelemetryAsync(lifetime.Token); var at=raw.GetProperty("observedAtUnixMilliseconds").GetInt64(); if(DateTimeOffset.UtcNow-DateTimeOffset.FromUnixTimeMilliseconds(at)>TimeSpan.FromMilliseconds(600))throw new InvalidOperationException("遥测已过期"); LiveFlightStatus.Apply(ShipStatusRows.Flight(raw)); ShipTelemetryStatus="实时接入 · "+DateTimeOffset.FromUnixTimeMilliseconds(at).ToLocalTime().ToString("HH:mm:ss.fff"); } else { LiveFlightStatus.Apply(ShipStatusRows.Flight(null)); ShipTelemetryStatus=p.InGame?"Status 已接入 · 原生遥测待连接":"游戏未运行 · 显示最后快照"; } } catch(OperationCanceledException)when(lifetime.IsCancellationRequested){} catch(Exception e){ShipTelemetryStatus="实时数据暂不可用 · "+e.Message;LiveFlightStatus.Apply(ShipStatusRows.Flight(null));} finally{shipTelemetryReading=false;} } private static bool SameEquipment(ShipRecord? a,ShipRecord? b)=>a?.ShipId==b?.ShipId&&a?.Type==b?.Type &&a?.ConfigurationAt==b?.ConfigurationAt&&a?.ConfigurationChanged==b?.ConfigurationChanged &&(ReferenceEquals(a?.Modules,b?.Modules)||a is not null&&b is not null&&a.Modules.SequenceEqual(b.Modules)); private async Task RefreshShipEquipmentAsync(PlayerState player) { var ship=player.Ships.FirstOrDefault(s=>s.ShipId==player.CurrentShipId); if(!SameEquipment(equipmentShip,ship)) { equipmentShip=ship;shipEquipment=null; ShipPowerSource="正在计算装配属性…"; try { if(ship is {ConfigurationAt:not null,ConfigurationChanged:false}) shipEquipment=await Task.Run(()=>EdsyLinkCodec.Statistics(ShipRefit.Create(ship,outfitting)),lifetime.Token); ShipPowerSource=shipEquipment is not null?$"装配计算 · {ShipStatusRows.At(ship?.ConfigurationAt)} · 包含当前工程参数":"等待完整装配"; } catch(OperationCanceledException)when(lifetime.IsCancellationRequested){throw;} catch(Exception){ShipPowerSource="装配属性暂不可用 · 等待完整且受支持的模块快照";} } var latest=CurrentPlayer; if(!SameEquipment(ship,latest.Ships.FirstOrDefault(s=>s.ShipId==latest.CurrentShipId)))return; player=latest; var capacity=shipEquipment?.Value("pwrcap");var retracted=shipEquipment?.Value("pwrdraw_ret");var deployed=shipEquipment?.Value("pwrdraw_dep"); static string MW(double? n)=>n is {} value?$"{value:F2} MW":"—"; ShipPowerCapacityText=MW(capacity);ShipPowerRetractedText=MW(retracted);ShipPowerDeployedText=MW(deployed); ShipPowerRetractedPercent=capacity>0&&retracted is {} r?Math.Clamp(r/capacity.Value*100,0,100):0; ShipPowerDeployedPercent=capacity>0&&deployed is {} d?Math.Clamp(d/capacity.Value*100,0,100):0; ShipPowerRetractedOverloaded=retracted>capacity;ShipPowerDeployedOverloaded=deployed>capacity; var current=player.Status is {} status?((status.Flags&(1L<<6))!=0?deployed:retracted):null; ShipPowerCurrentText=player.Status is null?"—":((player.Status.Flags&(1L<<6))!=0?"已部署 · ":"已收起 · ")+MW(current); ShipPowerCurrentOverloaded=current>capacity; ShipPowerMarginText=capacity is {} total&¤t is {} draw?MW(total-draw):"—"; LiveShipSpecifications.Apply(EquipmentStatusRows.Ship(shipEquipment,ShipPowerSource)); } }