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 XFEEDToolBox.Core.Presentation;
using XFEEDToolBox.Core.Engineering;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Outfitting;
using XFEEDToolBox.Core.Localization;
namespace XFEEDToolBox.ViewModels;

public sealed record EngineeringEffectChoice(string Name,EngineeringRecipe? Recipe,string EffectId="")
{
    public string Key=>Recipe is {} recipe?EngineeringExperimental.Key(recipe):EffectId;
    public string DisplayName=>Key.Length==0?"无实验效果":ChineseNames.Translate(Name);
    public string OriginalName=>Key.Length==0?"None":ChineseNames.English(Name);
}
public sealed record EngineeringSourceChoice(string Key,FittedModule? Module,string Origin)
{
    public string Name=>Module?.DisplayName??"从未改装模块开始";
    public string OriginalName=>Module?.OriginalName??"New / Unmodified";
    public string Detail=>Module is null?"按模块规格从 G1 开始规划":Origin+" · "+Module.EngineeringText;
}
public partial class ToolboxViewModel
{
    private readonly EngineeringCatalog engineeringCatalog=EngineeringCatalog.Default;
    private string engineeringFingerprint="";
    private int engineeringVersion,engineeringInputVersion;
    private bool refreshingEngineering,engineeringRequested;
    [ObservableProperty] public partial string MaterialSearch{get;set;}="";
    [ObservableProperty] public partial int MaterialCategoryIndex{get;set;}
    public LiveCollection<MaterialLiveRow,MaterialStock,string> MaterialStocks{get;}=new(x=>x.Key,x=>new(x));
    [ObservableProperty] public partial string WarehouseSummary{get;set;}="等待材料快照";
    public LiveCollection<EngineerLiveRow,EngineerAccess,long> EngineerAccessList{get;}=new(x=>x.Id,x=>new(x));
    [ObservableProperty] public partial string EngineerSummary{get;set;}="等待工程师权限";
    public IReadOnlyList<string> EngineeringTypes{get;}=EngineeringCatalog.Default.Recipes.Where(r=>r.Grade>0).Select(r=>r.Type).Distinct().OrderBy(ChineseNames.Translate).ToArray();
    [ObservableProperty] public partial string? EngineeringType{get;set;}
    [ObservableProperty] public partial IReadOnlyList<ModuleDefinition> EngineeringVariants{get;set;}=[];
    [ObservableProperty] public partial ModuleDefinition? EngineeringVariant{get;set;}
    [ObservableProperty] public partial IReadOnlyList<EngineeringSourceChoice> EngineeringSources{get;set;}=[];
    [ObservableProperty] public partial EngineeringSourceChoice? EngineeringSource{get;set;}
    // Retained as a read-only acceptance snapshot; the module catalogue does not depend on a selected ship.
    public LiveCollection<ModuleLiveRow,FittedModule,string> EngineeringModules{get;}=new(x=>x.Slot,x=>new(x));
    public ModuleLiveRow? EngineeringModule=>EngineeringModules.FirstOrDefault(m=>m.Item==EngineeringSource?.Module?.Item&&m.Slot==EngineeringSource?.Module?.Slot);
    [ObservableProperty] public partial IReadOnlyList<string> EngineeringBlueprints{get;set;}=[];
    [ObservableProperty] public partial string? EngineeringBlueprint{get;set;}
    [ObservableProperty] public partial IReadOnlyList<EngineerLiveRow> CompatibleEngineers{get;set;}=[];
    [ObservableProperty] public partial EngineerLiveRow? EngineeringEngineer{get;set;}
    [ObservableProperty] public partial IReadOnlyList<EngineeringEffectChoice> ExperimentalEffects{get;set;}=[];
    [ObservableProperty] public partial EngineeringEffectChoice? EngineeringEffect{get;set;}
    [ObservableProperty] public partial double EngineeringGrade{get;set;}=5;
    [ObservableProperty] public partial double EngineeringQuality{get;set;}=100;
    [ObservableProperty] public partial double EngineeringCount{get;set;}=1;
    [ObservableProperty] public partial bool EngineeringPurchase{get;set;}
    [ObservableProperty] public partial bool EngineeringPriceKnown{get;set;}
    [ObservableProperty] public partial double EngineeringUnitPrice{get;set;}
    [ObservableProperty] public partial string EngineeringPriceSource{get;set;}="参考价未收录,可手动填写。";
    [ObservableProperty] public partial bool EngineeringNewModule{get;set;}=true;
    private string engineeringPriceItem="";
    private bool settingEngineeringPrice;
    [ObservableProperty] public partial bool EngineeringRemote{get;set;}
    [ObservableProperty] public partial bool EngineeringFastAdvance{get;set;}=true;
    [ObservableProperty] public partial string EngineeringCurrent{get;set;}="选择模块类型与规格。";
    [ObservableProperty] public partial double EngineeringCurrentProgress{get;set;}
    [ObservableProperty] public partial string EngineeringCurrentProgressText{get;set;}="0%";
    [ObservableProperty] public partial string EngineeringResult{get;set;}="选择蓝图后计算,或点击+加入全局清单。";
    [ObservableProperty] public partial string EngineeringNotes{get;set;}="按真实库存、工程师权限与逐次声望计算。";
    [ObservableProperty] public partial string EngineeringTargetEffects{get;set;}="";
    [ObservableProperty] public partial string EngineeringTargetEffectsOriginal{get;set;}="";
    [ObservableProperty] public partial IReadOnlyList<EngineeringRoll> EngineeringRolls{get;set;}=[];
    [ObservableProperty] public partial IReadOnlyList<MaterialNeed> EngineeringNeeds{get;set;}=[];
    [ObservableProperty] public partial bool EngineeringBusy{get;set;}

