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.Galaxy;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;

namespace XFEEDToolBox.ViewModels;

public partial class ToolboxViewModel
{
    private readonly SemaphoreSlim tradeRegionGate=new(1,1);
    private Coordinates? tradeRegionCenterPosition;
    private string resolvedTradeRegionName="";
    [ObservableProperty] public partial string TradeRegionCenterText {get;set;}="";
    [ObservableProperty] public partial string TradeRegionStatus {get;set;}="留空跟随当前位置;缓存与规划使用同一范围。";
    [ObservableProperty] public partial bool TradeRegionBusy {get;set;}
    public bool TradeRegionEditable=>TradeSelectionEnabled&&!TradeRegionBusy&&!SessionBusy;
    public string MarketCacheScopeText=>runtime.Settings.MarketCacheCenterPosition is {IsValid:true}
        ?$"新增行情以 {runtime.Settings.MarketCacheCenterName} 为中心,半径 {runtime.Settings.MarketCacheRadiusLy:N0} ly;保留当前站。不自动下载或校验全量数据。"
        :$"新增行情跟随当前位置,半径 {runtime.Settings.MarketCacheRadiusLy:N0} ly;保留当前站。没有定位时等待,不自动下载或校验全量数据。";
    private void InitializeTradeRegionScope()
    {
        var settings=runtime.Settings;
        Radius=settings.MarketCacheRadiusLy;
        tradeRegionCenterPosition=settings.MarketCacheCenterPosition;
        resolvedTradeRegionName=settings.MarketCacheCenterPosition is {IsValid:true}?settings.MarketCacheCenterName:"";
        TradeRegionCenterText=resolvedTradeRegionName;
        TradeRegionStatus=resolvedTradeRegionName.Length>0?$"{resolvedTradeRegionName} · 半径 {Radius:N0} ly":"跟随当前位置 · 半径 "+Radius.ToString("N0")+" ly";
        PropertyChanged+=(_,args)=>
        {
            if(args.PropertyName is nameof(RunActive) or nameof(IsBusy) or nameof(SessionBusy))OnPropertyChanged(nameof(TradeRegionEditable));
        };
    }
    partial void OnTradeRegionCenterTextChanged(string value)
    {
        if(!value.Trim().Equals(resolvedTradeRegionName,StringComparison.OrdinalIgnoreCase))tradeRegionCenterPosition=null;
        InvalidateTradeCandidates();
    }
    partial void OnRadiusChanged(double value)=>InvalidateTradeCandidates();
    partial void OnTradeRegionBusyChanged(bool value)
    {OnPropertyChanged(nameof(TradeRegionEditable));CalculateCommand.NotifyCanExecuteChanged();RefreshMarketCacheSettingsAvailability();}
    private static void ValidateTradeRegionRadius(double radius)
    {if(!double.IsFinite(radius)||radius<=0)throw new ArgumentException("搜索半径须为有效的正数,单位 ly。");}
    private async Task SaveTradeRegionScopeAsync(string name,Coordinates? position,CancellationToken token)
    {
        token.ThrowIfCancellationRequested();ValidateTradeRegionRadius(Radius);
        var settings=runtime.Settings;
        if(settings.MarketCacheRadiusLy!=Radius||settings.MarketCacheCenterName!=name||settings.MarketCacheCenterPosition!=position)
        {
            await runtime.SaveSettingsAsync(settings with{MarketCacheRadiusLy=Radius,MarketCacheCenterName=name,MarketCacheCenterPosition=position,
                Planner=settings.Planner with{SearchRadiusLy=Radius,SearchCenterName=name,SearchCenterPosition=position}});
            OnPropertyChanged(nameof(MarketCacheScopeText));
        }
    }
    internal async Task CommitTradeRegionCenterAsync(string name,CancellationToken token=default)
    {
        await tradeRegionGate.WaitAsync(token);
        try
        {
            if(RunActive||SessionBusy)throw new InvalidOperationException("请先停止跑商或完成会话操作,再调整规划范围。");
            TradeRegionBusy=true;ValidateTradeRegionRadius(Radius);name=name.Trim();TradeRegionCenterText=name;
            Coordinates? position=null;
            if(name.Length>0)
            {
                TradeRegionStatus="正在核对中心星系坐标…";
                var found=await ResolveGalacticSelectionAsync(name,token);
                if(!found.Position.IsValid)throw new InvalidDataException("尚未取得该星系的真实坐标,请重新选择或提交名称。");
                if(!TradeRegionCenterText.Trim().Equals(name,StringComparison.OrdinalIgnoreCase))throw new OperationCanceledException(token);
                name=found.Name;position=found.Position;
            }
            await SaveTradeRegionScopeAsync(name,position,token);
            resolvedTradeRegionName=name;tradeRegionCenterPosition=position;TradeRegionCenterText=name;
            TradeRegionStatus=name.Length==0?$"跟随当前位置 · 半径 {Radius:N0} ly":$"{name} · 半径 {Radius:N0} ly";
        }
        finally{TradeRegionBusy=false;tradeRegionGate.Release();}
    }
    internal async Task SaveTradeRegionRadiusAsync(CancellationToken token=default)
    {
        if(!double.IsFinite(Radius))Radius=runtime.Settings.MarketCacheRadiusLy;
        await tradeRegionGate.WaitAsync(token);
        try
        {
            if(RunActive||SessionBusy)throw new InvalidOperationException("请先停止跑商或完成会话操作,再调整规划范围。");
            ValidateTradeRegionRadius(Radius);
            await SaveTradeRegionScopeAsync(runtime.Settings.MarketCacheCenterName,runtime.Settings.MarketCacheCenterPosition,token);
            TradeRegionStatus=runtime.Settings.MarketCacheCenterPosition is not null?$"{runtime.Settings.MarketCacheCenterName} · 半径 {Radius:N0} ly":$"跟随当前位置 · 半径 {Radius:N0} ly";
        }
        finally{tradeRegionGate.Release();}
    }
    [RelayCommand]
    private async Task FollowCurrentTradeRegionAsync()
    {
        try{await CommitTradeRegionCenterAsync("",lifetime.Token);}
        catch(OperationCanceledException){}
        catch(Exception error){ReportTradeRegionError(error);}
    }
    internal void ReportTradeRegionError(Exception error)
    {TradeRegionStatus=error is ArgumentException or InvalidDataException or InvalidOperationException?error.Message:"中心或范围尚未保存,请重试。";runtime.Log("跑商范围",error.ToString());}
    private Coordinates? EffectiveTradeRegionCenter=>TradeRegionCenterText.Trim().Length==0?CurrentPlayer.Position:tradeRegionCenterPosition;
    private async Task PrepareTradeRegionForPlanningAsync(CancellationToken token)
    {
        await CommitTradeRegionCenterAsync(TradeRegionCenterText,token);
        var player=CurrentPlayer;
        if(player.Position is not {IsValid:true})throw new InvalidOperationException("等待当前星系定位后再规划;也可先选择中心并手动补全周边行情。");
        if(EffectiveTradeRegionCenter is not {IsValid:true} center)throw new InvalidOperationException("中心坐标尚未就绪,请选择准确星系后重试。");
        var rows=DemoMode?DemoData.CreateBook().Stations.Where(station=>station.Position is {IsValid:true} position&&position.DistanceTo(center)<=Radius).ToArray()
            :await Task.Run(()=>runtime.Markets.GetStationsNear(center,Radius).ToArray(),token);
        if(rows.Length==0)throw new InvalidOperationException("此范围尚无缓存站点。请等待周边行情,或在设置中手动补全该中心星系。");
        var start=rows.FirstOrDefault(station=>station.MarketId==SelectedStart?.MarketId)
            ??rows.FirstOrDefault(station=>station.MarketId==player.MarketId)
            ??rows.OrderBy(station=>station.Position!.DistanceTo(player.Position)).ThenBy(station=>station.MarketId).First();
        var oldDestination=AutomaticDestination?.MarketId;
        Stations.Apply(rows.Concat(AutomaticDestination is null?[]:new[]{AutomaticDestination.Record}).DistinctBy(station=>station.MarketId));
        SelectedStart=Stations.First(station=>station.MarketId==start.MarketId);
        AutomaticDestination=Stations.FirstOrDefault(station=>station.MarketId==oldDestination);
    }
    internal void RegisterTradeRegionSystemForProbe(GalacticSystem system,string isolatedDirectory)
    {
        if(!OfflinePreview||system.Source!="isolated-fixture"||!system.Position.IsValid
            ||!Path.GetFullPath(runtime.DataDirectory).Equals(Path.GetFullPath(isolatedDirectory),StringComparison.OrdinalIgnoreCase))
            throw new InvalidOperationException("Synthetic region coordinates are limited to an explicit isolated interface fixture.");
        RememberGalaxy(system);
    }
}