Skip to content

Commit f7e20aa

Browse files
Jacqueb-1337Measurity
authored andcommitted
fix: increase DisconnectTimeout to 30s in release builds + building desync + client packet guards
- LiteNetLibServer/Client: set DisconnectTimeout=30000ms in release (was default 5000ms) Prevents false disconnects ~8s after initial sync completes when post-sync game loading briefly stalls LiteNetLib ping delivery. - BuildingManager.UpdateBase: call NotifyPlayerDesync on ghost/base entity not found paths (previously dropped packet silently instead of warning client) - BuildingDesyncWarningProcessor: reset LocalOperations=0 on desync warning so next build uses correct serverOp+1 operation ID - BuildingHandler.InitializeOperations: only call ResetToId when tracker is behind, preventing stale join-time snapshot from clobbering a tracker already advanced by relayed packets during the sync wait window - PlayerHeldItemChangedProcessor: guard against packet arriving before RemotePlayerInitialSyncProcessor runs (was throwing OptionalEmptyException) - ToggleLightsProcessor: guard with TryGetObjectFrom instead of RequireObjectFrom (was throwing on missing object) - Items.Dropped: null guard PrefabIdentifier.ClassId before use (was NullRef causing silent dropped-item desync)
1 parent b8fd6f5 commit f7e20aa

8 files changed

Lines changed: 43 additions & 15 deletions

File tree

Nitrox.Server.Subnautica/Models/Communication/LiteNetLibServer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ public Task StartAsync(CancellationToken cancellationToken)
6464
server.UpdateTime = 15;
6565
server.UnsyncedEvents = true;
6666
#if DEBUG
67-
server.DisconnectTimeout = 300000; //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
67+
server.DisconnectTimeout = 300_000; //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
68+
#else
69+
server.DisconnectTimeout = 30_000; // 30 seconds; prevents false disconnects when post-sync game-loading stalls LiteNetLib briefly
6870
#endif
69-
7071
server.Start(options.Value.ServerPort);
7172
return Task.CompletedTask;
7273
}

Nitrox.Server.Subnautica/Models/GameLogic/Bases/BuildingManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ public bool UpdateBase(Player player, UpdateBase updateBase, out int operationId
167167
if (!entityRegistry.TryGetEntityById<GhostEntity>(updateBase.FormerGhostId, out _))
168168
{
169169
logger.ZLogError($"Trying to place a base from a non-registered ghost (GhostId: {updateBase.FormerGhostId})");
170+
NotifyPlayerDesync(player);
170171
operationId = -1;
171172
return false;
172173
}
173174
if (!entityRegistry.TryGetEntityById(updateBase.BaseId, out BuildEntity buildEntity))
174175
{
175176
logger.ZLogError($"Trying to update a non-registered build (BaseId: {updateBase.BaseId})");
177+
NotifyPlayerDesync(player);
176178
operationId = -1;
177179
return false;
178180
}

NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ public LiteNetLibClient(PacketReceiver packetReceiver, INetworkDebugger networkD
5454
ChannelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length,
5555
IPv6Enabled = true,
5656
#if DEBUG
57-
DisconnectTimeout = 300000 //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
57+
DisconnectTimeout = 300_000, //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
58+
#else
59+
DisconnectTimeout = 30_000, // 30 seconds; prevents false disconnects when post-sync game-loading stalls LiteNetLib briefly
5860
#endif
5961
};
6062
}

