diff --git a/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs b/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs index ae40802ded..1d7314471e 100644 --- a/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs +++ b/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs @@ -1,7 +1,6 @@ using Nitrox.Test; using Nitrox.Test.Helper.Faker; using NitroxModel.Core; -using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.GameLogic.Entities.Bases; @@ -199,8 +198,9 @@ private static void EntityTest(Entity entity, Entity entityAfter) case BatteryMetadata metadata when entityAfter.Metadata is BatteryMetadata metadataAfter: Assert.AreEqual(metadata.Charge, metadataAfter.Charge); break; - case RepairedComponentMetadata metadata when entityAfter.Metadata is RepairedComponentMetadata metadataAfter: - Assert.AreEqual(metadata.TechType, metadataAfter.TechType); + case EscapePodMetadata metadata when entityAfter.Metadata is EscapePodMetadata metadataAfter: + Assert.AreEqual(metadata.PodRepaired, metadataAfter.PodRepaired); + Assert.AreEqual(metadata.RadioRepaired, metadataAfter.RadioRepaired); break; case CrafterMetadata metadata when entityAfter.Metadata is CrafterMetadata metadataAfter: Assert.AreEqual(metadata.TechType, metadataAfter.TechType); @@ -337,9 +337,9 @@ private static void EntityTest(Entity entity, Entity entityAfter) case PlaceholderGroupWorldEntity placeholderGroupWorldEntity when worldEntityAfter is PlaceholderGroupWorldEntity placeholderGroupWorldEntityAfter: Assert.AreEqual(placeholderGroupWorldEntity.ComponentIndex, placeholderGroupWorldEntityAfter.ComponentIndex); break; - case CellRootEntity _ when worldEntityAfter is CellRootEntity _: + case CellRootEntity when worldEntityAfter is CellRootEntity: break; - case PlacedWorldEntity _ when worldEntityAfter is PlacedWorldEntity _: + case PlacedWorldEntity when worldEntityAfter is PlacedWorldEntity: break; case OxygenPipeEntity oxygenPipeEntity when worldEntityAfter is OxygenPipeEntity oxygenPipeEntityAfter: Assert.AreEqual(oxygenPipeEntity.ParentPipeId, oxygenPipeEntityAfter.ParentPipeId); @@ -351,7 +351,7 @@ private static void EntityTest(Entity entity, Entity entityAfter) break; case SerializedWorldEntity serializedWorldEntity when entityAfter is SerializedWorldEntity serializedWorldEntityAfter: Assert.AreEqual(serializedWorldEntity.AbsoluteEntityCell, serializedWorldEntityAfter.AbsoluteEntityCell); - AssertHelper.IsListEqual(serializedWorldEntity.Components.OrderBy(c => c.GetHashCode()), serializedWorldEntityAfter.Components.OrderBy(c => c.GetHashCode()), (SerializedComponent c1, SerializedComponent c2) => c1.Equals(c2)); + AssertHelper.IsListEqual(serializedWorldEntity.Components.OrderBy(c => c.GetHashCode()), serializedWorldEntityAfter.Components.OrderBy(c => c.GetHashCode()), (c1, c2) => c1.Equals(c2)); Assert.AreEqual(serializedWorldEntity.Layer, serializedWorldEntityAfter.Layer); Assert.AreEqual(serializedWorldEntity.BatchId, serializedWorldEntityAfter.BatchId); Assert.AreEqual(serializedWorldEntity.CellId, serializedWorldEntityAfter.CellId); @@ -381,7 +381,6 @@ private static void EntityTest(Entity entity, Entity entityAfter) Assert.AreEqual(buildEntity.BaseData, buildEntityAfter.BaseData); break; case EscapePodWorldEntity escapePodWorldEntity when globalRootEntityAfter is EscapePodWorldEntity escapePodWorldEntityAfter: - Assert.AreEqual(escapePodWorldEntity.Damaged, escapePodWorldEntityAfter.Damaged); Assert.IsTrue(escapePodWorldEntity.Players.SequenceEqual(escapePodWorldEntityAfter.Players)); break; case InteriorPieceEntity interiorPieceEntity when globalRootEntityAfter is InteriorPieceEntity interiorPieceEntityAfter: @@ -411,9 +410,9 @@ private static void EntityTest(Entity entity, Entity entityAfter) case MoonpoolEntity moonpoolEntity when globalRootEntityAfter is MoonpoolEntity moonpoolEntityAfter: Assert.AreEqual(moonpoolEntity.Cell, moonpoolEntityAfter.Cell); break; - case PlanterEntity _ when globalRootEntityAfter is PlanterEntity: + case PlanterEntity when globalRootEntityAfter is PlanterEntity: break; - case PlayerWorldEntity _ when globalRootEntityAfter is PlayerWorldEntity: + case PlayerWorldEntity when globalRootEntityAfter is PlayerWorldEntity: break; case VehicleWorldEntity vehicleWorldEntity when globalRootEntityAfter is VehicleWorldEntity vehicleWorldEntityAfter: Assert.AreEqual(vehicleWorldEntity.SpawnerId, vehicleWorldEntityAfter.SpawnerId); @@ -449,7 +448,7 @@ private static void EntityTest(Entity entity, Entity entityAfter) case PathBasedChildEntity pathBasedChildEntity when entityAfter is PathBasedChildEntity pathBasedChildEntityAfter: Assert.AreEqual(pathBasedChildEntity.Path, pathBasedChildEntityAfter.Path); break; - case InstalledBatteryEntity _ when entityAfter is InstalledBatteryEntity _: + case InstalledBatteryEntity when entityAfter is InstalledBatteryEntity: break; case InstalledModuleEntity installedModuleEntity when entityAfter is InstalledModuleEntity installedModuleEntityAfter: Assert.AreEqual(installedModuleEntity.Slot, installedModuleEntityAfter.Slot); diff --git a/Nitrox.sln.DotSettings b/Nitrox.sln.DotSettings index 44bad62e91..50b2ca91c5 100644 --- a/Nitrox.sln.DotSettings +++ b/Nitrox.sln.DotSettings @@ -100,6 +100,7 @@ IP LAN PDA + UWE False <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> @@ -319,6 +320,7 @@ True True True + True True True True diff --git a/NitroxClient/Debuggers/Drawer/DrawerManager.cs b/NitroxClient/Debuggers/Drawer/DrawerManager.cs index 3c202ba262..3187b41fea 100644 --- a/NitroxClient/Debuggers/Drawer/DrawerManager.cs +++ b/NitroxClient/Debuggers/Drawer/DrawerManager.cs @@ -41,6 +41,8 @@ public DrawerManager(SceneDebugger sceneDebugger) AddDrawer(nitroxEntityDrawer); AddDrawer(nitroxEntityDrawer); AddDrawer(); + AddDrawer>(); + AddDrawer>(); AddDrawer(); AddDrawer(new(selectableDrawer, unityEventDrawer)); AddDrawer(new(sceneDebugger)); diff --git a/NitroxClient/Debuggers/Drawer/Subnautica/UWEEventDrawer.cs b/NitroxClient/Debuggers/Drawer/Subnautica/UWEEventDrawer.cs new file mode 100644 index 0000000000..127c02eb9e --- /dev/null +++ b/NitroxClient/Debuggers/Drawer/Subnautica/UWEEventDrawer.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using UnityEngine; +using UWE; + +namespace NitroxClient.Debuggers.Drawer.Subnautica; + +public class UWEEventDrawer : IDrawer>, IDrawer> +{ + private const float LABEL_WIDTH = 250; + + public void Draw(Event uweEvent) => UWEEventDrawer.Draw(uweEvent); + public void Draw(Event uweEvent) => UWEEventDrawer.Draw(uweEvent); + + private static void Draw(Event uweEvent) + { + using GUILayout.VerticalScope scope = new(); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Triggering", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + uweEvent.triggering = NitroxGUILayout.BoolField(uweEvent.triggering); + } + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Handlers", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + DrawUweEventHandlerList(uweEvent.handlers); + } + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("ToRemove", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + DrawUweEventHandlerList(uweEvent.toRemove); + } + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("HandlersToTrigger", NitroxGUILayout.DrawerLabel, GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + DrawUweEventHandlerList(uweEvent.handlersToTrigger); + } + } + + private static void DrawUweEventHandlerList(ICollection.Handler> uweEventHandlerList) + { + if (uweEventHandlerList == null) + { + GUILayout.Label("null", NitroxGUILayout.DrawerLabel); + return; + } + + if (uweEventHandlerList.Count == 0) + { + GUILayout.Label("empty", NitroxGUILayout.DrawerLabel); + return; + } + + foreach (Event.Handler uweEventHandler in uweEventHandlerList) + { + using (new GUILayout.HorizontalScope()) + { + NitroxGUILayout.Separator(); + if (uweEventHandler == null) + { + GUILayout.Label("Handler was null", NitroxGUILayout.DrawerLabel); + continue; + } + + string labelText = uweEventHandler.obj ? $"{uweEventHandler.obj.GetType().Name}." : string.Empty; + labelText += uweEventHandler.function; + GUILayout.Label(labelText, NitroxGUILayout.DrawerLabel); + } + } + } +} diff --git a/NitroxClient/GameLogic/Entities.cs b/NitroxClient/GameLogic/Entities.cs index 9c7e41296b..a16ec16a52 100644 --- a/NitroxClient/GameLogic/Entities.cs +++ b/NitroxClient/GameLogic/Entities.cs @@ -42,7 +42,7 @@ public class Entities private readonly HashSet deletedEntitiesIds = new(); private readonly List pendingSimulatedEntities = new(); - public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, LiveMixinManager liveMixinManager, TimeManager timeManager, SimulationOwnership simulationOwnership) + public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, EntityMetadataManager entityMetadataManager, PlayerManager playerManager, LocalPlayer localPlayer, LiveMixinManager liveMixinManager, TimeManager timeManager, SimulationOwnership simulationOwnership) { this.packetSender = packetSender; this.throttledPacketSender = throttledPacketSender; @@ -218,7 +218,7 @@ public IEnumerator SpawnBatchAsync(List batch, bool forceRespawn = false MarkAsSpawned(entity); // Finding out about all children (can be hidden in the object's hierarchy or in a pending list) - + if (!entitySpawner.SpawnsOwnChildren(entity)) { batch.AddRange(entity.ChildEntities); @@ -231,7 +231,7 @@ public IEnumerator SpawnBatchAsync(List batch, bool forceRespawn = false pendingParentEntitiesByParentId.Remove(entity.Id); } } - + // Skip a frame to maintain FPS if (Time.realtimeSinceStartup >= timeLimit && skipFrames) { diff --git a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs index 30a093f0d0..e8b2b43cbe 100644 --- a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs @@ -25,6 +25,7 @@ public PlayerPositionInitialSyncProcessor() public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem) { // We freeze the player so that he doesn't fall before the cells around him have loaded + // Is disabled manually or in Terrain.WaitForWorldLoad() Player.main.cinematicModeActive = true; AttachPlayerToEscapePod(packet.AssignedEscapePodId); @@ -37,13 +38,12 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW } Player.main.SetPosition(position, rotation); - // Player.Update is setting SubRootID to null after Player position is set + // Player.ValidateEscapePod is setting currentEscapePod to null if player is not inside EscapePod using (PacketSuppressor.Suppress()) { Player.main.ValidateEscapePod(); } - // Player position is relative to a subroot if in a subroot Optional subRootId = packet.PlayerSubRootId; if (!subRootId.HasValue) { @@ -59,16 +59,19 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW yield break; } - if (!sub.Value.TryGetComponent(out SubRoot subRoot)) + if (sub.Value.TryGetComponent(out SubRoot subRoot)) { - Log.Debug("SubRootId-GameObject has no SubRoot component, so it's assumed to be the EscapePod"); - yield return Terrain.WaitForWorldLoad(); - yield break; + Player.main.SetCurrentSub(subRoot, true); + Player.main.UpdateIsUnderwater(); + } + else if (sub.Value.GetComponent()) + { + Player.main.escapePod.Update(true); + } + else + { + Log.Error("SubRootId-GameObject has no SubRoot or EscapePod component"); } - - Player.main.SetCurrentSub(subRoot, true); - // If the player's in a base/cyclops we don't need to wait for the world to load - Player.main.UpdateIsUnderwater(); Player.main.cinematicModeActive = false; } diff --git a/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs b/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs index f73840dc07..ec4ef38dd5 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs @@ -54,6 +54,13 @@ public Optional Extract(GameObject o) return Optional.Empty; } + public bool TryExtract(object o, out EntityMetadata metadata) + { + Optional opMetadata = Extract(o); + metadata = opMetadata.Value; + return opMetadata.HasValue; + } + public Optional FromMetaData(EntityMetadata metadata) { if (metadata != null && processors.TryGetValue(metadata.GetType(), out IEntityMetadataProcessor processor)) diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Extractor/EscapePodMetadataExtractor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Extractor/EscapePodMetadataExtractor.cs new file mode 100644 index 0000000000..0bc092d6f8 --- /dev/null +++ b/NitroxClient/GameLogic/Spawning/Metadata/Extractor/EscapePodMetadataExtractor.cs @@ -0,0 +1,14 @@ +using NitroxClient.GameLogic.Spawning.Metadata.Extractor.Abstract; +using NitroxClient.Unity.Helper; +using NitroxModel.DataStructures.GameLogic.Entities.Metadata; + +namespace NitroxClient.GameLogic.Spawning.Metadata.Extractor; + +public class EscapePodMetadataExtractor : EntityMetadataExtractor +{ + public override EscapePodMetadata Extract(EscapePod entity) + { + Radio radio = entity.radioSpawner.spawnedObj.RequireComponent(); + return new EscapePodMetadata(entity.liveMixin.IsFullHealth(), radio.liveMixin.IsFullHealth()); + } +} diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs new file mode 100644 index 0000000000..2d4b9f353e --- /dev/null +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs @@ -0,0 +1,75 @@ +using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract; +using NitroxModel.DataStructures.GameLogic.Entities.Metadata; +using UnityEngine; + +namespace NitroxClient.GameLogic.Spawning.Metadata.Processor; + +public class EscapePodMetadataProcessor : EntityMetadataProcessor +{ + // For metadata changes outside initial sync we only care about broken -> repaired + public override void ProcessMetadata(GameObject gameObject, EscapePodMetadata metadata) + { + if (!gameObject.TryGetComponent(out EscapePod pod)) + { + Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get the EscapePod component from the provided gameobject."); + return; + } + + if (!pod.liveMixin.IsFullHealth() && metadata.PodRepaired) + { + pod.liveMixin.health = pod.liveMixin.maxHealth; + pod.healthScalar = 1; + pod.damageEffectsShowing = true; + pod.UpdateDamagedEffects(); + pod.OnRepair(); + } + + if (!pod.radioSpawner.spawnedObj.TryGetComponent(out Radio radio)) + { + Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get Radio from EscapePod."); + return; + } + + if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired) + { + radio.liveMixin.AddHealth(radio.liveMixin.maxHealth); + } + } + + /// + /// Applies repaired state without animations and minimal audio playback + /// + public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, EscapePodMetadata metadata) + { + if (metadata.PodRepaired) + { + pod.liveMixin.health = pod.liveMixin.maxHealth; + pod.healthScalar = 1; + pod.damageEffectsShowing = true; // Needs to be set to true for UpdateDamagedEffects() to function + pod.UpdateDamagedEffects(); + pod.vfxSpawner.SpawnManual(); // Spawn vfx to instantly disable it so no smoke is fading after player has joined + pod.vfxSpawner.spawnedObj.SetActive(false); + pod.lightingController.SnapToState(0); + } + else + { + IntroLifepodDirector introLifepodDirector = pod.GetComponent(); + introLifepodDirector.OnProtoDeserializeObjectTree(null); + introLifepodDirector.ToggleActiveObjects(false); + pod.lightingController.SnapToState(2); + } + + if (metadata.RadioRepaired) + { + radio.liveMixin.health = radio.liveMixin.maxHealth; + if (radio.liveMixin.loopingDamageEffectObj) + { + Object.Destroy(radio.liveMixin.loopingDamageEffectObj); + } + } + else + { + pod.DamageRadio(); + } + } +} diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs deleted file mode 100644 index a7be29fa8f..0000000000 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract; -using NitroxModel.DataStructures.GameLogic.Entities.Metadata; -using UnityEngine; - -namespace NitroxClient.GameLogic.Spawning.Metadata.Processor; - -public class RepairedComponentMetadataProcessor : EntityMetadataProcessor -{ - public override void ProcessMetadata(GameObject gameObject, RepairedComponentMetadata metadata) - { - Radio radio = gameObject.GetComponent(); - - if (radio) - { - radio.liveMixin.health = radio.liveMixin.maxHealth; - radio.repairNotification.Play(); - } - - EscapePod pod = gameObject.GetComponent(); - - if (pod) - { - pod.liveMixin.health = pod.liveMixin.maxHealth; - pod.animator.SetFloat("lifepod_damage", 1.0f); - pod.fixPanelGoal.Trigger(); - pod.fixPanelPowerUp.Play(); - } - } -} diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs index dd4f711a95..fd71f9f2ef 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs @@ -1,103 +1,114 @@ using System.Collections; -using NitroxClient.GameLogic.Spawning.Metadata; +using NitroxClient.Communication; +using NitroxClient.GameLogic.FMOD; +using NitroxClient.GameLogic.Spawning.Metadata.Processor; using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.CinematicController; using NitroxModel.DataStructures.GameLogic.Entities; +using NitroxModel.DataStructures.GameLogic.Entities.Metadata; using NitroxModel.DataStructures.Util; using NitroxModel_Subnautica.DataStructures; +using NitroxModel.DataStructures.GameLogic; +using NitroxModel.Packets; using UnityEngine; -namespace NitroxClient.GameLogic.Spawning.WorldEntities +namespace NitroxClient.GameLogic.Spawning.WorldEntities; + +public class EscapePodWorldEntitySpawner : IWorldEntitySpawner { - public class EscapePodWorldEntitySpawner : IWorldEntitySpawner + /* + * When creating additional escape pods (multiple users with multiple pods) + * we want to suppress the escape pod's awake method so it doesn't override + * EscapePod.main to the new escape pod. + */ + public static bool SuppressEscapePodAwakeMethod; + + private readonly LocalPlayer localPlayer; + + public EscapePodWorldEntitySpawner(LocalPlayer localPlayer) { - private EntityMetadataManager entityMetadataManager; + this.localPlayer = localPlayer; + } - public EscapePodWorldEntitySpawner(EntityMetadataManager entityMetadataManager) + public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) + { + if (entity is not EscapePodWorldEntity escapePodEntity) { - this.entityMetadataManager = entityMetadataManager; + result.Set(Optional.Empty); + Log.Error($"Received incorrect entity type: {entity.GetType()}"); + yield break; } - /* - * When creating additional escape pods (multiple users with multiple pods) - * we want to supress the escape pod's awake method so it doesn't override - * EscapePod.main to the new escape pod. - */ - public static bool SURPRESS_ESCAPE_POD_AWAKE_METHOD; + SuppressEscapePodAwakeMethod = true; - public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) - { - if (entity is not EscapePodWorldEntity escapePodEntity) - { - result.Set(Optional.Empty); - Log.Error($"Received incorrect entity type: {entity.GetType()}"); - yield break; - } + GameObject escapePod = CreateNewEscapePod(escapePodEntity); - SURPRESS_ESCAPE_POD_AWAKE_METHOD = true; + SuppressEscapePodAwakeMethod = false; - GameObject escapePod = CreateNewEscapePod(escapePodEntity); + result.Set(Optional.Of(escapePod)); + } - SURPRESS_ESCAPE_POD_AWAKE_METHOD = false; + private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity) + { + // TODO: When we want to implement multiple escape pods, instantiate the prefab. Backlog task: #1945 + // This will require some work as instantiating the prefab as-is will not make it visible. + //GameObject escapePod = Object.Instantiate(EscapePod.main.gameObject); + GameObject escapePod = EscapePod.main.gameObject; + EscapePod pod = escapePod.GetComponent(); - result.Set(Optional.Of(escapePod)); - } + Object.DestroyImmediate(escapePod.GetComponent()); // if template has a pre-existing NitroxEntity, remove. + NitroxEntity.SetNewId(escapePod, escapePodEntity.Id); - private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity) + if (escapePod.TryGetComponent(out Rigidbody rigidbody)) { - // TODO: When we want to implement multiple escape pods, instantiate the prefab. Backlog task: #1945 - // This will require some work as instantiating the prefab as-is will not make it visible. - //GameObject escapePod = Object.Instantiate(EscapePod.main.gameObject); + rigidbody.constraints = RigidbodyConstraints.FreezeAll; + } + else + { + Log.Error("Escape pod did not have a rigid body!"); + } - GameObject escapePod = EscapePod.main.gameObject; - UnityEngine.Component.DestroyImmediate(escapePod.GetComponent()); // if template has a pre-existing NitroxEntity, remove. - NitroxEntity.SetNewId(escapePod, escapePodEntity.Id); + pod.anchorPosition = escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); - entityMetadataManager.ApplyMetadata(escapePod, escapePodEntity.Metadata); + pod.ForceSkyApplier(); + pod.escapePodCinematicControl.StopAll(); - Rigidbody rigidbody = escapePod.GetComponent(); - if (rigidbody != null) - { - rigidbody.constraints = RigidbodyConstraints.FreezeAll; - } - else - { - Log.Error("Escape pod did not have a rigid body!"); - } + // Player is not new and has completed the intro cinematic. If not EscapePod repair status is handled by the intro cinematic. + if (escapePodEntity.Metadata is EscapePodMetadata metadata && localPlayer.IntroCinematicMode == IntroCinematicMode.COMPLETED) + { + using FMODSoundSuppressor soundSuppressor = FMODSystem.SuppressSubnauticaSounds(); + using PacketSuppressor packetSuppressor = PacketSuppressor.Suppress(); - escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); + Radio radio = pod.radioSpawner.spawnedObj.GetComponent(); + EscapePodMetadataProcessor.ProcessInitialSyncMetadata(pod, radio, metadata); + } - FixStartMethods(escapePod); + FixStartMethods(escapePod); - return escapePod; + return escapePod; + } + + /// + /// Start() isn't executed for the EscapePod and children (Why? Idk, maybe because it's a scene...) so we call the components here where we have patches in Start. + /// + private static void FixStartMethods(GameObject escapePod) + { + foreach (FMOD_CustomEmitter customEmitter in escapePod.GetComponentsInChildren(true)) + { + customEmitter.Start(); } - /// - /// Start() isn't executed for the EscapePod and children (Why? Idk, maybe because it's a scene...) so we call the components here where we have patches in Start. - /// - private static void FixStartMethods(GameObject escapePod) + foreach (FMOD_StudioEventEmitter studioEventEmitter in escapePod.GetComponentsInChildren(true)) { - foreach (FMOD_CustomEmitter customEmitter in escapePod.GetComponentsInChildren(true)) - { - customEmitter.Start(); - } - - foreach (FMOD_StudioEventEmitter studioEventEmitter in escapePod.GetComponentsInChildren(true)) - { - studioEventEmitter.Start(); - } - - MultiplayerCinematicReference reference = escapePod.AddComponent(); - foreach (PlayerCinematicController controller in escapePod.GetComponentsInChildren(true)) - { - reference.AddController(controller); - } + studioEventEmitter.Start(); } - public bool SpawnsOwnChildren() + MultiplayerCinematicReference reference = escapePod.EnsureComponent(); + foreach (PlayerCinematicController controller in escapePod.GetComponentsInChildren(true)) { - return false; + reference.AddController(controller); } } + public bool SpawnsOwnChildren() => false; } diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs index fa251e7f84..0bd4d55890 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract; using NitroxClient.GameLogic.Spawning.Metadata; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel_Subnautica.DataStructures; @@ -22,20 +21,20 @@ public class WorldEntitySpawnerResolver private readonly Dictionary customSpawnersByTechType = new(); - public WorldEntitySpawnerResolver(EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, Entities entities, SimulationOwnership simulationOwnership) + public WorldEntitySpawnerResolver(EntityMetadataManager entityMetadataManager, PlayerManager playerManager, LocalPlayer localPlayer, Entities entities, SimulationOwnership simulationOwnership) { customSpawnersByTechType[TechType.Crash] = new CrashEntitySpawner(); - customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(entityMetadataManager); + customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(localPlayer); - vehicleWorldEntitySpawner = new(entities); - prefabPlaceholderEntitySpawner = new(defaultEntitySpawner); + vehicleWorldEntitySpawner = new VehicleWorldEntitySpawner(entities); + prefabPlaceholderEntitySpawner = new PrefabPlaceholderEntitySpawner(defaultEntitySpawner); placeholderGroupWorldEntitySpawner = new PlaceholderGroupWorldEntitySpawner(entities, this, defaultEntitySpawner, entityMetadataManager, prefabPlaceholderEntitySpawner); playerWorldEntitySpawner = new PlayerWorldEntitySpawner(playerManager, localPlayer); serializedWorldEntitySpawner = new SerializedWorldEntitySpawner(); - geyserWorldEntitySpawner = new(entities); + geyserWorldEntitySpawner = new GeyserWorldEntitySpawner(entities); reefbackChildEntitySpawner = new ReefbackChildEntitySpawner(); reefbackEntitySpawner = new ReefbackEntitySpawner(reefbackChildEntitySpawner); - creatureRespawnEntitySpawner = new(simulationOwnership); + creatureRespawnEntitySpawner = new CreatureRespawnEntitySpawner(simulationOwnership); } public IWorldEntitySpawner ResolveEntitySpawner(WorldEntity entity) diff --git a/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs index 26e073d9ba..baef65683f 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs @@ -19,7 +19,7 @@ public class WorldEntitySpawner : SyncEntitySpawner private readonly WorldEntitySpawnerResolver worldEntitySpawnResolver; private readonly Dictionary batchCellsById; - public WorldEntitySpawner(EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, Entities entities, SimulationOwnership simulationOwnership) + public WorldEntitySpawner(EntityMetadataManager entityMetadataManager, PlayerManager playerManager, LocalPlayer localPlayer, Entities entities, SimulationOwnership simulationOwnership) { worldEntitySpawnResolver = new WorldEntitySpawnerResolver(entityMetadataManager, playerManager, localPlayer, entities, simulationOwnership); diff --git a/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs similarity index 67% rename from NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs rename to NitroxModel/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs index d1c98a92c3..cf28e3b4e7 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs @@ -12,9 +12,6 @@ namespace NitroxModel.DataStructures.GameLogic.Entities; public class EscapePodWorldEntity : GlobalRootEntity { [DataMember(Order = 1)] - public bool Damaged { get; set; } - - [DataMember(Order = 2)] public List Players { get; set; } [IgnoreConstructor] @@ -25,28 +22,25 @@ protected EscapePodWorldEntity() public EscapePodWorldEntity(NitroxVector3 position, NitroxId id, EntityMetadata metadata) { + Transform = new NitroxTransform(position, NitroxQuaternion.Identity, NitroxVector3.One); Id = id; Metadata = metadata; - Transform = new NitroxTransform(position, NitroxQuaternion.Identity, NitroxVector3.Zero); - Players = new List(); + Players = []; Level = 0; TechType = new NitroxTechType("EscapePod"); - Damaged = true; SpawnedByServer = true; - - ChildEntities = new List(); + ChildEntities = []; } /// Used for deserialization - public EscapePodWorldEntity(bool damaged, List players, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : + public EscapePodWorldEntity(List players, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, spawnedByServer, id, techType, metadata, parentId, childEntities) { - Damaged = damaged; Players = players; } public override string ToString() { - return $"[EscapePodWorldEntity Damaged: {Damaged} {base.ToString()}]"; + return $"[EscapePodWorldEntity Players: [{string.Join(", ", Players)}] {base.ToString()}]"; } } diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs b/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs index 9f2233ea93..bdb0c4b860 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs @@ -20,7 +20,7 @@ namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata [ProtoInclude(60, typeof(ConstructorMetadata))] [ProtoInclude(61, typeof(FlashlightMetadata))] [ProtoInclude(62, typeof(BatteryMetadata))] - [ProtoInclude(63, typeof(RepairedComponentMetadata))] + [ProtoInclude(63, typeof(EscapePodMetadata))] [ProtoInclude(64, typeof(CrafterMetadata))] [ProtoInclude(65, typeof(PlantableMetadata))] [ProtoInclude(66, typeof(CyclopsMetadata))] diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EscapePodMetadata.cs b/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EscapePodMetadata.cs new file mode 100644 index 0000000000..b95bb825d3 --- /dev/null +++ b/NitroxModel/DataStructures/GameLogic/Entities/Metadata/EscapePodMetadata.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.Serialization; +using BinaryPack.Attributes; + +namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata; + +[Serializable] +[DataContract] +public class EscapePodMetadata : EntityMetadata +{ + [DataMember(Order = 1)] + public bool PodRepaired { get; } + + [DataMember(Order = 2)] + public bool RadioRepaired { get; } + + [IgnoreConstructor] + protected EscapePodMetadata() + { + //Constructor for serialization. Has to be "protected" for json serialization. + } + + public EscapePodMetadata(bool podRepaired, bool radioRepaired) + { + PodRepaired = podRepaired; + RadioRepaired = radioRepaired; + } + + public override string ToString() + { + return $"[{nameof(EscapePodMetadata)} - PodRepaired: {PodRepaired}, RadioRepaired: {RadioRepaired}]"; + } +} diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Metadata/RepairedComponentMetadata.cs b/NitroxModel/DataStructures/GameLogic/Entities/Metadata/RepairedComponentMetadata.cs deleted file mode 100644 index 3223e96523..0000000000 --- a/NitroxModel/DataStructures/GameLogic/Entities/Metadata/RepairedComponentMetadata.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Runtime.Serialization; -using BinaryPack.Attributes; - -namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata; - -[Serializable] -[DataContract] -public class RepairedComponentMetadata : EntityMetadata -{ - [DataMember(Order = 1)] - public NitroxTechType TechType { get; } - - [IgnoreConstructor] - protected RepairedComponentMetadata() - { - //Constructor for serialization. Has to be "protected" for json serialization. - } - - public RepairedComponentMetadata(NitroxTechType techType) - { - TechType = techType; - } - - public override string ToString() - { - return $"[RepairedComponentMetadata - TechType: {TechType}]"; - } -} diff --git a/NitroxPatcher/Patches/Dynamic/EscapePod_Awake_Patch.cs b/NitroxPatcher/Patches/Dynamic/EscapePod_Awake_Patch.cs index 40247b8c48..f52c1d4559 100644 --- a/NitroxPatcher/Patches/Dynamic/EscapePod_Awake_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/EscapePod_Awake_Patch.cs @@ -10,6 +10,6 @@ public sealed partial class EscapePod_Awake_Patch : NitroxPatch, IDynamicPatch public static bool Prefix(EscapePod __instance) { - return !EscapePodWorldEntitySpawner.SURPRESS_ESCAPE_POD_AWAKE_METHOD; + return !EscapePodWorldEntitySpawner.SuppressEscapePodAwakeMethod; } } diff --git a/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs b/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs index 465a18ef78..09ccbf72ae 100644 --- a/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs @@ -1,21 +1,22 @@ using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.GameLogic.Spawning.Metadata; using NitroxModel.DataStructures.GameLogic.Entities.Metadata; using NitroxModel.DataStructures; using NitroxModel.Helper; -using NitroxModel_Subnautica.DataStructures; namespace NitroxPatcher.Patches.Dynamic; public sealed partial class EscapePod_OnRepair_Patch : NitroxPatch, IDynamicPatch { - public static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePod t) => t.OnRepair()); + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePod t) => t.OnRepair()); public static void Prefix(EscapePod __instance) { - if (__instance.TryGetIdOrWarn(out NitroxId id)) + if (__instance.TryGetIdOrWarn(out NitroxId id) && + Resolve().TryExtract(__instance, out EntityMetadata metadata)) { - Resolve().BroadcastMetadataUpdate(id, new RepairedComponentMetadata(TechType.EscapePod.ToDto())); + Resolve().BroadcastMetadataUpdate(id, metadata); } } } diff --git a/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs b/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs index 478f541f8c..eb0a0fbc8b 100644 --- a/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs @@ -1,9 +1,10 @@ using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.GameLogic.Spawning.Metadata; +using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic.Entities.Metadata; using NitroxModel.Helper; -using NitroxModel_Subnautica.DataStructures; namespace NitroxPatcher.Patches.Dynamic; @@ -13,9 +14,11 @@ public sealed partial class Radio_OnRepair_Patch : NitroxPatch, IDynamicPatch public static void Prefix(Radio __instance) { - if (__instance.TryGetIdOrWarn(out NitroxId id)) + if (__instance.TryGetComponentInParent(out EscapePod pod) && + pod.TryGetIdOrWarn(out NitroxId id) && + Resolve().TryExtract(pod, out EntityMetadata metadata)) { - Resolve().BroadcastMetadataUpdate(id, new RepairedComponentMetadata(TechType.Radio.ToDto())); + Resolve().BroadcastMetadataUpdate(id, metadata); } } } diff --git a/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_HandleInput_Patch.cs b/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_HandleInput_Patch.cs index 569891c1ad..5096d149a2 100644 --- a/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_HandleInput_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_HandleInput_Patch.cs @@ -52,6 +52,7 @@ private static bool HandleButtonHeld(uGUI_SceneIntro instance) if (!uGUI_SceneIntro_IntroSequence_Patch.IsWaitingForPartner && Resolve().IntroCinematicMode == IntroCinematicMode.SINGLEPLAYER) // Skipping intro alone { + EscapePod.main.DamageRadio(); // This is a special edge case where neither our EscapePodMetadataProcessor nor the intro is damaging the radio. Therefore we do it manually. uGUI_SceneIntro_IntroSequence_Patch.SkipLocalCinematic(instance); return false; } diff --git a/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs b/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs index 9f558fc3c5..79eba60da1 100644 --- a/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs @@ -112,9 +112,11 @@ private static bool IsRemoteCinematicReady(uGUI_SceneIntro uGuiSceneIntro) return true; } - if (GameModeUtils.currentGameMode.HasFlag(GameModeOption.Creative)) + // Skipping intro if creative like in normal SN or in debug configuration + if (!NitroxEnvironment.IsReleaseMode || + GameModeUtils.currentGameMode.HasFlag(GameModeOption.Creative)) { - SkipLocalCinematic(uGuiSceneIntro); // Skipping intro if Creative like in normal SN + SkipLocalCinematic(uGuiSceneIntro); return false; } @@ -199,8 +201,18 @@ private static void EndRemoteCinematic() public static void SkipLocalCinematic(uGUI_SceneIntro uGuiSceneIntro) { + // EscapePod.DamageRadio() is called by GuiSceneIntro.Stop(true) but is undesired. We revert it here + LiveMixin radioLiveMixin = EscapePod.main.radioSpawner.spawnedObj.GetComponent().liveMixin; + float radioHealthBefore = radioLiveMixin.health; + uGuiSceneIntro.Stop(true); + radioLiveMixin.health = radioHealthBefore; + if (radioLiveMixin.IsFullHealth()) + { + Object.Destroy(radioLiveMixin.loopingDamageEffectObj); + } + Transform introFireHolder = EscapePod.main.transform.Find("Intro"); if (introFireHolder) // Can be null if called very early { diff --git a/NitroxPatcher/Patches/Persistent/MainGameController_ShouldPlayIntro_Patch.cs b/NitroxPatcher/Patches/Persistent/MainGameController_ShouldPlayIntro_Patch.cs deleted file mode 100644 index 99a125e18a..0000000000 --- a/NitroxPatcher/Patches/Persistent/MainGameController_ShouldPlayIntro_Patch.cs +++ /dev/null @@ -1,18 +0,0 @@ -#if DEBUG -using System.Reflection; -using NitroxModel.Helper; -using NitroxPatcher.Patches.Dynamic; - -namespace NitroxPatcher.Patches.Persistent; - -public sealed partial class MainGameController_ShouldPlayIntro_Patch : NitroxPatch, IPersistentPatch -{ - private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => MainGameController.ShouldPlayIntro()); - - public static void Postfix(ref bool __result) - { - __result = false; - uGUI_SceneIntro_IntroSequence_Patch.SkipLocalCinematic(uGUI.main.intro); - } -} -#endif diff --git a/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs b/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs index 6d9fa4b4fb..d7844308aa 100644 --- a/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs @@ -44,6 +44,11 @@ public override void Process(PlayerJoiningMultiplayerSession packet, INitroxConn Player player = playerManager.PlayerConnected(connection, packet.ReservationKey, out bool wasBrandNewPlayer); NitroxId assignedEscapePodId = world.EscapePodManager.AssignPlayerToEscapePod(player.Id, out Optional newlyCreatedEscapePod); + if (wasBrandNewPlayer) + { + player.SubRootId = assignedEscapePodId; + } + if (newlyCreatedEscapePod.HasValue) { SpawnEntities spawnNewEscapePod = new(newlyCreatedEscapePod.Value); diff --git a/NitroxServer/GameLogic/EscapePodManager.cs b/NitroxServer/GameLogic/EscapePodManager.cs index a7ba742f72..46f8e58bb3 100644 --- a/NitroxServer/GameLogic/EscapePodManager.cs +++ b/NitroxServer/GameLogic/EscapePodManager.cs @@ -3,154 +3,153 @@ using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; +using NitroxModel.DataStructures.GameLogic.Entities.Metadata; using NitroxModel.DataStructures.Unity; using NitroxModel.DataStructures.Util; using NitroxServer.GameLogic.Entities; -namespace NitroxServer.GameLogic +namespace NitroxServer.GameLogic; + +public class EscapePodManager { - public class EscapePodManager - { - public const int PLAYERS_PER_ESCAPEPOD = 50; - public const int ESCAPE_POD_X_OFFSET = 40; + private const int PLAYERS_PER_ESCAPEPOD = 50; - private readonly EntityRegistry entityRegistry; - private readonly ThreadSafeDictionary escapePodsByPlayerId = new ThreadSafeDictionary(); - private EscapePodWorldEntity podForNextPlayer; - private readonly string seed; + private readonly EntityRegistry entityRegistry; + private readonly ThreadSafeDictionary escapePodsByPlayerId = new(); + private EscapePodWorldEntity podForNextPlayer; + private readonly string seed; - private readonly RandomStartGenerator randomStart; + private readonly RandomStartGenerator randomStart; - public EscapePodManager(EntityRegistry entityRegistry, RandomStartGenerator randomStart, string seed) - { - this.seed = seed; - this.randomStart = randomStart; - this.entityRegistry = entityRegistry; + public EscapePodManager(EntityRegistry entityRegistry, RandomStartGenerator randomStart, string seed) + { + this.seed = seed; + this.randomStart = randomStart; + this.entityRegistry = entityRegistry; + + List escapePods = entityRegistry.GetEntities(); + + InitializePodForNextPlayer(escapePods); + InitializeEscapePodsByPlayerId(escapePods); + } - List escapePods = entityRegistry.GetEntities(); + public NitroxId AssignPlayerToEscapePod(ushort playerId, out Optional newlyCreatedPod) + { + newlyCreatedPod = Optional.Empty; - InitializePodForNextPlayer(escapePods); - InitializeEscapePodsByPlayerId(escapePods); + if (escapePodsByPlayerId.TryGetValue(playerId, out EscapePodWorldEntity podEntity)) + { + return podEntity.Id; } - public NitroxId AssignPlayerToEscapePod(ushort playerId, out Optional newlyCreatedPod) + if (IsPodFull(podForNextPlayer)) { - newlyCreatedPod = Optional.Empty; + newlyCreatedPod = Optional.Of(CreateNewEscapePod()); + podForNextPlayer = newlyCreatedPod.Value; + } - if (escapePodsByPlayerId.ContainsKey(playerId)) - { - return escapePodsByPlayerId[playerId].Id; - } + podForNextPlayer.Players.Add(playerId); + escapePodsByPlayerId[playerId] = podForNextPlayer; - if (IsPodFull(podForNextPlayer)) - { - newlyCreatedPod = Optional.Of(CreateNewEscapePod()); - podForNextPlayer = newlyCreatedPod.Value; - } + return podForNextPlayer.Id; + } - podForNextPlayer.Players.Add(playerId); - escapePodsByPlayerId[playerId] = podForNextPlayer; + private EscapePodWorldEntity CreateNewEscapePod() + { + EscapePodWorldEntity escapePod = new(GetStartPosition(), new NitroxId(), new EscapePodMetadata(false, false)); - return podForNextPlayer.Id; - } + escapePod.ChildEntities.Add(new PrefabChildEntity(new NitroxId(), "5c06baec-0539-4f26-817d-78443548cc52", new NitroxTechType("Radio"), 0, null, escapePod.Id)); + escapePod.ChildEntities.Add(new PrefabChildEntity(new NitroxId(), "c0175cf7-0b6a-4a1d-938f-dad0dbb6fa06", new NitroxTechType("MedicalCabinet"), 0, null, escapePod.Id)); + escapePod.ChildEntities.Add(new PrefabChildEntity(new NitroxId(), "9f16d82b-11f4-4eeb-aedf-f2fa2bfca8e3", new NitroxTechType("Fabricator"), 0, null, escapePod.Id)); + escapePod.ChildEntities.Add(new InventoryEntity(0, new NitroxId(), new NitroxTechType("SmallStorage"), null, escapePod.Id, [])); - private EscapePodWorldEntity CreateNewEscapePod() - { - EscapePodWorldEntity escapePod = new EscapePodWorldEntity(GetStartPosition(), new NitroxId(), null); + entityRegistry.AddOrUpdate(escapePod); - escapePod.ChildEntities.Add(new PrefabChildEntity(new NitroxId(), "5c06baec-0539-4f26-817d-78443548cc52", new NitroxTechType("Radio"), 0, null, escapePod.Id)); - escapePod.ChildEntities.Add(new PrefabChildEntity(new NitroxId(), "c0175cf7-0b6a-4a1d-938f-dad0dbb6fa06", new NitroxTechType("MedicalCabinet"), 0, null, escapePod.Id)); - escapePod.ChildEntities.Add(new PrefabChildEntity(new NitroxId(), "9f16d82b-11f4-4eeb-aedf-f2fa2bfca8e3", new NitroxTechType("Fabricator"), 0, null, escapePod.Id)); - escapePod.ChildEntities.Add(new InventoryEntity(0, new NitroxId(), new NitroxTechType("SmallStorage"), null, escapePod.Id, new List())); + return escapePod; + } - entityRegistry.AddOrUpdate(escapePod); + private NitroxVector3 GetStartPosition() + { + List escapePods = entityRegistry.GetEntities(); - return escapePod; - } + Random rnd = new(seed.GetHashCode()); + NitroxVector3 position = randomStart.GenerateRandomStartPosition(rnd); - private NitroxVector3 GetStartPosition() + if (escapePods.Count == 0) { - List escapePods = entityRegistry.GetEntities(); - - Random rnd = new Random(seed.GetHashCode()); - NitroxVector3 position = randomStart.GenerateRandomStartPosition(rnd); + return position; + } - if (escapePods.Count == 0) + foreach (EscapePodWorldEntity escapePodModel in escapePods) + { + if (position == NitroxVector3.Zero) { - return position; + break; } - foreach (EscapePodWorldEntity escapePodModel in escapePods) + if (escapePodModel.Transform.Position != position) { - if (position == NitroxVector3.Zero) - { - break; - } - - if (escapePodModel.Transform.Position != position) - { - return position; - } + return position; } + } - float xNormed = (float)rnd.NextDouble(); - float zNormed = (float)rnd.NextDouble(); + float xNormed = (float)rnd.NextDouble(); + float zNormed = (float)rnd.NextDouble(); - if (xNormed < 0.3f) - { - xNormed = 0.3f; - } - else if (xNormed > 0.7f) - { - xNormed = 0.7f; - } + if (xNormed < 0.3f) + { + xNormed = 0.3f; + } + else if (xNormed > 0.7f) + { + xNormed = 0.7f; + } - if (zNormed < 0.3f) - { - zNormed = 0.3f; - } - else if (zNormed > 0.7f) - { - zNormed = 0.7f; - } + if (zNormed < 0.3f) + { + zNormed = 0.3f; + } + else if (zNormed > 0.7f) + { + zNormed = 0.7f; + } - NitroxVector3 lastEscapePodPosition = escapePods[escapePods.Count - 1].Transform.Position; + NitroxVector3 lastEscapePodPosition = escapePods[escapePods.Count - 1].Transform.Position; - float x = xNormed * 100 - 50; - float z = zNormed * 100 - 50; + float x = xNormed * 100 - 50; + float z = zNormed * 100 - 50; - return new NitroxVector3(lastEscapePodPosition.X + x, 0, lastEscapePodPosition.Z + z); - } + return new NitroxVector3(lastEscapePodPosition.X + x, 0, lastEscapePodPosition.Z + z); + } - private void InitializePodForNextPlayer(List escapePods) + private void InitializePodForNextPlayer(List escapePods) + { + foreach (EscapePodWorldEntity pod in escapePods) { - foreach (EscapePodWorldEntity pod in escapePods) + if (!IsPodFull(pod)) { - if (!IsPodFull(pod)) - { - podForNextPlayer = pod; - return; - } + podForNextPlayer = pod; + return; } - - podForNextPlayer = CreateNewEscapePod(); } - private void InitializeEscapePodsByPlayerId(List escapePods) + podForNextPlayer = CreateNewEscapePod(); + } + + private void InitializeEscapePodsByPlayerId(List escapePods) + { + escapePodsByPlayerId.Clear(); + foreach (EscapePodWorldEntity pod in escapePods) { - escapePodsByPlayerId.Clear(); - foreach (EscapePodWorldEntity pod in escapePods) + foreach (ushort playerId in pod.Players) { - foreach (ushort playerId in pod.Players) - { - escapePodsByPlayerId[playerId] = pod; - } + escapePodsByPlayerId[playerId] = pod; } } + } - private static bool IsPodFull(EscapePodWorldEntity pod) - { - return pod.Players.Count >= PLAYERS_PER_ESCAPEPOD; - } + private static bool IsPodFull(EscapePodWorldEntity pod) + { + return pod.Players.Count >= PLAYERS_PER_ESCAPEPOD; } }