using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.Core.Notifications;
public sealed record SessionReadyNotification(string Id,string Title,string Detail,NavigationArrival Scene);
/// <summary>A new process alone is not completion: wait for a fresh playable session.</summary>
public sealed class SessionReadyNotificationObserver
{
private readonly object gate=new();
private sealed record Pending(SessionReentryEvent Entry,string Commander,DateTimeOffset? PreviousSession,bool ProcessReady);
private Pending? pending;
public GameSessionTarget? ExpectedTarget{get{lock(gate)return pending is {ProcessReady:true} p?p.Entry.Target:null;}}
public void OnReentry(SessionReentryEvent entry,PlayerState player)
{
lock(gate)
{
if(entry.Stage==SessionReentryStage.Requested)
pending=new(entry,player.CommanderId,player.LoadGameAt,false);
else if(pending is {} p&&p.Entry.Id==entry.Id)
pending=entry.Stage==SessionReentryStage.Aborted?null:p with{Entry=entry,ProcessReady=true};
}
}
public SessionReadyNotification? Observe(PlayerState player,GameSessionTarget running,DateTimeOffset now)
{
lock(gate)
{
if(pending is not {ProcessReady:true} p)return null;
if(p.Commander.Length>0&&player.CommanderId.Length>0&&p.Commander!=player.CommanderId){pending=null;return null;}
var expected=p.Entry.Target;
if(running.ProcessId!=expected.ProcessId||running.StartedUtcTicks!=expected.StartedUtcTicks
||!string.Equals(running.Executable,expected.Executable,StringComparison.OrdinalIgnoreCase))return null;
if(!player.InGame||player.CommanderId.Length==0||player.LoadGameAt is not {} loaded
||p.PreviousSession is {} previous&&loaded<=previous||loaded<p.Entry.RequestedAt.AddSeconds(-1)
||loaded>now.AddSeconds(1)||player.LocationAt is not {} location||location<loaded
||player.StatusAt is not {} statusAt||statusAt<loaded||player.StatusReadAt is not {} readAt||readAt<loaded||readAt<now.AddSeconds(-5)
||player.Navigation.CurrentSystemAddress is not >0||player.SystemName.Length==0||player.Navigation.JumpInProgress
||player.Status is not {} status||((status.Flags&((1L<<24)|(1L<<25)|(1L<<26)))==0&&(status.Flags2&1)==0))return null;
pending=null;
var id=p.Entry.Id.ToString("N");
var action=p.Entry.Protection?"飞船保护大退":p.Entry.Operation switch
{SessionOperation.Relog=>"一键重进",SessionOperation.Restart=>"一键大退",_=>"启动游戏"};
var scene=new NavigationArrival(id,player.Navigation.CurrentSystemAddress.Value,player.SystemName,player.SystemName,
player.Docked,false,player.CommanderId,loaded,now);
return new(id,p.Entry.Operation==SessionOperation.Start?"游戏已准备就绪":"已重新进入游戏",$"{action}已完成 · {player.SystemName}",scene);
}
}
}
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.Core.Notifications;
public sealed record SessionReadyNotification(string Id,string Title,string Detail,NavigationArrival Scene);
/// <summary>A new process alone is not completion: wait for a fresh playable session.</summary>
public sealed class SessionReadyNotificationObserver
{
private readonly object gate=new();
private sealed record Pending(SessionReentryEvent Entry,string Commander,DateTimeOffset? PreviousSession,bool ProcessReady);
private Pending? pending;
public GameSessionTarget? ExpectedTarget{get{lock(gate)return pending is {ProcessReady:true} p?p.Entry.Target:null;}}
public void OnReentry(SessionReentryEvent entry,PlayerState player)
{
lock(gate)
{
if(entry.Stage==SessionReentryStage.Requested)
pending=new(entry,player.CommanderId,player.LoadGameAt,false);
else if(pending is {} p&&p.Entry.Id==entry.Id)
pending=entry.Stage==SessionReentryStage.Aborted?null:p with{Entry=entry,ProcessReady=true};
}
}
public SessionReadyNotification? Observe(PlayerState player,GameSessionTarget running,DateTimeOffset now)
{
lock(gate)
{
if(pending is not {ProcessReady:true} p)return null;
if(p.Commander.Length>0&&player.CommanderId.Length>0&&p.Commander!=player.CommanderId){pending=null;return null;}
var expected=p.Entry.Target;
if(running.ProcessId!=expected.ProcessId||running.StartedUtcTicks!=expected.StartedUtcTicks
||!string.Equals(running.Executable,expected.Executable,StringComparison.OrdinalIgnoreCase))return null;
if(!player.InGame||player.CommanderId.Length==0||player.LoadGameAt is not {} loaded
||p.PreviousSession is {} previous&&loaded<=previous||loaded<p.Entry.RequestedAt.AddSeconds(-1)
||loaded>now.AddSeconds(1)||player.LocationAt is not {} location||location<loaded
||player.StatusAt is not {} statusAt||statusAt<loaded||player.StatusReadAt is not {} readAt||readAt<loaded||readAt<now.AddSeconds(-5)
||player.Navigation.CurrentSystemAddress is not >0||player.SystemName.Length==0||player.Navigation.JumpInProgress
||player.Status is not {} status||((status.Flags&((1L<<24)|(1L<<25)|(1L<<26)))==0&&(status.Flags2&1)==0))return null;
pending=null;
var id=p.Entry.Id.ToString("N");
var action=p.Entry.Protection?"飞船保护大退":p.Entry.Operation switch
{SessionOperation.Relog=>"一键重进",SessionOperation.Restart=>"一键大退",_=>"启动游戏"};
var scene=new NavigationArrival(id,player.Navigation.CurrentSystemAddress.Value,player.SystemName,player.SystemName,
player.Docked,false,player.CommanderId,loaded,now);
return new(id,p.Entry.Operation==SessionOperation.Start?"游戏已准备就绪":"已重新进入游戏",$"{action}已完成 · {player.SystemName}",scene);
}
}
}