using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.Planning;
using XFEEDToolBox.Core.Engineering;
using XFEEDToolBox.Core.Presentation;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
public ObservableCollection<UpgradeListEntry> UpgradeEntries{get;}=[];
public LiveCollection<MaterialNeedLiveRow,MaterialNeed,string> UpgradeNeeds{get;}=new(x=>x.Key,x=>new(x));
[ObservableProperty] public partial string UpgradeBadge{get;set;}="清单 · 0";
[ObservableProperty] public partial int UpgradeItemCount{get;set;}
public bool HasUpgradeItems=>UpgradeItemCount>0;
public int UpgradeMissingMaterialCount=>UpgradeNeeds.Count(material=>material.Missing>0);
public bool HasUpgradeMaterialShortages=>UpgradeMissingMaterialCount>0;
public LiveCollection<MaterialNeedLiveRow,MaterialNeed,string> UpgradeShortages{get;}=new(x=>x.Key,x=>new(x));
partial void OnUpgradeItemCountChanged(int value){OnPropertyChanged(nameof(HasUpgradeItems));ClearUpgradeListCommand.NotifyCanExecuteChanged();}
private void RefreshUpgradeBadge()
{
UpgradeItemCount=UpgradeEntries.Sum(e=>e.Quantity);UpgradeBadge=$"清单 · {UpgradeItemCount}";
OnPropertyChanged(nameof(UpgradeMissingMaterialCount));OnPropertyChanged(nameof(HasUpgradeMaterialShortages));
}
[ObservableProperty] public partial string UpgradeSummary{get;set;}="在模块改装页点击+,建立全局材料清单。";
[ObservableProperty] public partial string UpgradeCreditSummary{get;set;}="已知购买预算 0 Cr";
[ObservableProperty] public partial string UpgradeNotes{get;set;}="";
[ObservableProperty] public partial bool UpgradeBusy{get;set;}
partial void OnUpgradeBusyChanged(bool value)=>OnPropertyChanged(nameof(RefitCanAddToList));
private string upgradeOwner="";
private string upgradeFingerprint="";
private int upgradeVersion;
private int upgradeListRevision;
private readonly SemaphoreSlim upgradeSave=new(1,1);
private readonly MaterialChecklist upgradeMaterials=new();
private string UpgradePath=>Path.Combine(runtime.DataDirectory,"plans",Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(upgradeOwner)))[..24]+".json");
private async Task RefreshUpgradeListAsync()
{
var p=CurrentPlayer;
if(p.CommanderId.Length==0||DemoMode)return;
if(upgradeOwner!=p.CommanderId)
{
++upgradeVersion;++upgradeListRevision;upgradeOwner=p.CommanderId;UpgradeEntries.Clear();upgradeMaterials.Clear();UpgradeNeeds.Apply([]);UpgradeShortages.Apply([]);RefreshWarehouseChecklist();upgradeFingerprint="";
RefreshUpgradeBadge();var owner=upgradeOwner;var revision=upgradeListRevision;var path=UpgradePath;
try
{
if(File.Exists(path)&&new FileInfo(path).Length<8*1024*1024)
{
var entries=JsonSerializer.Deserialize<UpgradeListEntry[]>(await File.ReadAllTextAsync(path,lifetime.Token),Core.Models.ToolboxSettings.JsonOptions)??[];
var materialPath=Path.ChangeExtension(path,"materials.json");
string[] materialKeys=[];
try
{
if(File.Exists(materialPath)&&new FileInfo(materialPath).Length<65536)
materialKeys=JsonSerializer.Deserialize<string[]>(await File.ReadAllTextAsync(materialPath,lifetime.Token),Core.Models.ToolboxSettings.JsonOptions)??[];
}
catch(Exception error)when(error is IOException or JsonException){UpgradeNotes="材料勾选状态读取失败,将按当前库存重新核对:"+error.Message;}
if(owner!=upgradeOwner||revision!=upgradeListRevision)return;
foreach(var entry in entries)UpgradeEntries.Add(entry);
upgradeMaterials.Restore(materialKeys);
}
}
catch(Exception e)when(e is IOException or JsonException){UpgradeNotes="清单读取失败:"+e.Message;}
}
RefreshUpgradeBadge();
var fingerprint=$"{p.MaterialsAt}:{p.MaterialsReliable}:{p.EngineersAt}:{p.Credits}:{p.CreditsReliable}:{string.Join('|',p.Ships.Select(s=>$"{s.ShipId}:{s.ConfigurationAt}:{s.ConfigurationChanged}"))}:{UpgradeEntries.Count}";
if(fingerprint==upgradeFingerprint)return;upgradeFingerprint=fingerprint;await CalculateUpgradeListAsync();
}
[RelayCommand] private async Task AddEngineeringToListAsync(CancellationToken cancellationToken=default)
{
using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
try
{
if(DemoMode||CurrentPlayer.CommanderId.Length==0)throw new InvalidOperationException("读取真实指挥官档案后才能保存全局清单。");
await RefreshUpgradeListAsync();
var module=GetEngineeringModule();var target=CaptureEngineeringTarget();
if(EngineeringCount is <1 or >100||EngineeringCount!=Math.Truncate(EngineeringCount))throw new InvalidOperationException("数量必须是 1–100 的整数。");
if(EngineeringSource?.Key.Length>0&&(EngineeringCount!=1||UpgradeEntries.Any(e=>e.SourceKey==EngineeringSource.Key)))throw new InvalidOperationException("同一件已有模块只能加入一次,且数量为 1。");
if(UpgradeEntries.Sum(e=>e.Quantity)+EngineeringCount>100)throw new InvalidOperationException("清单最多支持 100 件模块。");
if(EngineeringPriceKnown&&(!double.IsFinite(EngineeringUnitPrice)||EngineeringUnitPrice<0))throw new InvalidOperationException("请输入有效的单件价格。");
var entry=new UpgradeListEntry(Guid.NewGuid(),module,target,(int)EngineeringCount,EngineeringSource?.Key??"",EngineeringPurchase,EngineeringPriceKnown?(decimal)EngineeringUnitPrice:null,EngineeringPriceSource);
// Validate the complete sequence so entries share engineer reputation and inventory is subtracted once.
var entries=UpgradeEntries.Append(entry).ToArray();var player=CurrentPlayer;var revision=upgradeListRevision;
await Task.Run(()=>UpgradeListPlanner.Calculate(entries,player,engineeringCatalog,token),token);
token.ThrowIfCancellationRequested();
if(revision!=upgradeListRevision||player.CommanderId!=CurrentPlayer.CommanderId)throw new InvalidOperationException("清单或指挥官档案已变化,请重新添加。");
++upgradeListRevision;UpgradeEntries.Add(entry);RefreshUpgradeBadge();await SaveUpgradeListAsync();await CalculateUpgradeListAsync(token);
EngineeringResult="已加入右上角全局清单。";
}
catch(OperationCanceledException){EngineeringResult="添加已取消。";}
catch(Exception e)when(e is not OperationCanceledException){EngineeringResult=e.Message;}
}
[RelayCommand] private async Task RemoveUpgradeAsync(UpgradeListEntry entry)
{if(UpgradeEntries.Remove(entry)){++upgradeListRevision;RefreshUpgradeBadge();await SaveUpgradeListAsync();await CalculateUpgradeListAsync();}}
private bool CanClearUpgradeList()=>HasUpgradeItems&&!DemoMode&&upgradeOwner.Length>0&&upgradeOwner==CurrentPlayer.CommanderId;
[RelayCommand(CanExecute=nameof(CanClearUpgradeList))] private async Task ClearUpgradeListAsync()
{
if(!CanClearUpgradeList())return;
// Invalidate in-flight calculations and additions before clearing, so
// their older material totals cannot repopulate the empty flyout.
++upgradeVersion;++upgradeListRevision;upgradeFingerprint="";
UpgradeEntries.Clear();upgradeMaterials.Clear();UpgradeNeeds.Apply([]);UpgradeShortages.Apply([]);RefreshWarehouseChecklist();RefreshUpgradeBadge();UpgradeBusy=false;
UpgradeSummary="清单已清空。";UpgradeCreditSummary="已知购买预算 0 Cr";UpgradeNotes="";
try{await SaveUpgradeListAsync();}
catch(Exception error)when(error is not OperationCanceledException){UpgradeNotes="清单已清空,但保存失败:"+error.Message;}
}
public async Task<bool> AddRefitToListAsync(CancellationToken cancellationToken=default)
{
using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
if(RefitBusy||UpgradeBusy)return false;
RefitBusy=true;
try
{
if(DemoMode||CurrentPlayer.CommanderId.Length==0)throw new InvalidOperationException("读取真实指挥官档案后才能保存全局清单。");
SaveRefitEditor();
var document=refitDocument??throw new InvalidOperationException("请先选择飞船。");
await RefreshUpgradeListAsync();
var next=Core.Planning.UpgradeListPlanner.AddShip(UpgradeEntries.ToArray(),document);
var player=CurrentPlayer;var revision=upgradeListRevision;
await Task.Run(()=>Core.Planning.UpgradeListPlanner.Calculate(next,player,engineeringCatalog,token),token);
token.ThrowIfCancellationRequested();
if(revision!=upgradeListRevision||player.CommanderId!=CurrentPlayer.CommanderId)throw new InvalidOperationException("清单或指挥官档案已变化,请重新添加。");
++upgradeListRevision;
UpgradeEntries.Clear();foreach(var entry in next)UpgradeEntries.Add(entry);
RefreshUpgradeBadge();await SaveUpgradeListAsync();await CalculateUpgradeListAsync();
RefitResult="已将整船改装目标加入右上角清单。";
return true;
}
catch(OperationCanceledException){RefitResult="添加已取消。";return false;}
catch(Exception e){RefitResult=e.Message;return false;}
finally{RefitBusy=false;}
}
private async Task SaveUpgradeListAsync()
{
// Capture the owner and contents before waiting on another save.
var path=UpgradePath;var entries=UpgradeEntries.ToArray();var materialKeys=upgradeMaterials.Keys;
await upgradeSave.WaitAsync(lifetime.Token);
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var temp=path+".tmp";await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(entries,Core.Models.ToolboxSettings.JsonOptions),lifetime.Token);File.Move(temp,path,true);
var materialPath=Path.ChangeExtension(path,"materials.json");var materialTemp=materialPath+".tmp";
await File.WriteAllTextAsync(materialTemp,JsonSerializer.Serialize(materialKeys,Core.Models.ToolboxSettings.JsonOptions),lifetime.Token);File.Move(materialTemp,materialPath,true);
}
finally{upgradeSave.Release();}
}
[RelayCommand] private async Task CalculateUpgradeListAsync(CancellationToken cancellationToken=default)
{
using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
var version=++upgradeVersion;UpgradeBusy=true;
try
{
var entries=UpgradeEntries.ToArray();var p=CurrentPlayer;
if(p.MismatchedEngineeringRolls>0)throw new InvalidOperationException("改装进度与当前规则不符,请先校准。");
var result=await Task.Run(()=>UpgradeListPlanner.Calculate(entries,p,engineeringCatalog,token),token);
token.ThrowIfCancellationRequested();
if(version!=upgradeVersion||p.CommanderId!=upgradeOwner||p.CommanderId!=CurrentPlayer.CommanderId)return;
var previousKeys=upgradeMaterials.Keys;var materials=upgradeMaterials.Update(result.Engineering.Materials);
UpgradeNeeds.Apply(materials);UpgradeShortages.Apply(materials.Where(m=>m.Missing>0));RefreshUpgradeBadge();RefreshWarehouseChecklist();UpgradeSummary=$"{entries.Sum(e=>e.Quantity)} 件模块 · {materials.Count} 种材料 · 缺少 {materials.Sum(m=>m.Missing):N0} 份\n{(result.Engineering.Exact?"按已知声望计算":"按声望区间预算")} · {(result.Engineering.Complete?"改装路径可完成":"部分路径需要额外提升权限")}";
UpgradeCreditSummary=$"已知购买预算 {result.KnownCredits:N0} Cr"+(result.UnquotedItems>0?$" · {result.UnquotedItems} 件待报价(未计入合计)":"")+"\n"+(result.AvailableCredits is {} cash?$"当前现金 {cash:N0} Cr · 已知预算缺口 {result.MissingCredits:N0} Cr":"当前现金不完整,Cr 缺口待校准");
UpgradeNotes="蓝图改装消耗材料;Cr 合计为勾选购买的模块参考价或手动报价,未含站点折扣、转运、旅行或保险。参考价来自随应用更新的 Coriolis 目录,各项保留单价来源。\n"+(p.MaterialsReliable?"":"当前材料库存存在缺口,缺少数量需校准。\n")+string.Join("\n",result.Engineering.Notes);
if(upgradeOwner.Length>0&&!previousKeys.SequenceEqual(upgradeMaterials.Keys,StringComparer.OrdinalIgnoreCase))
try{await SaveUpgradeListAsync();}
catch(Exception error)when(error is IOException or UnauthorizedAccessException){if(version==upgradeVersion)UpgradeNotes+="\n材料勾选状态尚未保存:"+error.Message;}
}
catch(OperationCanceledException){if(version==upgradeVersion)UpgradeNotes="清单核对已取消。";}
catch(Exception e)when(e is not OperationCanceledException){if(version==upgradeVersion){UpgradeSummary=e.Message;UpgradeNeeds.Apply([]);UpgradeShortages.Apply([]);RefreshUpgradeBadge();RefreshWarehouseChecklist();UpgradeCreditSummary="当前清单未完成计算,预算不可用。";}}
finally{if(version==upgradeVersion)UpgradeBusy=false;}
}
}
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.Planning;
using XFEEDToolBox.Core.Engineering;
using XFEEDToolBox.Core.Presentation;
namespace XFEEDToolBox.ViewModels;
public partial class ToolboxViewModel
{
public ObservableCollection<UpgradeListEntry> UpgradeEntries{get;}=[];
public LiveCollection<MaterialNeedLiveRow,MaterialNeed,string> UpgradeNeeds{get;}=new(x=>x.Key,x=>new(x));
[ObservableProperty] public partial string UpgradeBadge{get;set;}="清单 · 0";
[ObservableProperty] public partial int UpgradeItemCount{get;set;}
public bool HasUpgradeItems=>UpgradeItemCount>0;
public int UpgradeMissingMaterialCount=>UpgradeNeeds.Count(material=>material.Missing>0);
public bool HasUpgradeMaterialShortages=>UpgradeMissingMaterialCount>0;
public LiveCollection<MaterialNeedLiveRow,MaterialNeed,string> UpgradeShortages{get;}=new(x=>x.Key,x=>new(x));
partial void OnUpgradeItemCountChanged(int value){OnPropertyChanged(nameof(HasUpgradeItems));ClearUpgradeListCommand.NotifyCanExecuteChanged();}
private void RefreshUpgradeBadge()
{
UpgradeItemCount=UpgradeEntries.Sum(e=>e.Quantity);UpgradeBadge=$"清单 · {UpgradeItemCount}";
OnPropertyChanged(nameof(UpgradeMissingMaterialCount));OnPropertyChanged(nameof(HasUpgradeMaterialShortages));
}
[ObservableProperty] public partial string UpgradeSummary{get;set;}="在模块改装页点击+,建立全局材料清单。";
[ObservableProperty] public partial string UpgradeCreditSummary{get;set;}="已知购买预算 0 Cr";
[ObservableProperty] public partial string UpgradeNotes{get;set;}="";
[ObservableProperty] public partial bool UpgradeBusy{get;set;}
partial void OnUpgradeBusyChanged(bool value)=>OnPropertyChanged(nameof(RefitCanAddToList));
private string upgradeOwner="";
private string upgradeFingerprint="";
private int upgradeVersion;
private int upgradeListRevision;
private readonly SemaphoreSlim upgradeSave=new(1,1);
private readonly MaterialChecklist upgradeMaterials=new();
private string UpgradePath=>Path.Combine(runtime.DataDirectory,"plans",Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(upgradeOwner)))[..24]+".json");
private async Task RefreshUpgradeListAsync()
{
var p=CurrentPlayer;
if(p.CommanderId.Length==0||DemoMode)return;
if(upgradeOwner!=p.CommanderId)
{
++upgradeVersion;++upgradeListRevision;upgradeOwner=p.CommanderId;UpgradeEntries.Clear();upgradeMaterials.Clear();UpgradeNeeds.Apply([]);UpgradeShortages.Apply([]);RefreshWarehouseChecklist();upgradeFingerprint="";
RefreshUpgradeBadge();var owner=upgradeOwner;var revision=upgradeListRevision;var path=UpgradePath;
try
{
if(File.Exists(path)&&new FileInfo(path).Length<8*1024*1024)
{
var entries=JsonSerializer.Deserialize<UpgradeListEntry[]>(await File.ReadAllTextAsync(path,lifetime.Token),Core.Models.ToolboxSettings.JsonOptions)??[];
var materialPath=Path.ChangeExtension(path,"materials.json");
string[] materialKeys=[];
try
{
if(File.Exists(materialPath)&&new FileInfo(materialPath).Length<65536)
materialKeys=JsonSerializer.Deserialize<string[]>(await File.ReadAllTextAsync(materialPath,lifetime.Token),Core.Models.ToolboxSettings.JsonOptions)??[];
}
catch(Exception error)when(error is IOException or JsonException){UpgradeNotes="材料勾选状态读取失败,将按当前库存重新核对:"+error.Message;}
if(owner!=upgradeOwner||revision!=upgradeListRevision)return;
foreach(var entry in entries)UpgradeEntries.Add(entry);
upgradeMaterials.Restore(materialKeys);
}
}
catch(Exception e)when(e is IOException or JsonException){UpgradeNotes="清单读取失败:"+e.Message;}
}
RefreshUpgradeBadge();
var fingerprint=$"{p.MaterialsAt}:{p.MaterialsReliable}:{p.EngineersAt}:{p.Credits}:{p.CreditsReliable}:{string.Join('|',p.Ships.Select(s=>$"{s.ShipId}:{s.ConfigurationAt}:{s.ConfigurationChanged}"))}:{UpgradeEntries.Count}";
if(fingerprint==upgradeFingerprint)return;upgradeFingerprint=fingerprint;await CalculateUpgradeListAsync();
}
[RelayCommand] private async Task AddEngineeringToListAsync(CancellationToken cancellationToken=default)
{
using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
try
{
if(DemoMode||CurrentPlayer.CommanderId.Length==0)throw new InvalidOperationException("读取真实指挥官档案后才能保存全局清单。");
await RefreshUpgradeListAsync();
var module=GetEngineeringModule();var target=CaptureEngineeringTarget();
if(EngineeringCount is <1 or >100||EngineeringCount!=Math.Truncate(EngineeringCount))throw new InvalidOperationException("数量必须是 1–100 的整数。");
if(EngineeringSource?.Key.Length>0&&(EngineeringCount!=1||UpgradeEntries.Any(e=>e.SourceKey==EngineeringSource.Key)))throw new InvalidOperationException("同一件已有模块只能加入一次,且数量为 1。");
if(UpgradeEntries.Sum(e=>e.Quantity)+EngineeringCount>100)throw new InvalidOperationException("清单最多支持 100 件模块。");
if(EngineeringPriceKnown&&(!double.IsFinite(EngineeringUnitPrice)||EngineeringUnitPrice<0))throw new InvalidOperationException("请输入有效的单件价格。");
var entry=new UpgradeListEntry(Guid.NewGuid(),module,target,(int)EngineeringCount,EngineeringSource?.Key??"",EngineeringPurchase,EngineeringPriceKnown?(decimal)EngineeringUnitPrice:null,EngineeringPriceSource);
// Validate the complete sequence so entries share engineer reputation and inventory is subtracted once.
var entries=UpgradeEntries.Append(entry).ToArray();var player=CurrentPlayer;var revision=upgradeListRevision;
await Task.Run(()=>UpgradeListPlanner.Calculate(entries,player,engineeringCatalog,token),token);
token.ThrowIfCancellationRequested();
if(revision!=upgradeListRevision||player.CommanderId!=CurrentPlayer.CommanderId)throw new InvalidOperationException("清单或指挥官档案已变化,请重新添加。");
++upgradeListRevision;UpgradeEntries.Add(entry);RefreshUpgradeBadge();await SaveUpgradeListAsync();await CalculateUpgradeListAsync(token);
EngineeringResult="已加入右上角全局清单。";
}
catch(OperationCanceledException){EngineeringResult="添加已取消。";}
catch(Exception e)when(e is not OperationCanceledException){EngineeringResult=e.Message;}
}
[RelayCommand] private async Task RemoveUpgradeAsync(UpgradeListEntry entry)
{if(UpgradeEntries.Remove(entry)){++upgradeListRevision;RefreshUpgradeBadge();await SaveUpgradeListAsync();await CalculateUpgradeListAsync();}}
private bool CanClearUpgradeList()=>HasUpgradeItems&&!DemoMode&&upgradeOwner.Length>0&&upgradeOwner==CurrentPlayer.CommanderId;
[RelayCommand(CanExecute=nameof(CanClearUpgradeList))] private async Task ClearUpgradeListAsync()
{
if(!CanClearUpgradeList())return;
// Invalidate in-flight calculations and additions before clearing, so
// their older material totals cannot repopulate the empty flyout.
++upgradeVersion;++upgradeListRevision;upgradeFingerprint="";
UpgradeEntries.Clear();upgradeMaterials.Clear();UpgradeNeeds.Apply([]);UpgradeShortages.Apply([]);RefreshWarehouseChecklist();RefreshUpgradeBadge();UpgradeBusy=false;
UpgradeSummary="清单已清空。";UpgradeCreditSummary="已知购买预算 0 Cr";UpgradeNotes="";
try{await SaveUpgradeListAsync();}
catch(Exception error)when(error is not OperationCanceledException){UpgradeNotes="清单已清空,但保存失败:"+error.Message;}
}
public async Task<bool> AddRefitToListAsync(CancellationToken cancellationToken=default)
{
using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
if(RefitBusy||UpgradeBusy)return false;
RefitBusy=true;
try
{
if(DemoMode||CurrentPlayer.CommanderId.Length==0)throw new InvalidOperationException("读取真实指挥官档案后才能保存全局清单。");
SaveRefitEditor();
var document=refitDocument??throw new InvalidOperationException("请先选择飞船。");
await RefreshUpgradeListAsync();
var next=Core.Planning.UpgradeListPlanner.AddShip(UpgradeEntries.ToArray(),document);
var player=CurrentPlayer;var revision=upgradeListRevision;
await Task.Run(()=>Core.Planning.UpgradeListPlanner.Calculate(next,player,engineeringCatalog,token),token);
token.ThrowIfCancellationRequested();
if(revision!=upgradeListRevision||player.CommanderId!=CurrentPlayer.CommanderId)throw new InvalidOperationException("清单或指挥官档案已变化,请重新添加。");
++upgradeListRevision;
UpgradeEntries.Clear();foreach(var entry in next)UpgradeEntries.Add(entry);
RefreshUpgradeBadge();await SaveUpgradeListAsync();await CalculateUpgradeListAsync();
RefitResult="已将整船改装目标加入右上角清单。";
return true;
}
catch(OperationCanceledException){RefitResult="添加已取消。";return false;}
catch(Exception e){RefitResult=e.Message;return false;}
finally{RefitBusy=false;}
}
private async Task SaveUpgradeListAsync()
{
// Capture the owner and contents before waiting on another save.
var path=UpgradePath;var entries=UpgradeEntries.ToArray();var materialKeys=upgradeMaterials.Keys;
await upgradeSave.WaitAsync(lifetime.Token);
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var temp=path+".tmp";await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(entries,Core.Models.ToolboxSettings.JsonOptions),lifetime.Token);File.Move(temp,path,true);
var materialPath=Path.ChangeExtension(path,"materials.json");var materialTemp=materialPath+".tmp";
await File.WriteAllTextAsync(materialTemp,JsonSerializer.Serialize(materialKeys,Core.Models.ToolboxSettings.JsonOptions),lifetime.Token);File.Move(materialTemp,materialPath,true);
}
finally{upgradeSave.Release();}
}
[RelayCommand] private async Task CalculateUpgradeListAsync(CancellationToken cancellationToken=default)
{
using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
var version=++upgradeVersion;UpgradeBusy=true;
try
{
var entries=UpgradeEntries.ToArray();var p=CurrentPlayer;
if(p.MismatchedEngineeringRolls>0)throw new InvalidOperationException("改装进度与当前规则不符,请先校准。");
var result=await Task.Run(()=>UpgradeListPlanner.Calculate(entries,p,engineeringCatalog,token),token);
token.ThrowIfCancellationRequested();
if(version!=upgradeVersion||p.CommanderId!=upgradeOwner||p.CommanderId!=CurrentPlayer.CommanderId)return;
var previousKeys=upgradeMaterials.Keys;var materials=upgradeMaterials.Update(result.Engineering.Materials);
UpgradeNeeds.Apply(materials);UpgradeShortages.Apply(materials.Where(m=>m.Missing>0));RefreshUpgradeBadge();RefreshWarehouseChecklist();UpgradeSummary=$"{entries.Sum(e=>e.Quantity)} 件模块 · {materials.Count} 种材料 · 缺少 {materials.Sum(m=>m.Missing):N0} 份\n{(result.Engineering.Exact?"按已知声望计算":"按声望区间预算")} · {(result.Engineering.Complete?"改装路径可完成":"部分路径需要额外提升权限")}";
UpgradeCreditSummary=$"已知购买预算 {result.KnownCredits:N0} Cr"+(result.UnquotedItems>0?$" · {result.UnquotedItems} 件待报价(未计入合计)":"")+"\n"+(result.AvailableCredits is {} cash?$"当前现金 {cash:N0} Cr · 已知预算缺口 {result.MissingCredits:N0} Cr":"当前现金不完整,Cr 缺口待校准");
UpgradeNotes="蓝图改装消耗材料;Cr 合计为勾选购买的模块参考价或手动报价,未含站点折扣、转运、旅行或保险。参考价来自随应用更新的 Coriolis 目录,各项保留单价来源。\n"+(p.MaterialsReliable?"":"当前材料库存存在缺口,缺少数量需校准。\n")+string.Join("\n",result.Engineering.Notes);
if(upgradeOwner.Length>0&&!previousKeys.SequenceEqual(upgradeMaterials.Keys,StringComparer.OrdinalIgnoreCase))
try{await SaveUpgradeListAsync();}
catch(Exception error)when(error is IOException or UnauthorizedAccessException){if(version==upgradeVersion)UpgradeNotes+="\n材料勾选状态尚未保存:"+error.Message;}
}
catch(OperationCanceledException){if(version==upgradeVersion)UpgradeNotes="清单核对已取消。";}
catch(Exception e)when(e is not OperationCanceledException){if(version==upgradeVersion){UpgradeSummary=e.Message;UpgradeNeeds.Apply([]);UpgradeShortages.Apply([]);RefreshUpgradeBadge();RefreshWarehouseChecklist();UpgradeCreditSummary="当前清单未完成计算,预算不可用。";}}
finally{if(version==upgradeVersion)UpgradeBusy=false;}
}
}