using Nitrox.Test; using Nitrox.Test.Helper.Faker; using NitroxModel.Core; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.GameLogic.Entities.Bases; using NitroxModel.DataStructures.GameLogic.Entities.Metadata; using NitroxModel.DataStructures.GameLogic.Entities.Metadata.Bases; using NitroxServer.GameLogic; using NitroxServer.GameLogic.Unlockables; using NitroxServer.Serialization.World; using NitroxServer_Subnautica; namespace NitroxServer.Serialization; [TestClass] public class WorldPersistenceTest { private static readonly string tempSaveFilePath = Path.Combine(Path.GetTempPath(), "NitroxTestTempDir"); private static PersistedWorldData worldData; public static PersistedWorldData[] WorldsDataAfter { get; private set; } public static IServerSerializer[] ServerSerializers { get; private set; } [ClassInitialize] public static void ClassInitialize(TestContext context) { NitroxServiceLocator.InitializeDependencyContainer(new SubnauticaServerAutoFacRegistrar(), new TestAutoFacRegistrar()); NitroxServiceLocator.BeginNewLifetimeScope(); WorldPersistence worldPersistence = NitroxServiceLocator.LocateService(); ServerSerializers = NitroxServiceLocator.LocateService(); WorldsDataAfter = new PersistedWorldData[ServerSerializers.Length]; worldData = new NitroxAutoFaker().Generate(); for (int index = 0; index < ServerSerializers.Length; index++) { //Checking saving worldPersistence.UpdateSerializer(ServerSerializers[index]); Assert.IsTrue(worldPersistence.Save(worldData, tempSaveFilePath), $"Saving normal world failed while using {ServerSerializers[index]}."); //Checking loading WorldsDataAfter[index] = worldPersistence.LoadDataFromPath(tempSaveFilePath); Assert.IsNotNull(WorldsDataAfter[index], $"Loading saved world failed while using {ServerSerializers[index]}."); } } [DataTestMethod, DynamicWorldDataAfter] public void WorldDataTest(PersistedWorldData worldDataAfter, string serializerName) { Assert.IsTrue(worldData.WorldData.ParsedBatchCells.SequenceEqual(worldDataAfter.WorldData.ParsedBatchCells)); Assert.AreEqual(worldData.WorldData.Seed, worldDataAfter.WorldData.Seed); PDAStateTest(worldData.WorldData.GameData.PDAState, worldDataAfter.WorldData.GameData.PDAState); StoryGoalTest(worldData.WorldData.GameData.StoryGoals, worldDataAfter.WorldData.GameData.StoryGoals); StoryTimingTest(worldData.WorldData.GameData.StoryTiming, worldDataAfter.WorldData.GameData.StoryTiming); } private static void PDAStateTest(PDAStateData pdaState, PDAStateData pdaStateAfter) { Assert.IsTrue(pdaState.KnownTechTypes.SequenceEqual(pdaStateAfter.KnownTechTypes)); Assert.IsTrue(pdaState.AnalyzedTechTypes.SequenceEqual(pdaStateAfter.AnalyzedTechTypes)); AssertHelper.IsListEqual(pdaState.PdaLog.OrderBy(x => x.Key), pdaStateAfter.PdaLog.OrderBy(x => x.Key), (entry, entryAfter) => { Assert.AreEqual(entry.Key, entryAfter.Key); Assert.AreEqual(entry.Timestamp, entryAfter.Timestamp); }); Assert.IsTrue(pdaState.EncyclopediaEntries.SequenceEqual(pdaStateAfter.EncyclopediaEntries)); Assert.IsTrue(pdaState.ScannerFragments.SequenceEqual(pdaStateAfter.ScannerFragments)); AssertHelper.IsListEqual(pdaState.ScannerPartial.OrderBy(x => x.TechType.Name), pdaStateAfter.ScannerPartial.OrderBy(x => x.TechType.Name), (entry, entryAfter) => { Assert.AreEqual(entry.TechType, entryAfter.TechType); Assert.AreEqual(entry.Unlocked, entryAfter.Unlocked); }); Assert.IsTrue(pdaState.ScannerComplete.SequenceEqual(pdaStateAfter.ScannerComplete)); } private static void StoryGoalTest(StoryGoalData storyGoal, StoryGoalData storyGoalAfter) { Assert.IsTrue(storyGoal.CompletedGoals.SequenceEqual(storyGoalAfter.CompletedGoals)); Assert.IsTrue(storyGoal.RadioQueue.SequenceEqual(storyGoalAfter.RadioQueue)); AssertHelper.IsListEqual(storyGoal.ScheduledGoals.OrderBy(x => x.GoalKey), storyGoalAfter.ScheduledGoals.OrderBy(x => x.GoalKey), (scheduledGoal, scheduledGoalAfter) => { Assert.AreEqual(scheduledGoal.TimeExecute, scheduledGoalAfter.TimeExecute); Assert.AreEqual(scheduledGoal.GoalKey, scheduledGoalAfter.GoalKey); Assert.AreEqual(scheduledGoal.GoalType, scheduledGoalAfter.GoalType); }); } private static void StoryTimingTest(StoryTimingData storyTiming, StoryTimingData storyTimingAfter) { Assert.AreEqual(storyTiming.ElapsedSeconds, storyTimingAfter.ElapsedSeconds); Assert.AreEqual(storyTiming.AuroraCountdownTime, storyTimingAfter.AuroraCountdownTime); Assert.AreEqual(storyTiming.AuroraWarningTime, storyTimingAfter.AuroraWarningTime); } [DataTestMethod, DynamicWorldDataAfter] public void PlayerDataTest(PersistedWorldData worldDataAfter, string serializerName) { AssertHelper.IsListEqual(worldData.PlayerData.Players.OrderBy(x => x.Id), worldDataAfter.PlayerData.Players.OrderBy(x => x.Id), (playerData, playerDataAfter) => { Assert.AreEqual(playerData.Name, playerDataAfter.Name); Assert.IsTrue(playerData.UsedItems.SequenceEqual(playerDataAfter.UsedItems)); Assert.IsTrue(playerData.QuickSlotsBindingIds.SequenceEqual(playerDataAfter.QuickSlotsBindingIds)); AssertHelper.IsDictionaryEqual(playerData.EquippedItems, playerDataAfter.EquippedItems, (keyValuePair, keyValuePairAfter) => { Assert.AreEqual(keyValuePair.Key, keyValuePairAfter.Key); Assert.AreEqual(keyValuePair.Value, keyValuePairAfter.Value); }); Assert.AreEqual(playerData.Id, playerDataAfter.Id); Assert.AreEqual(playerData.SpawnPosition, playerDataAfter.SpawnPosition); Assert.AreEqual(playerData.SpawnRotation, playerDataAfter.SpawnRotation); Assert.AreEqual(playerData.CurrentStats.Oxygen, playerDataAfter.CurrentStats.Oxygen); Assert.AreEqual(playerData.CurrentStats.MaxOxygen, playerDataAfter.CurrentStats.MaxOxygen); Assert.AreEqual(playerData.CurrentStats.Health, playerDataAfter.CurrentStats.Health); Assert.AreEqual(playerData.CurrentStats.Food, playerDataAfter.CurrentStats.Food); Assert.AreEqual(playerData.CurrentStats.Water, playerDataAfter.CurrentStats.Water); Assert.AreEqual(playerData.CurrentStats.InfectionAmount, playerDataAfter.CurrentStats.InfectionAmount); Assert.AreEqual(playerData.SubRootId, playerDataAfter.SubRootId); Assert.AreEqual(playerData.Permissions, playerDataAfter.Permissions); Assert.AreEqual(playerData.NitroxId, playerDataAfter.NitroxId); Assert.AreEqual(playerData.IsPermaDeath, playerDataAfter.IsPermaDeath); Assert.IsTrue(playerData.PersonalCompletedGoalsWithTimestamp.SequenceEqual(playerDataAfter.PersonalCompletedGoalsWithTimestamp)); AssertHelper.IsDictionaryEqual(playerData.PlayerPreferences.PingPreferences, playerDataAfter.PlayerPreferences.PingPreferences, (keyValuePair, keyValuePairAfter) => { Assert.AreEqual(keyValuePair.Key, keyValuePairAfter.Key); Assert.AreEqual(keyValuePair.Value.Color, keyValuePairAfter.Value.Color); Assert.AreEqual(keyValuePair.Value.Visible, keyValuePairAfter.Value.Visible); }); Assert.IsTrue(playerData.PlayerPreferences.PinnedTechTypes.SequenceEqual(playerDataAfter.PlayerPreferences.PinnedTechTypes)); }); } [DataTestMethod, DynamicWorldDataAfter] public void EntityDataTest(PersistedWorldData worldDataAfter, string serializerName) { AssertHelper.IsListEqual(worldData.EntityData.Entities.OrderBy(x => x.Id), worldDataAfter.EntityData.Entities.OrderBy(x => x.Id), EntityTest); } [DataTestMethod, DynamicWorldDataAfter] public void GlobalRootDataTest(PersistedWorldData worldDataAfter, string serializerName) { AssertHelper.IsListEqual(worldData.GlobalRootData.Entities.OrderBy(x => x.Id), worldDataAfter.GlobalRootData.Entities.OrderBy(x => x.Id), EntityTest); } private static void EntityTest(Entity entity, Entity entityAfter) { Assert.AreEqual(entity.Id, entityAfter.Id); Assert.AreEqual(entity.TechType, entityAfter.TechType); Assert.AreEqual(entity.ParentId, entityAfter.ParentId); switch (entity.Metadata) { case KeypadMetadata metadata when entityAfter.Metadata is KeypadMetadata metadataAfter: Assert.AreEqual(metadata.Unlocked, metadataAfter.Unlocked); break; case SealedDoorMetadata metadata when entityAfter.Metadata is SealedDoorMetadata metadataAfter: Assert.AreEqual(metadata.Sealed, metadataAfter.Sealed); Assert.AreEqual(metadata.OpenedAmount, metadataAfter.OpenedAmount); break; case PrecursorDoorwayMetadata metadata when entityAfter.Metadata is PrecursorDoorwayMetadata metadataAfter: Assert.AreEqual(metadata.IsOpen, metadataAfter.IsOpen); break; case PrecursorTeleporterMetadata metadata when entityAfter.Metadata is PrecursorTeleporterMetadata metadataAfter: Assert.AreEqual(metadata.IsOpen, metadataAfter.IsOpen); break; case PrecursorKeyTerminalMetadata metadata when entityAfter.Metadata is PrecursorKeyTerminalMetadata metadataAfter: Assert.AreEqual(metadata.Slotted, metadataAfter.Slotted); break; case PrecursorTeleporterActivationTerminalMetadata metadata when entityAfter.Metadata is PrecursorTeleporterActivationTerminalMetadata metadataAfter: Assert.AreEqual(metadata.Unlocked, metadataAfter.Unlocked); break; case StarshipDoorMetadata metadata when entityAfter.Metadata is StarshipDoorMetadata metadataAfter: Assert.AreEqual(metadata.DoorLocked, metadataAfter.DoorLocked); Assert.AreEqual(metadata.DoorOpen, metadataAfter.DoorOpen); break; case WeldableWallPanelGenericMetadata metadata when entityAfter.Metadata is WeldableWallPanelGenericMetadata metadataAfter: Assert.AreEqual(metadata.LiveMixInHealth, metadataAfter.LiveMixInHealth); break; case IncubatorMetadata metadata when entityAfter.Metadata is IncubatorMetadata metadataAfter: Assert.AreEqual(metadata.Powered, metadataAfter.Powered); Assert.AreEqual(metadata.Hatched, metadataAfter.Hatched); break; case EntitySignMetadata metadata when entityAfter.Metadata is EntitySignMetadata metadataAfter: Assert.AreEqual(metadata.Text, metadataAfter.Text); Assert.AreEqual(metadata.ColorIndex, metadataAfter.ColorIndex); Assert.AreEqual(metadata.ScaleIndex, metadataAfter.ScaleIndex); Assert.IsTrue(metadata.Elements.SequenceEqual(metadataAfter.Elements)); Assert.AreEqual(metadata.Background, metadataAfter.Background); break; case ConstructorMetadata metadata when entityAfter.Metadata is ConstructorMetadata metadataAfter: Assert.AreEqual(metadata.Deployed, metadataAfter.Deployed); break; case FlashlightMetadata metadata when entityAfter.Metadata is FlashlightMetadata metadataAfter: Assert.AreEqual(metadata.On, metadataAfter.On); break; case BatteryMetadata metadata when entityAfter.Metadata is BatteryMetadata metadataAfter: Assert.AreEqual(metadata.Charge, metadataAfter.Charge); break; 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); Assert.AreEqual(metadata.StartTime, metadataAfter.StartTime); Assert.AreEqual(metadata.Duration, metadataAfter.Duration); break; case PlantableMetadata metadata when entityAfter.Metadata is PlantableMetadata metadataAfter: Assert.AreEqual(metadata.TimeStartGrowth, metadataAfter.TimeStartGrowth); Assert.AreEqual(metadata.SlotID, metadataAfter.SlotID); // FruitPlantMetadata field is not checked before it's only temporary break; case FruitPlantMetadata metadata when entityAfter.Metadata is FruitPlantMetadata metadataAfter: Assert.IsTrue(metadata.PickedStates.SequenceEqual(metadataAfter.PickedStates)); Assert.AreEqual(metadata.TimeNextFruit, metadataAfter.TimeNextFruit); break; case CyclopsMetadata metadata when entityAfter.Metadata is CyclopsMetadata metadataAfter: Assert.AreEqual(metadata.SilentRunningOn, metadataAfter.SilentRunningOn); Assert.AreEqual(metadata.ShieldOn, metadataAfter.ShieldOn); Assert.AreEqual(metadata.SonarOn, metadataAfter.SonarOn); Assert.AreEqual(metadata.EngineOn, metadataAfter.EngineOn); Assert.AreEqual(metadata.EngineMode, metadataAfter.EngineMode); Assert.AreEqual(metadata.Health, metadataAfter.Health); break; case SeamothMetadata metadata when entityAfter.Metadata is SeamothMetadata metadataAfter: Assert.AreEqual(metadata.LightsOn, metadataAfter.LightsOn); Assert.AreEqual(metadata.Health, metadataAfter.Health); Assert.AreEqual(metadata.Name, metadataAfter.Name); Assert.IsTrue(metadata.Colors.SequenceEqual(metadataAfter.Colors)); break; case ExosuitMetadata metadata when entityAfter.Metadata is ExosuitMetadata metadataAfter: Assert.AreEqual(metadata.Health, metadataAfter.Health); Assert.AreEqual(metadata.Name, metadataAfter.Name); Assert.IsTrue(metadata.Colors.SequenceEqual(metadataAfter.Colors)); break; case SubNameInputMetadata metadata when entityAfter.Metadata is SubNameInputMetadata metadataAfter: Assert.AreEqual(metadata.Name, metadataAfter.Name); Assert.IsTrue(metadata.Colors.SequenceEqual(metadataAfter.Colors)); break; case RocketMetadata metadata when entityAfter.Metadata is RocketMetadata metadataAfter: Assert.AreEqual(metadata.CurrentStage, metadataAfter.CurrentStage); Assert.AreEqual(metadata.LastStageTransitionTime, metadataAfter.LastStageTransitionTime); Assert.AreEqual(metadata.ElevatorState, metadataAfter.ElevatorState); Assert.AreEqual(metadata.ElevatorPosition, metadataAfter.ElevatorPosition); Assert.IsTrue(metadata.PreflightChecks.SequenceEqual(metadataAfter.PreflightChecks)); break; case CyclopsLightingMetadata metadata when entityAfter.Metadata is CyclopsLightingMetadata metadataAfter: Assert.AreEqual(metadata.FloodLightsOn, metadataAfter.FloodLightsOn); Assert.AreEqual(metadata.InternalLightsOn, metadataAfter.InternalLightsOn); break; case FireExtinguisherHolderMetadata metadata when entityAfter.Metadata is FireExtinguisherHolderMetadata metadataAfter: Assert.AreEqual(metadata.HasExtinguisher, metadataAfter.HasExtinguisher); Assert.AreEqual(metadata.Fuel, metadataAfter.Fuel); break; case PlayerMetadata metadata when entityAfter.Metadata is PlayerMetadata metadataAfter: AssertHelper.IsListEqual(metadata.EquippedItems.OrderBy(x => x.Id), metadataAfter.EquippedItems.OrderBy(x => x.Id), (equippedItem, equippedItemAfter) => { Assert.AreEqual(equippedItem.Id, equippedItemAfter.Id); Assert.AreEqual(equippedItem.Slot, equippedItemAfter.Slot); Assert.AreEqual(equippedItem.TechType, equippedItemAfter.TechType); }); break; case GhostMetadata ghostMetadata when entityAfter.Metadata is GhostMetadata ghostMetadataAfter: Assert.AreEqual(ghostMetadata.TargetOffset, ghostMetadataAfter.TargetOffset); if (ghostMetadata.GetType() != ghostMetadataAfter.GetType()) { Assert.Fail($"Runtime type of {nameof(GhostMetadata)} in {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {ghostMetadata.GetType().Name} - {ghostMetadataAfter.GetType().Name}"); } switch (ghostMetadata) { case BaseAnchoredCellGhostMetadata metadata when ghostMetadataAfter is BaseAnchoredCellGhostMetadata metadataAfter: Assert.AreEqual(metadata.AnchoredCell, metadataAfter.AnchoredCell); break; case BaseAnchoredFaceGhostMetadata metadata when ghostMetadataAfter is BaseAnchoredFaceGhostMetadata metadataAfter: Assert.AreEqual(metadata.AnchoredFace, metadataAfter.AnchoredFace); break; case BaseDeconstructableGhostMetadata metadata when ghostMetadataAfter is BaseDeconstructableGhostMetadata metadataAfter: Assert.AreEqual(metadata.ModuleFace, metadataAfter.ModuleFace); Assert.AreEqual(metadata.ClassId, metadataAfter.ClassId); break; } break; case WaterParkCreatureMetadata metadata when entityAfter.Metadata is WaterParkCreatureMetadata metadataAfter: Assert.AreEqual(metadata.Age, metadataAfter.Age); Assert.AreEqual(metadata.MatureTime, metadataAfter.MatureTime); Assert.AreEqual(metadata.TimeNextBreed, metadataAfter.TimeNextBreed); Assert.AreEqual(metadata.BornInside, metadataAfter.BornInside); break; case FlareMetadata metadata when entityAfter.Metadata is FlareMetadata metadataAfter: Assert.AreEqual(metadata.EnergyLeft, metadataAfter.EnergyLeft); Assert.AreEqual(metadata.HasBeenThrown, metadataAfter.HasBeenThrown); Assert.AreEqual(metadata.FlareActivateTime, metadataAfter.FlareActivateTime); break; case BeaconMetadata metadata when entityAfter.Metadata is BeaconMetadata metadataAfter: Assert.AreEqual(metadata.Label, metadataAfter.Label); break; case RadiationMetadata metadata when entityAfter.Metadata is RadiationMetadata metadataAfter: Assert.AreEqual(metadata.Health, metadataAfter.Health); Assert.AreEqual(metadata.FixRealTime, metadataAfter.FixRealTime); break; case CrashHomeMetadata metadata when entityAfter.Metadata is CrashHomeMetadata metadataAfter: Assert.AreEqual(metadata.SpawnTime, metadataAfter.SpawnTime); break; case EatableMetadata metadata when entityAfter.Metadata is EatableMetadata metadataAfter: Assert.AreEqual(metadata.TimeDecayStart, metadataAfter.TimeDecayStart); break; case SeaTreaderMetadata metadata when entityAfter.Metadata is SeaTreaderMetadata metadataAfter: Assert.AreEqual(metadata.ReverseDirection, metadataAfter.ReverseDirection); Assert.AreEqual(metadata.GrazingEndTime, metadataAfter.GrazingEndTime); Assert.AreEqual(metadata.LeashPosition, metadataAfter.LeashPosition); break; case StayAtLeashPositionMetadata metadata when entityAfter.Metadata is StayAtLeashPositionMetadata metadataAfter: Assert.AreEqual(metadata.LeashPosition, metadataAfter.LeashPosition); break; case EggMetadata metadata when entityAfter.Metadata is EggMetadata metadataAfter: Assert.AreEqual(metadata.TimeStartHatching, metadataAfter.TimeStartHatching); Assert.AreEqual(metadata.Progress, metadataAfter.Progress); break; default: Assert.Fail($"Runtime type of {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {entity.Metadata?.GetType().Name} - {entityAfter.Metadata?.GetType().Name}"); break; } switch (entity) { case WorldEntity worldEntity when entityAfter is WorldEntity worldEntityAfter: Assert.AreEqual(worldEntity.Transform.LocalPosition, worldEntityAfter.Transform.LocalPosition); Assert.AreEqual(worldEntity.Transform.LocalRotation, worldEntityAfter.Transform.LocalRotation); Assert.AreEqual(worldEntity.Transform.LocalScale, worldEntityAfter.Transform.LocalScale); Assert.AreEqual(worldEntity.Level, worldEntityAfter.Level); Assert.AreEqual(worldEntity.ClassId, worldEntityAfter.ClassId); Assert.AreEqual(worldEntity.SpawnedByServer, worldEntityAfter.SpawnedByServer); if (worldEntity.GetType() != worldEntityAfter.GetType()) { Assert.Fail($"Runtime type of {nameof(WorldEntity)} is not equal: {worldEntity.GetType().Name} - {worldEntityAfter.GetType().Name}"); } else if (worldEntity.GetType() != typeof(WorldEntity)) { switch (worldEntity) { case PlaceholderGroupWorldEntity placeholderGroupWorldEntity when worldEntityAfter is PlaceholderGroupWorldEntity placeholderGroupWorldEntityAfter: Assert.AreEqual(placeholderGroupWorldEntity.ComponentIndex, placeholderGroupWorldEntityAfter.ComponentIndex); break; case CellRootEntity when worldEntityAfter is CellRootEntity: break; case PlacedWorldEntity when worldEntityAfter is PlacedWorldEntity: break; case OxygenPipeEntity oxygenPipeEntity when worldEntityAfter is OxygenPipeEntity oxygenPipeEntityAfter: Assert.AreEqual(oxygenPipeEntity.ParentPipeId, oxygenPipeEntityAfter.ParentPipeId); Assert.AreEqual(oxygenPipeEntity.RootPipeId, oxygenPipeEntityAfter.RootPipeId); Assert.AreEqual(oxygenPipeEntity.ParentPosition, oxygenPipeEntityAfter.ParentPosition); break; case PrefabPlaceholderEntity prefabPlaceholderEntity when entityAfter is PrefabPlaceholderEntity prefabPlaceholderEntityAfter: Assert.AreEqual(prefabPlaceholderEntity.ComponentIndex, prefabPlaceholderEntityAfter.ComponentIndex); 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()), (c1, c2) => c1.Equals(c2)); Assert.AreEqual(serializedWorldEntity.Layer, serializedWorldEntityAfter.Layer); Assert.AreEqual(serializedWorldEntity.BatchId, serializedWorldEntityAfter.BatchId); Assert.AreEqual(serializedWorldEntity.CellId, serializedWorldEntityAfter.CellId); break; case GeyserWorldEntity geyserEntity when entityAfter is GeyserWorldEntity geyserEntityAfter: Assert.AreEqual(geyserEntity.RandomIntervalVarianceMultiplier, geyserEntityAfter.RandomIntervalVarianceMultiplier); Assert.AreEqual(geyserEntity.StartEruptTime, geyserEntityAfter.StartEruptTime); break; case ReefbackEntity reefbackEntity when entityAfter is ReefbackEntity reefbackEntityAfter: Assert.AreEqual(reefbackEntity.GrassIndex, reefbackEntityAfter.GrassIndex); Assert.AreEqual(reefbackEntity.OriginalPosition, reefbackEntityAfter.OriginalPosition); break; case ReefbackChildEntity reefbackChildEntity when entityAfter is ReefbackChildEntity reefbackChildEntityAfter: Assert.AreEqual(reefbackChildEntity.Type, reefbackChildEntityAfter.Type); break; case CreatureRespawnEntity creatureRespawnEntity when entityAfter is CreatureRespawnEntity creatureRespawnEntityAfter: Assert.AreEqual(creatureRespawnEntity.SpawnTime, creatureRespawnEntityAfter.SpawnTime); Assert.AreEqual(creatureRespawnEntity.RespawnTechType, creatureRespawnEntityAfter.RespawnTechType); Assert.IsTrue(creatureRespawnEntity.AddComponents.SequenceEqual(creatureRespawnEntityAfter.AddComponents)); break; case GlobalRootEntity globalRootEntity when worldEntityAfter is GlobalRootEntity globalRootEntityAfter: if (globalRootEntity.GetType() != typeof(GlobalRootEntity)) { switch (globalRootEntity) { case BuildEntity buildEntity when globalRootEntityAfter is BuildEntity buildEntityAfter: Assert.AreEqual(buildEntity.BaseData, buildEntityAfter.BaseData); break; case EscapePodWorldEntity escapePodWorldEntity when globalRootEntityAfter is EscapePodWorldEntity escapePodWorldEntityAfter: Assert.IsTrue(escapePodWorldEntity.Players.SequenceEqual(escapePodWorldEntityAfter.Players)); break; case InteriorPieceEntity interiorPieceEntity when globalRootEntityAfter is InteriorPieceEntity interiorPieceEntityAfter: Assert.AreEqual(interiorPieceEntity.BaseFace, interiorPieceEntityAfter.BaseFace); break; case MapRoomEntity mapRoomEntity when globalRootEntityAfter is MapRoomEntity mapRoomEntityAfter: Assert.AreEqual(mapRoomEntity.Cell, mapRoomEntityAfter.Cell); break; case ModuleEntity moduleEntity when globalRootEntityAfter is ModuleEntity moduleEntityAfter: Assert.AreEqual(moduleEntity.ConstructedAmount, moduleEntityAfter.ConstructedAmount); Assert.AreEqual(moduleEntity.IsInside, moduleEntityAfter.IsInside); if (moduleEntity.GetType() != moduleEntityAfter.GetType()) { Assert.Fail($"Runtime type of {nameof(ModuleEntity)} is not equal: {moduleEntity.GetType().Name} - {moduleEntityAfter.GetType().Name}"); } switch (moduleEntity) { case GhostEntity ghostEntity when moduleEntityAfter is GhostEntity ghostEntityAfter: Assert.AreEqual(ghostEntity.BaseFace, ghostEntityAfter.BaseFace); Assert.AreEqual(ghostEntity.BaseData, ghostEntityAfter.BaseData); break; } break; case MoonpoolEntity moonpoolEntity when globalRootEntityAfter is MoonpoolEntity moonpoolEntityAfter: Assert.AreEqual(moonpoolEntity.Cell, moonpoolEntityAfter.Cell); break; case PlanterEntity when globalRootEntityAfter is PlanterEntity: break; case PlayerWorldEntity when globalRootEntityAfter is PlayerWorldEntity: break; case VehicleWorldEntity vehicleWorldEntity when globalRootEntityAfter is VehicleWorldEntity vehicleWorldEntityAfter: Assert.AreEqual(vehicleWorldEntity.SpawnerId, vehicleWorldEntityAfter.SpawnerId); Assert.AreEqual(vehicleWorldEntity.ConstructionTime, vehicleWorldEntityAfter.ConstructionTime); break; case RadiationLeakEntity radiationLeakEntity when globalRootEntityAfter is RadiationLeakEntity radiationLeakEntityAfter: Assert.AreEqual(radiationLeakEntity.ObjectIndex, radiationLeakEntityAfter.ObjectIndex); break; default: Assert.Fail($"Runtime type of {nameof(GlobalRootEntity)} is not equal even after the check: {worldEntity.GetType().Name} - {globalRootEntityAfter.GetType().Name}"); break; } } break; default: Assert.Fail($"Runtime type of {nameof(WorldEntity)} is not equal even after the check: {worldEntity.GetType().Name} - {worldEntityAfter.GetType().Name}"); break; } } break; case PrefabChildEntity prefabChildEntity when entityAfter is PrefabChildEntity prefabChildEntityAfter: Assert.AreEqual(prefabChildEntity.ComponentIndex, prefabChildEntityAfter.ComponentIndex); Assert.AreEqual(prefabChildEntity.ClassId, prefabChildEntityAfter.ClassId); break; case InventoryEntity inventoryEntity when entityAfter is InventoryEntity inventoryEntityAfter: Assert.AreEqual(inventoryEntity.ComponentIndex, inventoryEntityAfter.ComponentIndex); break; case InventoryItemEntity inventoryItemEntity when entityAfter is InventoryItemEntity inventoryItemEntityAfter: Assert.AreEqual(inventoryItemEntity.ClassId, inventoryItemEntityAfter.ClassId); break; case PathBasedChildEntity pathBasedChildEntity when entityAfter is PathBasedChildEntity pathBasedChildEntityAfter: Assert.AreEqual(pathBasedChildEntity.Path, pathBasedChildEntityAfter.Path); break; case InstalledBatteryEntity when entityAfter is InstalledBatteryEntity: break; case InstalledModuleEntity installedModuleEntity when entityAfter is InstalledModuleEntity installedModuleEntityAfter: Assert.AreEqual(installedModuleEntity.Slot, installedModuleEntityAfter.Slot); Assert.AreEqual(installedModuleEntity.ClassId, installedModuleEntityAfter.ClassId); break; case BaseLeakEntity baseLeakEntity when entityAfter is BaseLeakEntity baseLeakEntityAfter: Assert.AreEqual(baseLeakEntity.Health, baseLeakEntityAfter.Health); Assert.AreEqual(baseLeakEntity.RelativeCell, baseLeakEntityAfter.RelativeCell); break; default: Assert.Fail($"Runtime type of {nameof(Entity)} is not equal: {entity.GetType().Name} - {entityAfter.GetType().Name}"); break; } AssertHelper.IsListEqual(entity.ChildEntities.OrderBy(x => x.Id), entityAfter.ChildEntities.OrderBy(x => x.Id), EntityTest); } [ClassCleanup] public static void ClassCleanup() { NitroxServiceLocator.EndCurrentLifetimeScope(); if (Directory.Exists(tempSaveFilePath)) { Directory.Delete(tempSaveFilePath, true); } } } [AttributeUsage(AttributeTargets.Method)] public class DynamicWorldDataAfterAttribute : Attribute, ITestDataSource { public IEnumerable GetData(MethodInfo methodInfo) { return WorldPersistenceTest.WorldsDataAfter.Select((t, i) => new object[] { t, WorldPersistenceTest.ServerSerializers[i].GetType().Name }); } public string GetDisplayName(MethodInfo methodInfo, object[] data) { return data != null ? $"{methodInfo.Name} ({data[1]})" : $"{methodInfo.Name} (no-data)"; } }