mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-02 03:18:23 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
798 lines
20 KiB
C#
798 lines
20 KiB
C#
using Sandbox.Network;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json.Nodes;
|
|
using static Sandbox.Component;
|
|
|
|
namespace Sandbox;
|
|
|
|
internal sealed partial class NetworkObject : IValid, IDeltaSnapshot
|
|
{
|
|
internal NetworkObject RootNetworkObject => GameObject.RootNetwork.RootGameObject._net;
|
|
internal GameObject GameObject { get; set; }
|
|
|
|
Guid IDeltaSnapshot.Id => Id;
|
|
|
|
/// <summary>
|
|
/// The unique <see cref="Guid"/> of the underlying <see cref="GameObject"/>.
|
|
/// </summary>
|
|
internal Guid Id => GameObject.Id;
|
|
|
|
/// <summary>
|
|
/// The <see cref="Guid"/> of the connection that created this.
|
|
/// </summary>
|
|
public Guid Creator { get; set; }
|
|
|
|
public bool IsValid => GameObject.IsValid();
|
|
|
|
/// <summary>
|
|
/// If true then this object is spawning on the host, on behalf of another client. While it's
|
|
/// doing this we're going to act like the host is the owner.. so that anything that is called in
|
|
/// OnAwake will think we're not a proxy - until we've fully handed it off.
|
|
/// </summary>
|
|
bool _isNetworkSpawning;
|
|
|
|
/// <summary>
|
|
/// The <see cref="Guid"/> of the connection that owns this.
|
|
/// </summary>
|
|
public Guid Owner
|
|
{
|
|
get;
|
|
set
|
|
{
|
|
if ( field == value )
|
|
return;
|
|
|
|
var oldOwner = field;
|
|
field = value;
|
|
|
|
OnOwnerChanged( field, oldOwner );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Are we the owner of this networked object?
|
|
/// </summary>
|
|
public bool IsOwner => Owner == Connection.Local.Id;
|
|
|
|
/// <summary>
|
|
/// Is this networked object unowned?
|
|
/// </summary>
|
|
public bool IsUnowned => Owner == Guid.Empty;
|
|
|
|
/// <summary>
|
|
/// This is this a proxy if we don't own this networked object.
|
|
/// </summary>
|
|
public bool IsProxy
|
|
{
|
|
get
|
|
{
|
|
if ( _isNetworkSpawning ) return false;
|
|
if ( IsOwner ) return false;
|
|
if ( IsUnowned && Networking.IsHost ) return false;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current snapshot version for this networked object.
|
|
/// </summary>
|
|
public ushort SnapshotVersion => LocalSnapshotState.Version;
|
|
|
|
bool _clearInterpolationFlag;
|
|
bool _hasNetworkDestroyed;
|
|
bool _initialized;
|
|
|
|
internal void OnHotload()
|
|
{
|
|
// Build the network table again as properties may have changed.
|
|
CreateDataTable();
|
|
}
|
|
|
|
internal NetworkObject( GameObject source )
|
|
{
|
|
GameObject = source;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize and spawn this networked object with the specified owner <see cref="Connection"/>.
|
|
/// </summary>
|
|
internal void InitializeForConnection( Connection owner, bool enable )
|
|
{
|
|
if ( _initialized )
|
|
throw new( "NetworkObject already initialized" );
|
|
|
|
using var _ = PerformanceStats.Timings.Network.Scope();
|
|
|
|
_initialized = true;
|
|
|
|
if ( owner is not null )
|
|
{
|
|
Creator = owner.Id;
|
|
Owner = owner.Id;
|
|
}
|
|
else
|
|
{
|
|
Creator = Guid.Empty;
|
|
Owner = Guid.Empty;
|
|
}
|
|
|
|
CreateDataTable();
|
|
|
|
// Keep track of us
|
|
GameObject.Scene.RegisterNetworkedObject( this );
|
|
|
|
// Call OnAwake on everything. We allow you to enable here because if you're the
|
|
// host spawning this object for other connections, then you probably want to act
|
|
// like the owner of it while OnAwake/OnEnabled is being called.
|
|
_isNetworkSpawning = true;
|
|
GameObject.Enabled = enable;
|
|
CallNetworkSpawn( owner );
|
|
_isNetworkSpawning = false;
|
|
|
|
// Tell the world that we're here
|
|
BroadcastNetworkSpawn( owner );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call INetworkSpawn hooks
|
|
/// </summary>
|
|
private void CallNetworkSpawn( Connection owner )
|
|
{
|
|
foreach ( var target in GameObject.Components.GetAll<INetworkSpawn>( FindMode.EverythingInSelfAndDescendants ).ToArray() )
|
|
{
|
|
try
|
|
{
|
|
target.OnNetworkSpawn( owner );
|
|
}
|
|
catch ( Exception e )
|
|
{
|
|
Log.Error( e );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell everyone that we exist, and spawn any child network objects with the same owner.
|
|
/// </summary>
|
|
void BroadcastNetworkSpawn( Connection owner )
|
|
{
|
|
// Tell everyone that we exist
|
|
SceneNetworkSystem.Instance?.NetworkSpawnBroadcast( this );
|
|
|
|
// If we have any child network objects, then spawn them with the same owner
|
|
GameObject.NetworkSpawnRecursive( owner );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize this networked object from a create message.
|
|
/// </summary>
|
|
internal void Initialize( ObjectCreateMsg msg )
|
|
{
|
|
if ( _initialized )
|
|
throw new( "NetworkObject already initialized" );
|
|
|
|
using var _ = PerformanceStats.Timings.Network.Scope();
|
|
|
|
_initialized = true;
|
|
|
|
Creator = msg.Creator;
|
|
Owner = msg.Owner;
|
|
|
|
CreateDataTable();
|
|
OnCreateMessage( msg );
|
|
|
|
GameObject.Scene.RegisterNetworkedObject( this );
|
|
}
|
|
|
|
internal void Dispose()
|
|
{
|
|
GameObject.Scene.UnregisterNetworkObject( this );
|
|
GameObject = default;
|
|
}
|
|
|
|
internal void ClearInterpolation()
|
|
{
|
|
if ( IsProxy ) return;
|
|
_clearInterpolationFlag = true;
|
|
}
|
|
|
|
internal bool CanDropOwnership( Connection source )
|
|
{
|
|
// Conna: accept all requests for now. In future, maybe we can do something with INetworkListener?
|
|
return true;
|
|
}
|
|
|
|
internal bool CanAssignOwnership( Connection source, Guid target )
|
|
{
|
|
// Conna: accept all requests for now. In future, maybe we can do something with INetworkListener?
|
|
return true;
|
|
}
|
|
|
|
internal bool CanTakeOwnership( Connection source )
|
|
{
|
|
// Conna: accept all requests for now. In future, maybe we can do something with INetworkListener?
|
|
return true;
|
|
}
|
|
|
|
internal void OnNetworkDestroy()
|
|
{
|
|
_hasNetworkDestroyed = true;
|
|
GameObject.Destroy();
|
|
}
|
|
|
|
internal void SendNetworkDestroy()
|
|
{
|
|
if ( SceneNetworkSystem.Instance is null ) return;
|
|
if ( Networking.IsDisconnecting ) return;
|
|
if ( _hasNetworkDestroyed ) return;
|
|
if ( IsProxy && !Networking.IsHost ) return;
|
|
|
|
var msg = new ObjectDestroyMsg { Guid = GameObject.Id };
|
|
SceneNetworkSystem.Instance.Broadcast( msg );
|
|
}
|
|
|
|
internal ObjectRefreshMsg GetRefreshMessage()
|
|
{
|
|
var system = SceneNetworkSystem.Instance;
|
|
if ( system is null )
|
|
throw new Exception( "SceneNetworkSystem is null" );
|
|
|
|
var snapshot = ((IDeltaSnapshot)this).WriteSnapshotState();
|
|
|
|
var o = new GameObject.SerializeOptions
|
|
{
|
|
SingleNetworkObject = true
|
|
};
|
|
|
|
var msg = new ObjectRefreshMsg
|
|
{
|
|
Guid = GameObject.Id,
|
|
Parent = GameObject.Parent.Id,
|
|
JsonData = GameObject.Serialize( o ).ToJsonString(),
|
|
TableData = WriteReliableData(),
|
|
Snapshot = system.DeltaSnapshots.GetFullSnapshotData( snapshot )
|
|
};
|
|
|
|
return msg;
|
|
}
|
|
|
|
internal void SendNetworkRefresh( GameObject go )
|
|
{
|
|
var system = SceneNetworkSystem.Instance;
|
|
if ( system is null ) return;
|
|
if ( go is null ) return;
|
|
|
|
if ( go == GameObject )
|
|
{
|
|
// If the object passed isn't actually a descendant, then refresh
|
|
// the entire tree.
|
|
SendNetworkRefresh();
|
|
return;
|
|
}
|
|
|
|
if ( go.IsDestroyed )
|
|
{
|
|
// We want to tell clients that this component has been destroyed...
|
|
var msg = new ObjectDestroyDescendantMsg
|
|
{
|
|
Guid = go.Id
|
|
};
|
|
|
|
system.Broadcast( msg );
|
|
return;
|
|
}
|
|
|
|
if ( !go.IsAncestor( GameObject ) )
|
|
return;
|
|
|
|
{
|
|
var snapshot = ((IDeltaSnapshot)this).WriteSnapshotState();
|
|
|
|
// Only this one object...
|
|
var options = new GameObject.SerializeOptions
|
|
{
|
|
IgnoreChildren = true
|
|
};
|
|
|
|
var msg = new ObjectRefreshDescendantMsg
|
|
{
|
|
GameObjectId = GameObject.Id,
|
|
ParentId = go.Parent.Id,
|
|
JsonData = go.Serialize( options ).ToJsonString(),
|
|
TableData = WriteReliableData(),
|
|
Snapshot = system.DeltaSnapshots.GetFullSnapshotData( snapshot )
|
|
};
|
|
|
|
system.Broadcast( msg );
|
|
}
|
|
}
|
|
|
|
internal void SendNetworkRefresh( Component component )
|
|
{
|
|
var system = SceneNetworkSystem.Instance;
|
|
if ( system is null ) return;
|
|
|
|
if ( component is null ) return;
|
|
|
|
if ( !component.IsValid() )
|
|
{
|
|
// We want to tell clients that this component has been destroyed...
|
|
var msg = new ObjectDestroyComponentMsg
|
|
{
|
|
Guid = component.Id
|
|
};
|
|
|
|
system.Broadcast( msg );
|
|
return;
|
|
}
|
|
|
|
{
|
|
var snapshot = ((IDeltaSnapshot)this).WriteSnapshotState();
|
|
|
|
var msg = new ObjectRefreshComponentMsg
|
|
{
|
|
JsonData = component.Serialize().ToJsonString(),
|
|
GameObjectId = component.GameObject.Id,
|
|
TableData = WriteReliableData(),
|
|
Snapshot = system.DeltaSnapshots.GetFullSnapshotData( snapshot )
|
|
};
|
|
|
|
system.Broadcast( msg );
|
|
}
|
|
}
|
|
|
|
internal void SendNetworkRefresh()
|
|
{
|
|
var system = SceneNetworkSystem.Instance;
|
|
if ( system is null ) return;
|
|
|
|
var msg = GetRefreshMessage();
|
|
system.Broadcast( msg );
|
|
}
|
|
|
|
private struct CullState
|
|
{
|
|
public bool Culled;
|
|
public float LastVisibleAt;
|
|
}
|
|
|
|
internal readonly LocalSnapshotState LocalSnapshotState = new();
|
|
|
|
private readonly HashSet<Guid> _culledConnections = [];
|
|
private readonly Dictionary<Guid, CullState> _cullStates = new();
|
|
private readonly SnapshotValueCache _snapshotCache = new();
|
|
private TimeUntil _nextUpdateCachedBounds;
|
|
private BBox _cachedLocalBounds;
|
|
|
|
/// <summary>
|
|
/// Only cull this object if we've been invisible for this long.
|
|
/// </summary>
|
|
private const float CullDelay = 2f;
|
|
|
|
/// <summary>
|
|
/// Remove a connection id from any internal data structures.
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
internal void RemoveConnection( Guid id )
|
|
{
|
|
LocalSnapshotState.RemoveConnection( id );
|
|
_culledConnections.Remove( id );
|
|
_cullStates.Remove( id );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all connections associated with the local snapshot state.
|
|
/// </summary>
|
|
internal void ClearConnections()
|
|
{
|
|
LocalSnapshotState.ClearConnections();
|
|
}
|
|
|
|
bool IDeltaSnapshot.ShouldTransmit( Connection target )
|
|
{
|
|
return GameObject.Network.AlwaysTransmit || !_culledConnections.Contains( target.Id );
|
|
}
|
|
|
|
bool IDeltaSnapshot.UpdateTransmitState( Connection[] targets )
|
|
{
|
|
if ( GameObject.Network.AlwaysTransmit )
|
|
{
|
|
for ( var i = 0; i < targets.Length; i++ )
|
|
{
|
|
var target = targets[i];
|
|
|
|
if ( !_culledConnections.Remove( target.Id ) )
|
|
continue;
|
|
|
|
GameObject.Network.SetCullState( target, false );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( _nextUpdateCachedBounds )
|
|
{
|
|
// Let's update the cached local bounds every half a second.
|
|
_nextUpdateCachedBounds = 0.5f;
|
|
_cachedLocalBounds = GameObject.GetLocalBounds();
|
|
}
|
|
|
|
var shouldTransmitToAny = false;
|
|
var worldBounds = _cachedLocalBounds + GameObject.WorldPosition;
|
|
var timeNow = Time.Now;
|
|
|
|
var rootNetworkObject = RootNetworkObject;
|
|
IDeltaSnapshot root = rootNetworkObject != this ? rootNetworkObject : null;
|
|
|
|
for ( var i = 0; i < targets.Length; i++ )
|
|
{
|
|
var target = targets[i];
|
|
|
|
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault( _cullStates, target.Id, out var exists );
|
|
|
|
if ( !exists )
|
|
{
|
|
state = new CullState
|
|
{
|
|
Culled = false,
|
|
LastVisibleAt = timeNow
|
|
};
|
|
}
|
|
|
|
if ( !state.Culled )
|
|
shouldTransmitToAny = true;
|
|
|
|
if ( (root?.ShouldTransmit( target ) ?? false) || IsVisible( target, worldBounds ) )
|
|
{
|
|
state.LastVisibleAt = timeNow;
|
|
|
|
if ( !state.Culled )
|
|
continue;
|
|
|
|
if ( !_culledConnections.Remove( target.Id ) )
|
|
continue;
|
|
|
|
LocalSnapshotState.RemoveConnection( target.Id );
|
|
GameObject.Network.SetCullState( target, false );
|
|
|
|
shouldTransmitToAny = true;
|
|
state.Culled = false;
|
|
}
|
|
else
|
|
{
|
|
var timeSinceVisible = timeNow - state.LastVisibleAt;
|
|
|
|
if ( state.Culled || timeSinceVisible < CullDelay )
|
|
continue;
|
|
|
|
if ( !_culledConnections.Add( target.Id ) )
|
|
continue;
|
|
|
|
GameObject.Network.SetCullState( target, true );
|
|
state.Culled = true;
|
|
}
|
|
}
|
|
|
|
return shouldTransmitToAny;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this network object visible to the provided <see cref="Connection"/>. We'll check if we
|
|
/// have a culler component and use that, but we'll also use our bounds to determine if we're
|
|
/// visible.
|
|
/// </summary>
|
|
private bool IsVisible( Connection target, BBox worldBounds )
|
|
{
|
|
// Do we have a INetworkVisible? We're going to let that take priority.
|
|
var go = GameObject;
|
|
if ( go.IsValid() && go.Enabled && go.NetworkVisibility is not null )
|
|
{
|
|
return go.NetworkVisibility.IsVisibleToConnection( target, worldBounds );
|
|
}
|
|
|
|
// Global culling
|
|
return GameObject.Scene.IsBBoxVisibleToConnection( target, worldBounds );
|
|
}
|
|
|
|
void IDeltaSnapshot.OnSnapshotAck( Connection source, DeltaSnapshot snapshot, RemoteSnapshotState state )
|
|
{
|
|
IDeltaSnapshot snapshotter = this;
|
|
|
|
if ( !snapshotter.ShouldTransmit( source ) )
|
|
return;
|
|
|
|
var hasFullSnapshotState = true;
|
|
|
|
foreach ( var entry in LocalSnapshotState.Entries )
|
|
{
|
|
if ( state.IsValueHashEqual( entry.Slot, entry.Hash, snapshot.SnapshotId ) )
|
|
{
|
|
entry.Connections.Add( source.Id );
|
|
}
|
|
else
|
|
{
|
|
entry.Connections.Remove( source.Id );
|
|
hasFullSnapshotState = false;
|
|
}
|
|
}
|
|
|
|
if ( hasFullSnapshotState )
|
|
LocalSnapshotState.UpdatedConnections.Add( source.Id );
|
|
else
|
|
LocalSnapshotState.UpdatedConnections.Remove( source.Id );
|
|
}
|
|
|
|
private const int SnapshotPositionSlot = 1;
|
|
private const int SnapshotRotationSlot = 2;
|
|
private const int SnapshotScaleSlot = 3;
|
|
private const int SnapshotInterpolationSlot = 4;
|
|
private const int SnapshotEnabledSlot = 5;
|
|
|
|
LocalSnapshotState IDeltaSnapshot.WriteSnapshotState()
|
|
{
|
|
var system = SceneNetworkSystem.Instance;
|
|
if ( system is null ) return null;
|
|
|
|
LocalSnapshotState.SnapshotId = system.DeltaSnapshots.CreateSnapshotId( Id );
|
|
LocalSnapshotState.ObjectId = Id;
|
|
|
|
if ( !IsProxy )
|
|
{
|
|
var tx = GameObject.Transform.TargetLocal;
|
|
LocalSnapshotState.AddCached( _snapshotCache, SnapshotPositionSlot, tx.Position );
|
|
LocalSnapshotState.AddCached( _snapshotCache, SnapshotRotationSlot, tx.Rotation );
|
|
LocalSnapshotState.AddCached( _snapshotCache, SnapshotScaleSlot, tx.Scale );
|
|
LocalSnapshotState.AddCached( _snapshotCache, SnapshotInterpolationSlot, _clearInterpolationFlag );
|
|
LocalSnapshotState.AddCached( _snapshotCache, SnapshotEnabledSlot, GameObject.Enabled );
|
|
}
|
|
|
|
dataTable.QueryValues();
|
|
dataTable.WriteSnapshotState( LocalSnapshotState );
|
|
|
|
_clearInterpolationFlag = false;
|
|
|
|
return LocalSnapshotState;
|
|
}
|
|
|
|
void IDeltaSnapshot.SendNetworkUpdate( bool queryValues )
|
|
{
|
|
if ( queryValues )
|
|
dataTable.QueryValues( true );
|
|
|
|
if ( !dataTable.HasReliableChanges() )
|
|
return;
|
|
|
|
var msg = new ObjectNetworkTableMsg { Guid = GameObject.Id };
|
|
var data = ByteStream.Create( 4096 );
|
|
|
|
dataTable.WriteReliableChanged( ref data );
|
|
msg.TableData = data.ToArray();
|
|
|
|
data.Dispose();
|
|
|
|
SceneNetworkSystem.Instance.Broadcast( msg );
|
|
}
|
|
|
|
internal void TransmitStateChanged()
|
|
{
|
|
LocalSnapshotState.ClearConnections();
|
|
}
|
|
|
|
internal ObjectCreateMsg GetCreateMessage()
|
|
{
|
|
var o = new GameObject.SerializeOptions { SingleNetworkObject = true };
|
|
|
|
if ( GameObject.Parent is null )
|
|
{
|
|
throw new( $"GameObject {GameObject.Id} ({GameObject.Name} has invalid parent" );
|
|
}
|
|
|
|
var jsonData = GameObject.Serialize( o );
|
|
if ( jsonData is null )
|
|
{
|
|
throw new( $"Unable to serialize {GameObject.Id} ({GameObject.Name})" );
|
|
}
|
|
|
|
var create = new ObjectCreateMsg
|
|
{
|
|
Guid = GameObject.Id,
|
|
SnapshotVersion = GameObject._net.LocalSnapshotState.Version,
|
|
Transform = GameObject.Transform.TargetLocal,
|
|
JsonData = jsonData.ToJsonString(),
|
|
Creator = Creator,
|
|
Parent = GameObject.Parent.Id,
|
|
Owner = Owner,
|
|
TableData = WriteDataTable( true ),
|
|
Enabled = GameObject.Enabled
|
|
};
|
|
|
|
return create;
|
|
}
|
|
|
|
internal void DoOrphanedAction()
|
|
{
|
|
var action = GameObject.Network.NetworkOrphaned;
|
|
|
|
if ( action == NetworkOrphaned.Destroy )
|
|
{
|
|
GameObject.Destroy();
|
|
}
|
|
else if ( action == NetworkOrphaned.ClearOwner )
|
|
{
|
|
if ( Networking.IsHost )
|
|
GameObject.Network.AssignOwnership( Guid.Empty );
|
|
else
|
|
Owner = Guid.Empty;
|
|
}
|
|
else if ( action == NetworkOrphaned.Random )
|
|
{
|
|
// Only the host can assign ownership to a random connection. Because they'll need to broadcast
|
|
// the random selection to everyone else.
|
|
if ( Networking.IsHost )
|
|
{
|
|
var connections = Connection.All.ToArray();
|
|
var randomIndex = Game.Random.Int( 0, connections.Length - 1 );
|
|
var connection = connections[randomIndex];
|
|
GameObject.Network.AssignOwnership( connection );
|
|
}
|
|
else
|
|
{
|
|
// We're not the host so let's just clear the owner until we get the new randomly
|
|
// selected owner from the host.
|
|
Owner = Guid.Empty;
|
|
}
|
|
}
|
|
else if ( action == NetworkOrphaned.Host )
|
|
{
|
|
if ( Networking.IsHost )
|
|
GameObject.Network.AssignOwnership( Connection.Host?.Id ?? Guid.Empty );
|
|
else
|
|
Owner = Guid.Empty;
|
|
}
|
|
}
|
|
|
|
internal void OnNetworkTableMessage( ObjectNetworkTableMsg message )
|
|
{
|
|
ReadDataTable( message.TableData );
|
|
}
|
|
|
|
internal void OnRefreshMessage( Connection source, ObjectRefreshMsg message )
|
|
{
|
|
var scene = Game.ActiveScene;
|
|
if ( !scene.IsValid() ) return;
|
|
|
|
var jsonObj = JsonNode.Parse( message.JsonData ).AsObject();
|
|
|
|
GameObject.SetParentFromNetwork( scene.Directory.FindByGuid( message.Parent ) );
|
|
GameObject.NetworkRefresh( jsonObj );
|
|
|
|
UpdateFromRefresh( source, message.TableData, message.Snapshot );
|
|
}
|
|
|
|
internal void UpdateFromRefresh( Connection source, byte[] tableData, byte[] snapshotData )
|
|
{
|
|
RegisterPropertiesRecursive();
|
|
|
|
var system = SceneNetworkSystem.Instance;
|
|
if ( system is null ) return;
|
|
|
|
ReadDataTable( tableData );
|
|
|
|
var bs = ByteStream.CreateReader( snapshotData );
|
|
system.DeltaSnapshots.OnDeltaSnapshot( source, bs );
|
|
bs.Dispose();
|
|
}
|
|
|
|
internal void OnCreateMessage( ObjectCreateMsg msg )
|
|
{
|
|
LocalSnapshotState.Version = msg.SnapshotVersion;
|
|
|
|
var parent = GameObject.Scene.Directory.FindByGuid( msg.Parent );
|
|
GameObject.Transform.SetLocalTransformFast( msg.Transform );
|
|
GameObject.SetParentFromNetwork( parent );
|
|
GameObject.Enabled = msg.Enabled;
|
|
ReadDataTable( msg.TableData );
|
|
}
|
|
|
|
bool IDeltaSnapshot.OnSnapshot( Connection source, DeltaSnapshot snapshot )
|
|
{
|
|
// Don't process this if the source connection does not have control, and they
|
|
// are not the host.
|
|
if ( !HasControl( source ) && !source.IsHost )
|
|
return false;
|
|
|
|
// Conna: only what we regard as the owner can modify this shit.
|
|
if ( HasControl( source ) )
|
|
{
|
|
snapshot.TryGetValue<bool>( SnapshotInterpolationSlot, out var clearInterpolation );
|
|
|
|
var didTransformChange = false;
|
|
var transform = GameObject.Transform.TargetLocal;
|
|
|
|
if ( snapshot.TryGetValue<Vector3>( SnapshotPositionSlot, out var position ) )
|
|
{
|
|
didTransformChange = true;
|
|
transform.Position = position;
|
|
}
|
|
|
|
if ( snapshot.TryGetValue<Rotation>( SnapshotRotationSlot, out var rotation ) )
|
|
{
|
|
didTransformChange = true;
|
|
transform.Rotation = rotation;
|
|
}
|
|
|
|
if ( snapshot.TryGetValue<Vector3>( SnapshotScaleSlot, out var scale ) )
|
|
{
|
|
didTransformChange = true;
|
|
transform.Scale = scale;
|
|
}
|
|
|
|
if ( didTransformChange )
|
|
{
|
|
GameObject.Transform.FromNetwork( transform, clearInterpolation );
|
|
}
|
|
else if ( clearInterpolation )
|
|
{
|
|
GameObject.Transform.ClearLocalInterpolation();
|
|
}
|
|
|
|
if ( snapshot.TryGetValue<bool>( SnapshotEnabledSlot, out var enabled ) )
|
|
{
|
|
GameObject.Enabled = enabled;
|
|
}
|
|
}
|
|
|
|
dataTable.ReadSnapshot( source, snapshot );
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the specified <see cref="Connection"/> has control over this networked object. A connection
|
|
/// has control if the object is unowned and they are the host, or if they own it directly.
|
|
/// </summary>
|
|
internal bool HasControl( Connection c )
|
|
{
|
|
if ( IsUnowned )
|
|
return c.IsHost;
|
|
|
|
return c.Id == Owner;
|
|
}
|
|
|
|
void OnOwnerChanged( Guid newOwner, Guid prevOwner )
|
|
{
|
|
var wasOwner = (prevOwner == Connection.Local.Id) || (prevOwner == Guid.Empty && Networking.IsHost);
|
|
var isOwner = (newOwner == Connection.Local.Id) || (newOwner == Guid.Empty && Networking.IsHost);
|
|
|
|
var newConnection = Connection.Find( newOwner );
|
|
var oldConnection = Connection.Find( prevOwner );
|
|
|
|
// Conna: clear interpolation when ownership changes.
|
|
GameObject.Transform.ClearLocalInterpolation();
|
|
|
|
IGameObjectNetworkEvents.PostToGameObject( GameObject, x => x.NetworkOwnerChanged( newConnection, oldConnection ) );
|
|
|
|
if ( wasOwner && !isOwner )
|
|
{
|
|
IGameObjectNetworkEvents.PostToGameObject( GameObject, x => x.StopControl() );
|
|
}
|
|
|
|
if ( isOwner && !wasOwner )
|
|
{
|
|
IGameObjectNetworkEvents.PostToGameObject( GameObject, x => x.StartControl() );
|
|
}
|
|
|
|
var system = SceneNetworkSystem.Instance;
|
|
system?.DeltaSnapshots.ClearNetworkObject( this );
|
|
|
|
LocalSnapshotState.ClearConnections();
|
|
|
|
if ( !isOwner )
|
|
return;
|
|
|
|
GameObject.IsNetworkCulled = false;
|
|
GameObject.UpdateNetworkCulledState();
|
|
}
|
|
}
|