NitroxClient/Communication/Packets/Processors/BuildingDesyncWarningProcessor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public Task Process(ClientProcessorContext context, BuildingDesyncWarning packet
2020
{
2121
OperationTracker tracker = BuildingHandler.Main.EnsureTracker(operation.Key);
2222
tracker.LastOperationId = operation.Value;
23+
tracker.LocalOperations = 0; // discard locally-queued ops, server's value is now authoritative
2324
tracker.FailedOperations++;
2425
}
2526

NitroxClient/Communication/Packets/Processors/PlayerHeldItemChangedProcessor.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ public PlayerHeldItemChangedProcessor(PlayerManager playerManager)
2828

2929
public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet)
3030
{
31-
Optional<RemotePlayer> opPlayer = playerManager.Find(packet.SessionId);
32-
Validate.IsPresent(opPlayer);
31+
if (!playerManager.TryFind(packet.SessionId, out RemotePlayer opPlayer))
32+
{
33+
Log.Warn($"[{nameof(PlayerHeldItemChangedProcessor)}] Could not find player with session id: {packet.SessionId}.");
34+
return Task.CompletedTask;
35+
}
3336

3437
if (!NitroxEntity.TryGetObjectFrom(packet.ItemId, out GameObject item))
3538
{
39+
Log.Warn($"[{nameof(PlayerHeldItemChangedProcessor)}] Could not find entity with id: {packet.ItemId}.");
3640
return Task.CompletedTask;
3741
}
3842

@@ -41,15 +45,15 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
4145

4246
Validate.NotNull(pickupable.inventoryItem);
4347

44-
ItemsContainer inventory = opPlayer.Value.Inventory;
48+
ItemsContainer inventory = opPlayer.Inventory;
4549
PlayerTool tool = item.GetComponent<PlayerTool>();
4650

4751
// Copied from QuickSlots
4852
switch (packet.Type)
4953
{
5054
case PlayerHeldItemChanged.ChangeType.DRAW_AS_TOOL:
5155
Validate.IsTrue(tool);
52-
ModelPlug.PlugIntoSocket(tool, opPlayer.Value.ItemAttachPoint);
56+
ModelPlug.PlugIntoSocket(tool, opPlayer.ItemAttachPoint);
5357
Utils.SetLayerRecursively(item, viewModelLayer);
5458
foreach (Animator componentsInChild in tool.GetComponentsInChildren<Animator>())
5559
{
@@ -66,8 +70,8 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
6670
}
6771
item.SetActive(true);
6872
tool.SetHandIKTargetsEnabled(true);
69-
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", true);
70-
opPlayer.Value.AnimationController["using_tool_first"] = packet.IsFirstTime != null;
73+
SafeAnimator.SetBool(opPlayer.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", true);
74+
opPlayer.AnimationController["using_tool_first"] = packet.IsFirstTime != null;
7175

7276
if (item.TryGetComponent(out FPModel fpModelDraw)) //FPModel needs to be updated
7377
{
@@ -93,8 +97,8 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
9397
{
9498
componentsInChild.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
9599
}
96-
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", false);
97-
opPlayer.Value.AnimationController["using_tool_first"] = false;
100+
SafeAnimator.SetBool(opPlayer.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", false);
101+
opPlayer.AnimationController["using_tool_first"] = false;
98102

99103
if (item.TryGetComponent(out FPModel fpModelHolster)) //FPModel needs to be updated
100104
{
@@ -104,7 +108,7 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
104108
break;
105109

106110
case PlayerHeldItemChanged.ChangeType.DRAW_AS_ITEM:
107-
pickupable.inventoryItem.item.Reparent(opPlayer.Value.ItemAttachPoint);
111+
pickupable.inventoryItem.item.Reparent(opPlayer.ItemAttachPoint);
108112
pickupable.inventoryItem.item.SetVisible(true);
109113
Utils.SetLayerRecursively(pickupable.inventoryItem.item.gameObject, viewModelLayer);
110114
break;

NitroxClient/Communication/Packets/Processors/ToggleLightsProcessor.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ internal sealed class ToggleLightsProcessor : IClientPacketProcessor<Nitrox.Mode
99
{
1010
public Task Process(ClientProcessorContext context, Nitrox.Model.Subnautica.Packets.ToggleLights packet)
1111
{
12-
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
12+
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject gameObject))
13+
{
14+
Log.Warn($"[{nameof(ToggleLightsProcessor)}] Could not find entity with id: {packet.Id}.");
15+
return Task.CompletedTask;
16+
}
1317
ToggleLights toggleLights = gameObject.RequireComponent<ToggleLights>();
1418

1519
if (packet.IsOn == toggleLights.GetLightsActive())

NitroxClient/GameLogic/Bases/BuildingHandler.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,15 @@ public void InitializeOperations(Dictionary<NitroxId, int> operations)
386386
{
387387
foreach (KeyValuePair<NitroxId, int> pair in operations)
388388
{
389-
EnsureTracker(pair.Key).ResetToId(pair.Value);
389+
OperationTracker tracker = EnsureTracker(pair.Key);
390+
// Only reset if the server's snapshot is newer than what we already have.
391+
// RegisterOperation() may have already advanced the tracker beyond this
392+
// snapshot (e.g. UpdateBase packets received during the initial-sync wait),
393+
// so overwriting with a stale value would immediately desync the client.
394+
if (tracker.LastOperationId < pair.Value)
395+
{
396+
tracker.ResetToId(pair.Value);
397+
}
390398
}
391399
}
392400

NitroxClient/GameLogic/Items.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ public void Dropped(GameObject gameObject, TechType? techType = null)
106106

107107
NitroxId id = NitroxEntity.GetIdOrGenerateNew(gameObject);
108108
Optional<EntityMetadata> metadata = entityMetadataManager.Extract(gameObject);
109-
string classId = gameObject.GetComponent<PrefabIdentifier>().ClassId;
109+
PrefabIdentifier prefabIdentifier = gameObject.GetComponent<PrefabIdentifier>();
110+
if (!prefabIdentifier)
111+
{
112+
Log.Error($"[{nameof(Items)}] Cannot sync dropped object '{gameObject.name}': missing PrefabIdentifier");
113+
return;
114+
}
115+
string classId = prefabIdentifier.ClassId;
110116
int level = gameObject.TryGetComponent(out LargeWorldEntity largeWorldEntity) ? (int)largeWorldEntity.cellLevel : 0;
111117

112118
WorldEntity droppedItem;

0 commit comments

Comments
 (0)