DeskNet.cs
namespace PixelPusher;
/// <summary>
/// Watch + fork. Host Desk snapshots to the lobby. Guests watch live or edit a local copy.
/// No co-paint on the same pixel.
/// </summary>
public sealed class DeskNet : Component
{
public static DeskNet Instance { get; private set; }
public static bool Watching { get; private set; }
public static int Version { get; private set; }
public static string Presence { get; private set; } = "";
static ProjectSave _watch;
static ProjectSave _stash;
static bool _preferWatch = true;
static int _sentHash;
static float _sendIn;
static float _presenceIn;
public static bool Live
{
get
{
try { return Networking.IsActive; }
catch { return false; }
}
}
public static bool Host
{
get
{
try { return !Networking.IsActive || Networking.IsHost; }
catch { return true; }
}
}
public static bool GuestLocked => Live && !Host && Watching;
protected override void OnEnabled() => Instance = this;
protected override void OnDisabled()
{
if ( Instance == this ) Instance = null;
}
protected override void OnDestroy()
{
if ( Instance == this ) Instance = null;
}
public static void Tick()
{
EnsureHostRelay();
if ( !Live )
{
if ( Watching )
StopWatch();
return;
}
_presenceIn -= Time.Delta;
if ( _presenceIn <= 0f )
{
_presenceIn = 1f;
Presence = BuildPresence();
Version++;
GameFlow.Bump();
}
if ( !Host )
return;
Watching = false;
_sendIn -= Time.Delta;
if ( _sendIn > 0f )
return;
_sendIn = 0.35f;
var hash = HashCode.Combine(
Desk.Sprite?.Version ?? 0,
Desk.Tileset?.Version ?? 0,
Desk.Stage?.Version ?? 0,
Desk.Chip?.Version ?? 0,
Desk.Name );
if ( hash == _sentHash )
return;
_sentHash = hash;
var net = Instance;
if ( net.IsValid() )
net.PushSnapshot();
}
public static void Watch()
{
if ( Host || !Live )
return;
_preferWatch = true;
if ( !Watching && Desk.Ready )
_stash = Desk.ToSave();
Watching = true;
if ( _watch is not null )
Desk.Apply( _watch );
GameFlow.ShowToast( "watching the host desk" );
Version++;
GameFlow.Bump();
}
public static void MyDesk()
{
if ( Host )
return;
_preferWatch = false;
Watching = false;
if ( _stash is not null )
Desk.Apply( _stash );
GameFlow.ShowToast( "your desk" );
Version++;
GameFlow.Bump();
}
public static void Fork()
{
if ( Host || _watch is null )
return;
_preferWatch = false;
Watching = false;
_stash = null;
Desk.Apply( _watch );
Desk.Touch();
GameFlow.ShowToast( "forked to your desk" );
Version++;
GameFlow.Bump();
}
public static void DropOnDesk()
{
if ( Host || !Live )
return;
var net = Instance;
if ( !net.IsValid() )
{
GameFlow.ShowToast( "no live desk" );
return;
}
var save = Watching && _stash is not null ? _stash : Desk.ToSave();
net.RequestDrop( Json.Serialize( save ) );
GameFlow.ShowToast( "dropped on host desk" );
}
static void StopWatch()
{
Watching = false;
_watch = null;
}
public static void EnsureHostRelay()
{
try
{
if ( !Networking.IsActive )
return;
if ( Instance.IsValid() )
return;
if ( !Networking.IsHost )
return;
var go = new GameObject( true, "DeskNet" );
go.Components.Create<DeskNet>();
go.NetworkSpawn();
Log.Info( "[PIXEL PUSHER] desk net spawned" );
}
catch ( Exception e )
{
Log.Warning( $"[PIXEL PUSHER] desk net: {e.Message}" );
}
}
void PushSnapshot()
{
if ( !this.IsValid() )
return;
try
{
if ( GameObject is null || !GameObject.Network.Active )
return;
ReceiveSnapshot( Json.Serialize( Desk.ToSave() ) );
}
catch ( Exception e )
{
Log.Warning( $"[PIXEL PUSHER] snapshot: {e.Message}" );
}
}
[Rpc.Broadcast]
void ReceiveSnapshot( string json )
{
if ( Host )
return;
if ( string.IsNullOrWhiteSpace( json ) )
return;
try
{
_watch = Json.Deserialize<ProjectSave>( json );
if ( _watch is null )
return;
if ( _preferWatch )
{
if ( Watching )
Desk.Apply( _watch );
else
Watch();
}
Version++;
GameFlow.Bump();
}
catch ( Exception e )
{
Log.Warning( $"[PIXEL PUSHER] watch apply: {e.Message}" );
}
}
[Rpc.Host]
void RequestDrop( string json )
{
if ( string.IsNullOrWhiteSpace( json ) )
return;
try
{
var save = Json.Deserialize<ProjectSave>( json );
if ( save is null )
return;
var who = "guest";
try { who = Rpc.Caller?.DisplayName ?? who; } catch { }
Desk.Apply( save );
Desk.Touch();
_sentHash = 0;
_sendIn = 0;
GameFlow.ShowToast( $"{who} dropped their desk" );
GameFlow.Bump();
}
catch ( Exception e )
{
Log.Warning( $"[PIXEL PUSHER] drop: {e.Message}" );
}
}
static string BuildPresence()
{
try
{
var n = 0;
foreach ( var _ in Connection.All )
n++;
if ( n <= 0 )
n = 1;
return n == 1 ? "1 at the CRT" : $"{n} at the CRT";
}
catch
{
return "live";
}
}
}