Skip to content

Commit a2e1cdc

Browse files
Fix disconnect after joining, building desync, and a few crashes (SubnauticaNitrox#2686)
Co-authored-by: Measurity <1107063+Measurity@users.noreply.github.com>
1 parent 2a3b6e4 commit a2e1cdc

9 files changed

Lines changed: 45 additions & 17 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 & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ 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);
33-
31+
if (!playerManager.TryFind(packet.SessionId, out RemotePlayer player))
32+
{
33+
Log.Warn($"[{nameof(PlayerHeldItemChangedProcessor)}] Could not find player with session id: {packet.SessionId}.");
34+
return Task.CompletedTask;
35+
}
3436
if (!NitroxEntity.TryGetObjectFrom(packet.ItemId, out GameObject item))
3537
{
38+
Log.Warn($"[{nameof(PlayerHeldItemChangedProcessor)}] Could not find entity with id: {packet.ItemId}.");
3639
return Task.CompletedTask;
3740
}
3841

@@ -41,15 +44,15 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
4144

4245
Validate.NotNull(pickupable.inventoryItem);
4346

44-
ItemsContainer inventory = opPlayer.Value.Inventory;
47+
ItemsContainer inventory = player.Inventory;
4548
PlayerTool tool = item.GetComponent<PlayerTool>();
4649

4750
// Copied from QuickSlots
4851
switch (packet.Type)
4952
{
5053
case PlayerHeldItemChanged.ChangeType.DRAW_AS_TOOL:
5154
Validate.IsTrue(tool);
52-
ModelPlug.PlugIntoSocket(tool, opPlayer.Value.ItemAttachPoint);
55+
ModelPlug.PlugIntoSocket(tool, player.ItemAttachPoint);
5356
Utils.SetLayerRecursively(item, viewModelLayer);
5457
foreach (Animator componentsInChild in tool.GetComponentsInChildren<Animator>())
5558
{
@@ -66,8 +69,8 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
6669
}
6770
item.SetActive(true);
6871
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;
72+
SafeAnimator.SetBool(player.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", true);
73+
player.AnimationController["using_tool_first"] = packet.IsFirstTime != null;
7174

7275
if (item.TryGetComponent(out FPModel fpModelDraw)) //FPModel needs to be updated
7376
{
@@ -93,8 +96,8 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
9396
{
9497
componentsInChild.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
9598
}
96-
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", false);
97-
opPlayer.Value.AnimationController["using_tool_first"] = false;
99+
SafeAnimator.SetBool(player.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", false);
100+
player.AnimationController["using_tool_first"] = false;
98101

99102
if (item.TryGetComponent(out FPModel fpModelHolster)) //FPModel needs to be updated
100103
{
@@ -104,7 +107,7 @@ public Task Process(ClientProcessorContext context, PlayerHeldItemChanged packet
104107
break;
105108

106109
case PlayerHeldItemChanged.ChangeType.DRAW_AS_ITEM:
107-
pickupable.inventoryItem.item.Reparent(opPlayer.Value.ItemAttachPoint);
110+
pickupable.inventoryItem.item.Reparent(player.ItemAttachPoint);
108111
pickupable.inventoryItem.item.SetVisible(true);
109112
Utils.SetLayerRecursively(pickupable.inventoryItem.item.gameObject, viewModelLayer);
110113
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;

NitroxClient/MonoBehaviours/NitroxEntity.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.Linq;
45
using System.Runtime.Serialization;
56
using Nitrox.Model.DataStructures;
@@ -59,7 +60,7 @@ public static Dictionary<NitroxId, GameObject> GetObjectsFrom(HashSet<NitroxId>
5960
.ToDictionary(kv => kv.Key, kv => kv.Value);
6061
}
6162

62-
public static bool TryGetObjectFrom(NitroxId? id, out GameObject? gameObject)
63+
public static bool TryGetObjectFrom(NitroxId? id, [NotNullWhen(true)] out GameObject? gameObject)
6364
{
6465
gameObject = null;
6566
return id != null && gameObjectsById.TryGetValue(id, out gameObject) && gameObject;

0 commit comments

Comments
 (0)