Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix escape pod status on joining and repairing it #2257

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
19 changes: 9 additions & 10 deletions Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -411,9 +410,9 @@ private static void EntityTest(Entity entity, Entity entityAfter)
case MoonpoolEntity moonpoolEntity when globalRootEntityAfter is MoonpoolEntity moonpoolEntityAfter:
Assert.AreEqual(moonpoolEntity.Cell, moonpoolEntityAfter.Cell);
break;
case PlanterEntity _ when globalRootEntityAfter is PlanterEntity:
case PlanterEntity when globalRootEntityAfter is PlanterEntity:
break;
case PlayerWorldEntity _ when globalRootEntityAfter is PlayerWorldEntity:
case PlayerWorldEntity when globalRootEntityAfter is PlayerWorldEntity:
break;
case VehicleWorldEntity vehicleWorldEntity when globalRootEntityAfter is VehicleWorldEntity vehicleWorldEntityAfter:
Assert.AreEqual(vehicleWorldEntity.SpawnerId, vehicleWorldEntityAfter.SpawnerId);
Expand Down Expand Up @@ -449,7 +448,7 @@ private static void EntityTest(Entity entity, Entity entityAfter)
case PathBasedChildEntity pathBasedChildEntity when entityAfter is PathBasedChildEntity pathBasedChildEntityAfter:
Assert.AreEqual(pathBasedChildEntity.Path, pathBasedChildEntityAfter.Path);
break;
case InstalledBatteryEntity _ when entityAfter is InstalledBatteryEntity _:
case InstalledBatteryEntity when entityAfter is InstalledBatteryEntity:
break;
case InstalledModuleEntity installedModuleEntity when entityAfter is InstalledModuleEntity installedModuleEntityAfter:
Assert.AreEqual(installedModuleEntity.Slot, installedModuleEntityAfter.Slot);
Expand Down
2 changes: 2 additions & 0 deletions Nitrox.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LAN/@EntryIndexedValue">LAN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PDA/@EntryIndexedValue">PDA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UWE/@EntryIndexedValue">UWE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
Expand Down Expand Up @@ -319,6 +320,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gameobject/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gameobjects/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ingame/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=lifepod/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mathf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Medkit/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Moonpool/@EntryIndexedValue">True</s:Boolean>
Expand Down
2 changes: 2 additions & 0 deletions NitroxClient/Debuggers/Drawer/DrawerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public DrawerManager(SceneDebugger sceneDebugger)
AddDrawer<NitroxEntityDrawer, NitroxEntity>(nitroxEntityDrawer);
AddDrawer<NitroxEntityDrawer, NitroxId>(nitroxEntityDrawer);
AddDrawer<FMODAssetDrawer, FMODAsset>();
AddDrawer<UWEEventDrawer, UWE.Event<float>>();
AddDrawer<UWEEventDrawer, UWE.Event<PowerRelay>>();
AddDrawer<AspectRatioFitterDrawer, AspectRatioFitter>();
AddDrawer<ButtonDrawer, Button>(new(selectableDrawer, unityEventDrawer));
AddDrawer<CanvasDrawer, Canvas>(new(sceneDebugger));
Expand Down
76 changes: 76 additions & 0 deletions NitroxClient/Debuggers/Drawer/Subnautica/UWEEventDrawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Collections.Generic;
using UnityEngine;
using UWE;

namespace NitroxClient.Debuggers.Drawer.Subnautica;

public class UWEEventDrawer : IDrawer<Event<float>>, IDrawer<Event<PowerRelay>>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be UweEventDrawer as it's 3+ letter acronym.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this but I think we should be consistent. In this file we have GUI did we want to change that as well?

Copy link
Collaborator

@Measurity Measurity Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we could, yes I think so. But that's a Unity type. Renaming external types would be too tedious for maintaining a naming convention imo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the naming from the namspace uwe gave us. There it is ALl uppercase.

{
private const float LABEL_WIDTH = 250;

public void Draw(Event<float> uweEvent) => UWEEventDrawer.Draw(uweEvent);
public void Draw(Event<PowerRelay> uweEvent) => UWEEventDrawer.Draw(uweEvent);

private static void Draw<T>(Event<T> 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<T>(ICollection<Event<T>.Handler> uweEventHandlerList)
{
if (uweEventHandlerList == null)
{
GUILayout.Label("null", NitroxGUILayout.DrawerLabel);
return;
}

if (uweEventHandlerList.Count == 0)
{
GUILayout.Label("empty", NitroxGUILayout.DrawerLabel);
return;
}

foreach (Event<T>.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);
}
}
}
}
6 changes: 3 additions & 3 deletions NitroxClient/GameLogic/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class Entities
private readonly HashSet<NitroxId> deletedEntitiesIds = new();
private readonly List<SimulatedEntity> 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;
Expand Down Expand Up @@ -218,7 +218,7 @@ public IEnumerator SpawnBatchAsync(List<Entity> 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);
Expand All @@ -231,7 +231,7 @@ public IEnumerator SpawnBatchAsync(List<Entity> batch, bool forceRespawn = false
pendingParentEntitiesByParentId.Remove(entity.Id);
}
}

