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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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;}
    }
}