[Add] FishNet
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
using FishNet.Object;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// When using dual prefabs, defines which prefab to spawn for server, and which for clients.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct DualPrefab
|
||||
{
|
||||
public NetworkObject Server;
|
||||
public NetworkObject Client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76840b2b810d8fc45aeccef03122763c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/DualPrefab.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,525 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using System;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Serializing.Helping;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public abstract partial class ManagedObjects
|
||||
{
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Number of bytes to reserve for a predicted spawn length.
|
||||
/// </summary>
|
||||
internal const byte PREDICTED_SPAWN_BYTES = 2;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Reads and outputs a transforms values.
|
||||
/// </summary>
|
||||
protected void ReadTransformProperties(Reader reader, out Vector3? localPosition, out Quaternion? localRotation, out Vector3? localScale)
|
||||
{
|
||||
// Read changed.
|
||||
TransformPropertiesFlag tpf = (TransformPropertiesFlag)reader.ReadUInt8Unpacked();
|
||||
// Position.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Position))
|
||||
localPosition = reader.ReadVector3();
|
||||
else
|
||||
localPosition = null;
|
||||
// Rotation.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Rotation))
|
||||
localRotation = reader.ReadQuaternion(NetworkManager.ServerManager.SpawnPacking.Rotation);
|
||||
else
|
||||
localRotation = null;
|
||||
// Scale.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Scale))
|
||||
localScale = reader.ReadVector3();
|
||||
else
|
||||
localScale = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a spawn to a client or server.
|
||||
/// If connection is not null the spawn is sent ot a client, otherwise it will be considered a predicted spawn.
|
||||
/// </summary>
|
||||
/// <returns>True if spawn was written.</returns>
|
||||
internal bool WriteSpawn(NetworkObject nob, PooledWriter writer, NetworkConnection connection)
|
||||
{
|
||||
writer.WritePacketIdUnpacked(PacketId.ObjectSpawn);
|
||||
|
||||
ReservedLengthWriter asClientReservedWriter = ReservedWritersExtensions.Retrieve();
|
||||
bool predictedSpawn = connection == null;
|
||||
|
||||
if (predictedSpawn)
|
||||
asClientReservedWriter.Initialize(writer, PREDICTED_SPAWN_BYTES);
|
||||
|
||||
bool sceneObject = nob.IsSceneObject;
|
||||
// Write type of spawn.
|
||||
SpawnType st = SpawnType.Unset;
|
||||
if (sceneObject)
|
||||
st |= SpawnType.Scene;
|
||||
else
|
||||
st |= nob.IsGlobal ? SpawnType.InstantiatedGlobal : SpawnType.Instantiated;
|
||||
|
||||
if (connection == nob.PredictedSpawner)
|
||||
st |= SpawnType.IsPredictedSpawner;
|
||||
|
||||
// Call before writing SpawnType so nested can be appended to it if needed.
|
||||
PooledWriter nestedWriter = WriteNestedSpawn(nob, ref st);
|
||||
|
||||
writer.WriteUInt8Unpacked((byte)st);
|
||||
// Write parent here if writer for parent is valid.
|
||||
if (nestedWriter != null)
|
||||
{
|
||||
writer.WriteArraySegment(nestedWriter.GetArraySegment());
|
||||
WriterPool.Store(nestedWriter);
|
||||
}
|
||||
|
||||
writer.WriteSpawnedNetworkObject(nob);
|
||||
writer.WriteNetworkConnection(nob.Owner);
|
||||
|
||||
// Properties on the transform which diff from serialized value.
|
||||
WriteChangedTransformProperties(nob, sceneObject, writer);
|
||||
|
||||
/* Writing a scene object. */
|
||||
if (sceneObject)
|
||||
{
|
||||
writer.WriteUInt64Unpacked(nob.SceneId);
|
||||
#if DEVELOPMENT
|
||||
CheckWriteSceneObjectDetails(nob, writer);
|
||||
#endif
|
||||
}
|
||||
/* Writing a spawned object. */
|
||||
else
|
||||
{
|
||||
writer.WriteNetworkObjectId(nob.PrefabId);
|
||||
}
|
||||
|
||||
NetworkConnection payloadSender = predictedSpawn ? NetworkManager.EmptyConnection : connection;
|
||||
WritePayload(payloadSender, nob, writer);
|
||||
|
||||
/* RPCLinks and SyncTypes are ONLY written by the server.
|
||||
* Although not necessary, both sides will write the length
|
||||
* to keep the reading of spawns consistent. */
|
||||
WriteRpcLinks(nob, writer);
|
||||
WriteSyncTypesForSpawn(nob, writer, connection);
|
||||
|
||||
bool canWrite;
|
||||
// Need to validate predicted spawn length.
|
||||
if (predictedSpawn)
|
||||
{
|
||||
int maxContentLength;
|
||||
if (PREDICTED_SPAWN_BYTES == 2)
|
||||
{
|
||||
maxContentLength = ushort.MaxValue;
|
||||
}
|
||||
else
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
{
|
||||
NetworkManager.LogError($"Unhandled spawn bytes value of {PREDICTED_SPAWN_BYTES}.");
|
||||
maxContentLength = 0;
|
||||
}
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
|
||||
// Too much content; this really should absolutely never happen.
|
||||
canWrite = asClientReservedWriter.Length <= maxContentLength;
|
||||
if (!canWrite)
|
||||
NetworkManager.LogError($"A single predicted spawns may not exceed {maxContentLength} bytes in length. Written length is {asClientReservedWriter.Length}. Predicted spawn for {nob.name} will be despawned immediately.");
|
||||
// Not too large.
|
||||
else
|
||||
asClientReservedWriter.WriteLength();
|
||||
}
|
||||
|
||||
// Not predicted, server can always write.
|
||||
else
|
||||
{
|
||||
canWrite = true;
|
||||
}
|
||||
|
||||
asClientReservedWriter.Store();
|
||||
return canWrite;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes RPCLinks for a NetworkObject.
|
||||
/// </summary>
|
||||
protected void WriteRpcLinks(NetworkObject nob, PooledWriter writer)
|
||||
{
|
||||
ReservedLengthWriter rw = ReservedWritersExtensions.Retrieve();
|
||||
|
||||
rw.Initialize(writer, NetworkBehaviour.RPCLINK_RESERVED_BYTES);
|
||||
|
||||
if (NetworkManager.IsServerStarted)
|
||||
{
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteRpcLinks(writer);
|
||||
}
|
||||
|
||||
rw.WriteLength();
|
||||
|
||||
rw.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads RpcLinks from a spawn into an arraySegment.
|
||||
/// </summary>
|
||||
protected ArraySegment<byte> ReadRpcLinks(PooledReader reader)
|
||||
{
|
||||
uint segmentSize = ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.RPCLINK_RESERVED_BYTES);
|
||||
return reader.ReadArraySegment((int)segmentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes SyncTypes for a NetworkObject.
|
||||
/// </summary>
|
||||
protected void WriteSyncTypesForSpawn(NetworkObject nob, PooledWriter writer, NetworkConnection connection)
|
||||
{
|
||||
ReservedLengthWriter rw = ReservedWritersExtensions.Retrieve();
|
||||
|
||||
// SyncTypes.
|
||||
rw.Initialize(writer, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES);
|
||||
|
||||
if (NetworkManager.IsServerStarted)
|
||||
{
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteSyncTypesForSpawn(writer, connection);
|
||||
}
|
||||
|
||||
rw.WriteLength();
|
||||
rw.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads SyncTypes from a spawn into an arraySegment.
|
||||
/// </summary>
|
||||
protected ArraySegment<byte> ReadSyncTypesForSpawn(PooledReader reader)
|
||||
{
|
||||
uint segmentSize = ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES);
|
||||
return reader.ReadArraySegment((int)segmentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writers a nested spawn and returns writer used.
|
||||
/// If nested was not written null is returned.
|
||||
/// </summary>
|
||||
internal PooledWriter WriteNestedSpawn(NetworkObject nob, ref SpawnType st)
|
||||
{
|
||||
// Check to write parent behaviour or nob.
|
||||
NetworkBehaviour parentNb;
|
||||
Transform t = nob.transform.parent;
|
||||
if (t != null)
|
||||
{
|
||||
parentNb = nob.CurrentParentNetworkBehaviour;
|
||||
/* Check for a NetworkObject if there is no NetworkBehaviour.
|
||||
* There is a small chance the parent object will only contain
|
||||
* a NetworkObject. */
|
||||
if (parentNb == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// No parent.
|
||||
else
|
||||
{
|
||||
if (!parentNb.IsSpawned)
|
||||
{
|
||||
NetworkManager.LogWarning($"Parent {t.name} is not spawned. {nob.name} will not have it's parent sent in the spawn message.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
st |= SpawnType.Nested;
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.WriteUInt8Unpacked(nob.ComponentIndex);
|
||||
writer.WriteNetworkBehaviour(parentNb);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
}
|
||||
// CurrentNetworkBehaviour is not set.
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If flags indicate there is a nested spawn the objectId and NetworkBehaviourId are output.
|
||||
/// Otherwise, output value sare set to null.
|
||||
/// </summary>
|
||||
internal void ReadNestedSpawnIds(PooledReader reader, SpawnType st, out byte? nobComponentIndex, out int? parentObjectId, out byte? parentComponentIndex, HashSet<int> readSpawningObjects = null)
|
||||
{
|
||||
if (st.FastContains(SpawnType.Nested))
|
||||
{
|
||||
nobComponentIndex = reader.ReadUInt8Unpacked();
|
||||
reader.ReadNetworkBehaviour(out int objectId, out byte componentIndex, readSpawningObjects);
|
||||
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
{
|
||||
parentObjectId = objectId;
|
||||
parentComponentIndex = componentIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through, not nested.
|
||||
nobComponentIndex = null;
|
||||
parentObjectId = null;
|
||||
parentComponentIndex = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes reading a scene object.
|
||||
/// </summary>
|
||||
protected void ReadSceneObjectId(PooledReader reader, out ulong sceneId)
|
||||
{
|
||||
sceneId = reader.ReadUInt64Unpacked();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes changed transform proeprties to writer.
|
||||
/// </summary>
|
||||
protected void WriteChangedTransformProperties(NetworkObject nob, bool sceneObject, Writer headerWriter)
|
||||
{
|
||||
/* Write changed transform properties. */
|
||||
TransformPropertiesFlag tpf;
|
||||
/* If a scene object or nested during initialization then
|
||||
* write changes compared to initialized values. */
|
||||
if (sceneObject || nob.InitializedParentNetworkBehaviour != null)
|
||||
{
|
||||
tpf = nob.GetTransformChanges(nob.SerializedTransformProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This should not be possible when spawning non-nested.
|
||||
if (nob.PrefabId == NetworkObject.UNSET_PREFABID_VALUE)
|
||||
{
|
||||
NetworkManager.LogWarning($"NetworkObject {nob.ToString()} unexpectedly has an unset PrefabId while it's not nested. Please report this warning.");
|
||||
tpf = TransformPropertiesFlag.Everything;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrefabObjects po = NetworkManager.GetPrefabObjects<PrefabObjects>(nob.SpawnableCollectionId, false);
|
||||
tpf = nob.GetTransformChanges(po.GetObject(asServer: true, nob.PrefabId).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
headerWriter.WriteUInt8Unpacked((byte)tpf);
|
||||
// If properties have changed.
|
||||
if (tpf != TransformPropertiesFlag.Unset)
|
||||
{
|
||||
// Write any changed properties.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Position))
|
||||
headerWriter.WriteVector3(nob.transform.localPosition);
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Rotation))
|
||||
headerWriter.WriteQuaternion(nob.transform.localRotation, NetworkManager.ServerManager.SpawnPacking.Rotation);
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Scale))
|
||||
headerWriter.WriteVector3(nob.transform.localScale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a despawn.
|
||||
/// </summary>
|
||||
protected void WriteDespawn(NetworkObject nob, DespawnType despawnType, Writer everyoneWriter)
|
||||
{
|
||||
everyoneWriter.WritePacketIdUnpacked(PacketId.ObjectDespawn);
|
||||
everyoneWriter.WriteNetworkObjectForDespawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a scene NetworkObject and sets transform values.
|
||||
/// </summary>
|
||||
internal NetworkObject GetSceneNetworkObject(ulong sceneId, string sceneName, string objectName)
|
||||
{
|
||||
NetworkObject nob;
|
||||
SceneObjects_Internal.TryGetValueIL2CPP(sceneId, out nob);
|
||||
// If found in scene objects.
|
||||
if (nob == null)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
string missingObjectDetails = sceneName == string.Empty ? "For more information on the missing object add DebugManager to your NetworkManager and enable WriteSceneObjectDetails" : $"Scene containing the object is '{sceneName}', object name is '{objectName}";
|
||||
NetworkManager.LogError($"SceneId of {sceneId} not found in SceneObjects. {missingObjectDetails}. This may occur if your scene differs between client and server, if client does not have the scene loaded, or if networked scene objects do not have a SceneCondition. See ObserverManager in the documentation for more on conditions.");
|
||||
#else
|
||||
NetworkManager.LogError($"SceneId of {sceneId} not found in SceneObjects. This may occur if your scene differs between client and server, if client does not have the scene loaded, or if networked scene objects do not have a SceneCondition. See ObserverManager in the documentation for more on conditions.");
|
||||
#endif
|
||||
}
|
||||
|
||||
return nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject meets basic criteria for being predicted spawned.
|
||||
/// </summary>
|
||||
/// <param name = "reader">If not null reader will be cleared on error.</param>
|
||||
/// <returns></returns>
|
||||
protected bool CanPredictedSpawn(NetworkObject nob, NetworkConnection spawner, bool asServer, Reader reader = null)
|
||||
{
|
||||
// Does not allow predicted spawning.
|
||||
if (!nob.AllowPredictedSpawning)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object {nob.name} which does not support predicted spawning.");
|
||||
else
|
||||
NetworkManager.LogError($"Object {nob.name} does not support predicted spawning. Add a PredictedSpawn component to the object and configure appropriately.");
|
||||
|
||||
if (reader != null)
|
||||
reader.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// // Parenting is not yet supported.
|
||||
// if (nob.CurrentParentNetworkBehaviour != null)
|
||||
// {
|
||||
// if (asServer)
|
||||
// spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object that is not root.");
|
||||
// else
|
||||
// NetworkManager.LogError($"Predicted spawning as a child is not supported.");
|
||||
//
|
||||
// if (reader != null)
|
||||
// reader.Clear();
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// Nested nobs not yet supported.
|
||||
if (nob.InitializedNestedNetworkObjects.Count > 0)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object {nob.name} which has nested NetworkObjects.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted spawning prefabs which contain nested NetworkObjects is not yet supported but will be in a later release.");
|
||||
|
||||
if (reader != null)
|
||||
reader.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject meets basic criteria for being predicted despawned.
|
||||
/// </summary>
|
||||
/// <param name = "reader">If not null reader will be cleared on error.</param>
|
||||
/// <returns></returns>
|
||||
protected bool CanPredictedDespawn(NetworkObject nob, NetworkConnection despawner, bool asServer, Reader reader = null)
|
||||
{
|
||||
// Does not allow predicted spawning.
|
||||
if (!nob.AllowPredictedDespawning)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object {nob.name} which does not support predicted despawning.");
|
||||
else
|
||||
NetworkManager.LogError($"Object {nob.name} does not support predicted despawning. Modify the PredictedSpawn component settings to allow predicted despawning.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
////Parenting is not yet supported.
|
||||
// if (nob.transform.parent != null)
|
||||
// {
|
||||
// if (asServer)
|
||||
// despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object that is not root.");
|
||||
// else
|
||||
// NetworkManager.LogError($"Predicted despawning as a child is not supported.");
|
||||
|
||||
// reader?.Clear();
|
||||
// return false;
|
||||
// }
|
||||
// Nested nobs not yet supported.
|
||||
if (nob.InitializedNestedNetworkObjects.Count > 0)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object {nob.name} which has nested NetworkObjects.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted despawning prefabs which contain nested NetworkObjects is not yet supported but will be in a later release.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Blocked by PredictedSpawn settings or user logic.
|
||||
if ((asServer && !nob.PredictedSpawn.OnTryDespawnServer(despawner)) || (!asServer && !nob.PredictedSpawn.OnTryDespawnClient()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a payload for a NetworkObject.
|
||||
/// </summary>
|
||||
internal void ReadPayload(NetworkConnection sender, NetworkObject nob, PooledReader reader, int? payloadLength = null)
|
||||
{
|
||||
if (!payloadLength.HasValue)
|
||||
payloadLength = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.PAYLOAD_RESERVE_BYTES);
|
||||
// If there is a payload.
|
||||
if (payloadLength > 0)
|
||||
{
|
||||
if (nob != null)
|
||||
{
|
||||
foreach (NetworkBehaviour networkBehaviour in nob.NetworkBehaviours)
|
||||
networkBehaviour.ReadPayload(sender, reader);
|
||||
}
|
||||
// NetworkObject could be null if payload is for a predicted spawn.
|
||||
else
|
||||
{
|
||||
reader.Skip((int)payloadLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the payload returning it as an arraySegment.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal ArraySegment<byte> ReadPayload(PooledReader reader)
|
||||
{
|
||||
int payloadLength = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.PAYLOAD_RESERVE_BYTES);
|
||||
return reader.ReadArraySegment(payloadLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// /Writers a payload for a NetworkObject.
|
||||
/// </summary>
|
||||
protected void WritePayload(NetworkConnection sender, NetworkObject nob, PooledWriter writer)
|
||||
{
|
||||
ReservedLengthWriter rw = ReservedWritersExtensions.Retrieve();
|
||||
|
||||
rw.Initialize(writer, NetworkBehaviour.PAYLOAD_RESERVE_BYTES);
|
||||
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WritePayload(sender, writer);
|
||||
|
||||
rw.WriteLength();
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Writes a payload for a NetworkObject.
|
||||
// /// </summary>
|
||||
// protected ArraySegment<byte> ReadPayload(PooledReader reader)
|
||||
// {
|
||||
// PooledWriter nbWriter = WriterPool.Retrieve();
|
||||
// foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
// {
|
||||
// nbWriter.Reset();
|
||||
// nb.WritePayload(conn, nbWriter);
|
||||
// if (nbWriter.Length > 0)
|
||||
// {
|
||||
// writer.WriteUInt8Unpacked(nb.ComponentIndex);
|
||||
// writer.WriteArraySegment(nbWriter.GetArraySegment());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fcb759226359ad48926ff17cbf0ec6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/ManagedObjects.Spawning.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,573 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using FishNet.Managing.Statistic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public abstract partial class ManagedObjects
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// NetworkObjects which are currently active.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<int, NetworkObject> Spawned => _spawned;
|
||||
private Dictionary<int, NetworkObject> _spawned = new();
|
||||
/// <summary>
|
||||
/// Invoked when an object is added to Spawned.
|
||||
/// </summary>
|
||||
public event OnSpawnedChanged OnSpawnedAdd;
|
||||
/// <summary>
|
||||
/// Invoked when an object is removed from Spawned.
|
||||
/// </summary>
|
||||
public event OnSpawnedChanged OnSpawnedRemove;
|
||||
/// <summary>
|
||||
/// Invoked when Spawned is cleared.
|
||||
/// </summary>
|
||||
public event Action OnSpawnedClear;
|
||||
/// <summary>
|
||||
/// Delegate for when there is change to Spawned.
|
||||
/// </summary>
|
||||
public delegate void OnSpawnedChanged(int objectId, NetworkObject networkObject);
|
||||
#endregion
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_ParseReplicateRpc =
|
||||
new("ManagedObjects.ParseReplicateRpc(PooledReader, NetworkConnection, Channel)");
|
||||
#endregion
|
||||
|
||||
#region Protected.
|
||||
/// <summary>
|
||||
/// Returns the next ObjectId to use.
|
||||
/// </summary>
|
||||
protected internal virtual bool GetNextNetworkObjectId(out int nextNetworkObjectId)
|
||||
{
|
||||
nextNetworkObjectId = NetworkObject.UNSET_OBJECTID_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetworkManager handling this.
|
||||
/// </summary>
|
||||
protected NetworkManager NetworkManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
||||
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
||||
/// </summary>
|
||||
protected Dictionary<ulong, NetworkObject> SceneObjects_Internal = new();
|
||||
/// <summary>
|
||||
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
||||
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ulong, NetworkObject> SceneObjects => SceneObjects_Internal;
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[NonSerialized] protected NetworkTrafficStatistics NetworkTrafficStatistics;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called to add an object to Spawned.
|
||||
/// </summary>
|
||||
protected void AddtoSpawnedCollectionAndInvoke(NetworkObject nob)
|
||||
{
|
||||
_spawned[nob.ObjectId] = nob;
|
||||
OnSpawnedAdd?.Invoke(nob.ObjectId, nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to remove an object from Spawned.
|
||||
/// </summary>
|
||||
protected void RemoveFromSpawnedCollectionAndInvoke(NetworkObject nob)
|
||||
{
|
||||
if (_spawned.Remove(nob.ObjectId))
|
||||
OnSpawnedRemove?.Invoke(nob.ObjectId, nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to clear Spawned.
|
||||
/// </summary>
|
||||
protected void ClearSpawnedCollectionAndInvoke()
|
||||
{
|
||||
_spawned.Clear();
|
||||
OnSpawnedClear?.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Cached HashGrid. Will be null if not used.
|
||||
/// </summary>
|
||||
private HashGrid _hashGrid;
|
||||
#endregion
|
||||
|
||||
protected virtual void Initialize(NetworkManager manager)
|
||||
{
|
||||
NetworkManager = manager;
|
||||
manager.StatisticsManager.TryGetNetworkTrafficStatistics(out NetworkTrafficStatistics);
|
||||
|
||||
manager.TryGetInstance(out _hashGrid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to SceneManager.SceneLoaded event.
|
||||
/// </summary>
|
||||
/// <param name = "subscribe"></param>
|
||||
internal void SubscribeToSceneLoaded(bool subscribe)
|
||||
{
|
||||
if (subscribe)
|
||||
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
|
||||
else
|
||||
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a scene is loaded.
|
||||
/// </summary>
|
||||
/// <param name = "s"></param>
|
||||
/// <param name = "arg1"></param>
|
||||
protected internal virtual void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when a NetworkObject runs Deactivate.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal virtual void NetworkObjectDestroyed(NetworkObject nob, bool asServer)
|
||||
{
|
||||
if (nob == null)
|
||||
return;
|
||||
|
||||
RemoveFromSpawned(nob, fromOnDestroy: true, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkedObject from spawned.
|
||||
/// </summary>
|
||||
protected virtual void RemoveFromSpawned(NetworkObject nob, bool fromOnDestroy, bool asServer)
|
||||
{
|
||||
RemoveFromSpawnedCollectionAndInvoke(nob);
|
||||
|
||||
// Do the same with SceneObjects.
|
||||
if (fromOnDestroy && nob.IsSceneObject)
|
||||
RemoveFromSceneObjects(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a NetworkObject.
|
||||
/// </summary>
|
||||
internal virtual void Despawn(NetworkObject nob, DespawnType despawnType, bool asServer)
|
||||
{
|
||||
if (nob == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot despawn a null NetworkObject.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If not asServer and the object is not initialized on client
|
||||
* then it likely is already despawned. This bit of code should
|
||||
* never be reached as checks should be placed before-hand. */
|
||||
if (!asServer && !nob.IsClientInitialized)
|
||||
{
|
||||
NetworkManager.LogError($"Object {nob.ToString()} is already despawned. Please report this error.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// True if should be destroyed, false if deactivated.
|
||||
bool destroy = false;
|
||||
bool wasRemovedFromPending = false;
|
||||
|
||||
/* Only modify object state if asServer,
|
||||
* or !asServer and not host. This is so clients, when acting as
|
||||
* host, don't destroy objects they lost observation of. */
|
||||
|
||||
/* Nested prefabs can never be destroyed. Only check to
|
||||
* destroy if not nested. By nested prefab, this means the object
|
||||
* despawning is part of another prefab that is also a spawned
|
||||
* network object. */
|
||||
if (!nob.IsNested)
|
||||
{
|
||||
// If as server.
|
||||
if (asServer)
|
||||
{
|
||||
// Scene object.
|
||||
if (!nob.IsSceneObject)
|
||||
{
|
||||
/* If client-host has visibility
|
||||
* then disable and wait for client-host to get destroy
|
||||
* message. Otherwise destroy immediately. */
|
||||
if (nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
||||
NetworkManager.ServerManager.Objects.AddToPending(nob);
|
||||
else
|
||||
destroy = true;
|
||||
}
|
||||
}
|
||||
// Not as server.
|
||||
else
|
||||
{
|
||||
bool isServer = NetworkManager.IsServerStarted;
|
||||
// Only check to destroy if not a scene object.
|
||||
if (!nob.IsSceneObject)
|
||||
{
|
||||
/* If was removed from pending then also destroy.
|
||||
* Pending objects are ones that exist on the server
|
||||
* side only to await destruction from client side.
|
||||
* Objects can also be destroyed if server is not
|
||||
* active. */
|
||||
wasRemovedFromPending = NetworkManager.ServerManager.Objects.RemoveFromPending(nob);
|
||||
destroy = !isServer || wasRemovedFromPending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TryUnsetParent();
|
||||
|
||||
/* If this had a parent set at runtime then
|
||||
* unset parent before checks are completed.
|
||||
* If we did not do this then this nob would
|
||||
* just be disabled beneath its runtime parent,
|
||||
* when it should be pooled separately or destroyed. */
|
||||
void TryUnsetParent()
|
||||
{
|
||||
if (!asServer || wasRemovedFromPending)
|
||||
{
|
||||
if (nob.RuntimeParentNetworkBehaviour != null)
|
||||
{
|
||||
nob.UnsetParent();
|
||||
/* DespawnType also has to be updated to use default
|
||||
* for the networkObject since this despawn is happening
|
||||
* automatically. */
|
||||
despawnType = nob.GetDefaultDespawnType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nob.SetIsDestroying(despawnType);
|
||||
// Deinitialize to invoke callbacks.
|
||||
nob.Deinitialize(asServer);
|
||||
// Remove from match condition only if server.
|
||||
if (asServer)
|
||||
MatchCondition.RemoveFromMatchWithoutRebuild(nob, NetworkManager);
|
||||
RemoveFromSpawned(nob, false, asServer);
|
||||
|
||||
// If to destroy.
|
||||
if (destroy)
|
||||
{
|
||||
if (despawnType == DespawnType.Destroy)
|
||||
UnityEngine.Object.Destroy(nob.gameObject);
|
||||
else
|
||||
NetworkManager.StorePooledInstantiated(nob, asServer);
|
||||
}
|
||||
/* If to potentially disable instead of destroy.
|
||||
* This is such as something is despawning server side
|
||||
* but a clientHost is present, or if a scene object. */
|
||||
else
|
||||
{
|
||||
// If as server.
|
||||
if (asServer)
|
||||
{
|
||||
/* If not clientHost the object can be disabled.
|
||||
*
|
||||
* Also, if clientHost and clientHost is not an observer, the object
|
||||
* can be disabled. */
|
||||
// If not clientHost then the object can be disabled.
|
||||
if (!NetworkManager.IsClientStarted || !nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
// Not as server.
|
||||
else
|
||||
{
|
||||
// If the server is not active then the object can be disabled.
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
{
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
// If also server then checks must be done.
|
||||
else
|
||||
{
|
||||
/* Object is still spawned on the server side. This means
|
||||
* the clientHost likely lost visibility. When this is the case
|
||||
* update clientHost renderers. */
|
||||
if (NetworkManager.ServerManager.Objects.Spawned.ContainsKey(nob.ObjectId))
|
||||
nob.SetRenderersVisible(false);
|
||||
/* No longer spawned on the server, can
|
||||
* deactivate on the client. */
|
||||
else
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Also despawn child objects.
|
||||
* This only must be done when not destroying
|
||||
* as destroying would result in the despawn being
|
||||
* forced.
|
||||
*
|
||||
* Only run if asServer as well. The server will send
|
||||
* individual despawns for each child. */
|
||||
if (asServer)
|
||||
{
|
||||
List<NetworkObject> childNobs = nob.GetNetworkObjects(GetNetworkObjectOption.AllNested);
|
||||
foreach (NetworkObject childNob in childNobs)
|
||||
{
|
||||
if (childNob != null && !childNob.IsDeinitializing)
|
||||
Despawn(childNob, despawnType, asServer: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a prefab, not to be mistaken for initializing a spawned object.
|
||||
/// </summary>
|
||||
/// <param name = "prefab">Prefab to initialize.</param>
|
||||
/// <param name = "index">Index within spawnable prefabs.</param>
|
||||
public static void InitializePrefab(NetworkObject prefab, int index, ushort? collectionId = null)
|
||||
{
|
||||
const int invalidIndex = -1;
|
||||
if (index == invalidIndex)
|
||||
{
|
||||
Debug.LogError($"An index of {invalidIndex} cannot be assigned as a PrefabId for {prefab.name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefab == null)
|
||||
return;
|
||||
|
||||
prefab.PrefabId = (ushort)index;
|
||||
if (collectionId != null)
|
||||
prefab.SpawnableCollectionId = collectionId.Value;
|
||||
|
||||
prefab.SetInitializedValues(null, ignoreSerializedTimestamp: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns Spawned NetworkObjects. Scene objects will be disabled, others will be destroyed.
|
||||
/// </summary>
|
||||
internal virtual void DespawnWithoutSynchronization(bool recursive, bool asServer)
|
||||
{
|
||||
foreach (NetworkObject nob in Spawned.Values)
|
||||
{
|
||||
if (nob == null)
|
||||
continue;
|
||||
|
||||
DespawnWithoutSynchronization(nob, recursive, asServer, nob.GetDefaultDespawnType(), removeFromSpawned: false);
|
||||
}
|
||||
|
||||
ClearSpawnedCollectionAndInvoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a network object.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
protected virtual void DespawnWithoutSynchronization(NetworkObject nob, bool recursive, bool asServer, DespawnType despawnType, bool removeFromSpawned)
|
||||
{
|
||||
#if FISHNET_STABLE_RECURSIVE_DESPAWNS
|
||||
recursive = false;
|
||||
#endif
|
||||
|
||||
GetNetworkObjectOption getOption = recursive ? GetNetworkObjectOption.All : GetNetworkObjectOption.Self;
|
||||
List<NetworkObject> allNobs = nob.GetNetworkObjects(getOption);
|
||||
|
||||
// True if can deactivate or destroy.
|
||||
bool canCleanup = asServer || !NetworkManager.IsServerStarted;
|
||||
|
||||
foreach (NetworkObject lNob in allNobs)
|
||||
{
|
||||
lNob.SetIsDestroying(despawnType);
|
||||
lNob.Deinitialize(asServer);
|
||||
|
||||
if (canCleanup && removeFromSpawned)
|
||||
RemoveFromSpawned(lNob, fromOnDestroy: false, asServer);
|
||||
}
|
||||
|
||||
/* Only need to check the first nob. If it's stored, deactivated,
|
||||
* or destroyed, the rest will follow. */
|
||||
if (canCleanup)
|
||||
{
|
||||
NetworkObject firstNob = allNobs[0];
|
||||
|
||||
if (firstNob.IsSceneObject || firstNob.IsInitializedNested)
|
||||
{
|
||||
firstNob.gameObject.SetActive(value: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (despawnType == DespawnType.Destroy)
|
||||
UnityEngine.Object.Destroy(firstNob.gameObject);
|
||||
else
|
||||
NetworkManager.StorePooledInstantiated(firstNob, asServer);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(allNobs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkObject to Spawned.
|
||||
/// </summary>
|
||||
internal virtual void AddToSpawned(NetworkObject nob, bool asServer)
|
||||
{
|
||||
AddtoSpawnedCollectionAndInvoke(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkObject to SceneObjects.
|
||||
/// </summary>
|
||||
protected internal void AddToSceneObjects(NetworkObject nob)
|
||||
{
|
||||
SceneObjects_Internal[nob.SceneId] = nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkObject from SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
protected internal void RemoveFromSceneObjects(NetworkObject nob)
|
||||
{
|
||||
SceneObjects_Internal.Remove(nob.SceneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkObject from SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
protected internal void RemoveFromSceneObjects(ulong sceneId)
|
||||
{
|
||||
SceneObjects_Internal.Remove(sceneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a NetworkObject within Spawned.
|
||||
/// </summary>
|
||||
/// <param name = "objectId"></param>
|
||||
/// <returns></returns>
|
||||
protected internal NetworkObject GetSpawnedNetworkObject(int objectId)
|
||||
{
|
||||
NetworkObject r;
|
||||
if (!_spawned.TryGetValueIL2CPP(objectId, out r))
|
||||
NetworkManager.LogError($"Spawned NetworkObject not found for ObjectId {objectId}.");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to skip data length for a packet.
|
||||
/// </summary>
|
||||
/// <param name = "packetId"></param>
|
||||
/// <param name = "reader"></param>
|
||||
/// <param name = "dataLength"></param>
|
||||
protected internal void SkipDataLength(ushort packetId, PooledReader reader, int dataLength, int rpcLinkObjectId = -1)
|
||||
{
|
||||
/* -1 means length wasn't set, which would suggest a reliable packet.
|
||||
* Object should never be missing for reliable packets since spawns
|
||||
* and despawns are reliable in order. */
|
||||
if (dataLength == (int)MissingObjectPacketLength.Reliable)
|
||||
{
|
||||
string msg;
|
||||
bool isRpcLink = packetId >= NetworkManager.StartingRpcLinkIndex;
|
||||
if (isRpcLink)
|
||||
{
|
||||
msg = rpcLinkObjectId == -1 ? $"RPCLink of Id {(PacketId)packetId} could not be found. Remaining data will be purged." : $"ObjectId {rpcLinkObjectId} for RPCLink {(PacketId)packetId} could not be found.";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"NetworkBehaviour could not be found for packetId {(PacketId)packetId}. Remaining data will be purged.";
|
||||
}
|
||||
|
||||
/* Default logging for server is errors only. Use error on client and warning
|
||||
* on servers to reduce chances of allocation attacks. */
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR || !UNITY_SERVER
|
||||
NetworkManager.LogError(msg);
|
||||
#else
|
||||
NetworkManager.LogWarning(msg);
|
||||
#endif
|
||||
reader.Clear();
|
||||
}
|
||||
/* If length is known then is unreliable packet. It's possible
|
||||
* this packetId arrived before or after the object was spawned/destroyed.
|
||||
* Skip past the data for this packet and use rest in reader. With non-linked
|
||||
* RPCs length is sent before object information. */
|
||||
else if (dataLength >= 0)
|
||||
{
|
||||
reader.Skip(Math.Min(dataLength, reader.Remaining));
|
||||
}
|
||||
/* -2 indicates the length is very long. Don't even try saving
|
||||
* the packet, user shouldn't be sending this much data over unreliable. */
|
||||
else if (dataLength == (int)MissingObjectPacketLength.PurgeRemaiming)
|
||||
{
|
||||
reader.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ReplicateRpc.
|
||||
/// </summary>
|
||||
internal void ParseReplicateRpc(PooledReader reader, NetworkConnection conn, Channel channel)
|
||||
{
|
||||
using (_pm_ParseReplicateRpc.Auto())
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining,
|
||||
out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
|
||||
if (nb != null && nb.IsSpawned)
|
||||
nb.OnReplicateRpc(readerStartAfterDebug, hash: null, reader, conn, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
|
||||
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader,
|
||||
startReaderRemaining, rpcInformation, expectedReadAmount, channel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
/// <summary>
|
||||
/// Checks to write a scene object's details into a writer.
|
||||
/// </summary>
|
||||
protected void CheckWriteSceneObjectDetails(NetworkObject nob, Writer w)
|
||||
{
|
||||
// Check to write additional information if a scene object.
|
||||
if (NetworkManager.DebugManager.WriteSceneObjectDetails)
|
||||
{
|
||||
w.WriteString(nob.gameObject.scene.name);
|
||||
w.WriteString(nob.gameObject.name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to read a scene object's details and populates values if read was successful.
|
||||
/// </summary>
|
||||
protected void CheckReadSceneObjectDetails(Reader r, ref string sceneName, ref string objectName)
|
||||
{
|
||||
if (NetworkManager.DebugManager.WriteSceneObjectDetails)
|
||||
{
|
||||
sceneName = r.ReadStringAllocated();
|
||||
objectName = r.ReadStringAllocated();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1363007244792145846afddc31ac12c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,42 @@
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
[System.Flags]
|
||||
internal enum SpawnType : byte
|
||||
{
|
||||
Unset = 0,
|
||||
/// <summary>
|
||||
/// Is nested beneath a NetworkBehaviour.
|
||||
/// </summary>
|
||||
Nested = 1,
|
||||
/// <summary>
|
||||
/// Is a scene object.
|
||||
/// </summary>
|
||||
Scene = 2,
|
||||
/// <summary>
|
||||
/// Instantiate into active scene.
|
||||
/// </summary>
|
||||
Instantiated = 4,
|
||||
/// <summary>
|
||||
/// Instantiate into the global scene.
|
||||
/// </summary>
|
||||
InstantiatedGlobal = 8,
|
||||
/// <summary>
|
||||
/// Indicates the receiver is the predicted spawner.
|
||||
/// </summary>
|
||||
IsPredictedSpawner = 16
|
||||
}
|
||||
|
||||
[APIExclude]
|
||||
internal static class SpawnTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if whole contains part.
|
||||
/// </summary>
|
||||
public static bool FastContains(this SpawnType whole, SpawnType part)
|
||||
{
|
||||
return (whole & part) == part;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed15edf5a1a100d45b05f6adace574cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/ObjectSpawnType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f3c9013358962e4786b12d363c7a3fc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
using FishNet.Documenting;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Text;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using FishNet.Object;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
[APIExclude]
|
||||
// [CreateAssetMenu(fileName = "New DefaultPrefabObjects", menuName = "FishNet/Spawnable Prefabs/Default Prefab Objects")]
|
||||
public class DefaultPrefabObjects : SinglePrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for version rebuilding.
|
||||
/// </summary>
|
||||
private StringBuilder _stringBuilder = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets asset path hashes for prefabs starting at index, or if missing.
|
||||
/// </summary
|
||||
/// <return>Returns true if one or more NetworkObjects were updated.</return>
|
||||
internal bool SetAssetPathHashes(int index)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
bool dirtied = false;
|
||||
int count = base.GetObjectCount();
|
||||
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
Debug.LogError($"Index {index} is out of range when trying to set asset path hashes. Collection length is {count}. Defaulf prefabs may need to be rebuilt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkObject n = Prefabs[i];
|
||||
if (i < index)
|
||||
continue;
|
||||
|
||||
string pathAndName = $"{AssetDatabase.GetAssetPath(n.gameObject)}{n.gameObject.name}".Trim().ToLowerInvariant();
|
||||
|
||||
_stringBuilder.Clear();
|
||||
foreach (char c in pathAndName)
|
||||
{
|
||||
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
|
||||
_stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
ulong hashcode = _stringBuilder.ToString().GetStableHashU64();
|
||||
// Already set.
|
||||
if (n.AssetPathHash == hashcode)
|
||||
continue;
|
||||
|
||||
n.SetAssetPathHash(hashcode);
|
||||
EditorUtility.SetDirty(n);
|
||||
dirtied = true;
|
||||
}
|
||||
|
||||
//Check for conflicts.
|
||||
Dictionary<ulong, string> hashesAndPaths = new();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkObject n = Prefabs[i];
|
||||
|
||||
string pathAndName = $"{AssetDatabase.GetAssetPath(n.gameObject)}{n.gameObject.name}";
|
||||
|
||||
if (hashesAndPaths.TryGetValueIL2CPP(n.AssetPathHash, out string path))
|
||||
{
|
||||
Debug.LogError($"Assets {pathAndName} and {path} have the same assetPath hash of {n.AssetPathHash}. Please modify the prefab name of either to resolve.");
|
||||
dirtied = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
hashesAndPaths.Add(n.AssetPathHash, pathAndName);
|
||||
}
|
||||
}
|
||||
|
||||
return dirtied;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts prefabs by name and path hashcode.
|
||||
/// </summary>
|
||||
internal void Sort()
|
||||
{
|
||||
if (base.GetObjectCount() == 0)
|
||||
return;
|
||||
|
||||
Dictionary<ulong, NetworkObject> hashcodesAndNobs = new();
|
||||
List<ulong> hashcodes = new();
|
||||
|
||||
bool error = false;
|
||||
foreach (NetworkObject n in Prefabs)
|
||||
{
|
||||
hashcodes.Add(n.AssetPathHash);
|
||||
// If hashcode is 0 something is wrong
|
||||
if (n.AssetPathHash == 0)
|
||||
{
|
||||
error = true;
|
||||
Debug.LogError($"AssetPathHash is not set for GameObject {n.name}.");
|
||||
}
|
||||
hashcodesAndNobs.Add(n.AssetPathHash, n);
|
||||
}
|
||||
// An error occured, no reason to continue.
|
||||
if (error)
|
||||
{
|
||||
Debug.LogError($"One or more NetworkObject prefabs did not have their AssetPathHash set. This usually occurs when a prefab cannot be saved. Check the specified prefabs for missing scripts or serialization errors and correct them, then use Fish-Networking -> Refresh Default Prefabs.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Once all hashes have been made re-add them to prefabs sorted.
|
||||
hashcodes.Sort();
|
||||
// Build to a new list using sorted hashcodes.
|
||||
List<NetworkObject> sortedNobs = new();
|
||||
foreach (ulong hc in hashcodes)
|
||||
sortedNobs.Add(hashcodesAndNobs[hc]);
|
||||
|
||||
base.Clear();
|
||||
base.AddObjects(sortedNobs, checkForDuplicates: false, initializeAdded: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ad70174b079c2f4ebc7931d3dd1af6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/DefaultPrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,130 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
// document
|
||||
[APIExclude]
|
||||
[CreateAssetMenu(fileName = "New DualPrefabObjects", menuName = "FishNet/Spawnable Prefabs/Dual Prefab Objects")]
|
||||
public class DualPrefabObjects : PrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Prefabs which may be spawned.")]
|
||||
[SerializeField]
|
||||
private List<DualPrefab> _prefabs = new();
|
||||
/// <summary>
|
||||
/// Prefabs which may be spawned.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DualPrefab> Prefabs => _prefabs;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_prefabs.Clear();
|
||||
}
|
||||
|
||||
public override int GetObjectCount()
|
||||
{
|
||||
return _prefabs.Count;
|
||||
}
|
||||
|
||||
public override NetworkObject GetObject(bool asServer, int id)
|
||||
{
|
||||
if (id < 0 || id >= _prefabs.Count)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"PrefabId {id} is out of range.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
DualPrefab dp = _prefabs[id];
|
||||
NetworkObject nob = asServer ? dp.Server : dp.Client;
|
||||
if (nob == null)
|
||||
{
|
||||
string lookupSide = asServer ? "server" : "client";
|
||||
NetworkManagerExtensions.LogError($"Prefab for {lookupSide} on id {id} is null ");
|
||||
}
|
||||
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveNull()
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i].Server == null || _prefabs[i].Client == null)
|
||||
{
|
||||
_prefabs.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
AddObjects(new DualPrefab[] { dualPrefab }, checkForDuplicates, initializeAdded);
|
||||
}
|
||||
|
||||
public override void AddObjects(List<DualPrefab> dualPrefabs, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
AddObjects(dualPrefabs.ToArray(), checkForDuplicates, initializeAdded);
|
||||
}
|
||||
|
||||
public override void AddObjects(DualPrefab[] dualPrefabs, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
{
|
||||
_prefabs.AddRange(dualPrefabs);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (DualPrefab dp in dualPrefabs)
|
||||
AddUniqueNetworkObjects(dp);
|
||||
}
|
||||
|
||||
if (initializeAdded && Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
private void AddUniqueNetworkObjects(DualPrefab dp)
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i].Server == dp.Server && _prefabs[i].Client == dp.Client)
|
||||
return;
|
||||
}
|
||||
|
||||
_prefabs.Add(dp);
|
||||
}
|
||||
|
||||
public override void InitializePrefabRange(int startIndex)
|
||||
{
|
||||
for (int i = startIndex; i < _prefabs.Count; i++)
|
||||
{
|
||||
ManagedObjects.InitializePrefab(_prefabs[i].Server, i, CollectionId);
|
||||
ManagedObjects.InitializePrefab(_prefabs[i].Client, i, CollectionId);
|
||||
}
|
||||
}
|
||||
|
||||
#region Unused.
|
||||
public override void AddObject(NetworkObject networkObject, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4b890523e001c74a9a2bf0d6340e5f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/DualPrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,34 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
// document
|
||||
[APIExclude]
|
||||
public abstract class PrefabObjects : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// CollectionId for this PrefabObjects.
|
||||
/// </summary>
|
||||
public ushort CollectionId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets CollectionIdValue.
|
||||
/// </summary>
|
||||
internal void SetCollectionId(ushort id) => CollectionId = id;
|
||||
|
||||
public abstract void Clear();
|
||||
public abstract int GetObjectCount();
|
||||
public abstract NetworkObject GetObject(bool asServer, int id);
|
||||
public abstract void RemoveNull();
|
||||
public abstract void AddObject(NetworkObject networkObject, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(List<DualPrefab> dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(DualPrefab[] dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void InitializePrefabRange(int startIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5a7beb0d6ee75a4fb1f058eb3e2640a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/PrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,125 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
// document
|
||||
[APIExclude]
|
||||
[CreateAssetMenu(fileName = "New SinglePrefabObjects", menuName = "FishNet/Spawnable Prefabs/Single Prefab Objects")]
|
||||
public class SinglePrefabObjects : PrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Prefabs which may be spawned.")]
|
||||
[SerializeField]
|
||||
private List<NetworkObject> _prefabs = new();
|
||||
/// <summary>
|
||||
/// Prefabs which may be spawned.
|
||||
/// </summary>
|
||||
public IReadOnlyList<NetworkObject> Prefabs => _prefabs;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_prefabs.Clear();
|
||||
}
|
||||
|
||||
public override int GetObjectCount()
|
||||
{
|
||||
return _prefabs.Count;
|
||||
}
|
||||
|
||||
public override NetworkObject GetObject(bool asServer, int id)
|
||||
{
|
||||
if (id < 0 || id >= _prefabs.Count)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"PrefabId {id} is out of range.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkObject nob = _prefabs[id];
|
||||
if (nob == null)
|
||||
NetworkManagerExtensions.LogError($"Prefab on id {id} is null.");
|
||||
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveNull()
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i] == null)
|
||||
{
|
||||
_prefabs.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddObject(NetworkObject networkObject, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
_prefabs.Add(networkObject);
|
||||
else
|
||||
AddUniqueNetworkObject(networkObject);
|
||||
|
||||
if (initializeAdded && Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
{
|
||||
_prefabs.AddRange(networkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject nob in networkObjects)
|
||||
AddUniqueNetworkObject(nob);
|
||||
}
|
||||
|
||||
if (initializeAdded && Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
AddObjects(networkObjects.ToList(), checkForDuplicates, initializeAdded);
|
||||
}
|
||||
|
||||
private void AddUniqueNetworkObject(NetworkObject nob)
|
||||
{
|
||||
if (!_prefabs.Contains(nob))
|
||||
_prefabs.Add(nob);
|
||||
}
|
||||
|
||||
public override void InitializePrefabRange(int startIndex)
|
||||
{
|
||||
for (int i = startIndex; i < _prefabs.Count; i++)
|
||||
ManagedObjects.InitializePrefab(_prefabs[i], i, CollectionId);
|
||||
}
|
||||
|
||||
#region Unused.
|
||||
public override void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(List<DualPrefab> dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(DualPrefab[] dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4489d77032a81ef42b0067acf2737d4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/SinglePrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,11 @@
|
||||
// namespace FishNet.Managing.Object // Remove in V5
|
||||
// {
|
||||
// public enum SpawnParentType : byte
|
||||
// {
|
||||
// Unset = 0,
|
||||
// NetworkObject = 1,
|
||||
// NetworkBehaviour = 2
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbace351ced9ff94eb294dbb2e1d6a75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/SpawnParentType.cs
|
||||
uploadId: 866910
|
||||
Reference in New Issue
Block a user