diff --git a/Nitrox.Test/Patcher/Patches/Dynamic/SpawnConsoleCommand_OnConsoleCommand_PatchTest.cs b/Nitrox.Test/Patcher/Patches/Dynamic/SpawnConsoleCommand_OnConsoleCommand_PatchTest.cs deleted file mode 100644 index 014b1b9750..0000000000 --- a/Nitrox.Test/Patcher/Patches/Dynamic/SpawnConsoleCommand_OnConsoleCommand_PatchTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using HarmonyLib; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NitroxTest.Patcher; - -namespace NitroxPatcher.Patches.Dynamic; - -[TestClass] -public class SpawnConsoleCommand_OnConsoleCommand_PatchTest -{ - [TestMethod] - public void Sanity() - { - IEnumerable originalIl = PatchTestHelper.GetInstructionsFromMethod(SpawnConsoleCommand_OnConsoleCommand_Patch.TARGET_METHOD); - IEnumerable transformedIl = SpawnConsoleCommand_OnConsoleCommand_Patch.Transpiler(originalIl); - transformedIl.Count().Should().Be(originalIl.Count() + 2); - } -} diff --git a/NitroxClient/GameLogic/Spawning/InventoryItemEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/InventoryItemEntitySpawner.cs index d085ba780d..b7b6046ce2 100644 --- a/NitroxClient/GameLogic/Spawning/InventoryItemEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/InventoryItemEntitySpawner.cs @@ -87,6 +87,10 @@ private void SetupObject(InventoryItemEntity entity, GameObject gameObject, Game Pickupable pickupable = gameObject.RequireComponent(); pickupable.Initialize(); + // Items eventually get "secured" once a player gets into a SubRoot (or for other reasons) so we need to force this state by default + // so that player don't risk their whole inventory if they reconnect in the water. + pickupable.destroyOnDeath = false; + using (PacketSuppressor.Suppress()) using (PacketSuppressor.Suppress()) { diff --git a/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs b/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs index ccd115893d..f73840dc07 100644 --- a/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs +++ b/NitroxClient/GameLogic/Spawning/Metadata/EntityMetadataManager.cs @@ -71,6 +71,7 @@ public void ApplyMetadata(GameObject gameObject, EntityMetadata metadata) newerMetadataById.TryGetValue(objectId, out EntityMetadata newMetadata)) { metadata = newMetadata; + newerMetadataById.Remove(objectId); } Optional metadataProcessor = FromMetaData(metadata); diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/GeyserWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/GeyserWorldEntitySpawner.cs index 6efd5d455b..c120ea956b 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/GeyserWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/GeyserWorldEntitySpawner.cs @@ -9,9 +9,14 @@ namespace NitroxClient.GameLogic.Spawning.WorldEntities; -public class GeyserWorldEntitySpawner(Entities entities) : IWorldEntitySpawner, IWorldEntitySyncSpawner +public class GeyserWorldEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner { - private readonly Entities entities = entities; + private readonly Entities entities; + + public GeyserWorldEntitySpawner(Entities entities) + { + this.entities = entities; + } public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) { diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackEntitySpawner.cs index fbcd576761..43854a0fc3 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackEntitySpawner.cs @@ -83,32 +83,30 @@ private static bool VerifyCanSpawnOrError(ReefbackEntity entity, GameObject pref return false; } - private static void SetupObject(ReefbackEntity entity, GameObject gameObject, EntityCell cellRoot, ReefbackLife reefbackLife) + private static void SetupObject(ReefbackEntity entity, GameObject gameObject, EntityCell entityCell, ReefbackLife reefbackLife) { Transform transform = gameObject.transform; transform.localPosition = entity.Transform.Position.ToUnity(); transform.localRotation = entity.Transform.Rotation.ToUnity(); transform.localScale = entity.Transform.LocalScale.ToUnity(); - transform.SetParent(cellRoot.liveRoot.transform); + entityCell.EnsureRoot(); + transform.SetParent(entityCell.liveRoot.transform); // Replicate only the useful parts of ReefbackLife.Initialize reefbackLife.initialized = true; reefbackLife.needToRemovePlantPhysics = false; reefbackLife.hasCorals = gameObject.transform.localScale.x > 0.8f; - if (reefbackLife.hasCorals) + if (reefbackLife.hasCorals && LargeWorld.main) { - if (LargeWorld.main) + string biome = LargeWorld.main.GetBiome(entity.OriginalPosition.ToUnity()); + if (!string.IsNullOrEmpty(biome) && biome.StartsWith("grassyplateaus", StringComparison.OrdinalIgnoreCase)) { - string biome = LargeWorld.main.GetBiome(entity.OriginalPosition.ToUnity()); - if (!string.IsNullOrEmpty(biome) && biome.StartsWith("grassyplateaus", StringComparison.OrdinalIgnoreCase)) - { - reefbackLife.grassIndex = 0; - } - else - { - reefbackLife.grassIndex = entity.GrassIndex; - } + reefbackLife.grassIndex = 0; + } + else + { + reefbackLife.grassIndex = entity.GrassIndex; } } diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Bases/BuildEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/Bases/BuildEntity.cs index d92a700082..866bdff860 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/Bases/BuildEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/Bases/BuildEntity.cs @@ -28,7 +28,10 @@ public static BuildEntity MakeEmpty() return new BuildEntity(); } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public BuildEntity(BaseData baseData, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) { diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Bases/InteriorPieceEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/Bases/InteriorPieceEntity.cs index 151d0174fc..44589f52b0 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/Bases/InteriorPieceEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/Bases/InteriorPieceEntity.cs @@ -28,7 +28,10 @@ public static InteriorPieceEntity MakeEmpty() return new InteriorPieceEntity(); } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public InteriorPieceEntity(NitroxBaseFace baseFace, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) { diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Bases/MapRoomEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/Bases/MapRoomEntity.cs index 915cb79bf8..cc919a32ae 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/Bases/MapRoomEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/Bases/MapRoomEntity.cs @@ -28,7 +28,10 @@ public MapRoomEntity(NitroxId id, NitroxId parentId, NitroxInt3 cell) Transform = new(); } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public MapRoomEntity(NitroxInt3 cell, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) { diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Bases/ModuleEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/Bases/ModuleEntity.cs index a1272dacd5..e578c5ee79 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/Bases/ModuleEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/Bases/ModuleEntity.cs @@ -29,7 +29,10 @@ public static ModuleEntity MakeEmpty() return new ModuleEntity(); } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public ModuleEntity(float constructedAmount, bool isInside, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) { diff --git a/NitroxModel/DataStructures/GameLogic/Entities/Bases/MoonpoolEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/Bases/MoonpoolEntity.cs index bd87c663c6..ec755cd855 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/Bases/MoonpoolEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/Bases/MoonpoolEntity.cs @@ -26,7 +26,10 @@ public MoonpoolEntity(NitroxId id, NitroxId parentId, NitroxInt3 cell) Cell = cell; } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public MoonpoolEntity(NitroxInt3 cell, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) { diff --git a/NitroxModel/DataStructures/GameLogic/Entities/PlanterEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/PlanterEntity.cs index 3d4d00f3e7..64c51bd162 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/PlanterEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/PlanterEntity.cs @@ -22,7 +22,10 @@ public PlanterEntity(NitroxId id, NitroxId parentId) ParentId = parentId; } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public PlanterEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) {} diff --git a/NitroxModel/DataStructures/GameLogic/Entities/PlayerWorldEntity.cs b/NitroxModel/DataStructures/GameLogic/Entities/PlayerWorldEntity.cs index cf40753ee6..4cf3c55160 100644 --- a/NitroxModel/DataStructures/GameLogic/Entities/PlayerWorldEntity.cs +++ b/NitroxModel/DataStructures/GameLogic/Entities/PlayerWorldEntity.cs @@ -17,7 +17,10 @@ protected PlayerWorldEntity() // Constructor for serialization. Has to be "protected" for json serialization. } - /// Used for deserialization + /// + /// Used for deserialization. + /// is set to true because this entity is meant to receive simulation locks + /// public PlayerWorldEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) : base(transform, level, classId, true, id, techType, metadata, parentId, childEntities) {} diff --git a/NitroxPatcher/Patches/Dynamic/CrashHome_Spawn_Patch.cs b/NitroxPatcher/Patches/Dynamic/CrashHome_Spawn_Patch.cs index 1ddd6abab9..4d1213febb 100644 --- a/NitroxPatcher/Patches/Dynamic/CrashHome_Spawn_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CrashHome_Spawn_Patch.cs @@ -52,8 +52,7 @@ public static void BroadcastFishCreated(GameObject crashFishObject) { return; } - NitroxId crashFishId = new(); - NitroxEntity.SetNewId(crashFishObject, crashFishId); + NitroxId crashFishId = NitroxEntity.GenerateNewId(crashFishObject); LargeWorldEntity largeWorldEntity = crashFishObject.GetComponent(); UniqueIdentifier uniqueIdentifier = crashFishObject.GetComponent(); diff --git a/NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKillAsync_Patch.cs b/NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKillAsync_Patch.cs index b55875479a..45384976fe 100644 --- a/NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKillAsync_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKillAsync_Patch.cs @@ -31,15 +31,15 @@ public sealed partial class CreatureDeath_OnKillAsync_Patch : NitroxPatch, IDyna * gameObject.GetComponent().angularDrag = base.gameObject.GetComponent().angularDrag * 3f; * UnityEngine.Object.Destroy(base.gameObject); * result = null; - * CreatureDeath_OnKillAsync_Patch.BroadcastCookedSpawned(this, gameObject, cookedData); + * CreatureDeath_OnKillAsync_Patch.BroadcastCookedSpawned(this, gameObject, cookedData); <---- INSERTED LINE * * 2nd injection: * base.Invoke("RemoveCorpse", this.removeCorpseAfterSeconds); - * CreatureDeath_OnKillAsync_Patch. + * CreatureDeath_OnKillAsync_Patch.BroadcastRemoveCorpse(this); <---- INSERTED LINE * * 3rd injection: * this.eatable.SetDecomposes(true); - * CreatureDeath_OnKillAsync_Patch.BroadcastCookedSpawned(this.eatable); + * CreatureDeath_OnKillAsync_Patch.BroadcastCookedSpawned(this.eatable); <---- INSERTED LINE */ public static IEnumerable Transpiler(IEnumerable instructions) { diff --git a/NitroxPatcher/Patches/Dynamic/CreatureDeath_SpawnRespawner_Patch.cs b/NitroxPatcher/Patches/Dynamic/CreatureDeath_SpawnRespawner_Patch.cs index be4027693c..c604601f59 100644 --- a/NitroxPatcher/Patches/Dynamic/CreatureDeath_SpawnRespawner_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CreatureDeath_SpawnRespawner_Patch.cs @@ -44,8 +44,7 @@ public static void BroadcastSpawnedRespawner(Respawn respawn) { int cellLevel = respawn.TryGetComponent(out LargeWorldEntity largeWorldEntity) ? (int)largeWorldEntity.cellLevel : 0; string classId = respawn.GetComponent().ClassId; - NitroxId respawnId = new(); - NitroxEntity.SetNewId(respawn.gameObject, respawnId); + NitroxId respawnId = NitroxEntity.GenerateNewId(respawn.gameObject); NitroxId parentId = null; if (respawn.transform.parent) diff --git a/NitroxServer-Subnautica/GameLogic/Entities/Spawning/ReefbackBootstrapper.cs b/NitroxServer-Subnautica/GameLogic/Entities/Spawning/ReefbackBootstrapper.cs index d6ad540260..6414b33bc3 100644 --- a/NitroxServer-Subnautica/GameLogic/Entities/Spawning/ReefbackBootstrapper.cs +++ b/NitroxServer-Subnautica/GameLogic/Entities/Spawning/ReefbackBootstrapper.cs @@ -33,7 +33,7 @@ public void Prepare(ref WorldEntity entity, DeterministicGenerator generator) return; } - // In case the grassIndex will is chosen randomly + // In case the grassIndex is chosen randomly int grassIndex = XORRandom.NextIntRange(1, GRASS_VARIANTS_COUNT); entity = new ReefbackEntity(entity.Transform, entity.Level, entity.ClassId, diff --git a/NitroxServer/GameLogic/Entities/WorldEntityManager.cs b/NitroxServer/GameLogic/Entities/WorldEntityManager.cs index 4db9b37889..9a57d30b99 100644 --- a/NitroxServer/GameLogic/Entities/WorldEntityManager.cs +++ b/NitroxServer/GameLogic/Entities/WorldEntityManager.cs @@ -22,8 +22,6 @@ public class WorldEntityManager /// /// World entities can disappear if you go out of range. /// - private readonly Dictionary> worldEntitiesByBatchId; - private readonly Dictionary> worldEntitiesByCell; /// @@ -42,10 +40,6 @@ public WorldEntityManager(EntityRegistry entityRegistry, BatchEntitySpawner batc globalRootEntitiesById = entityRegistry.GetEntities().ToDictionary(entity => entity.Id); - worldEntitiesByBatchId = worldEntities.Where(entity => entity is not GlobalRootEntity) - .GroupBy(entity => entity.AbsoluteEntityCell.BatchId) - .ToDictionary(group => group.Key, group => group.ToDictionary(entity => entity.Id, entity => entity)); - worldEntitiesByCell = worldEntities.Where(entity => entity is not GlobalRootEntity) .GroupBy(entity => entity.AbsoluteEntityCell) .ToDictionary(group => group.Key, group => group.ToDictionary(entity => entity.Id, entity => entity)); @@ -166,12 +160,6 @@ public void RegisterWorldEntityInCell(WorldEntity entity, AbsoluteEntityCell cel { lock (worldEntitiesLock) { - if (!worldEntitiesByBatchId.TryGetValue(cell.BatchId, out Dictionary worldEntitiesInBatch)) - { - worldEntitiesInBatch = worldEntitiesByBatchId[cell.BatchId] = []; - } - worldEntitiesInBatch[entity.Id] = entity; - if (!worldEntitiesByCell.TryGetValue(cell, out Dictionary worldEntitiesInCell)) { worldEntitiesInCell = worldEntitiesByCell[cell] = []; @@ -192,11 +180,6 @@ public void UnregisterWorldEntityFromCell(NitroxId entityId, AbsoluteEntityCell { lock (worldEntitiesLock) { - if (worldEntitiesByBatchId.TryGetValue(cell.BatchId, out Dictionary worldEntitiesInBatch)) - { - worldEntitiesInBatch.Remove(entityId); - } - if (worldEntitiesByCell.TryGetValue(cell, out Dictionary worldEntitiesInCell)) { worldEntitiesInCell.Remove(entityId); @@ -333,6 +316,9 @@ public void AddOrUpdateGlobalRootEntity(GlobalRootEntity entity, bool addOrUpdat } } + /// + /// Iterative breadth-first search which gets all children player entities in 's hierarchy. + /// private List FindPlayerEntitiesInChildren(Entity parentEntity) { List playerWorldEntities = []; @@ -349,7 +335,7 @@ private List FindPlayerEntitiesInChildren(Entity parentEntity } else { - entitiesToSearch.AddRange(currentEntity.ChildEntities); + entitiesToSearch.InsertRange(0, currentEntity.ChildEntities); } } return playerWorldEntities;