using System.Collections; using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxClient.MonoBehaviours; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.Util; using NitroxModel_Subnautica.DataStructures; using UnityEngine; namespace NitroxClient.GameLogic.Spawning.WorldEntities; public class PlayerWorldEntitySpawner : IWorldEntitySpawner { private readonly PlayerManager playerManager; private readonly ILocalNitroxPlayer localPlayer; public PlayerWorldEntitySpawner(PlayerManager playerManager, ILocalNitroxPlayer localPlayer) { this.playerManager = playerManager; this.localPlayer = localPlayer; } public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) { if (Player.main.TryGetNitroxId(out NitroxId localPlayerId) && localPlayerId == entity.Id) { // No special setup for the local player. Simply return saying it is spawned. result.Set(Player.main.gameObject); yield break; } Optional remotePlayer = playerManager.Find(entity.Id); // The server may send us a player entity but they are not guarenteed to be actively connected at the moment - don't spawn them. In the // future, we could make this configurable to be able to spawn disconnected players in the world. if (remotePlayer.HasValue && !remotePlayer.Value.Body) { GameObject remotePlayerBody = CloneLocalPlayerBodyPrototype(); remotePlayer.Value.InitializeGameObject(remotePlayerBody); if (!IsSwimming(entity.Transform.Position.ToUnity(), parent)) { remotePlayer.Value.UpdateAnimationAndCollider(AnimChangeType.UNDERWATER, AnimChangeState.OFF); } if (parent.HasValue) { AttachToParent(remotePlayer.Value, parent.Value); } result.Set(Optional.Of(remotePlayerBody)); yield break; } result.Set(Optional.Empty); } public bool SpawnsOwnChildren() { return false; } private GameObject CloneLocalPlayerBodyPrototype() { GameObject clone = Object.Instantiate(localPlayer.BodyPrototype, null, false); clone.SetActive(true); return clone; } private void AttachToParent(RemotePlayer remotePlayer, GameObject parent) { if (parent.TryGetComponent(out SubRoot subRoot)) { Log.Debug($"Found sub root for {remotePlayer.PlayerName}. Will add him and update animation."); remotePlayer.SetSubRoot(subRoot); } else if (parent.TryGetComponent(out EscapePod escapePod)) { Log.Debug($"Found EscapePod for {remotePlayer.PlayerName}."); remotePlayer.SetEscapePod(escapePod); } else { Log.Error($"Found neither SubRoot component nor EscapePod on {parent.name} for {remotePlayer.PlayerName}."); } } private bool IsSwimming(Vector3 playerPosition, Optional parent) { if (parent.HasValue) { parent.Value.TryGetComponent(out SubRoot subroot); // Set the animation for the remote player to standing instead of swimming if player is not in a flooded subroot // or in a waterpark if (subroot) { if (subroot.IsUnderwater(playerPosition)) { return true; } if (subroot.isCyclops) { return false; } // We know that we are in a subroot. But we can also be in a waterpark in a subroot, where we would swim BaseRoot baseRoot = subroot.GetComponentInParent(); if (baseRoot) { WaterPark[] waterParks = baseRoot.GetComponentsInChildren(); foreach (WaterPark waterPark in waterParks) { if (waterPark.IsPointInside(playerPosition)) { return true; } } return false; } } Log.Debug($"Trying to find escape pod for {parent}."); parent.Value.TryGetComponent(out EscapePod escapePod); if (escapePod) { Log.Debug("Found escape pod for player. Will add him and update animation."); return false; } } // Player can be above ocean level. float oceanLevel = Ocean.GetOceanLevel(); return playerPosition.y < oceanLevel; } }