/// <summary> /// Server-side only. Performs the spawn and syncs it to all clients. /// </summary> /// <returns></returns> private static SpawnResult Server(SpawnInfo info) { if (info == null) { Logger.LogError("Cannot spawn, info is null", Category.ItemSpawn); return(SpawnResult.Fail(info)); } EnsureInit(); Logger.LogTraceFormat("Server spawning {0}", Category.ItemSpawn, info); List <GameObject> spawnedObjects = new List <GameObject>(); for (int i = 0; i < info.Count; i++) { var result = info.SpawnableToSpawn.SpawnAt(info.SpawnDestination); if (result.Successful) { spawnedObjects.Add(result.GameObject); //apply scattering if it was specified if (info.ScatterRadius != null) { foreach (var spawned in spawnedObjects) { var cnt = spawned.GetComponent <CustomNetTransform>(); var scatterRadius = info.ScatterRadius.GetValueOrDefault(0); if (cnt != null) { cnt.SetPosition(info.SpawnDestination.WorldPosition + new Vector3(Random.Range(-scatterRadius, scatterRadius), Random.Range(-scatterRadius, scatterRadius))); } } } } else { return(SpawnResult.Fail(info)); } } //fire hooks for all spawned objects SpawnResult spawnResult = null; if (spawnedObjects.Count == 1) { spawnResult = SpawnResult.Single(info, spawnedObjects[0]); } else { spawnResult = SpawnResult.Multiple(info, spawnedObjects); } _ServerFireClientServerSpawnHooks(spawnResult); return(spawnResult); }
/// <summary> /// Spawns a ghost for the indicated mind's body and transfers the connection's control to it. /// </summary> /// <param name="conn"></param> /// <param name="oldBody"></param> /// <param name="characterSettings"></param> /// <param name="occupation"></param> /// <returns></returns> public static void ServerSpawnGhost(Mind forMind) { //determine where to spawn the ghost var body = forMind.GetCurrentMob(); var settings = body.GetComponent <PlayerScript>().characterSettings; var connection = body.GetComponent <NetworkIdentity>().connectionToClient; var registerTile = body.GetComponent <RegisterTile>(); if (registerTile == null) { Logger.LogErrorFormat("Cannot spawn ghost for body {0} because it has no registerTile", Category.ItemSpawn, body.name); return; } Vector3Int spawnPosition = body.GetComponent <ObjectBehaviour>().AssumedWorldPositionServer().RoundToInt(); if (spawnPosition == TransformState.HiddenPos) { //spawn ghost at occupation location if we can't determine where their body is Transform spawnTransform = GetSpawnForJob(forMind.occupation.JobType); if (spawnTransform == null) { Logger.LogErrorFormat("Unable to determine spawn position for occupation {1}. Cannot spawn ghost.", Category.ItemSpawn, forMind.occupation.DisplayName); return; } spawnPosition = spawnTransform.transform.position.CutToInt(); } var matrixInfo = MatrixManager.AtPoint(spawnPosition, true); var parentNetId = matrixInfo.NetID; var parentTransform = matrixInfo.Objects; //using parentTransform.rotation rather than Quaternion.identity because objects should always //be upright w.r.t. localRotation, NOT world rotation var ghost = Object.Instantiate(CustomNetworkManager.Instance.ghostPrefab, spawnPosition, parentTransform.rotation, parentTransform); ghost.GetComponent <PlayerScript>().registerTile.ServerSetNetworkedMatrixNetID(parentNetId); forMind.Ghosting(ghost); ServerTransferPlayer(connection, ghost, body, EVENT.GhostSpawned, settings); //fire all hooks var info = SpawnInfo.Ghost(forMind.occupation, settings, CustomNetworkManager.Instance.ghostPrefab, SpawnDestination.At(spawnPosition, parentTransform)); Spawn._ServerFireClientServerSpawnHooks(SpawnResult.Single(info, ghost)); }
/// <summary> /// Performs the specified spawn locally, for this client only. /// </summary> /// <returns></returns> public static SpawnResult Client(SpawnInfo info) { if (info == null) { Logger.LogError("Cannot spawn, info is null", Category.ItemSpawn); return(SpawnResult.Fail(info)); } if (info.SpawnableToSpawn is IClientSpawnable clientSpawnable) { List <GameObject> spawnedObjects = new List <GameObject>(); for (var i = 0; i < info.Count; i++) { var result = clientSpawnable.ClientSpawnAt(info.SpawnDestination); if (result.Successful) { spawnedObjects.Add(result.GameObject); } } //fire client side lifecycle hooks foreach (var spawnedObject in spawnedObjects) { var hooks = spawnedObject.GetComponents <IClientSpawn>(); if (hooks != null) { foreach (var hook in hooks) { hook.OnSpawnClient(ClientSpawnInfo.Default()); } } } if (spawnedObjects.Count == 1) { return(SpawnResult.Single(info, spawnedObjects[0])); } return(SpawnResult.Multiple(info, spawnedObjects)); } else { Logger.LogErrorFormat("Cannot spawn {0} client side, spawnable does not" + " implement IClientSpawnable", Category.ItemSpawn, info); return(SpawnResult.Fail(info)); } }
/// <summary> /// Spawns an assistant dummy /// </summary> public static void ServerSpawnDummy() { Transform spawnTransform = GetSpawnForJob(JobType.ASSISTANT); if (spawnTransform != null) { var dummy = ServerCreatePlayer(spawnTransform.position.RoundToInt()); ServerTransferPlayer(null, dummy, null, EVENT.PlayerSpawned, new CharacterSettings()); //fire all hooks var info = SpawnInfo.Player(OccupationList.Instance.Get(JobType.ASSISTANT), new CharacterSettings(), CustomNetworkManager.Instance.humanPlayerPrefab, SpawnDestination.At(spawnTransform.gameObject)); Spawn._ServerFireClientServerSpawnHooks(SpawnResult.Single(info, dummy)); } }
/// <summary> /// Performs the specified spawn locally, for this client only. /// </summary> /// <returns></returns> public static SpawnResult Client(SpawnInfo info) { if (info == null) { Logger.LogError("Cannot spawn, info is null", Category.ItemSpawn); return(SpawnResult.Fail(info)); } List <GameObject> spawnedObjects = new List <GameObject>(); for (var i = 0; i < info.Count; i++) { if (info.SpawnableType == SpawnableType.Cloth) { Logger.LogErrorFormat("Spawning cloths on client side is not currently supported. {0}", Category.ItemSpawn, info); return(SpawnResult.Fail(info)); } bool isPooled; // not used for Client-only instantiation var go = PoolInstantiate(info.PrefabUsed, info.WorldPosition, info.Rotation, info.Parent, out isPooled); spawnedObjects.Add(go); } //fire client side lifecycle hooks foreach (var spawnedObject in spawnedObjects) { var hooks = spawnedObject.GetComponents <IClientSpawn>(); if (hooks != null) { foreach (var hook in hooks) { hook.OnSpawnClient(ClientSpawnInfo.Default()); } } } if (spawnedObjects.Count == 1) { return(SpawnResult.Single(info, spawnedObjects[0])); } return(SpawnResult.Multiple(info, spawnedObjects)); }
/// <summary> /// Spawns an assistant dummy /// </summary> public static void ServerSpawnDummy(Transform spawnTransform = null) { if (spawnTransform == null) { spawnTransform = SpawnPoint.GetRandomPointForJob(JobType.ASSISTANT); } if (spawnTransform != null) { var dummy = ServerCreatePlayer(spawnTransform.position.RoundToInt()); CharacterSettings randomSettings = CharacterSettings.RandomizeCharacterSettings(); ServerTransferPlayer(null, dummy, null, Event.PlayerSpawned, randomSettings); //fire all hooks var info = SpawnInfo.Player(OccupationList.Instance.Get(JobType.ASSISTANT), randomSettings, CustomNetworkManager.Instance.humanPlayerPrefab, SpawnDestination.At(spawnTransform.gameObject)); Spawn._ServerFireClientServerSpawnHooks(SpawnResult.Single(info, dummy)); } }
/// <summary> /// Spawns a new player character and transfers the connection's control into the new body. /// If existingMind is null, creates the new mind and assigns it to the new body. /// /// Fires server and client side player spawn hooks. /// </summary> /// <param name="connection">connection to give control to the new player character</param> /// <param name="occupation">occupation of the new player character</param> /// <param name="characterSettings">settings of the new player character</param> /// <param name="existingMind">existing mind to transfer to the new player, if null new mind will be created /// and assigned to the new player character</param> /// <param name="spawnPos">world position to spawn at</param> /// <param name="spawnItems">If spawning a player, should the player spawn without the defined initial equipment for their occupation?</param> /// <param name="willDestroyOldBody">if true, indicates the old body is going to be destroyed rather than pooled, /// thus we shouldn't send any network message which reference's the old body's ID since it won't exist.</param> /// /// <returns>the spawned object</returns> private static GameObject ServerSpawnInternal(NetworkConnection connection, Occupation occupation, CharacterSettings characterSettings, Mind existingMind, Vector3Int?spawnPos = null, bool spawnItems = true, bool willDestroyOldBody = false) { //determine where to spawn them if (spawnPos == null) { Transform spawnTransform; //Spawn normal location for special jobs or if less than 2 minutes passed if (GameManager.Instance.stationTime < ARRIVALS_SPAWN_TIME || occupation.LateSpawnIsArrivals == false) { spawnTransform = GetSpawnForJob(occupation.JobType); } else { spawnTransform = GetSpawnForLateJoin(occupation.JobType); //Fallback to assistant spawn location if none found for late join if (spawnTransform == null && occupation.JobType != JobType.NULL) { spawnTransform = GetSpawnForJob(JobType.ASSISTANT); } } if (spawnTransform == null) { Logger.LogErrorFormat( "Unable to determine spawn position for connection {0} occupation {1}. Cannot spawn player.", Category.ItemSpawn, connection.address, occupation.DisplayName); return(null); } spawnPos = spawnTransform.transform.position.CutToInt(); } //create the player object var newPlayer = ServerCreatePlayer(spawnPos.GetValueOrDefault()); var newPlayerScript = newPlayer.GetComponent <PlayerScript>(); //get the old body if they have one. var oldBody = existingMind?.GetCurrentMob(); //transfer control to the player object ServerTransferPlayer(connection, newPlayer, oldBody, EVENT.PlayerSpawned, characterSettings, willDestroyOldBody); if (existingMind == null) { //create the mind of the player Mind.Create(newPlayer, occupation); } else { //transfer the mind to the new body existingMind.SetNewBody(newPlayerScript); } var ps = newPlayer.GetComponent <PlayerScript>(); var connectedPlayer = PlayerList.Instance.Get(connection); connectedPlayer.Name = ps.playerName; connectedPlayer.Job = ps.mind.occupation.JobType; UpdateConnectedPlayersMessage.Send(); //fire all hooks var info = SpawnInfo.Player(occupation, characterSettings, CustomNetworkManager.Instance.humanPlayerPrefab, SpawnDestination.At(spawnPos), spawnItems: spawnItems); Spawn._ServerFireClientServerSpawnHooks(SpawnResult.Single(info, newPlayer)); return(newPlayer); }
/// <summary> /// Spawns a ghost for the indicated mind's body and transfers the connection's control to it. /// </summary> /// <param name="conn"></param> /// <param name="oldBody"></param> /// <param name="characterSettings"></param> /// <param name="occupation"></param> /// <returns></returns> public static void ServerSpawnGhost(Mind forMind) { if (forMind == null) { Logger.LogError("Mind was null for ServerSpawnGhost", Category.Ghosts); return; } //determine where to spawn the ghost var body = forMind.GetCurrentMob(); if (body == null) { Logger.LogError("Body was null for ServerSpawnGhost", Category.Ghosts); return; } var settings = body.GetComponent <PlayerScript>().characterSettings; var connection = body.GetComponent <NetworkIdentity>().connectionToClient; var registerTile = body.GetComponent <RegisterTile>(); if (registerTile == null) { Logger.LogErrorFormat("Cannot spawn ghost for body {0} because it has no registerTile", Category.Ghosts, body.name); return; } Vector3Int spawnPosition = TransformState.HiddenPos; var objBeh = body.GetComponent <ObjectBehaviour>(); if (objBeh != null) { spawnPosition = objBeh.AssumedWorldPositionServer(); } if (spawnPosition == TransformState.HiddenPos) { //spawn ghost at occupation location if we can't determine where their body is Transform spawnTransform = SpawnPoint.GetRandomPointForJob(forMind.occupation.JobType, true); if (spawnTransform == null) { Logger.LogErrorFormat("Unable to determine spawn position for occupation {1}. Cannot spawn ghost.", Category.Ghosts, forMind.occupation.DisplayName); return; } spawnPosition = spawnTransform.transform.position.CutToInt(); } var matrixInfo = MatrixManager.AtPoint(spawnPosition, true); var parentTransform = matrixInfo.Objects; //using parentTransform.rotation rather than Quaternion.identity because objects should always //be upright w.r.t. localRotation, NOT world rotation var ghost = UnityEngine.Object.Instantiate(CustomNetworkManager.Instance.ghostPrefab, spawnPosition, parentTransform.rotation, parentTransform); forMind.Ghosting(ghost); ServerTransferPlayer(connection, ghost, body, Event.GhostSpawned, settings); //fire all hooks var info = SpawnInfo.Ghost(forMind.occupation, settings, CustomNetworkManager.Instance.ghostPrefab, SpawnDestination.At(spawnPosition, parentTransform)); Spawn._ServerFireClientServerSpawnHooks(SpawnResult.Single(info, ghost)); if (PlayerList.Instance.IsAdmin(forMind.ghost.connectedPlayer)) { var adminItemStorage = AdminManager.Instance.GetItemSlotStorage(forMind.ghost.connectedPlayer); adminItemStorage.ServerAddObserverPlayer(ghost); ghost.GetComponent <GhostSprites>().SetAdminGhost(); } }
/// <summary> /// FOR DEV / TESTING ONLY! Simulates destroying and recreating an item by putting it in the pool and taking it back /// out again. If item is not pooled, simply destroys and recreates it as if calling Despawn and then Spawn /// Can use this to validate that the object correctly re-initializes itself after spawning - /// no state should be left over from its previous incarnation. /// </summary> /// <returns>the re-created object</returns> public static GameObject ServerPoolTestRespawn(GameObject target) { var poolPrefabTracker = target.GetComponent <PoolPrefabTracker>(); if (poolPrefabTracker == null) { //destroy / create using normal approach with no pooling Logger.LogWarningFormat("Object {0} has no pool prefab tracker, thus cannot be pooled. It will be destroyed / created" + " without going through the pool.", Category.ItemSpawn, target.name); //determine prefab var position = target.TileWorldPosition(); var prefab = DeterminePrefab(target); if (prefab == null) { Logger.LogErrorFormat("Object {0} at {1} cannot be respawned because it has no PoolPrefabTracker and its name" + " does not match a prefab name, so we cannot" + " determine the prefab to instantiate. Please fix this object so that it" + " has an attached PoolPrefabTracker or so its name matches the prefab it was created from.", Category.ItemSpawn, target.name, position); return(null); } Despawn.ServerSingle(target); return(ServerPrefab(prefab, position.To3Int()).GameObject); } else { //destroy / create with pooling //save previous position var destination = SpawnDestination.At(target); var worldPos = target.TileWorldPosition(); var transform = target.GetComponent <IPushable>(); var prevParent = target.transform.parent; //this simulates going into the pool Despawn._ServerFireDespawnHooks(DespawnResult.Single(DespawnInfo.Single(target))); if (transform != null) { transform.VisibleState = false; } //this simulates coming back out of the pool target.SetActive(true); target.transform.parent = prevParent; target.transform.position = worldPos.To3Int(); var cnt = target.GetComponent <CustomNetTransform>(); if (cnt) { cnt.ReInitServerState(); cnt.NotifyPlayers(); //Sending out clientState for already spawned items } var prefab = DeterminePrefab(target); SpawnInfo spawnInfo = SpawnInfo.Spawnable( SpawnablePrefab.For(prefab), destination); _ServerFireClientServerSpawnHooks(SpawnResult.Single(spawnInfo, target)); return(target); } }
/// <summary> /// Server-side only. Performs the spawn and syncs it to all clients. /// </summary> /// <returns></returns> private static SpawnResult Server(SpawnInfo info, bool AutoOnSpawnServerHook = true) { if (info == null) { Logger.LogError("Cannot spawn, info is null", Category.ItemSpawn); return(SpawnResult.Fail(info)); } EnsureInit(); Logger.LogTraceFormat("Server spawning {0}", Category.ItemSpawn, info); List <GameObject> spawnedObjects = new List <GameObject>(); for (int i = 0; i < info.Count; i++) { var result = info.SpawnableToSpawn.SpawnAt(info.SpawnDestination); if (result.Successful) { if (info.Mapspawn == false) { result.GameObject.AddComponent <RuntimeSpawned>(); } spawnedObjects.Add(result.GameObject); //apply scattering if it was specified if (info.ScatterRadius != null) { var cnt = result.GameObject.GetComponent <CustomNetTransform>(); var scatterRadius = info.ScatterRadius.GetValueOrDefault(0); if (cnt != null) { cnt.SetPosition(info.SpawnDestination.WorldPosition + new Vector3( Random.Range(-scatterRadius, scatterRadius), Random.Range(-scatterRadius, scatterRadius))); } } if (info.SpawnDestination.SharePosition != null && info.SpawnDestination.SharePosition.parentContainer != null) { if (result.GameObject.TryGetComponent <ObjectBehaviour>(out var objectBehaviour)) { var closetControl = info.SpawnDestination.SharePosition.parentContainer .GetComponent <ClosetControl>(); closetControl.ServerAddInternalItem(objectBehaviour); } } } else { return(SpawnResult.Fail(info)); } } //fire hooks for all spawned objects SpawnResult spawnResult = null; if (spawnedObjects.Count == 1) { spawnResult = SpawnResult.Single(info, spawnedObjects[0]); } else { spawnResult = SpawnResult.Multiple(info, spawnedObjects); } if (AutoOnSpawnServerHook) { _ServerFireClientServerSpawnHooks(spawnResult); } return(spawnResult); }
/// <summary> /// Server-side only. Performs the spawn and syncs it to all clients. /// </summary> /// <returns></returns> private static SpawnResult Server(SpawnInfo info) { if (info == null) { Logger.LogError("Cannot spawn, info is null", Category.ItemSpawn); return(SpawnResult.Fail(info)); } EnsureInit(); Logger.LogTraceFormat("Server spawning {0}", Category.ItemSpawn, info); List <GameObject> spawnedObjects = new List <GameObject>(); for (int i = 0; i < info.Count; i++) { if (info.SpawnableType == SpawnableType.Prefab) { if (info.PrefabUsed == null) { Logger.LogError("Cannot spawn, prefab to use is null", Category.ItemSpawn); return(SpawnResult.Fail(info)); } if (info.CancelIfImpassable) { if (IsTotallyImpassable(info.WorldPosition.CutToInt())) { Logger.LogTraceFormat("Cancelling spawn of {0} because" + " the position being spawned to {1} is impassable", Category.ItemSpawn, info.PrefabUsed, info.WorldPosition.CutToInt()); return(SpawnResult.Fail(info)); } } bool isPooled; GameObject tempObject = PoolInstantiate(info.PrefabUsed, info.WorldPosition, info.Rotation, info.Parent, out isPooled); if (!isPooled) { Logger.LogTrace("Prefab to spawn was not pooled, spawning new instance.", Category.ItemSpawn); NetworkServer.Spawn(tempObject); tempObject.GetComponent <CustomNetTransform>() ?.NotifyPlayers(); //Sending clientState for newly spawned items } else { Logger.LogTrace("Prefab to spawn was pooled, reusing it...", Category.ItemSpawn); } spawnedObjects.Add(tempObject); } else if (info.SpawnableType == SpawnableType.Cloth) { var result = ServerCloth(info); if (result == null) { return(SpawnResult.Fail(info)); } spawnedObjects.Add(result); } else if (info.SpawnableType == SpawnableType.Clone) { var prefab = DeterminePrefab(info.ClonedFrom); if (prefab == null) { Logger.LogErrorFormat( "Object {0} cannot be cloned because it has no PoolPrefabTracker and its name" + " does not match a prefab name, so we cannot" + " determine the prefab to instantiate. Please fix this object so that it" + " has an attached PoolPrefabTracker or so its name matches the prefab it was created from.", Category.ItemSpawn, info.ClonedFrom); } GameObject tempObject = PoolInstantiate(prefab, info.WorldPosition, info.Rotation, info.Parent, out var isPooled); if (!isPooled) { NetworkServer.Spawn(tempObject); tempObject.GetComponent <CustomNetTransform>() ?.NotifyPlayers(); //Sending clientState for newly spawned items } spawnedObjects.Add(tempObject); } //apply scattering if it was specified if (info.ScatterRadius != null) { foreach (var spawned in spawnedObjects) { var cnt = spawned.GetComponent <CustomNetTransform>(); var scatterRadius = info.ScatterRadius.GetValueOrDefault(0); if (cnt != null) { cnt.SetPosition(info.WorldPosition + new Vector3(Random.Range(-scatterRadius, scatterRadius), Random.Range(-scatterRadius, scatterRadius))); } } } //fire hooks for all spawned objects if (spawnedObjects.Count == 1) { _ServerFireClientServerSpawnHooks(SpawnResult.Single(info, spawnedObjects[0])); } else { _ServerFireClientServerSpawnHooks(SpawnResult.Multiple(info, spawnedObjects)); } } return(SpawnResult.Multiple(info, spawnedObjects)); }