    private void RefreshEngineering(PlayerState player)
    {
        RefreshWarehouse(player);EngineerAccessList.Apply(player.Engineers);RefreshRefitPlayer(player);
        refreshingEngineering=true;
        try
        {
            EngineerSummary=$"已读取 {player.Engineers.Count} 位工程师 · {player.Engineers.Count(e=>e.Unlocked)} 位已解锁 · 改装进度校验 {player.VerifiedEngineeringRolls} 次通过 / {player.MismatchedEngineeringRolls} 次不符";
            EngineeringModules.Apply(player.Ships.FirstOrDefault(s=>s.ShipId==player.CurrentShipId)?.Modules.Where(m=>EngineeringCatalog.ModuleType(m.Item).Length>0)??[]);
            EngineeringType??="Frame Shift Drive";
            RefreshEngineeringCatalogue();RefreshEngineeringChoices();
        }
        finally{refreshingEngineering=false;}
        var fingerprint=$"{player.MaterialsAt}:{player.MaterialsReliable}:{player.EngineersAt}:{EngineeringSource?.Module?.Engineering}:{EngineeringBlueprint}:{EngineeringEngineer?.Id}:{EngineeringGrade}:{EngineeringQuality}:{EngineeringEffect?.Name}:{EngineeringRemote}:{EngineeringFastAdvance}";
        if(fingerprint!=engineeringFingerprint&&engineeringRequested){engineeringFingerprint=fingerprint;QueueEngineeringCalculation();}
    }
    private void RefreshWarehouse(PlayerState p)
    {
        var known=engineeringCatalog.Materials.ToDictionary(x=>x.Key,StringComparer.OrdinalIgnoreCase);
        var counts=p.Materials.GroupBy(m=>m.Key,StringComparer.OrdinalIgnoreCase).ToDictionary(g=>g.Key,g=>g.Sum(m=>m.Count),StringComparer.OrdinalIgnoreCase);
        var rows=known.Values.Select(m=>new MaterialStock(m.Key,m.Name,m.Category,m.Grade,counts.GetValueOrDefault(m.Key),m.Capacity)).ToList();
        rows.AddRange(p.Materials.Where(m=>!known.ContainsKey(m.Key)).DistinctBy(m=>m.Key,StringComparer.OrdinalIgnoreCase).Select(m=>new MaterialStock(m.Key,m.Name,m.Group,0,counts[m.Key],0)));
        var filter=WarehouseFilter;
        var visible=MaterialWarehouseQuery.Apply(rows,filter,UpgradeNeeds.Select(m=>m.Record));
        MaterialStocks.Apply(visible);
        WarehouseResultCount=visible.Count;
        WarehouseFilterSummary=$"显示 {visible.Count:N0} / {rows.Count:N0} 种 · 筛选内库存 {visible.Sum(m=>m.Owned):N0} 份";
        WarehouseEmptyText=filter.ValidationMessage.Length>0?filter.ValidationMessage:"没有符合条件的材料,试试重置或放宽筛选。";
        WarehouseSummary=$"材料总数 {counts.Values.Sum():N0} · {counts.Count} 种 · 原始 {rows.Where(m=>m.Category=="Raw").Sum(m=>m.Owned):N0} / 制造 {rows.Where(m=>m.Category=="Manufactured").Sum(m=>m.Owned):N0} / 编码 {rows.Where(m=>m.Category=="Encoded").Sum(m=>m.Owned):N0}\n"+(p.MaterialsAt is {} at?$"更新 {at.ToLocalTime():MM-dd HH:mm:ss} · {(p.MaterialsReliable?"库存快照与增量已接入":"库存存在缺口,数量需校准")}":"尚未读取完整库存快照");
    }
    private void RefreshEngineeringCatalogue()
    {
        var type=EngineeringType??"";
        EngineeringVariants=StableSequence(EngineeringVariants,outfitting.Modules.Where(m=>EngineeringCatalog.ModuleType(m.Item)==type).OrderByDescending(m=>m.Size).ThenBy(m=>m.Rating).ThenBy(m=>m.Name));
        EngineeringVariant=EngineeringVariants.FirstOrDefault(m=>m.Item==EngineeringVariant?.Item)??EngineeringVariants.FirstOrDefault();
        var sources=new List<EngineeringSourceChoice>{new("",null,"新模块")};
        foreach(var ship in CurrentPlayer.Ships.Where(s=>s.ConfigurationAt is not null&&!s.ConfigurationChanged))
            sources.AddRange(ship.Modules.Where(m=>EngineeringCatalog.ModuleType(m.Item)==type).Select(m=>new EngineeringSourceChoice($"ship:{ship.ShipId}:{m.Slot}",m,ship.DisplayName+" / "+m.SlotName)));
        var key=EngineeringSource?.Key??"";
        EngineeringSources=StableSequence(EngineeringSources,sources);EngineeringSource=EngineeringSources.FirstOrDefault(s=>s.Key==key)??EngineeringSources[0];
        EngineeringNewModule=EngineeringSource.Module is null;
        if(EngineeringSource.Module is {} owned)EngineeringVariant=EngineeringVariants.FirstOrDefault(m=>m.Item.Equals(owned.Item,StringComparison.OrdinalIgnoreCase))??EngineeringVariant;
        UpdateEngineeringPrice();
        EngineeringCurrent=EngineeringSource is {Module:{} module}?EngineeringSource.Detail+"\n当前完成度 "+(module.Engineering?.Quality is {} q?q.ToString("P1"):module.Engineering is null?"未改装":"未记录"):ChineseNames.Translate(type)+" · 新模块从 G1、0% 开始";
        EngineeringCurrentProgress=(EngineeringSource?.Module?.Engineering?.Quality??0)*100;
        EngineeringCurrentProgressText=EngineeringSource?.Module?.Engineering is {} actual?actual.Quality?.ToString("P0")??"—":"0%";
    }
    private void RefreshEngineeringChoices()
    {
        var available=engineeringCatalog.Recipes.Where(r=>r.Type==EngineeringType&&r.Grade>0).ToArray();
        EngineeringBlueprints=StableSequence(EngineeringBlueprints,available.Select(r=>r.Name).Distinct().OrderBy(ChineseNames.Translate));
        if(!EngineeringBlueprints.Contains(EngineeringBlueprint??""))EngineeringBlueprint=available.FirstOrDefault(r=>EngineeringCatalog.SameBlueprint(r,EngineeringSource?.Module?.Engineering))?.Name??EngineeringBlueprints.FirstOrDefault();
        var names=available.Where(r=>r.Name==EngineeringBlueprint&&r.Grade==(int)EngineeringGrade).SelectMany(r=>r.Engineers).Distinct().ToArray();
        var old=EngineeringEngineer?.Id;
        CompatibleEngineers=StableSequence(CompatibleEngineers,EngineerAccessList.Where(e=>names.Any(n=>EngineeringCatalog.SameEngineer(n,e.Name))).OrderByDescending(e=>e.Unlocked).ThenByDescending(e=>e.Rank).ThenBy(e=>e.Name));
        EngineeringEngineer=CompatibleEngineers.FirstOrDefault(e=>e.Id==old)??CompatibleEngineers.FirstOrDefault();
        var effect=EngineeringEffect?.Key??EngineeringExperimental.Current(EngineeringSource?.Module?.Engineering);
        ExperimentalEffects=StableSequence(ExperimentalEffects,BuildEngineeringEffects(EngineeringType??"",effect));
        EngineeringEffect=ExperimentalEffects.FirstOrDefault(e=>EngineeringExperimental.Same(engineeringCatalog,EngineeringType??"",e.Key,effect))??ExperimentalEffects[0];
        var target=available.FirstOrDefault(r=>r.Name==EngineeringBlueprint&&r.Grade==(int)EngineeringGrade);
        EngineeringTargetEffects=target is null?"该蓝图没有此目标等级。":"目标等级满改效果(参考):\n"+string.Join("\n",target.Effects.Select(ChineseNames.Effect));
        EngineeringTargetEffectsOriginal=target is null?"":string.Join("\n",target.Effects);
    }
    private IReadOnlyList<EngineeringEffectChoice> BuildEngineeringEffects(string type,string current)
    {
        var choices=new[]{new EngineeringEffectChoice("无实验效果",null)}.Concat(engineeringCatalog.Recipes.Where(r=>r.Type==type&&r.Grade==0).Select(r=>new EngineeringEffectChoice(r.Name,r))).ToList();
        if(current.Length>0&&!choices.Any(e=>EngineeringExperimental.Same(engineeringCatalog,type,e.Key,current)))
            choices.Add(new(ChineseNames.English(current),null,current));
        return choices;
    }
    private void EngineeringInputChanged(bool catalogue=false)
    {
        if(refreshingEngineering)return;refreshingEngineering=true;
        try{if(catalogue)RefreshEngineeringCatalogue();RefreshEngineeringChoices();}
        finally{refreshingEngineering=false;}
        if(engineeringRequested)QueueEngineeringCalculation();
    }
    private async void QueueEngineeringCalculation()
    {
        var version=++engineeringInputVersion;
        try{await Task.Delay(180,lifetime.Token);if(version==engineeringInputVersion)await CalculateEngineeringAsync();}
        catch(OperationCanceledException){}
    }
    partial void OnMaterialSearchChanged(string value)=>RefreshMaterialWarehouse();
    partial void OnMaterialCategoryIndexChanged(int value)=>RefreshMaterialWarehouse();
    partial void OnEngineeringTypeChanged(string? value)
    {
        if(!refreshingEngineering)
        {
            refreshingEngineering=true;
            try{EngineeringEffect=null;}finally{refreshingEngineering=false;}
        }
        EngineeringInputChanged(true);
    }
    partial void OnEngineeringSourceChanged(EngineeringSourceChoice? value)
    {
        if(refreshingEngineering)return;
        if(value?.Module is {} module)
        {
            EngineeringCount=1;EngineeringPurchase=false;
            // A newly selected owned module starts from its own blueprint, not the previous catalogue selection.
            // Keep the selected target when the same source merely receives a live progress update.
            if(engineeringSelectedSource!=value.Key)
            {
                var recipe=engineeringCatalog.Recipes.FirstOrDefault(r=>r.Type==EngineeringCatalog.ModuleType(module.Item)&&r.Grade>0&&EngineeringCatalog.SameBlueprint(r,module.Engineering));
                var wasRefreshing=refreshingEngineering;refreshingEngineering=true;
                try{EngineeringBlueprint=recipe?.Name;EngineeringEffect=null;}
                finally{refreshingEngineering=wasRefreshing;}
            }
        }
        else if(engineeringSelectedSource!=(value?.Key??""))
        {
            var wasRefreshing=refreshingEngineering;refreshingEngineering=true;
            try{EngineeringEffect=null;}finally{refreshingEngineering=wasRefreshing;}
        }
        engineeringSelectedSource=value?.Key??"";
        EngineeringInputChanged(true);
    }
    private string engineeringSelectedSource="";
    partial void OnEngineeringVariantChanged(ModuleDefinition? value)=>UpdateEngineeringPrice();
    partial void OnEngineeringUnitPriceChanged(double value){if(!settingEngineeringPrice)EngineeringPriceSource="手动报价";}
    private void UpdateEngineeringPrice()
    {
        var item=EngineeringSource?.Module?.Item??EngineeringVariant?.Item??"";if(item==engineeringPriceItem)return;engineeringPriceItem=item;
        settingEngineeringPrice=true;
        try{var price=ModulePrices.Find(item);EngineeringPriceKnown=price is not null;EngineeringUnitPrice=(double)(price??0);EngineeringPriceSource=price is not null?"Coriolis 参考基础价 · 不含站点折扣,可修改":"参考价未收录,可手动填写。";}
        finally{settingEngineeringPrice=false;}
    }
    partial void OnEngineeringBlueprintChanged(string? value)=>EngineeringInputChanged();
    partial void OnEngineeringEngineerChanged(EngineerLiveRow? value){if(!refreshingEngineering&&engineeringRequested)QueueEngineeringCalculation();}
    partial void OnEngineeringGradeChanged(double value)=>EngineeringInputChanged();
    partial void OnEngineeringQualityChanged(double value){if(!refreshingEngineering&&engineeringRequested)QueueEngineeringCalculation();}
    partial void OnEngineeringEffectChanged(EngineeringEffectChoice? value){if(!refreshingEngineering&&engineeringRequested)QueueEngineeringCalculation();}
    partial void OnEngineeringRemoteChanged(bool value){if(engineeringRequested)QueueEngineeringCalculation();}
    partial void OnEngineeringFastAdvanceChanged(bool value){if(engineeringRequested)QueueEngineeringCalculation();}
    private FittedModule GetEngineeringModule()=>EngineeringSource?.Module??(EngineeringVariant is {} m?new(){Item=m.Item,Name=m.Name,Enabled=true}:throw new InvalidOperationException("该模块类型没有可用规格。"));
    private RefitTarget CaptureEngineeringTarget()
    {
        var engineer=EngineeringEngineer??throw new InvalidOperationException("未读取到提供此等级蓝图的工程师权限。");
        var blueprint=EngineeringBlueprint??throw new InvalidOperationException("请选择蓝图。");
        if(!double.IsFinite(EngineeringGrade)||EngineeringGrade!=Math.Truncate(EngineeringGrade)||EngineeringGrade is <1 or >5||!double.IsFinite(EngineeringQuality)||EngineeringQuality is <=0 or >100)throw new InvalidOperationException("目标等级为 G1–G5,完成度为 1%–100%。");
        return new(blueprint,(int)EngineeringGrade,EngineeringQuality/100,EngineeringEffect?.Key??"",engineer.Id,EngineeringRemote,EngineeringFastAdvance);
    }
    [RelayCommand] private async Task CalculateEngineeringAsync(CancellationToken cancellationToken=default)
    {
        using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token,cancellationToken);var token=stop.Token;
        engineeringRequested=true;var version=++engineeringVersion;EngineeringBusy=true;
        try
        {
            var p=CurrentPlayer;if(p.MismatchedEngineeringRolls>0)throw new InvalidOperationException("检测到改装进度与当前规则不符,需先校准。");
            var module=GetEngineeringModule();var target=CaptureEngineeringTarget();
            var engineer=EngineeringEngineer!.Record;var effect=EngineeringEffect?.Recipe;
            var result=await Task.Run(()=>EngineeringPlanner.Calculate(engineeringCatalog,module,target.Blueprint,target.Grade,target.Quality,effect,engineer,p.Materials,target.Remote,target.FastAdvance,target.Experimental,token),token);
            token.ThrowIfCancellationRequested();
            if(version!=engineeringVersion)return;
            EngineeringRolls=result.Rolls;EngineeringNeeds=result.Materials;
            EngineeringResult=$"单件 · {(result.Exact?"按已知声望计算":"声望区间预算")} · {result.Rolls.Count} 次改装 · {result.Materials.Count} 种材料\n缺少 {result.Materials.Sum(m=>m.Missing):N0} 份 · 最终权限 G{result.FinalRank} · {(result.Complete?"路径可完成":"需要额外提升权限")}";
            EngineeringNotes=string.Join("\n",result.Notes)+(p.MaterialsReliable?"":"\n库存可能不完整,缺少数量需校准。");
        }
        catch(OperationCanceledException){if(version==engineeringVersion)EngineeringResult="材料计算已取消。";}
        catch(Exception e){if(version==engineeringVersion){EngineeringRolls=[];EngineeringNeeds=[];EngineeringResult=e.Message;}}
        finally{if(version==engineeringVersion)EngineeringBusy=false;}
    }
}