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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Xml.Linq;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Localization;

namespace XFEEDToolBox.Core.Notifications;

public sealed record NavigationArrival(string Id,long SystemAddress,string SystemName,string Destination,bool Docked,
    bool LocalNavigation,string CommanderId,DateTimeOffset? SessionAt,DateTimeOffset ArrivedAt)
{
    public string Title=>$"已抵达 {Destination}";
    public string Detail=>Docked?$"{SystemName} · 已完成停靠":LocalNavigation?$"{SystemName} · 已到达指定位置":"自动导航已完成";
    public string Page=>LocalNavigation?"current-system":"navigation";
    public bool SameScene(PlayerState player)=>player.InGame&&player.CommanderId==CommanderId&&player.LoadGameAt==SessionAt
        &&player.Navigation.CurrentSystemAddress==SystemAddress&&!player.Navigation.JumpInProgress;
}

/// <summary>One notification per completed run, after the actual arrival has been confirmed.</summary>
public sealed class NavigationArrivalObserver
{
    private readonly object gate=new();
    private (string Commander,DateTimeOffset? Session,string Destination)? interstellarRun;
    private (string Commander,DateTimeOffset? Session,SystemNavigationTarget Target)? localRun;
    public NavigationArrival? Observe(NavigationProgress progress,PlayerState player,DateTimeOffset now)
    {
        lock(gate)
        {
            if(progress.State==NavigationRunState.Running){interstellarRun??=(player.CommanderId,player.LoadGameAt,progress.Destination.Trim());return null;}
            var run=interstellarRun;interstellarRun=null;
            if(run is not {} started||progress.State!=NavigationRunState.Completed||!Valid(player)
                ||started.Commander!=player.CommanderId||started.Session!=player.LoadGameAt
                ||!started.Destination.Equals(progress.Destination.Trim(),StringComparison.OrdinalIgnoreCase)
                ||!player.SystemName.Equals(progress.Destination.Trim(),StringComparison.OrdinalIgnoreCase))return null;
            return Create(player,player.SystemName,false,now);
        }
    }
    public NavigationArrival? Observe(SystemNavigationProgress progress,PlayerState player,DateTimeOffset now)
    {
        lock(gate)
        {
            if(progress.State==NavigationRunState.Running){localRun??=(player.CommanderId,player.LoadGameAt,progress.Target);return null;}
            var run=localRun;localRun=null;
            if(run is not {} started||progress.State!=NavigationRunState.Completed||!Valid(player)
                ||started.Commander!=player.CommanderId||started.Session!=player.LoadGameAt||!started.Target.SameIdentity(progress.Target)
                ||progress.Target.SystemAddress!=player.Navigation.CurrentSystemAddress
                ||progress.Target.NeedsDocking&&(!player.Docked||player.MarketId!=progress.Target.MarketId))return null;
            return Create(player,GameNavigationNames.Location(progress.Target.Name,"指定位置"),true,now);
        }
    }
    private static bool Valid(PlayerState player)=>player.InGame&&player.Navigation.CurrentSystemAddress is >0&&!player.Navigation.JumpInProgress;
    private static NavigationArrival Create(PlayerState player,string destination,bool local,DateTimeOffset now)
        =>new(Guid.NewGuid().ToString("N"),player.Navigation.CurrentSystemAddress!.Value,player.SystemName,destination,
            player.Docked,local,player.CommanderId,player.LoadGameAt,now);
}

public static class ArrivalToastContent
{
    // Windows owns the outer toast. The hero is a full-width screenshot card,
    // with its own readable caption; it is never a separate simulated window.
    public static string Build(NavigationArrival arrival,string? heroPath=null)
        =>BuildCore(arrival.Title,arrival.Detail,$"action=arrival&page={arrival.Page}&system={arrival.SystemAddress}",
            $"{arrival.SystemName} 抵达时的游戏画面",heroPath);
    public static string Build(SessionReadyNotification ready,string? heroPath=null)
        =>BuildCore(ready.Title,ready.Detail,"action=session&page=home",$"{ready.Scene.SystemName} 当前游戏画面",heroPath);
    private static string BuildCore(string title,string detail,string launch,string imageDescription,string? heroPath)
    {
        var binding=new XElement("binding",new XAttribute("template","ToastGeneric"),
            new XElement("text",title),new XElement("text",detail));
        if(heroPath is not null)
        {
            var file=new Uri(Path.GetFullPath(heroPath));
            binding.AddFirst(new XElement("image",new XAttribute("placement","hero"),new XAttribute("src",file.AbsoluteUri),
                new XAttribute("alt",imageDescription)));
        }
        return new XElement("toast",new XAttribute("launch",launch),
            new XAttribute("duration","long"),new XElement("visual",binding),
            new XElement("audio",new XAttribute("src","ms-winsoundevent:Notification.Default"))).ToString(SaveOptions.DisableFormatting);
    }
}