// Skip a frame to maintain FPS
if (Time.realtimeSinceStartup >= timeLimit && skipFrames)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -37,13 +38,12 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW
}
Player.main.SetPosition(position, rotation);

// Player.Update is setting SubRootID to null after Player position is set
// Player.ValidateEscapePod is setting currentEscapePod to null if player is not inside EscapePod
using (PacketSuppressor<EscapePodChanged>.Suppress())
{
Player.main.ValidateEscapePod();
}

// Player position is relative to a subroot if in a subroot
Optional<NitroxId> subRootId = packet.PlayerSubRootId;
if (!subRootId.HasValue)
{
Expand All @@ -59,16 +59,19 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW
yield break;
}

if (!sub.Value.TryGetComponent(out SubRoot subRoot))
if (sub.Value.TryGetComponent(out SubRoot subRoot))
{
Log.Debug("SubRootId-GameObject has no SubRoot component, so it's assumed to be the EscapePod");
yield return Terrain.WaitForWorldLoad();
yield break;
Player.main.SetCurrentSub(subRoot, true);
Player.main.UpdateIsUnderwater();
}
else if (sub.Value.GetComponent<EscapePod>())
{
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ public Optional<EntityMetadata> Extract(GameObject o)
return Optional.Empty;
}

public bool TryExtract(object o, out EntityMetadata metadata)
{
Optional<EntityMetadata> opMetadata = Extract(o);
metadata = opMetadata.Value;
return opMetadata.HasValue;
}

public Optional<IEntityMetadataProcessor> FromMetaData(EntityMetadata metadata)
{
if (metadata != null && processors.TryGetValue(metadata.GetType(), out IEntityMetadataProcessor processor))
Expand Down
Original file line number Diff line number Diff line change
@@ -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<EscapePod, EscapePodMetadata>
{
public override EscapePodMetadata Extract(EscapePod entity)
{
Radio radio = entity.radioSpawner.spawnedObj.RequireComponent<Radio>();
return new EscapePodMetadata(entity.liveMixin.IsFullHealth(), radio.liveMixin.IsFullHealth());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;

namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;

public class EscapePodMetadataProcessor : EntityMetadataProcessor<EscapePodMetadata>
{
// For metadata changes outside initial sync we only care about broken -> repaired
public override void ProcessMetadata(GameObject gameObject, EscapePodMetadata metadata)
{
if (!gameObject.TryGetComponent(out EscapePod pod))
{
Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get the EscapePod component from the provided gameobject.");
return;
}

if (!pod.liveMixin.IsFullHealth() && metadata.PodRepaired)
{
pod.liveMixin.health = pod.liveMixin.maxHealth;
pod.healthScalar = 1;
pod.damageEffectsShowing = true;
pod.UpdateDamagedEffects();
pod.OnRepair();
}

if (!pod.radioSpawner.spawnedObj.TryGetComponent(out Radio radio))
{
Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get Radio from EscapePod.");
return;
}

if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired)
{
radio.liveMixin.AddHealth(radio.liveMixin.maxHealth);
}
}

/// <summary>
/// Applies repaired state without animations and minimal audio playback
/// </summary>
public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, EscapePodMetadata metadata)
{
if (metadata.PodRepaired)
{
pod.liveMixin.health = pod.liveMixin.maxHealth;
pod.healthScalar = 1;
pod.damageEffectsShowing = true; // Needs to be set to true for UpdateDamagedEffects() to function
pod.UpdateDamagedEffects();
pod.vfxSpawner.SpawnManual(); // Spawn vfx to instantly disable it so no smoke is fading after player has joined
pod.vfxSpawner.spawnedObj.SetActive(false);
pod.lightingController.SnapToState(0);
}
else
{
IntroLifepodDirector introLifepodDirector = pod.GetComponent<IntroLifepodDirector>();
introLifepodDirector.OnProtoDeserializeObjectTree(null);
introLifepodDirector.ToggleActiveObjects(false);
pod.lightingController.SnapToState(2);
}

if (metadata.RadioRepaired)
{
radio.liveMixin.health = radio.liveMixin.maxHealth;
if (radio.liveMixin.loopingDamageEffectObj)
{
Object.Destroy(radio.liveMixin.loopingDamageEffectObj);
}
}
else
{
pod.DamageRadio();
}
}
}

This file was deleted.

Loading