From ff12bf9832116a886a536ca18c94c00ccb546f7d Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:50:46 +0100 Subject: [PATCH 01/10] Remove not used field in EscapePodWorldEntity --- Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs | 1 - .../DataStructures/GameLogic/Entities/EscapePodEntity.cs | 9 ++------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs b/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs index ae40802ded..794ca22270 100644 --- a/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs +++ b/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs @@ -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: diff --git a/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs index d1c98a92c3..63922b2838 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.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] @@ -31,22 +28,20 @@ public EscapePodWorldEntity(NitroxVector3 position, NitroxId id, EntityMetadata Players = new List(); Level = 0; TechType = new NitroxTechType("EscapePod"); - Damaged = true; SpawnedByServer = true; ChildEntities = new List(); } /// 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()}]"; } } From d5c16e0a21ca52dc1d5babb386a90f0286f2203a Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:54:54 +0100 Subject: [PATCH 02/10] Apply current coding standard to touched code --- .../RepairedComponentMetadataProcessor.cs | 8 +- .../EscapePodWorldEntitySpawner.cs | 128 +++++++++--------- .../GameLogic/Entities/EscapePodEntity.cs | 7 +- .../Patches/Dynamic/EscapePod_Awake_Patch.cs | 2 +- 4 files changed, 67 insertions(+), 78 deletions(-) diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs index a7be29fa8f..d9ba44e63d 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs @@ -8,17 +8,13 @@ public class RepairedComponentMetadataProcessor : EntityMetadataProcessor(); - - if (radio) + if (gameObject.TryGetComponent(out Radio radio)) { radio.liveMixin.health = radio.liveMixin.maxHealth; radio.repairNotification.Play(); } - EscapePod pod = gameObject.GetComponent(); - - if (pod) + if (gameObject.TryGetComponent(out EscapePod pod)) { pod.liveMixin.health = pod.liveMixin.maxHealth; pod.animator.SetFloat("lifepod_damage", 1.0f); diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs index dd4f711a95..fb9248c72f 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs @@ -7,97 +7,91 @@ using NitroxModel_Subnautica.DataStructures; using UnityEngine; -namespace NitroxClient.GameLogic.Spawning.WorldEntities +namespace NitroxClient.GameLogic.Spawning.WorldEntities; + +public class EscapePodWorldEntitySpawner : IWorldEntitySpawner { - public class EscapePodWorldEntitySpawner : IWorldEntitySpawner + private readonly EntityMetadataManager entityMetadataManager; + + public EscapePodWorldEntitySpawner(EntityMetadataManager entityMetadataManager) { - private EntityMetadataManager entityMetadataManager; + this.entityMetadataManager = entityMetadataManager; + } - public EscapePodWorldEntitySpawner(EntityMetadataManager entityMetadataManager) + /* + * 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; + + 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; - } - - SURPRESS_ESCAPE_POD_AWAKE_METHOD = true; + GameObject escapePod = CreateNewEscapePod(escapePodEntity); - GameObject escapePod = CreateNewEscapePod(escapePodEntity); + SuppressEscapePodAwakeMethod = false; - SURPRESS_ESCAPE_POD_AWAKE_METHOD = false; + result.Set(Optional.Of(escapePod)); + } - result.Set(Optional.Of(escapePod)); - } + 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; - 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); + Object.DestroyImmediate(escapePod.GetComponent()); // if template has a pre-existing NitroxEntity, remove. + NitroxEntity.SetNewId(escapePod, escapePodEntity.Id); - GameObject escapePod = EscapePod.main.gameObject; - UnityEngine.Component.DestroyImmediate(escapePod.GetComponent()); // if template has a pre-existing NitroxEntity, remove. - NitroxEntity.SetNewId(escapePod, escapePodEntity.Id); + entityMetadataManager.ApplyMetadata(escapePod, escapePodEntity.Metadata); - entityMetadataManager.ApplyMetadata(escapePod, escapePodEntity.Metadata); + if (escapePod.TryGetComponent(out Rigidbody rigidbody)) + { + rigidbody.constraints = RigidbodyConstraints.FreezeAll; + } + else + { + Log.Error("Escape pod did not have a rigid body!"); + } - Rigidbody rigidbody = escapePod.GetComponent(); - if (rigidbody != null) - { - rigidbody.constraints = RigidbodyConstraints.FreezeAll; - } - else - { - Log.Error("Escape pod did not have a rigid body!"); - } + escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); - escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); + 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/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs index 63922b2838..cf28e3b4e7 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs @@ -22,15 +22,14 @@ 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"); SpawnedByServer = true; - - ChildEntities = new List(); + ChildEntities = []; } /// Used for deserialization 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; } } From e42b7588cda62f465b316f55afd924ce5c08f0bc Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:12:20 +0100 Subject: [PATCH 03/10] Fix joining singleplayer not possible --- .../MainGameController_ShouldPlayIntro_Patch.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename NitroxPatcher/Patches/{Persistent => Dynamic}/MainGameController_ShouldPlayIntro_Patch.cs (78%) diff --git a/NitroxPatcher/Patches/Persistent/MainGameController_ShouldPlayIntro_Patch.cs b/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs similarity index 78% rename from NitroxPatcher/Patches/Persistent/MainGameController_ShouldPlayIntro_Patch.cs rename to NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs index 99a125e18a..00cb22e7ad 100644 --- a/NitroxPatcher/Patches/Persistent/MainGameController_ShouldPlayIntro_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs @@ -1,11 +1,10 @@ #if DEBUG using System.Reflection; using NitroxModel.Helper; -using NitroxPatcher.Patches.Dynamic; -namespace NitroxPatcher.Patches.Persistent; +namespace NitroxPatcher.Patches.Dynamic; -public sealed partial class MainGameController_ShouldPlayIntro_Patch : NitroxPatch, IPersistentPatch +public sealed partial class MainGameController_ShouldPlayIntro_Patch : NitroxPatch, IDynamicPatch { private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => MainGameController.ShouldPlayIntro()); From d70fea62c6b930303b65a2ad2aa3dc86cadced57 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:55:31 +0100 Subject: [PATCH 04/10] Add debug drawer for UWE.Event --- Nitrox.sln.DotSettings | 1 + .../Debuggers/Drawer/DrawerManager.cs | 2 + .../Drawer/Subnautica/UWEEventDrawer.cs | 76 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 NitroxClient/Debuggers/Drawer/Subnautica/UWEEventDrawer.cs diff --git a/Nitrox.sln.DotSettings b/Nitrox.sln.DotSettings index 44bad62e91..cb2864e284 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" /> 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); + } + } + } +} From 0035aadce41d51409a155070028a6d1bd7311b55 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:38:23 +0100 Subject: [PATCH 05/10] Fix EscapePod lighting being wrong when joining --- .../PlayerPositionInitialSyncProcessor.cs | 25 +++++++++++-------- ...layerJoiningMultiplayerSessionProcessor.cs | 5 ++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs index 30a093f0d0..4b304549f5 100644 --- a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs @@ -37,17 +37,17 @@ 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) { yield return Terrain.WaitForWorldLoad(); + Player.main.cinematicModeActive = false; yield break; } @@ -56,19 +56,24 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW { Log.Error($"Could not spawn player into subroot with id: {subRootId.Value}"); yield return Terrain.WaitForWorldLoad(); + Player.main.cinematicModeActive = false; 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(); + Player.main.cinematicModeActive = false; + } + 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/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); From 072b5e584c56d255cf5c2e7f4827cacd3e9d6561 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Mon, 13 Jan 2025 01:05:57 +0100 Subject: [PATCH 06/10] Refactor and fix EscapePodMetadata handling --- .../Serialization/WorldPersistenceTest.cs | 18 +- Nitrox.sln.DotSettings | 1 + .../Processor/EscapePodMetadataProcessor.cs | 74 +++++++ .../RepairedComponentMetadataProcessor.cs | 25 --- .../EscapePodWorldEntitySpawner.cs | 26 ++- .../WorldEntitySpawnerResolver.cs | 10 +- ...pePodEntity.cs => EscapePodWorldEntity.cs} | 0 .../Entities/Metadata/EntityMetadata.cs | 2 +- .../Entities/Metadata/EscapePodMetadata.cs | 33 +++ .../Metadata/RepairedComponentMetadata.cs | 29 --- .../Dynamic/EscapePod_OnRepair_Patch.cs | 8 +- .../Patches/Dynamic/Radio_OnRepair_Patch.cs | 7 +- NitroxServer/GameLogic/EscapePodManager.cs | 205 +++++++++--------- 13 files changed, 249 insertions(+), 189 deletions(-) create mode 100644 NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs delete mode 100644 NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs rename NitroxModel/DataStructures/GameLogic/Entities/{EscapePodEntity.cs => EscapePodWorldEntity.cs} (100%) create mode 100644 NitroxModel/DataStructures/GameLogic/Entities/Metadata/EscapePodMetadata.cs delete mode 100644 NitroxModel/DataStructures/GameLogic/Entities/Metadata/RepairedComponentMetadata.cs diff --git a/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs b/Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs index 794ca22270..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); @@ -410,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); @@ -448,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 cb2864e284..50b2ca91c5 100644 --- a/Nitrox.sln.DotSettings +++ b/Nitrox.sln.DotSettings @@ -320,6 +320,7 @@ True True True + True True True True diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs new file mode 100644 index 0000000000..c28f4202f4 --- /dev/null +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs @@ -0,0 +1,74 @@ +using NitroxClient.Communication; +using NitroxClient.GameLogic.FMOD; +using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract; +using NitroxModel.DataStructures.GameLogic.Entities.Metadata; +using NitroxModel.Packets; +using UnityEngine; + +namespace NitroxClient.GameLogic.Spawning.Metadata.Processor; + +public class EscapePodMetadataProcessor : EntityMetadataProcessor +{ + 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.radioSpawner.spawnedObj.TryGetComponent(out Radio radio)) + { + Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get Radio from EscapePod."); + return; + } + + if (!pod.liveMixin.IsFullHealth() && metadata.PodRepaired) + { + pod.OnRepair(); + } + + if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired) + { + radio.OnRepair(); + } + + ProcessInitialSyncMetadata(pod, radio, metadata); + } + + public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, EscapePodMetadata metadata) + { + using FMODSoundSuppressor soundSuppressor = FMODSystem.SuppressSubnauticaSounds(); + using PacketSuppressor packetSuppressor = PacketSuppressor.Suppress(); + + if (metadata.PodRepaired) + { + pod.liveMixin.health = pod.liveMixin.maxHealth; + pod.healthScalar = 1; + pod.damageEffectsShowing = true; + + using (FMODSystem.SuppressSubnauticaSounds()) + { + pod.UpdateDamagedEffects(); + } + 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; + 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 d9ba44e63d..0000000000 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/RepairedComponentMetadataProcessor.cs +++ /dev/null @@ -1,25 +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) - { - if (gameObject.TryGetComponent(out Radio radio)) - { - radio.liveMixin.health = radio.liveMixin.maxHealth; - radio.repairNotification.Play(); - } - - if (gameObject.TryGetComponent(out EscapePod 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 fb9248c72f..4b04d0bbe5 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs @@ -1,8 +1,9 @@ using System.Collections; -using NitroxClient.GameLogic.Spawning.Metadata; +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 UnityEngine; @@ -11,13 +12,6 @@ namespace NitroxClient.GameLogic.Spawning.WorldEntities; public class EscapePodWorldEntitySpawner : IWorldEntitySpawner { - private readonly EntityMetadataManager entityMetadataManager; - - public EscapePodWorldEntitySpawner(EntityMetadataManager entityMetadataManager) - { - this.entityMetadataManager = entityMetadataManager; - } - /* * 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 @@ -49,12 +43,11 @@ private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity) // 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(); Object.DestroyImmediate(escapePod.GetComponent()); // if template has a pre-existing NitroxEntity, remove. NitroxEntity.SetNewId(escapePod, escapePodEntity.Id); - entityMetadataManager.ApplyMetadata(escapePod, escapePodEntity.Metadata); - if (escapePod.TryGetComponent(out Rigidbody rigidbody)) { rigidbody.constraints = RigidbodyConstraints.FreezeAll; @@ -66,6 +59,19 @@ private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity) escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); + pod.ForceSkyApplier(); + pod.escapePodCinematicControl.StopAll(); + + if (escapePodEntity.Metadata is EscapePodMetadata metadata) + { + Radio radio = pod.radioSpawner.spawnedObj.GetComponent(); + EscapePodMetadataProcessor.ProcessInitialSyncMetadata(pod, radio, metadata); + } + else + { + Log.Error($"[{nameof(EscapePodWorldEntitySpawner)}] Metadata was not of type {nameof(EscapePodMetadata)}."); + } + FixStartMethods(escapePod); return escapePod; diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs index fa251e7f84..0f90a03de9 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs @@ -25,17 +25,17 @@ public class WorldEntitySpawnerResolver public WorldEntitySpawnerResolver(EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, Entities entities, SimulationOwnership simulationOwnership) { customSpawnersByTechType[TechType.Crash] = new CrashEntitySpawner(); - customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(entityMetadataManager); + customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(); - 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/NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs similarity index 100% rename from NitroxModel/DataStructures/GameLogic/Entities/EscapePodEntity.cs rename to NitroxModel/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs 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_OnRepair_Patch.cs b/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs index 465a18ef78..ea0adac101 100644 --- a/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs @@ -3,19 +3,19 @@ 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) && + __instance.radioSpawner.spawnedObj.TryGetComponent(out Radio radio)) { - Resolve().BroadcastMetadataUpdate(id, new RepairedComponentMetadata(TechType.EscapePod.ToDto())); + Resolve().BroadcastMetadataUpdate(id, new EscapePodMetadata(true, radio.liveMixin.IsFullHealth())); } } } diff --git a/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs b/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs index 478f541f8c..af9bc85f76 100644 --- a/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs @@ -1,9 +1,9 @@ using System.Reflection; using NitroxClient.GameLogic; +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 +13,10 @@ 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().BroadcastMetadataUpdate(id, new RepairedComponentMetadata(TechType.Radio.ToDto())); + Resolve().BroadcastMetadataUpdate(id, new EscapePodMetadata(pod.liveMixin.IsFullHealth(), true)); } } } 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; } } From e4ee3ec26e950e4ba60295c7e1300c4ec7ca2b5f Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:07:12 +0100 Subject: [PATCH 07/10] Fix smoke being visible on joining --- .../Metadata/Processor/EscapePodMetadataProcessor.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs index c28f4202f4..759c9450f8 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs @@ -46,11 +46,9 @@ public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, Escape pod.liveMixin.health = pod.liveMixin.maxHealth; pod.healthScalar = 1; pod.damageEffectsShowing = true; - - using (FMODSystem.SuppressSubnauticaSounds()) - { 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 From 350cf743fc68820e8a2822c231a87244d30fa9e5 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:06:11 +0100 Subject: [PATCH 08/10] Fix escapePod sparks in intro cinematic and black smoke But now repairing when online is broken --- NitroxClient/GameLogic/Entities.cs | 6 ++--- .../Processor/EscapePodMetadataProcessor.cs | 25 +++++++++++-------- .../EscapePodWorldEntitySpawner.cs | 22 ++++++++++++---- .../WorldEntitySpawnerResolver.cs | 5 ++-- .../GameLogic/Spawning/WorldEntitySpawner.cs | 2 +- ...ainGameController_ShouldPlayIntro_Patch.cs | 1 + .../uGUI_SceneIntro_HandleInput_Patch.cs | 1 + .../uGUI_SceneIntro_IntroSequence_Patch.cs | 7 ++++++ 8 files changed, 46 insertions(+), 23 deletions(-) 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/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs index 759c9450f8..e323f971c4 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs @@ -1,8 +1,5 @@ -using NitroxClient.Communication; -using NitroxClient.GameLogic.FMOD; using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract; using NitroxModel.DataStructures.GameLogic.Entities.Metadata; -using NitroxModel.Packets; using UnityEngine; namespace NitroxClient.GameLogic.Spawning.Metadata.Processor; @@ -23,30 +20,33 @@ public override void ProcessMetadata(GameObject gameObject, EscapePodMetadata me return; } + bool repairedSomething = false; if (!pod.liveMixin.IsFullHealth() && metadata.PodRepaired) { - pod.OnRepair(); + pod.OnRepair(); // Only plays visuals and sounds + repairedSomething = true; } if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired) { - radio.OnRepair(); + radio.OnRepair(); // Only plays visuals and sounds + repairedSomething = true; } - ProcessInitialSyncMetadata(pod, radio, metadata); + if (repairedSomething) + { + ProcessInitialSyncMetadata(pod, radio, metadata); + } } public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, EscapePodMetadata metadata) { - using FMODSoundSuppressor soundSuppressor = FMODSystem.SuppressSubnauticaSounds(); - using PacketSuppressor packetSuppressor = PacketSuppressor.Suppress(); - if (metadata.PodRepaired) { pod.liveMixin.health = pod.liveMixin.maxHealth; pod.healthScalar = 1; pod.damageEffectsShowing = true; - pod.UpdateDamagedEffects(); + 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); @@ -62,7 +62,10 @@ public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, Escape if (metadata.RadioRepaired) { radio.liveMixin.health = radio.liveMixin.maxHealth; - Object.Destroy(radio.liveMixin.loopingDamageEffectObj); + if (radio.liveMixin.loopingDamageEffectObj) + { + Object.Destroy(radio.liveMixin.loopingDamageEffectObj); + } } else { diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs index 4b04d0bbe5..52034d7e3c 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs @@ -1,4 +1,6 @@ using System.Collections; +using NitroxClient.Communication; +using NitroxClient.GameLogic.FMOD; using NitroxClient.GameLogic.Spawning.Metadata.Processor; using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.CinematicController; @@ -6,6 +8,8 @@ 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; @@ -19,6 +23,14 @@ public class EscapePodWorldEntitySpawner : IWorldEntitySpawner */ public static bool SuppressEscapePodAwakeMethod; + private readonly LocalPlayer localPlayer; + + public EscapePodWorldEntitySpawner(LocalPlayer localPlayer) + { + this.localPlayer = localPlayer; + } + + public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) { if (entity is not EscapePodWorldEntity escapePodEntity) @@ -62,15 +74,15 @@ private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity) pod.ForceSkyApplier(); pod.escapePodCinematicControl.StopAll(); - if (escapePodEntity.Metadata is EscapePodMetadata metadata) + // 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(); + Radio radio = pod.radioSpawner.spawnedObj.GetComponent(); EscapePodMetadataProcessor.ProcessInitialSyncMetadata(pod, radio, metadata); } - else - { - Log.Error($"[{nameof(EscapePodWorldEntitySpawner)}] Metadata was not of type {nameof(EscapePodMetadata)}."); - } FixStartMethods(escapePod); diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs index 0f90a03de9..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,10 +21,10 @@ 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(); + customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(localPlayer); vehicleWorldEntitySpawner = new VehicleWorldEntitySpawner(entities); prefabPlaceholderEntitySpawner = new PrefabPlaceholderEntitySpawner(defaultEntitySpawner); 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/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs b/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs index 00cb22e7ad..028e12eace 100644 --- a/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs @@ -11,6 +11,7 @@ public sealed partial class MainGameController_ShouldPlayIntro_Patch : NitroxPat public static void Postfix(ref bool __result) { __result = false; + EscapePod.main.DamageRadio(); // For explanation see similar code in uGUI_SceneIntro_HandleInput_Patch. uGUI_SceneIntro_IntroSequence_Patch.SkipLocalCinematic(uGUI.main.intro); } } 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..d7fa337988 100644 --- a/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs @@ -199,8 +199,15 @@ 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; + Object.Destroy(radioLiveMixin.loopingDamageEffectObj); + Transform introFireHolder = EscapePod.main.transform.Find("Intro"); if (introFireHolder) // Can be null if called very early { From 834e8c08a94fca2b3a82a137fe3831a3ec0bf2b7 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:57:40 +0100 Subject: [PATCH 09/10] Fix escapePod metadata handling when playing --- .../Processor/EscapePodMetadataProcessor.cs | 28 +++++++++---------- ...ainGameController_ShouldPlayIntro_Patch.cs | 18 ------------ .../uGUI_SceneIntro_IntroSequence_Patch.cs | 11 ++++++-- 3 files changed, 22 insertions(+), 35 deletions(-) delete mode 100644 NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs index e323f971c4..a91bd126ee 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs @@ -6,6 +6,7 @@ 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)) @@ -14,31 +15,30 @@ public override void ProcessMetadata(GameObject gameObject, EscapePodMetadata me return; } - if (!pod.radioSpawner.spawnedObj.TryGetComponent(out Radio radio)) - { - Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get Radio from EscapePod."); - return; - } - - bool repairedSomething = false; if (!pod.liveMixin.IsFullHealth() && metadata.PodRepaired) { - pod.OnRepair(); // Only plays visuals and sounds - repairedSomething = true; + pod.liveMixin.health = pod.liveMixin.maxHealth; + pod.healthScalar = 1; + pod.damageEffectsShowing = true; + pod.UpdateDamagedEffects(); + pod.OnRepair(); } - if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired) + if (!pod.radioSpawner.spawnedObj.TryGetComponent(out Radio radio)) { - radio.OnRepair(); // Only plays visuals and sounds - repairedSomething = true; + Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get Radio from EscapePod."); + return; } - if (repairedSomething) + if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired) { - ProcessInitialSyncMetadata(pod, radio, metadata); + 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) diff --git a/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs b/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs deleted file mode 100644 index 028e12eace..0000000000 --- a/NitroxPatcher/Patches/Dynamic/MainGameController_ShouldPlayIntro_Patch.cs +++ /dev/null @@ -1,18 +0,0 @@ -#if DEBUG -using System.Reflection; -using NitroxModel.Helper; - -namespace NitroxPatcher.Patches.Dynamic; - -public sealed partial class MainGameController_ShouldPlayIntro_Patch : NitroxPatch, IDynamicPatch -{ - private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => MainGameController.ShouldPlayIntro()); - - public static void Postfix(ref bool __result) - { - __result = false; - EscapePod.main.DamageRadio(); // For explanation see similar code in uGUI_SceneIntro_HandleInput_Patch. - uGUI_SceneIntro_IntroSequence_Patch.SkipLocalCinematic(uGUI.main.intro); - } -} -#endif diff --git a/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs b/NitroxPatcher/Patches/Dynamic/uGUI_SceneIntro_IntroSequence_Patch.cs index d7fa337988..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; } @@ -206,7 +208,10 @@ public static void SkipLocalCinematic(uGUI_SceneIntro uGuiSceneIntro) uGuiSceneIntro.Stop(true); radioLiveMixin.health = radioHealthBefore; - Object.Destroy(radioLiveMixin.loopingDamageEffectObj); + if (radioLiveMixin.IsFullHealth()) + { + Object.Destroy(radioLiveMixin.loopingDamageEffectObj); + } Transform introFireHolder = EscapePod.main.transform.Find("Intro"); if (introFireHolder) // Can be null if called very early From 9bae0b72e379a603042f47d3665d755410feff99 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Thu, 23 Jan 2025 02:20:35 +0100 Subject: [PATCH 10/10] Address requested changes --- .../PlayerPositionInitialSyncProcessor.cs | 4 +--- .../Spawning/Metadata/EntityMetadataManager.cs | 7 +++++++ .../Extractor/EscapePodMetadataExtractor.cs | 14 ++++++++++++++ .../Processor/EscapePodMetadataProcessor.cs | 2 +- .../WorldEntities/EscapePodWorldEntitySpawner.cs | 3 +-- .../Patches/Dynamic/EscapePod_OnRepair_Patch.cs | 5 +++-- .../Patches/Dynamic/Radio_OnRepair_Patch.cs | 6 ++++-- 7 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 NitroxClient/GameLogic/Spawning/Metadata/Extractor/EscapePodMetadataExtractor.cs diff --git a/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/PlayerPositionInitialSyncProcessor.cs index 4b304549f5..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); @@ -47,7 +48,6 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW if (!subRootId.HasValue) { yield return Terrain.WaitForWorldLoad(); - Player.main.cinematicModeActive = false; yield break; } @@ -56,7 +56,6 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW { Log.Error($"Could not spawn player into subroot with id: {subRootId.Value}"); yield return Terrain.WaitForWorldLoad(); - Player.main.cinematicModeActive = false; yield break; } @@ -64,7 +63,6 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW { Player.main.SetCurrentSub(subRoot, true); Player.main.UpdateIsUnderwater(); - Player.main.cinematicModeActive = false; } else if (sub.Value.GetComponent()) { 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 index a91bd126ee..2d4b9f353e 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/EscapePodMetadataProcessor.cs @@ -45,7 +45,7 @@ public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, Escape { pod.liveMixin.health = pod.liveMixin.maxHealth; pod.healthScalar = 1; - pod.damageEffectsShowing = true; + 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); diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs index 52034d7e3c..fd71f9f2ef 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/EscapePodWorldEntitySpawner.cs @@ -30,7 +30,6 @@ public EscapePodWorldEntitySpawner(LocalPlayer localPlayer) this.localPlayer = localPlayer; } - public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) { if (entity is not EscapePodWorldEntity escapePodEntity) @@ -69,7 +68,7 @@ private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity) Log.Error("Escape pod did not have a rigid body!"); } - escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); + pod.anchorPosition = escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity(); pod.ForceSkyApplier(); pod.escapePodCinematicControl.StopAll(); diff --git a/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs b/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs index ea0adac101..09ccbf72ae 100644 --- a/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs @@ -1,5 +1,6 @@ using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.GameLogic.Spawning.Metadata; using NitroxModel.DataStructures.GameLogic.Entities.Metadata; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -13,9 +14,9 @@ public sealed partial class EscapePod_OnRepair_Patch : NitroxPatch, IDynamicPatc public static void Prefix(EscapePod __instance) { if (__instance.TryGetIdOrWarn(out NitroxId id) && - __instance.radioSpawner.spawnedObj.TryGetComponent(out Radio radio)) + Resolve().TryExtract(__instance, out EntityMetadata metadata)) { - Resolve().BroadcastMetadataUpdate(id, new EscapePodMetadata(true, radio.liveMixin.IsFullHealth())); + Resolve().BroadcastMetadataUpdate(id, metadata); } } } diff --git a/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs b/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs index af9bc85f76..eb0a0fbc8b 100644 --- a/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Radio_OnRepair_Patch.cs @@ -1,5 +1,6 @@ using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.GameLogic.Spawning.Metadata; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic.Entities.Metadata; @@ -14,9 +15,10 @@ public sealed partial class Radio_OnRepair_Patch : NitroxPatch, IDynamicPatch public static void Prefix(Radio __instance) { if (__instance.TryGetComponentInParent(out EscapePod pod) && - pod.TryGetIdOrWarn(out NitroxId id)) + pod.TryGetIdOrWarn(out NitroxId id) && + Resolve().TryExtract(pod, out EntityMetadata metadata)) { - Resolve().BroadcastMetadataUpdate(id, new EscapePodMetadata(pod.liveMixin.IsFullHealth(), true)); + Resolve().BroadcastMetadataUpdate(id, metadata); } } }