using UnityEngine; using System.Collections.Generic; using System; using FishNet.Object.Helping; using System.Linq; #if UNITY_EDITOR using UnityEditor.Experimental.SceneManagement; using UnityEditor.SceneManagement; using UnityEditor; #endif namespace FishNet.Object { public sealed partial class NetworkObject : MonoBehaviour { #region Serialized. /// /// Networked PrefabId assigned to this Prefab. /// [field: SerializeField, HideInInspector] public ushort PrefabId { get; internal set; } = 0; /// /// Spawn collection to use assigned to this Prefab. /// [field: SerializeField, HideInInspector] public ushort SpawnableCollectionId { get; internal set; } = 0; #pragma warning disable 414 //Disabled because Unity thinks tihs is unused when building. /// /// Hash to the scene which this object resides. /// [SerializeField, HideInInspector] private uint _scenePathHash; #pragma warning restore 414 /// /// Network Id for this scene object. /// [field: SerializeField, HideInInspector] internal ulong SceneId { get; private set; } /// /// Hash for the path which this asset resides. This value is set during edit time. /// [field: SerializeField, HideInInspector] public ulong AssetPathHash { get; private set; } /// /// Sets AssetPathhash value. /// /// Value to use. public void SetAssetPathHash(ulong value) => AssetPathHash = value; #endregion #if UNITY_EDITOR /// /// This is used to store NetworkObjects in the scene during edit time. /// SceneIds are compared against this collection to ensure there are no duplicated. /// [SerializeField, HideInInspector] private List _sceneNetworkObjects = new List(); #endif /// /// Removes SceneObject state. /// This may only be called at runtime. /// internal void ClearRuntimeSceneObject() { if (!Application.isPlaying) { Debug.LogError($"ClearRuntimeSceneObject may only be called at runtime."); return; } SceneId = 0; } #if UNITY_EDITOR /// /// Tries to generate a SceneId. /// internal void TryCreateSceneID() { if (Application.isPlaying) return; //Unity bug, sometimes this can be null depending on editor callback orders. if (gameObject == null) return; //Not a scene object. if (string.IsNullOrEmpty(gameObject.scene.name)) { SceneId = 0; return; } ulong startId = SceneId; uint startPath = _scenePathHash; ulong sceneId = 0; uint scenePathHash = 0; //If prefab or part of a prefab, not a scene object. if (PrefabUtility.IsPartOfPrefabAsset(this) || IsEditingInPrefabMode() || //Not in a scene, another prefab check. !gameObject.scene.IsValid() || //Stored on disk, so is a prefab. Somehow prefabutility missed it. EditorUtility.IsPersistent(this)) { //These are all failing conditions, don't do additional checks. } else { System.Random rnd = new System.Random(); scenePathHash = gameObject.scene.path.ToLower().GetStableHash32(); sceneId = SceneId; //Not a valid sceneId or is a duplicate. if (scenePathHash != _scenePathHash || SceneId == 0 || IsDuplicateSceneId(SceneId)) { /* If a scene has not been opened since an id has been * generated then it will not be serialized in editor. The id * would be correct in build but not if running in editor. * Should conditions be true where scene is building without * being opened then cancel build and request user to open and save * scene. */ if (BuildPipeline.isBuildingPlayer) throw new InvalidOperationException($"Networked GameObject {gameObject.name} in scene {gameObject.scene.path} is missing a SceneId. Open the scene, select the Fish-Networking menu, and choose Rebuild SceneIds. If the problem persist ensures {gameObject.name} does not have any missing script references on it's prefab or in the scene. Also ensure that you have any prefab changes for the object applied."); ulong shiftedHash = (ulong)scenePathHash << 32; ulong randomId = 0; while (randomId == 0 || IsDuplicateSceneId(randomId)) { uint next = (uint)(rnd.Next(int.MinValue, int.MaxValue) + int.MaxValue); /* Since the collection is lost when a scene loads the it's possible to * have a sceneid from another scene. Because of this the scene path is * inserted into the sceneid. */ randomId = (next & 0xFFFFFFFF) | shiftedHash; } sceneId = randomId; } } bool idChanged = (sceneId != startId); bool pathChanged = (startPath != scenePathHash); //If either changed then dirty and set. if (idChanged || pathChanged) { //Set dirty so changes will be saved. EditorUtility.SetDirty(this); /* Add to sceneIds collection. This must be done * even if a new sceneId was not generated because * the collection information is lost when the * scene is existed. Essentially, it gets repopulated * when the scene is re-opened. */ SceneId = sceneId; _scenePathHash = scenePathHash; } } private bool IsEditingInPrefabMode() { if (EditorUtility.IsPersistent(this)) { // if the game object is stored on disk, it is a prefab of some kind, despite not returning true for IsPartOfPrefabAsset =/ return true; } else { // If the GameObject is not persistent let's determine which stage we are in first because getting Prefab info depends on it StageHandle mainStage = StageUtility.GetMainStageHandle(); StageHandle currentStage = StageUtility.GetStageHandle(gameObject); if (currentStage != mainStage) { var prefabStage = PrefabStageUtility.GetPrefabStage(gameObject); if (prefabStage != null) { return true; } } } return false; } /// /// Returns if the Id used is a sceneId already belonging to another object. /// /// /// private bool IsDuplicateSceneId(ulong id) { //Find all nobs in scene. _sceneNetworkObjects = GameObject.FindObjectsOfType().ToList(); foreach (NetworkObject nob in _sceneNetworkObjects) { if (nob != null && nob != this && nob.SceneId == id) return true; } //If here all checks pass. return false; } private void ReferenceIds_OnValidate() { TryCreateSceneID(); } private void ReferenceIds_Reset() { TryCreateSceneID(); } #endif } }