From 2cbf209a1c02cb5ac36c460b9db4d2897b6f5709 Mon Sep 17 00:00:00 2001
From: MistaOmega
Date: Sun, 25 Jan 2026 00:34:09 +0000
Subject: [PATCH 01/59] base method for monobehaviours (to impl) added some
event cleanups for PlayerCinematics.cs and StoryGoalInitialSyncProcessor.cs
removed application.quit from IngameMenu_QuitGame_Patch.cs
---
.../StoryGoalInitialSyncProcessor.cs | 21 +++++++++++++++-
.../GameLogic/PlayerLogic/PlayerCinematics.cs | 25 +++++++++++++++++--
.../MonoBehaviours/NitroxSessionBehaviour.cs | 22 ++++++++++++++++
.../Dynamic/IngameMenu_QuitGame_Patch.cs | 2 +-
4 files changed, 66 insertions(+), 4 deletions(-)
create mode 100644 NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs
diff --git a/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs
index 03282d6dd0..66de3e3577 100644
--- a/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs
+++ b/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs
@@ -12,6 +12,8 @@ namespace NitroxClient.GameLogic.InitialSync;
public sealed class StoryGoalInitialSyncProcessor : InitialSyncProcessor
{
+ private static bool storyGoalHandlerRegistered;
+
private readonly TimeManager timeManager;
public StoryGoalInitialSyncProcessor(TimeManager timeManager)
@@ -152,7 +154,12 @@ private static void SetScheduledGoals(InitialPlayerSync packet)
// We don't want any scheduled goal we add now to be executed before initial sync has finished, else they might not get broadcasted
StoryGoalScheduler.main.paused = true;
- Multiplayer.OnLoadingComplete += () => StoryGoalScheduler.main.paused = false;
+ if (!storyGoalHandlerRegistered)
+ {
+ Multiplayer.OnLoadingComplete += OnLoadingCompleteUnpauseStoryGoals;
+ Multiplayer.OnAfterMultiplayerEnd += CleanupStoryGoalHandler;
+ storyGoalHandlerRegistered = true;
+ }
foreach (NitroxScheduledGoal scheduledGoal in scheduledGoals)
{
@@ -195,4 +202,16 @@ private void SetTimeData(InitialPlayerSync packet)
timeManager.InitRealTimeElapsed(packet.TimeData.TimePacket.RealTimeElapsed, packet.TimeData.TimePacket.UpdateTime, packet.IsFirstPlayer);
timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime;
}
+
+ private static void OnLoadingCompleteUnpauseStoryGoals()
+ {
+ StoryGoalScheduler.main.paused = false;
+ }
+
+ private static void CleanupStoryGoalHandler()
+ {
+ Multiplayer.OnLoadingComplete -= OnLoadingCompleteUnpauseStoryGoals;
+ Multiplayer.OnAfterMultiplayerEnd -= CleanupStoryGoalHandler;
+ storyGoalHandlerRegistered = false;
+ }
}
diff --git a/NitroxClient/GameLogic/PlayerLogic/PlayerCinematics.cs b/NitroxClient/GameLogic/PlayerLogic/PlayerCinematics.cs
index 9928c52182..abeb09a177 100644
--- a/NitroxClient/GameLogic/PlayerLogic/PlayerCinematics.cs
+++ b/NitroxClient/GameLogic/PlayerLogic/PlayerCinematics.cs
@@ -14,6 +14,7 @@ public class PlayerCinematics
private readonly LocalPlayer localPlayer;
private IntroCinematicMode lastModeToSend = IntroCinematicMode.NONE;
+ private bool cinematicsHandlerRegistered;
public ushort? IntroCinematicPartnerId = null;
@@ -26,6 +27,9 @@ public PlayerCinematics(IPacketSender packetSender, LocalPlayer localPlayer)
{
this.packetSender = packetSender;
this.localPlayer = localPlayer;
+
+ // Register for cleanup when session ends
+ Multiplayer.OnAfterMultiplayerEnd += CleanupCinematicsHandler;
}
public void StartCinematicMode(ushort playerId, NitroxId controllerID, int controllerNameHash, string key)
@@ -66,11 +70,28 @@ public void SetLocalIntroCinematicMode(IntroCinematicMode introCinematicMode)
return;
}
- if (lastModeToSend == IntroCinematicMode.NONE)
+ if (lastModeToSend == IntroCinematicMode.NONE && !cinematicsHandlerRegistered)
{
- Multiplayer.OnLoadingComplete += () => SetLocalIntroCinematicMode(lastModeToSend);
+ Multiplayer.OnLoadingComplete += OnLoadingCompleteSendCinematicMode;
+ cinematicsHandlerRegistered = true;
}
lastModeToSend = introCinematicMode;
}
+
+ private void OnLoadingCompleteSendCinematicMode()
+ {
+ SetLocalIntroCinematicMode(lastModeToSend);
+ }
+
+ private void CleanupCinematicsHandler()
+ {
+ if (cinematicsHandlerRegistered)
+ {
+ Multiplayer.OnLoadingComplete -= OnLoadingCompleteSendCinematicMode;
+ cinematicsHandlerRegistered = false;
+ }
+ Multiplayer.OnAfterMultiplayerEnd -= CleanupCinematicsHandler;
+ lastModeToSend = IntroCinematicMode.NONE;
+ }
}
diff --git a/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs b/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs
new file mode 100644
index 0000000000..4a14fbd002
--- /dev/null
+++ b/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs
@@ -0,0 +1,22 @@
+using UnityEngine;
+
+namespace NitroxClient.MonoBehaviours;
+
+///
+/// Base class for Monos that exist only during a multiplayer session
+/// This auto-handles cleanup
+///
+public abstract class NitroxSessionBehaviour : MonoBehaviour
+{
+ protected virtual void Awake()
+ {
+ Multiplayer.OnAfterMultiplayerEnd += OnSessionEnd;
+ }
+
+ protected virtual void OnDestroy()
+ {
+ Multiplayer.OnAfterMultiplayerEnd += OnSessionEnd;
+ }
+
+ protected virtual void OnSessionEnd(){ }
+}
diff --git a/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs b/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs
index 32a47971b9..79f143f6e8 100644
--- a/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs
@@ -10,7 +10,7 @@ public sealed partial class IngameMenu_QuitGame_Patch : NitroxPatch, IDynamicPat
public static bool Prefix()
{
// TODO: Remove this patch after fixing that no MP resources are left on disconnect. So that we can return to main menu.
- Application.Quit();
+ //Application.Quit();
return false;
}
}
From e95936e1e217541db95531b2dd2a05d5668951eb Mon Sep 17 00:00:00 2001
From: MistaOmega
Date: Sun, 25 Jan 2026 19:28:39 +0000
Subject: [PATCH 02/59] session cleanup logic and resource management for exit
to main menu
- Properly unhook events for multiplayer session lifecycle.
- Add `OnSessionEnd` handling to session-scoped behaviours for automatic cleanup.
- Clear registries and caches on session end.
- Fix issues w/ stale references in service lifetimes.
---
Nitrox.Model/Core/NitroxServiceLocator.cs | 9 +++++-
Nitrox.Model/GameLogic/FMOD/FMODWhitelist.cs | 5 ++-
.../GameLogic/Bases/BuildingHandler.cs | 13 +++++++-
NitroxClient/GameLogic/PlayerManager.cs | 11 +++++++
.../MonoBehaviours/Cyclops/VirtualCyclops.cs | 29 ++++++++++++++---
.../EntityPositionBroadcaster.cs | 11 +++++--
.../MonoBehaviours/MovementBroadcaster.cs | 17 ++++++----
NitroxClient/MonoBehaviours/Multiplayer.cs | 31 +++++++++++++++++++
NitroxClient/MonoBehaviours/NitroxEntity.cs | 8 +++++
.../MonoBehaviours/NitroxSessionBehaviour.cs | 2 +-
.../PlayerMovementBroadcaster.cs | 5 +--
.../Dynamic/IngameMenu_QuitGame_Patch.cs | 3 +-
12 files changed, 124 insertions(+), 20 deletions(-)
diff --git a/Nitrox.Model/Core/NitroxServiceLocator.cs b/Nitrox.Model/Core/NitroxServiceLocator.cs
index 9929d8b94d..d31fb7a5e3 100644
--- a/Nitrox.Model/Core/NitroxServiceLocator.cs
+++ b/Nitrox.Model/Core/NitroxServiceLocator.cs
@@ -53,7 +53,14 @@ public static void BeginNewLifetimeScope()
throw new InvalidOperationException("You must install an Autofac container before initializing a new lifetime scope.");
}
- CurrentLifetimeScope?.Dispose();
+ // If there's an existing scope, invalidate caches before disposing
+ // Should allow us to handle instances of stale refs for issue 2545
+ if (CurrentLifetimeScope != null)
+ {
+ OnLifetimeScopeEnded();
+ CurrentLifetimeScope.Dispose();
+ }
+
CurrentLifetimeScope = DependencyContainer.BeginLifetimeScope();
}
diff --git a/Nitrox.Model/GameLogic/FMOD/FMODWhitelist.cs b/Nitrox.Model/GameLogic/FMOD/FMODWhitelist.cs
index 0c41b41f69..0d57c76e6c 100644
--- a/Nitrox.Model/GameLogic/FMOD/FMODWhitelist.cs
+++ b/Nitrox.Model/GameLogic/FMOD/FMODWhitelist.cs
@@ -96,7 +96,10 @@ public ReadOnlyDictionary GetWhitelist()
public void Dispose()
{
- ThrowIfDisposed();
+ if (isDisposed)
+ {
+ return;
+ }
isDisposed = true;
soundsWhitelist.Clear();
whitelistedPaths.Clear();
diff --git a/NitroxClient/GameLogic/Bases/BuildingHandler.cs b/NitroxClient/GameLogic/Bases/BuildingHandler.cs
index 4e99650514..20a11f2b2d 100644
--- a/NitroxClient/GameLogic/Bases/BuildingHandler.cs
+++ b/NitroxClient/GameLogic/Bases/BuildingHandler.cs
@@ -18,7 +18,7 @@
namespace NitroxClient.GameLogic.Bases;
-public partial class BuildingHandler : MonoBehaviour
+public partial class BuildingHandler : NitroxSessionBehaviour
{
public static BuildingHandler Main;
@@ -409,4 +409,15 @@ public void AskForResync()
this.Resolve().Send(new BuildingResyncRequest());
Log.InGame(Language.main.Get("Nitrox_ResyncRequested"));
}
+
+ protected override void OnSessionEnd()
+ {
+ if (Main == this)
+ {
+ Main = null; // todo: this valid?
+ }
+ BuildQueue?.Clear();
+ BasesCooldown?.Clear();
+ Operations?.Clear();
+ }
}
diff --git a/NitroxClient/GameLogic/PlayerManager.cs b/NitroxClient/GameLogic/PlayerManager.cs
index 6feb95f25b..ab8118a9d8 100644
--- a/NitroxClient/GameLogic/PlayerManager.cs
+++ b/NitroxClient/GameLogic/PlayerManager.cs
@@ -88,6 +88,17 @@ public void RemovePlayer(ushort playerId)
}
}
+ ///
+ /// Removes all remote players
+ ///
+ public void RemoveAllPlayers()
+ {
+ foreach (ushort playerId in playersById.Keys.ToList())
+ {
+ RemovePlayer(playerId);
+ }
+ }
+
/// Remote players + You => X + 1
public int GetTotalPlayerCount() => playersById.Count + 1;
diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs
index 2fb6f1b4cd..ca68d0a47a 100644
--- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs
+++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs
@@ -19,6 +19,7 @@ public class VirtualCyclops : MonoBehaviour
public const string NAME = "VirtualCyclops";
private static readonly Dictionary cacheColliderCopy = [];
+ private static bool isDisposed;
private readonly Dictionary virtualOpenableByName = [];
private readonly Dictionary realOpenableByName = [];
private readonly Dictionary virtualConstructableByRealGameObject = [];
@@ -31,14 +32,30 @@ public class VirtualCyclops : MonoBehaviour
public static void Initialize()
{
+ isDisposed = false;
CreateVirtualCyclops();
Multiplayer.OnAfterMultiplayerEnd += Dispose;
}
public static void Dispose()
{
- Destroy(Instance.gameObject);
- Instance = null;
+ isDisposed = true;
+
+ // Destroy cached collider copies from previous session
+ foreach (GameObject cachedCollider in cacheColliderCopy.Values)
+ {
+ if (cachedCollider)
+ {
+ Destroy(cachedCollider);
+ }
+ }
+ cacheColliderCopy.Clear();
+
+ if (Instance)
+ {
+ Destroy(Instance.gameObject);
+ Instance = null;
+ }
Multiplayer.OnAfterMultiplayerEnd -= Dispose;
}
@@ -99,6 +116,10 @@ public static IEnumerator InitializeConstructablesCache()
TaskResult result = new();
foreach (TechType techType in constructableTechTypes)
{
+ if (isDisposed)
+ {
+ yield break;
+ }
yield return DefaultWorldEntitySpawner.RequestPrefab(techType, result);
if (result.value && result.value.GetComponent())
{
@@ -234,7 +255,7 @@ public void ReplicateConstructable(Constructable constructable)
///
public static GameObject CreateColliderCopy(GameObject realObject, TechType techType)
{
- if (cacheColliderCopy.TryGetValue(techType, out GameObject colliderCopy))
+ if (cacheColliderCopy.TryGetValue(techType, out GameObject colliderCopy) && colliderCopy)
{
return GameObject.Instantiate(colliderCopy);
}
@@ -276,7 +297,7 @@ public static GameObject CreateColliderCopy(GameObject realObject, TechType tech
child.SetParent(colliderCopy.transform, false);
}
- cacheColliderCopy.Add(techType, colliderCopy);
+ cacheColliderCopy[techType] = colliderCopy;
return GameObject.Instantiate(colliderCopy);
}
diff --git a/NitroxClient/MonoBehaviours/EntityPositionBroadcaster.cs b/NitroxClient/MonoBehaviours/EntityPositionBroadcaster.cs
index 6fcdf2fd02..709b886845 100644
--- a/NitroxClient/MonoBehaviours/EntityPositionBroadcaster.cs
+++ b/NitroxClient/MonoBehaviours/EntityPositionBroadcaster.cs
@@ -12,7 +12,7 @@
namespace NitroxClient.MonoBehaviours;
-public class EntityPositionBroadcaster : MonoBehaviour
+public class EntityPositionBroadcaster : NitroxSessionBehaviour
{
public static readonly float BROADCAST_INTERVAL = 0.25f;
@@ -24,11 +24,18 @@ public class EntityPositionBroadcaster : MonoBehaviour
private float time;
- public void Awake()
+ protected override void Awake()
{
+ base.Awake();
packetSender = NitroxServiceLocator.LocateService();
}
+ protected override void OnSessionEnd()
+ {
+ watchingEntityIds.Clear();
+ splineUpdatesById.Clear();
+ }
+
public void Update()
{
time += Time.deltaTime;
diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs
index ed3384e0e1..0fcaf245f9 100644
--- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs
+++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs
@@ -9,7 +9,7 @@
namespace NitroxClient.MonoBehaviours;
-public class MovementBroadcaster : MonoBehaviour
+public class MovementBroadcaster : NitroxSessionBehaviour
{
public const int BROADCAST_FREQUENCY = 30;
public const float BROADCAST_PERIOD = 1f / BROADCAST_FREQUENCY;
@@ -31,11 +31,6 @@ public void Start()
Instance = this;
}
- public void OnDestroy()
- {
- Instance = null;
- }
-
public void Update()
{
float currentTime = (float)this.Resolve().RealTimeElapsed;
@@ -106,4 +101,14 @@ public static void UnregisterReplicator(MovementReplicator movementReplicator)
Instance.Replicators.Remove(movementReplicator.objectId);
}
}
+ protected override void OnDestroy()
+ {
+ base.OnDestroy();
+ Instance = null;
+ }
+
+ protected override void OnSessionEnd()
+ {
+ Replicators?.Clear();
+ }
}
diff --git a/NitroxClient/MonoBehaviours/Multiplayer.cs b/NitroxClient/MonoBehaviours/Multiplayer.cs
index 02a9f643fc..5cfa3b2643 100644
--- a/NitroxClient/MonoBehaviours/Multiplayer.cs
+++ b/NitroxClient/MonoBehaviours/Multiplayer.cs
@@ -191,9 +191,40 @@ public void InitMonoBehaviours()
public void StopCurrentSession()
{
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
+
+ // Destroy session-scoped Mono's before cleanup events
+ DestroySessionMonoBehaviours();
+
+ // Clear entity registry before invoking end event
+ NitroxEntity.ClearAll();
+
+ // clear remote players
+ PlayerManager remotePlayerManager = NitroxServiceLocator.LocateService();
+ remotePlayerManager.RemoveAllPlayers();
+
OnAfterMultiplayerEnd?.Invoke();
UnregisterConnectedDelegates();
+
+ // Reset state
+ InitialSyncCompleted = false;
+ }
+
+ ///
+ /// Destroys session-scoped MonoBehaviours to clean up resources before multiplayer end.
+ /// todo: consider replacing manual Destroy calls by having components inherit
+ /// so cleanup happens automatically on session end.
+ ///
+ private void DestroySessionMonoBehaviours()
+ {
+ Destroy(GetComponent());
+ Destroy(GetComponent());
+ Destroy(GetComponent());
+ Destroy(GetComponent());
+ Destroy(GetComponent());
+ Destroy(GetComponent());
+ Destroy(GetComponent());
+ Destroy(GetComponent());
}
private static void SetLoadingComplete()
diff --git a/NitroxClient/MonoBehaviours/NitroxEntity.cs b/NitroxClient/MonoBehaviours/NitroxEntity.cs
index bfb232ee16..bc671c023a 100644
--- a/NitroxClient/MonoBehaviours/NitroxEntity.cs
+++ b/NitroxClient/MonoBehaviours/NitroxEntity.cs
@@ -135,6 +135,14 @@ public static void RemoveFrom(GameObject gameObject)
}
}
+ ///
+ /// Clears all tracked entity-to-GO mappings.
+ ///
+ public static void ClearAll()
+ {
+ gameObjectsById.Clear();
+ }
+
///
/// Removes the from the global directory and set's its to null.
///
diff --git a/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs b/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs
index 4a14fbd002..80966390d3 100644
--- a/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs
+++ b/NitroxClient/MonoBehaviours/NitroxSessionBehaviour.cs
@@ -15,7 +15,7 @@ protected virtual void Awake()
protected virtual void OnDestroy()
{
- Multiplayer.OnAfterMultiplayerEnd += OnSessionEnd;
+ Multiplayer.OnAfterMultiplayerEnd -= OnSessionEnd;
}
protected virtual void OnSessionEnd(){ }
diff --git a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs
index 3e38d6fe5a..85600df44a 100644
--- a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs
+++ b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs
@@ -8,12 +8,13 @@
namespace NitroxClient.MonoBehaviours;
-public class PlayerMovementBroadcaster : MonoBehaviour
+public class PlayerMovementBroadcaster : NitroxSessionBehaviour
{
private LocalPlayer localPlayer;
- public void Awake()
+ protected override void Awake()
{
+ base.Awake();
localPlayer = this.Resolve();
}
diff --git a/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs b/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs
index 79f143f6e8..de9de5118d 100644
--- a/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/IngameMenu_QuitGame_Patch.cs
@@ -10,7 +10,6 @@ public sealed partial class IngameMenu_QuitGame_Patch : NitroxPatch, IDynamicPat
public static bool Prefix()
{
// TODO: Remove this patch after fixing that no MP resources are left on disconnect. So that we can return to main menu.
- //Application.Quit();
- return false;
+ return true;
}
}
From 91302572d2ee02fea13657d80c33baaed894cec8 Mon Sep 17 00:00:00 2001
From: MistaOmega
Date: Mon, 26 Jan 2026 12:54:11 +0000
Subject: [PATCH 03/59] fix(movement): clear pawn controllers
---
NitroxClient/GameLogic/CyclopsPawn.cs | 14 +++++++++++++-
.../MonoBehaviours/Cyclops/VirtualCyclops.cs | 4 ++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/NitroxClient/GameLogic/CyclopsPawn.cs b/NitroxClient/GameLogic/CyclopsPawn.cs
index 923417f519..1bc227b8b0 100644
--- a/NitroxClient/GameLogic/CyclopsPawn.cs
+++ b/NitroxClient/GameLogic/CyclopsPawn.cs
@@ -87,11 +87,23 @@ public void RegisterController()
{
foreach (CharacterController controller in controllers)
{
- Physics.IgnoreCollision(controller, Controller);
+ // Just-in-case check if ClearController hasn't been called, or some stale controllers exist
+ if (controller && controller.GetComponent())
+ {
+ Physics.IgnoreCollision(controller, Controller);
+ }
}
controllers.Add(Controller);
}
+ ///
+ /// Clears the static controllers list
+ ///
+ public static void ClearControllers()
+ {
+ controllers.Clear();
+ }
+
public void SetReference()
{
Handle.transform.localPosition = RealObject.transform.localPosition;
diff --git a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs
index ca68d0a47a..4f3cea337f 100644
--- a/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs
+++ b/NitroxClient/MonoBehaviours/Cyclops/VirtualCyclops.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using NitroxClient.Communication;
+using NitroxClient.GameLogic;
using NitroxClient.GameLogic.Spawning.WorldEntities;
using Nitrox.Model.Packets;
using Nitrox.Model.Subnautica.Packets;
@@ -51,6 +52,9 @@ public static void Dispose()
}
cacheColliderCopy.Clear();
+ // Clear pawn controllers from previous session
+ CyclopsPawn.ClearControllers();
+
if (Instance)
{
Destroy(Instance.gameObject);
From 8dd2b1f731d060592c1fa6ffa19dee21bbd44f20 Mon Sep 17 00:00:00 2001
From: "Weblate (bot)"
Date: Fri, 30 Jan 2026 14:17:21 +0100
Subject: [PATCH 04/59] [Weblate] Translations update from Hosted Weblate
(#2660)
---
.../LanguageFiles/cs.json | 3 +++
.../LanguageFiles/de.json | 3 +++
.../LanguageFiles/en.json | 2 ++
.../LanguageFiles/es.json | 10 +++++++++-
.../LanguageFiles/ga.json | 3 +++
.../LanguageFiles/nl.json | 10 ++++++++--
.../LanguageFiles/pt-BR.json | 20 +++++++++++++------
.../LanguageFiles/uk.json | 1 +
8 files changed, 43 insertions(+), 9 deletions(-)
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/cs.json b/Nitrox.Assets.Subnautica/LanguageFiles/cs.json
index 5dfe18b165..cc484803f2 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/cs.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/cs.json
@@ -79,11 +79,14 @@
"Nitrox_ServerEntry_DeleteWarning": "Jste si jistý, že chcete smazat tento server?",
"Nitrox_ServerStopped": "Server byl zastaven",
"Nitrox_Settings_Bandwidth": "Nastavení šířky pásma",
+ "Nitrox_Settings_ChatVisibilityDuration": "Doba viditelnosti chatu (s)",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "Jak dlouho zůstane chat viditelný. Nastavte hodnotu 0, chcete-li zakázat vyskakovací okna chatu.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Zvyš hodnotu pro nestabilní připojení",
"Nitrox_Settings_Keybind_FocusDiscord": "Zaměření na okno s pozvánkou na Discordu",
"Nitrox_Settings_Keybind_OpenChat": "Otevřít chat",
"Nitrox_Settings_LatencyUpdatePeriod": "Doba zpoždění aktualizace (s)",
"Nitrox_Settings_OfflineClockSyncDuration": "Doba trvání procedury synchronizace hodin (s)",
+ "Nitrox_Settings_Privacy": "Soukromí",
"Nitrox_Settings_SafetyLatencyMargin": "Bezpečnostní rozpětí zpoždění (ms)",
"Nitrox_ShowPing": "Zobrazit odezvu",
"Nitrox_SilenceChat": "Ztlumit chat",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/de.json b/Nitrox.Assets.Subnautica/LanguageFiles/de.json
index 267c82d980..3c3c54975b 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/de.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/de.json
@@ -79,11 +79,14 @@
"Nitrox_ServerEntry_DeleteWarning": "Willst du diesen Server wirklich löschen?",
"Nitrox_ServerStopped": "Der Server wurde gestoppt",
"Nitrox_Settings_Bandwidth": "Bandbreiteneinstellungen",
+ "Nitrox_Settings_ChatVisibilityDuration": "Sichtbarkeitsdauer des Chats (s)",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "Die Dauer, für die der Chat sichtbar bleibt. Setzen Sie den Wert auf 0, um Chat-Popups zu deaktivieren.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Gib bei instabilen Verbindungen einen höheren Wert ein",
"Nitrox_Settings_Keybind_FocusDiscord": "Das Discord Anfragefenster fokussieren",
"Nitrox_Settings_Keybind_OpenChat": "Chat öffnen",
"Nitrox_Settings_LatencyUpdatePeriod": "Latenzaktualisierungszeitraum (s)",
"Nitrox_Settings_OfflineClockSyncDuration": "Dauer des Zeitsynchronisations-Prozesses (s)",
+ "Nitrox_Settings_Privacy": "Datenschutz",
"Nitrox_Settings_SafetyLatencyMargin": "Sicherheitslatenzpuffer (ms)",
"Nitrox_ShowPing": "Signal anzeigen",
"Nitrox_SilenceChat": "Chat stumm schalten",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/en.json b/Nitrox.Assets.Subnautica/LanguageFiles/en.json
index d258473e43..2b13c384b2 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/en.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/en.json
@@ -79,6 +79,8 @@
"Nitrox_ServerEntry_DeleteWarning": "Are you sure you want to delete this server?",
"Nitrox_ServerStopped": "The server has been stopped",
"Nitrox_Settings_Bandwidth": "Bandwidth settings",
+ "Nitrox_Settings_ChatVisibilityDuration": "Chat visibility duration (s)",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "How long the chat stays visible. Set to 0 to disable chat popups.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Give a higher value for unstable connections",
"Nitrox_Settings_Keybind_FocusDiscord": "Focus on the Discord invite window",
"Nitrox_Settings_Keybind_OpenChat": "Open chat",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/es.json b/Nitrox.Assets.Subnautica/LanguageFiles/es.json
index 731879918b..64ecb0e09c 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/es.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/es.json
@@ -9,13 +9,14 @@
"Nitrox_AddServer_NamePlaceholder": "Introduce un nombre para el servidor",
"Nitrox_AddServer_PortDescription": "Puerto:",
"Nitrox_AddServer_PortPlaceholder": "Ingresa el puerto numérico del servidor",
+ "Nitrox_BedGetUp": "Levantarse",
"Nitrox_BuildingDesyncDetected": "El servidor detectó una desincronización con un edificio del cliente local (Ve a las opciones de Nitrox para solicitar una resincronización)",
"Nitrox_BuildingSettings": "Construcción de bases",
"Nitrox_Cancel": "Cancelar",
"Nitrox_CommandNotAvailable": "Este comando no está disponible con Nitrox",
"Nitrox_Confirm": "Confirmar",
"Nitrox_ConnectTo": "Conectarse a",
- "Nitrox_DenyOwnershipHand": "Otro jugador esta interactiando con ese objeto",
+ "Nitrox_DenyOwnershipHand": "Otro jugador esta interactuando con ese objeto",
"Nitrox_DisconnectedSession": "Desconectado del servidor",
"Nitrox_DiscordAccept": "Aceptar",
"Nitrox_DiscordDecline": "Cancelar",
@@ -78,12 +79,19 @@
"Nitrox_ServerEntry_DeleteWarning": "¿Estás seguro de que deseas eliminar este servidor?",
"Nitrox_ServerStopped": "El servidor se ha parado",
"Nitrox_Settings_Bandwidth": "Configuración del ancho de banda",
+ "Nitrox_Settings_ChatVisibilityDuration": "Duración de la visibilidad del chat",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "Cuánto tiempo permanece visible el chat. Establécelo en 0 para desactivar las ventanas emergentes del chat.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Dar un valor más alto para conexiones inestables",
+ "Nitrox_Settings_Keybind_FocusDiscord": "Centrarse en la ventana de invitación de Discord",
+ "Nitrox_Settings_Keybind_OpenChat": "Abrir chat",
"Nitrox_Settings_LatencyUpdatePeriod": "Periodo(s) de actualización de la latencia",
+ "Nitrox_Settings_OfflineClockSyncDuration": "Duración del procedimiento de sincronización del reloj",
+ "Nitrox_Settings_Privacy": "Privacidad",
"Nitrox_Settings_SafetyLatencyMargin": "Margen de latencia de seguridad (ms)",
"Nitrox_ShowPing": "Mostrar señal",
"Nitrox_SilenceChat": "Silenciar el chat",
"Nitrox_SilencedChatNotif": "Se silencia el chat",
+ "Nitrox_SleepingPlayers": "[Durmiendo]/[Total]jugadores durmiendo",
"Nitrox_StartServer": "Inicie su servidor para alcanzar a su mundo",
"Nitrox_SyncingWorld": "Sincronización del mundo multijugador…",
"Nitrox_TeleportTo": "Teleportar hacia {PLAYER}",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/ga.json b/Nitrox.Assets.Subnautica/LanguageFiles/ga.json
index ff16c16477..991d286917 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/ga.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/ga.json
@@ -79,11 +79,14 @@
"Nitrox_ServerEntry_DeleteWarning": "An bhfuil tú cinnte go dteastaíonn uait an freastalaí seo a scriosadh?",
"Nitrox_ServerStopped": "Stopadh an freastalaí",
"Nitrox_Settings_Bandwidth": "Socruithe bandaleithead",
+ "Nitrox_Settings_ChatVisibilityDuration": "Fad infheictheachta comhrá (s)",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "Cé chomh fada is a fhanann an comhrá le feiceáil. Socraigh go 0 chun fuinneoga aníos comhrá a dhíchumasú.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Tabhair luach níos airde do naisc éagobhsaí",
"Nitrox_Settings_Keybind_FocusDiscord": "Dírigh ar fhuinneog cuireadh Discord",
"Nitrox_Settings_Keybind_OpenChat": "Oscail comhrá",
"Nitrox_Settings_LatencyUpdatePeriod": "Tréimhse (nó) nuashonraithe moille",
"Nitrox_Settings_OfflineClockSyncDuration": "Fad nós imeachta sioncrónaithe cloig (s)",
+ "Nitrox_Settings_Privacy": "Príobháideacht",
"Nitrox_Settings_SafetyLatencyMargin": "Corrlach moille sábháilteachta (ms)",
"Nitrox_ShowPing": "Taispeáin Ping",
"Nitrox_SilenceChat": "Balbhaigh comhrá",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/nl.json b/Nitrox.Assets.Subnautica/LanguageFiles/nl.json
index f543117be4..827a6aacb7 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/nl.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/nl.json
@@ -9,6 +9,7 @@
"Nitrox_AddServer_NamePlaceholder": "Voer een naam in voor de server",
"Nitrox_AddServer_PortDescription": "Poort:",
"Nitrox_AddServer_PortPlaceholder": "Voer de numerieke poort van de server in",
+ "Nitrox_BedGetUp": "Sta op",
"Nitrox_BuildingDesyncDetected": "Synchronisatie probleem gedetecteerd in basis gebouwen (ga naar Nitrox instellingen om basissen opnieuw in te laden)",
"Nitrox_BuildingSettings": "Basis bouwen",
"Nitrox_Cancel": "Annuleer",
@@ -78,12 +79,17 @@
"Nitrox_ServerEntry_DeleteWarning": "Weet u zeker dat u deze server wilt verwijderen?",
"Nitrox_ServerStopped": "Server is gestopt",
"Nitrox_Settings_Bandwidth": "Bandbreedte-instellingen",
+ "Nitrox_Settings_ChatVisibilityDuration": "Chat zichtbaarheid duur (s)",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "Hoelang de chat zichtbaar blijft. Zet op 0 op chat popups uit te zetten.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Geef een hogere waarde voor onstabiele verbindingen",
- "Nitrox_Settings_LatencyUpdatePeriod": "Latentie-updateperiode (s)",
- "Nitrox_Settings_SafetyLatencyMargin": "Veiligheidslatentiemarge (ms)",
+ "Nitrox_Settings_Keybind_OpenChat": "Open chat",
+ "Nitrox_Settings_LatencyUpdatePeriod": "Latentie-updateperiode(s)",
+ "Nitrox_Settings_Privacy": "Privacy",
+ "Nitrox_Settings_SafetyLatencyMargin": "Veiligheids latentie marge (ms)",
"Nitrox_ShowPing": "Laat pin zien",
"Nitrox_SilenceChat": "Chat dempen",
"Nitrox_SilencedChatNotif": "Chat is gedempt",
+ "Nitrox_SleepingPlayers": "{SLEEPING}/{TOTAL} spelers zijn aan het slapen.",
"Nitrox_StartServer": "Start je server eerst om mee toe doen met je eigen gehostte wereld",
"Nitrox_SyncingWorld": "Multiplayer Wereld Synchroniseren…",
"Nitrox_TeleportTo": "Teleporteer naar {PLAYER}",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/pt-BR.json b/Nitrox.Assets.Subnautica/LanguageFiles/pt-BR.json
index 6855a510a7..c2bccdbb22 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/pt-BR.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/pt-BR.json
@@ -9,6 +9,7 @@
"Nitrox_AddServer_NamePlaceholder": "Digite um nome para o servidor",
"Nitrox_AddServer_PortDescription": "Porta:",
"Nitrox_AddServer_PortPlaceholder": "Digite o número da porta do servidor",
+ "Nitrox_BedGetUp": "Levantar-se",
"Nitrox_BuildingDesyncDetected": "O servidor detectou uma dessincronização com as construções do cliente local (vá para as configurações do Nitrox para solicitar uma ressincronização)",
"Nitrox_BuildingSettings": "Construção de base",
"Nitrox_Cancel": "Cancelar",
@@ -28,8 +29,8 @@
"Nitrox_EnterName": "Nome do jogador",
"Nitrox_ErrorDesyncDetected": "[Construção Segura] Esta base está atualmente dessincronizada, então você não pode modificá-la, a menos que sincronize novamente as construções (nas configurações do Nitrox)",
"Nitrox_ErrorRecentBuildUpdate": "Não é possível modificar uma base que foi atualizada recentemente por outro jogador",
- "Nitrox_Failure": "Um erro ocorreu",
- "Nitrox_FinishedResyncRequest": "Levou {TIME}ms para ressincronizar {COUNT} entities",
+ "Nitrox_Failure": "Ocorreu um erro",
+ "Nitrox_FinishedResyncRequest": "Levou {TIME}ms para ressincronizar {COUNT} entidades",
"Nitrox_FirewallInterfering": "As configurações do seu Firewall estão em conflito",
"Nitrox_HideIp": "Ocultar endereço IP",
"Nitrox_HidePing": "Ocultar Ping",
@@ -39,7 +40,7 @@
"Nitrox_Join": "Entrar",
"Nitrox_JoinServer": "Entrando:",
"Nitrox_JoinServerPassword": "Senha:",
- "Nitrox_JoinServerPasswordHeader": "O Servidor selecionado necessita de senha",
+ "Nitrox_JoinServerPasswordHeader": "Senha do servidor necessária",
"Nitrox_JoinServerPlaceholder": "Por favor insira a senha do servidor",
"Nitrox_JoiningSession": "Entrando na Sessão",
"Nitrox_Kick": "Expulsar {PLAYER}",
@@ -58,7 +59,7 @@
"Nitrox_OK": "Ok",
"Nitrox_OutOfDateClient": "A sua instalação do Nitrox está desatualizada. Servidor: {serverVersion}, Sua: {localVersion}.",
"Nitrox_OutOfDateServer": "O servidor está rodando em uma versão antiga do Nitrox. Peça para o administrador do servidor para atualizar ou desatualizar a sua instalação do Nitrox. Versão do Servidor: {serverVersion}, Sua Versão: {localVersion}.",
- "Nitrox_PlayerDeathBeaconLabel": "{PLAYER}'s morto",
+ "Nitrox_PlayerDeathBeaconLabel": "{PLAYER} morto",
"Nitrox_PlayerDied": "{PLAYER} morreu",
"Nitrox_PlayerDisconnected": "{PLAYER} desconectou-se",
"Nitrox_PlayerJoined": "{PLAYER} entrou no jogo.",
@@ -76,21 +77,28 @@
"Nitrox_SafeBuildingLog": "Registro de construção segura",
"Nitrox_ScannerRoomWarn": "Atenção: as salas de scanner não funcionam corretamente nesta versão do Nitrox. Espere muitos bugs.",
"Nitrox_ServerEntry_DeleteWarning": "Tem certeza de que deseja excluir este servidor?",
- "Nitrox_ServerStopped": "O servidor foi fechado",
+ "Nitrox_ServerStopped": "O servidor foi desligado",
"Nitrox_Settings_Bandwidth": "Configurações de largura de banda",
+ "Nitrox_Settings_ChatVisibilityDuration": "Duração da visibilidade do chat",
+ "Nitrox_Settings_ChatVisibilityDuration_Tooltip": "Por quanto tempo o chat permanece visível. Defina como 0 para desativar os pop-ups do chat.",
"Nitrox_Settings_HigherForUnstable_Tooltip": "Dê um valor maior para conexões instáveis",
+ "Nitrox_Settings_Keybind_FocusDiscord": "Foco na janela de convite do Discord",
+ "Nitrox_Settings_Keybind_OpenChat": "Abrir chat",
"Nitrox_Settings_LatencyUpdatePeriod": "Período(s) de atualização(ões) de latência",
+ "Nitrox_Settings_OfflineClockSyncDuration": "Duração de procedimento de sinc. do relógio (s)",
+ "Nitrox_Settings_Privacy": "Privacidade",
"Nitrox_Settings_SafetyLatencyMargin": "Margem de latência de segurança (ms)",
"Nitrox_ShowPing": "Mostrar ping",
"Nitrox_SilenceChat": "Silenciar a conversa",
"Nitrox_SilencedChatNotif": "O chat agora está silenciado",
+ "Nitrox_SleepingPlayers": "{SLEEPING}/{TOTAL} jogadores dormindo.",
"Nitrox_StartServer": "Inicie seu servidor primeiro para entrar na sua sessão",
"Nitrox_SyncingWorld": "Sincronizando com Servidor Multiplayer…",
"Nitrox_TeleportTo": "Teletransportar para {PLAYER}",
"Nitrox_TeleportToMe": "Teletransportar {PLAYER} para mim",
"Nitrox_TeleportToMeQuestion": "Teletransportar {PLAYER} para mim?",
"Nitrox_TeleportToQuestion": "Teletransportar para {PLAYER}?",
- "Nitrox_ThankForPlaying": "Obrigado por utilizar Nitrox !",
+ "Nitrox_ThankForPlaying": "Obrigado por utilizar Nitrox!",
"Nitrox_UnableToConnect": "Não foi possível conectar com o Servidor remoto:",
"Nitrox_Unmute": "Desmutar {PLAYER}",
"Nitrox_UnmuteQuestion": "Desmutar {PLAYER}?",
diff --git a/Nitrox.Assets.Subnautica/LanguageFiles/uk.json b/Nitrox.Assets.Subnautica/LanguageFiles/uk.json
index bc28e561b7..3a01f853bd 100644
--- a/Nitrox.Assets.Subnautica/LanguageFiles/uk.json
+++ b/Nitrox.Assets.Subnautica/LanguageFiles/uk.json
@@ -9,6 +9,7 @@
"Nitrox_AddServer_NamePlaceholder": "Введіть ім'я серверу",
"Nitrox_AddServer_PortDescription": "Порт:",
"Nitrox_AddServer_PortPlaceholder": "Введіть числовий порт сервера",
+ "Nitrox_BedGetUp": "Вставай",
"Nitrox_BuildingDesyncDetected": "Сервер виявив десинхронізацію з локальними клієнтськими будівлями (перейдіть до налаштувань Nitrox, щоб надіслати запит на повторну синхронізацію)",
"Nitrox_BuildingSettings": "Налаштування будівель",
"Nitrox_Cancel": "Скасувати",
From bde6d8fc5f0b9edae29a0bf051db6f9674725e37 Mon Sep 17 00:00:00 2001
From: Meas <1107063+Measurity@users.noreply.github.com>
Date: Fri, 30 Jan 2026 16:05:09 +0100
Subject: [PATCH 05/59] Enabled CRC for packets and use native sockets (latter
only server-side) (#2661)
---
.../Models/Communication/LiteNetLibServer.cs | 5 ++++-
.../NetworkingLayer/LiteNetLib/LiteNetLibClient.cs | 5 +++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/Nitrox.Server.Subnautica/Models/Communication/LiteNetLibServer.cs b/Nitrox.Server.Subnautica/Models/Communication/LiteNetLibServer.cs
index ff064222e9..d56ba2a925 100644
--- a/Nitrox.Server.Subnautica/Models/Communication/LiteNetLibServer.cs
+++ b/Nitrox.Server.Subnautica/Models/Communication/LiteNetLibServer.cs
@@ -1,7 +1,9 @@
using System.Buffers;
using System.Collections.Generic;
using LiteNetLib;
+using LiteNetLib.Layers;
using LiteNetLib.Utils;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Server.Subnautica.Models.GameLogic;
using Nitrox.Server.Subnautica.Models.GameLogic.Entities;
@@ -37,8 +39,9 @@ public LiteNetLibServer(PacketHandler packetHandler, PlayerManager playerManager
this.options = options;
this.logger = logger;
listener = new EventBasedNetListener();
- server = new NetManager(listener)
+ server = new NetManager(listener, NitroxEnvironment.IsReleaseMode ? new Crc32cLayer() : null)
{
+ UseNativeSockets = true,
IPv6Enabled = true
};
}
diff --git a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs
index dc24917775..2970386ec5 100644
--- a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs
+++ b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs
@@ -4,14 +4,15 @@
using System.Threading;
using System.Threading.Tasks;
using LiteNetLib;
+using LiteNetLib.Layers;
using LiteNetLib.Utils;
+using Nitrox.Model.Core;
using NitroxClient.Communication.Abstract;
using NitroxClient.Debuggers;
using NitroxClient.MonoBehaviours;
using NitroxClient.MonoBehaviours.Gui.Modals;
using Nitrox.Model.Networking;
using Nitrox.Model.Packets;
-using Nitrox.Model.Subnautica.Packets;
namespace NitroxClient.Communication.NetworkingLayer.LiteNetLib;
@@ -47,7 +48,7 @@ public LiteNetLibClient(PacketReceiver packetReceiver, INetworkDebugger networkD
};
- client = new NetManager(listener)
+ client = new NetManager(listener, NitroxEnvironment.IsReleaseMode ? new Crc32cLayer() : null)
{
UpdateTime = 15,
ChannelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length,
From 3f06268114f0ca01b517e7e87e073455bafbb553 Mon Sep 17 00:00:00 2001
From: MrBub <106004257+misterbubb@users.noreply.github.com>
Date: Thu, 5 Feb 2026 17:48:39 -0500
Subject: [PATCH 06/59] Sync DataBox (BlueprintHandTarget) state across players
(#2644)
Co-authored-by: dartasen <10561268+dartasen@users.noreply.github.com>
---
.../Metadata/BlueprintHandTargetMetadata.cs | 29 +++++++++++
.../Entities/Metadata/EntityMetadata.cs | 1 +
.../Server/Serialization/WorldServiceTest.cs | 3 ++
.../BlueprintHandTargetMetadataExtractor.cs | 15 ++++++
.../BlueprintHandTargetMetadataProcessor.cs | 51 +++++++++++++++++++
...ueprintHandTarget_UnlockBlueprint_Patch.cs | 23 +++++++++
6 files changed, 122 insertions(+)
create mode 100644 Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/BlueprintHandTargetMetadata.cs
create mode 100644 NitroxClient/GameLogic/Spawning/Metadata/Extractor/BlueprintHandTargetMetadataExtractor.cs
create mode 100644 NitroxClient/GameLogic/Spawning/Metadata/Processor/BlueprintHandTargetMetadataProcessor.cs
create mode 100644 NitroxPatcher/Patches/Dynamic/BlueprintHandTarget_UnlockBlueprint_Patch.cs
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/BlueprintHandTargetMetadata.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/BlueprintHandTargetMetadata.cs
new file mode 100644
index 0000000000..81c063ed21
--- /dev/null
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/BlueprintHandTargetMetadata.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Runtime.Serialization;
+using BinaryPack.Attributes;
+
+namespace Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities.Metadata;
+
+[Serializable]
+[DataContract]
+public class BlueprintHandTargetMetadata : EntityMetadata
+{
+ [DataMember(Order = 1)]
+ public bool Used { get; }
+
+ [IgnoreConstructor]
+ protected BlueprintHandTargetMetadata()
+ {
+ // Constructor for serialization. Has to be "protected" for json serialization.
+ }
+
+ public BlueprintHandTargetMetadata(bool used)
+ {
+ Used = used;
+ }
+
+ public override string ToString()
+ {
+ return $"[BlueprintHandTargetMetadata Used: {Used}]";
+ }
+}
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs
index 22e02c6c2e..815bb037ce 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/Metadata/EntityMetadata.cs
@@ -43,6 +43,7 @@ namespace Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities.Metadata
[ProtoInclude(84, typeof(DrillableMetadata))]
[ProtoInclude(85, typeof(PrecursorComputerTerminalMetadata))]
[ProtoInclude(86, typeof(GenericConsoleMetadata))]
+ [ProtoInclude(87, typeof(BlueprintHandTargetMetadata))]
public abstract class EntityMetadata
{
}
diff --git a/Nitrox.Test/Server/Serialization/WorldServiceTest.cs b/Nitrox.Test/Server/Serialization/WorldServiceTest.cs
index f5c3e13f34..5de23c6606 100644
--- a/Nitrox.Test/Server/Serialization/WorldServiceTest.cs
+++ b/Nitrox.Test/Server/Serialization/WorldServiceTest.cs
@@ -345,6 +345,9 @@ private static void EntityTest(Entity entity, Entity entityAfter)
case GenericConsoleMetadata metadata when entityAfter.Metadata is GenericConsoleMetadata metadataAfter:
Assert.AreEqual(metadata.GotUsed, metadataAfter.GotUsed);
break;
+ case BlueprintHandTargetMetadata metadata when entityAfter.Metadata is BlueprintHandTargetMetadata metadataAfter:
+ Assert.AreEqual(metadata.Used, metadataAfter.Used);
+ break;
default:
Assert.Fail($"Runtime type of {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {entity.Metadata?.GetType().Name} - {entityAfter.Metadata?.GetType().Name}");
break;
diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Extractor/BlueprintHandTargetMetadataExtractor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Extractor/BlueprintHandTargetMetadataExtractor.cs
new file mode 100644
index 0000000000..6991fee285
--- /dev/null
+++ b/NitroxClient/GameLogic/Spawning/Metadata/Extractor/BlueprintHandTargetMetadataExtractor.cs
@@ -0,0 +1,15 @@
+using Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities.Metadata;
+using NitroxClient.GameLogic.Spawning.Metadata.Extractor.Abstract;
+
+namespace NitroxClient.GameLogic.Spawning.Metadata.Extractor;
+
+///
+/// Extracts the current state of a DataBox (BlueprintHandTarget) for syncing to new players.
+///
+public class BlueprintHandTargetMetadataExtractor : EntityMetadataExtractor
+{
+ public override BlueprintHandTargetMetadata Extract(BlueprintHandTarget entity)
+ {
+ return new BlueprintHandTargetMetadata(entity.used);
+ }
+}
diff --git a/NitroxClient/GameLogic/Spawning/Metadata/Processor/BlueprintHandTargetMetadataProcessor.cs b/NitroxClient/GameLogic/Spawning/Metadata/Processor/BlueprintHandTargetMetadataProcessor.cs
new file mode 100644
index 0000000000..f379b12726
--- /dev/null
+++ b/NitroxClient/GameLogic/Spawning/Metadata/Processor/BlueprintHandTargetMetadataProcessor.cs
@@ -0,0 +1,51 @@
+using Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities.Metadata;
+using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
+using UnityEngine;
+
+namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
+
+///
+/// Processes DataBox (BlueprintHandTarget) metadata updates from other players.
+/// When another player opens a DataBox, this applies the visual state change locally.
+///
+public sealed class BlueprintHandTargetMetadataProcessor : EntityMetadataProcessor
+{
+ public override void ProcessMetadata(GameObject gameObject, BlueprintHandTargetMetadata metadata)
+ {
+ BlueprintHandTarget blueprintHandTarget = gameObject.GetComponent();
+ if (!blueprintHandTarget)
+ {
+ Log.Error($"[BlueprintHandTargetMetadataProcessor] No BlueprintHandTarget component found on {gameObject.name}");
+ return;
+ }
+
+ // Skip if already in the target state
+ if (blueprintHandTarget.used == metadata.Used)
+ {
+ return;
+ }
+
+ blueprintHandTarget.used = metadata.Used;
+
+ if (metadata.Used)
+ {
+ // Trigger the animation if the DataBox has an animator
+ if (!string.IsNullOrEmpty(blueprintHandTarget.animParam) && blueprintHandTarget.animator)
+ {
+ blueprintHandTarget.animator.SetBool(blueprintHandTarget.animParam, true);
+ }
+
+ // Disable the visual game object (the lid/door of the DataBox)
+ if (blueprintHandTarget.disableGameObject)
+ {
+ blueprintHandTarget.disableGameObject.SetActive(false);
+ }
+
+ // Unregister from resource tracker to remove the scanner ping
+ if (blueprintHandTarget.resourceTracker)
+ {
+ blueprintHandTarget.resourceTracker.OnBlueprintHandTargetUsed();
+ }
+ }
+ }
+}
diff --git a/NitroxPatcher/Patches/Dynamic/BlueprintHandTarget_UnlockBlueprint_Patch.cs b/NitroxPatcher/Patches/Dynamic/BlueprintHandTarget_UnlockBlueprint_Patch.cs
new file mode 100644
index 0000000000..c7339fc4ab
--- /dev/null
+++ b/NitroxPatcher/Patches/Dynamic/BlueprintHandTarget_UnlockBlueprint_Patch.cs
@@ -0,0 +1,23 @@
+using System.Reflection;
+using NitroxClient.GameLogic;
+using Nitrox.Model.DataStructures;
+using Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities.Metadata;
+
+namespace NitroxPatcher.Patches.Dynamic;
+
+///
+/// Syncs DataBox (BlueprintHandTarget) usage across players.
+/// When a player opens a DataBox to unlock a blueprint, this broadcasts the state to other players.
+///
+public sealed partial class BlueprintHandTarget_UnlockBlueprint_Patch : NitroxPatch, IDynamicPatch
+{
+ private static readonly MethodInfo TARGET_METHOD = Reflect.Method((BlueprintHandTarget t) => t.UnlockBlueprint());
+
+ public static void Postfix(BlueprintHandTarget __instance)
+ {
+ if (__instance.used && __instance.TryGetIdOrWarn(out NitroxId id))
+ {
+ Resolve().BroadcastMetadataUpdate(id, new BlueprintHandTargetMetadata(__instance.used));
+ }
+ }
+}
From 0d1fa0cd814a5e242342a1e9be03495b571e2936 Mon Sep 17 00:00:00 2001
From: Measurity <1107063+Measurity@users.noreply.github.com>
Date: Fri, 6 Feb 2026 16:56:10 +0100
Subject: [PATCH 07/59] Improved Steam exe finding in launcher for Linux
This caused proton to be launched without a proper Steam exe.
---
.../InstallationFinders/SteamFinder.cs | 61 ++-----------------
Nitrox.Model/Platforms/Store/Steam.cs | 61 ++++++++++++++++---
2 files changed, 56 insertions(+), 66 deletions(-)
diff --git a/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs b/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs
index 59a5147bb0..f2b16aed17 100644
--- a/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs
+++ b/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs
@@ -3,7 +3,7 @@
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Nitrox.Model.Platforms.Discovery.InstallationFinders.Core;
-using Nitrox.Model.Platforms.OS.Windows;
+using Nitrox.Model.Platforms.Store;
using static Nitrox.Model.Platforms.Discovery.InstallationFinders.Core.GameFinderResult;
namespace Nitrox.Model.Platforms.Discovery.InstallationFinders;
@@ -50,69 +50,18 @@ public GameFinderResult FindGame(GameInfo gameInfo)
return Ok(path);
}
- private static string? GetSteamPath()
+ private static string GetSteamPath()
{
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- string steamPath = RegistryEx.Read(@"Software\Valve\Steam\SteamPath");
-
- if (string.IsNullOrWhiteSpace(steamPath))
- {
- steamPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
- "Steam"
- );
- }
-
- return Directory.Exists(steamPath) ? steamPath : null;
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
- if (string.IsNullOrWhiteSpace(homePath))
- {
- homePath = Environment.GetEnvironmentVariable("HOME");
- }
-
- if (!Directory.Exists(homePath))
- {
- return null;
- }
-
- string[] commonSteamPath =
- [
- // Default install location
- // https://github.com/ValveSoftware/steam-for-linux
- Path.Combine(homePath, ".local", "share", "Steam"),
- // Those symlinks are often use as a backward-compatibility (Debian, Ubuntu, Fedora, ArchLinux)
- // https://wiki.archlinux.org/title/steam, https://askubuntu.com/questions/227502/where-are-steam-games-installed
- Path.Combine(homePath, ".steam", "steam"),
- Path.Combine(homePath, ".steam", "root"),
- // Flatpack install
- // https://github.com/flathub/com.valvesoftware.Steam/wiki, https://flathub.org/apps/com.valvesoftware.Steam
- Path.Combine(homePath, ".var", "app", "com.valvesoftware.Steam", ".local", "share", "Steam"),
- Path.Combine(homePath, ".var", "app", "com.valvesoftware.Steam", ".steam", "steam"),
- ];
-
- foreach (string path in commonSteamPath)
- {
- if (Directory.Exists(path))
- {
- return path;
- }
- }
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (string.IsNullOrWhiteSpace(homePath))
{
homePath = Environment.GetEnvironmentVariable("HOME");
}
-
if (!Directory.Exists(homePath))
{
- return null;
+ return "";
}
// Steam should always be here
@@ -123,7 +72,7 @@ public GameFinderResult FindGame(GameInfo gameInfo)
}
}
- return null;
+ return Path.GetDirectoryName(Steam.GetExeFile()) ?? "";
}
///
diff --git a/Nitrox.Model/Platforms/Store/Steam.cs b/Nitrox.Model/Platforms/Store/Steam.cs
index 608c882218..f56c1c0c54 100644
--- a/Nitrox.Model/Platforms/Store/Steam.cs
+++ b/Nitrox.Model/Platforms/Store/Steam.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
@@ -107,13 +108,23 @@ await RegistryEx.CompareWaitAsync(@"SOFTWARE\Valve\Steam\ActiveProcess\Acti
return steam;
}
- private static string? GetExeFile()
+ public static string? GetExeFile()
{
string steamExecutable = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- steamExecutable = Path.Combine(RegistryEx.Read(@"SOFTWARE\Valve\Steam\SteamPath", steamExecutable), "steam.exe");
+ string steamPath = RegistryEx.Read(@"Software\Valve\Steam\SteamPath");
+
+ if (string.IsNullOrWhiteSpace(steamPath))
+ {
+ steamPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
+ "Steam"
+ );
+ }
+
+ steamExecutable = Directory.Exists(steamPath) ? Path.Combine(steamPath, "steam.exe") : "";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
@@ -121,20 +132,50 @@ await RegistryEx.CompareWaitAsync(@"SOFTWARE\Valve\Steam\ActiveProcess\Acti
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- string userHomePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
- if (!Directory.Exists(userHomePath))
+ string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ if (string.IsNullOrWhiteSpace(homePath))
+ {
+ homePath = Environment.GetEnvironmentVariable("HOME");
+ }
+ if (!Directory.Exists(homePath))
{
return null;
}
- string steamPath = Path.Combine(userHomePath, ".steam", "steam");
- // support flatpak
- if (!Directory.Exists(steamPath))
+ string[] commonPaths = [
+ // Default install location
+ // https://github.com/ValveSoftware/steam-for-linux
+ Path.Combine(homePath, ".local", "share", "Steam"),
+ // Those symlinks are often use as a backward-compatibility (Debian, Ubuntu, Fedora, ArchLinux)
+ // https://wiki.archlinux.org/title/steam, https://askubuntu.com/questions/227502/where-are-steam-games-installed
+ Path.Combine(homePath, ".steam", "steam"),
+ Path.Combine(homePath, ".steam", "root"),
+ // Flatpack install
+ // https://github.com/flathub/com.valvesoftware.Steam/wiki, https://flathub.org/apps/com.valvesoftware.Steam
+ Path.Combine(homePath, ".var", "app", "com.valvesoftware.Steam", ".local", "share", "Steam"),
+ Path.Combine(homePath, ".var", "app", "com.valvesoftware.Steam", ".steam", "steam"),
+ ];
+
+ string steamPath = "";
+ foreach (string path in commonPaths)
+ {
+ try
+ {
+ if (Directory.GetFileSystemEntries(path).Any())
+ {
+ steamPath = path;
+ break;
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ if (!string.IsNullOrWhiteSpace(steamPath))
{
- steamPath = Path.Combine(userHomePath, ".var", "app", "com.valvesoftware.Steam", "data", "Steam");
+ steamExecutable = Path.Combine(steamPath, "steam.sh");
}
-
- steamExecutable = Path.Combine(steamPath, "steam.sh");
}
return File.Exists(steamExecutable) ? Path.GetFullPath(steamExecutable) : null;
From 4830234b431656a992e2be2fe6c67e17aac719a1 Mon Sep 17 00:00:00 2001
From: Measurity <1107063+Measurity@users.noreply.github.com>
Date: Sat, 7 Feb 2026 23:55:52 +0100
Subject: [PATCH 08/59] Added code comment to SteamFinder explaining OSX
special case
---
.../Platforms/Discovery/InstallationFinders/SteamFinder.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs b/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs
index f2b16aed17..3b6429d835 100644
--- a/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs
+++ b/Nitrox.Model/Platforms/Discovery/InstallationFinders/SteamFinder.cs
@@ -52,6 +52,7 @@ public GameFinderResult FindGame(GameInfo gameInfo)
private static string GetSteamPath()
{
+ // OSX: Steam dynamic data isn't near the steam exe. Because it can't (or isn't supposed to) write anything inside application bundle.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
From b00d56715e9e7fa378c4053664fe739d94fcfc9b Mon Sep 17 00:00:00 2001
From: Meas <1107063+Measurity@users.noreply.github.com>
Date: Sun, 8 Feb 2026 19:55:56 +0100
Subject: [PATCH 09/59] Fixed prefab resource cache not storing all required
data (#2665)
---
.../Parsers/EntityDistributionsResource.cs | 16 +--
.../PrefabPlaceholderGroupsResource.cs | 98 ++++++++++---------
2 files changed, 57 insertions(+), 57 deletions(-)
diff --git a/Nitrox.Server.Subnautica/Models/Resources/Parsers/EntityDistributionsResource.cs b/Nitrox.Server.Subnautica/Models/Resources/Parsers/EntityDistributionsResource.cs
index c4c6c2ca84..ed37e5021a 100644
--- a/Nitrox.Server.Subnautica/Models/Resources/Parsers/EntityDistributionsResource.cs
+++ b/Nitrox.Server.Subnautica/Models/Resources/Parsers/EntityDistributionsResource.cs
@@ -12,13 +12,12 @@ internal class EntityDistributionsResource(SubnauticaAssetsManager assetsManager
private readonly SubnauticaAssetsManager assetsManager = assetsManager;
private readonly IOptions options = options;
- private ValueTask lootDistribution;
- public LootDistributionData LootDistribution => GetLootDistributionDataAsync().GetAwaiter().GetResult();
+ private readonly TaskCompletionSource lootDistributionTcs = new();
+ public LootDistributionData LootDistribution => lootDistributionTcs.Task.GetAwaiter().GetResult();
- public Task LoadAsync(CancellationToken cancellationToken)
+ public async Task LoadAsync(CancellationToken cancellationToken)
{
- lootDistribution = GetLootDistributionDataAsync(cancellationToken);
- return Task.CompletedTask;
+ lootDistributionTcs.TrySetResult(await GetLootDistributionDataAsync(cancellationToken));
}
public Task CleanupAsync()
@@ -27,13 +26,8 @@ public Task CleanupAsync()
return Task.CompletedTask;
}
- private async ValueTask GetLootDistributionDataAsync(CancellationToken cancellationToken = default)
+ private async Task GetLootDistributionDataAsync(CancellationToken cancellationToken = default)
{
- if (lootDistribution is { IsCompletedSuccessfully : true, Result: not null })
- {
- return await lootDistribution;
- }
-
// TODO: Do not depend on game code; use custom types to map to game JSON files.
LootDictionary result = JsonSerializer.Deserialize(await GetJsonAsync(cancellationToken),
new JsonSerializerOptions
diff --git a/Nitrox.Server.Subnautica/Models/Resources/Parsers/PrefabPlaceholderGroupsResource.cs b/Nitrox.Server.Subnautica/Models/Resources/Parsers/PrefabPlaceholderGroupsResource.cs
index f606dc8d3c..7caf26f0be 100644
--- a/Nitrox.Server.Subnautica/Models/Resources/Parsers/PrefabPlaceholderGroupsResource.cs
+++ b/Nitrox.Server.Subnautica/Models/Resources/Parsers/PrefabPlaceholderGroupsResource.cs
@@ -10,6 +10,8 @@
using Nitrox.Server.Subnautica.Models.Helper;
using Nitrox.Server.Subnautica.Models.Resources.AddressablesTools.Catalog;
using Nitrox.Server.Subnautica.Models.Resources.Core;
+using ClassIdByRuntimeKeyDictionary = System.Collections.Generic.Dictionary;
+using AddressableCatalogDictionary = System.Collections.Generic.Dictionary;
namespace Nitrox.Server.Subnautica.Models.Resources.Parsers;
@@ -23,18 +25,17 @@ internal sealed class PrefabPlaceholderGroupsResource(SubnauticaAssetsManager as
/// the cache is rebuilt
///
///
- private const int CACHE_VERSION = 3;
+ private const int CACHE_VERSION = 4;
+
private const string CACHE_FILENAME = "PrefabPlaceholdersGroupAssetsCache.json";
- private readonly ConcurrentDictionary addressableCatalog = new();
private readonly SubnauticaAssetsManager assetsManager = assetsManager;
- private readonly ConcurrentDictionary classIdByRuntimeKey = new();
- private readonly ConcurrentDictionary groupsByClassId = new();
private readonly ILogger logger = logger;
private readonly IOptions options = options;
- private readonly ConcurrentDictionary placeholdersByClassId = [];
private readonly TaskCompletionSource resourceLoadFinished = new();
private readonly JsonSerializer serializer = new() { TypeNameHandling = TypeNameHandling.Auto };
+ private ConcurrentDictionary groupsByClassId = [];
+ private ConcurrentDictionary placeholdersByClassId = [];
private ConcurrentDictionary randomPossibilitiesByClassId = [];
public ConcurrentDictionary GroupsByClassId
@@ -62,10 +63,6 @@ public ConcurrentDictionary RandomPossibilitiesByClassId
resourceLoadFinished.Task.GetAwaiter().GetResult();
return randomPossibilitiesByClassId;
}
- private set
- {
- randomPossibilitiesByClassId = value;
- }
}
public async Task LoadAsync(CancellationToken cancellationToken)
@@ -89,7 +86,7 @@ public void PickRandomClassIdIfRequired(ref string classId)
}
}
- private static Dictionary LoadPrefabDatabase(string fullFilename)
+ private static ClassIdByRuntimeKeyDictionary LoadPrefabDatabase(string fullFilename)
{
Dictionary prefabFiles = new();
if (!File.Exists(fullFilename))
@@ -122,40 +119,29 @@ private static void GetPrefabGameObjectInfoFromBundle(SubnauticaAssetsManager am
prefabGameObjectInfo = assetFileInst.file.Metadata.GetAssetInfo(rootAssetPathId);
}
- private Task> LoadPrefabsAndSpawnPossibilitiesAsync(CancellationToken cancellationToken = default)
+ private async Task LoadPrefabsAndSpawnPossibilitiesAsync(CancellationToken cancellationToken = default)
{
- string prefabDatabasePath = Path.Combine(options.Value.GetSubnauticaResourcesPath(), "StreamingAssets", "SNUnmanagedData", "prefabs.db");
-
- // Get all prefab-classIds linked to the (partial) bundle path
- Dictionary prefabDatabase = LoadPrefabDatabase(prefabDatabasePath);
- cancellationToken.ThrowIfCancellationRequested();
-
// Loading all prefabs by their classId and file paths (first the path to the prefab then the dependencies)
- LoadAddressableCatalog(options.Value.GetSubnauticaAaResourcePath(), prefabDatabase);
cancellationToken.ThrowIfCancellationRequested();
- Dictionary result = CreateOrLoadPrefabCache(options.Value.GetServerCachePath());
+ await CreateOrLoadPrefabCacheAsync(options.Value.GetServerCachePath());
cancellationToken.ThrowIfCancellationRequested();
// Select only prefabs with a PrefabPlaceholdersGroups component in the root and link them with their dependencyPaths
// Do not remove: the internal cache list is slowing down the process more than loading a few assets again. There maybe is a better way in the new AssetToolsNetVersion but, we need a byte to texture library bc ATNs sub-package is only for netstandard.
assetsManager.UnloadAll(true);
- // Clear private collections that were used temporarily to parse the files.
- addressableCatalog.Clear();
- classIdByRuntimeKey.Clear();
// Get all needed data for the filtered PrefabPlaceholdersGroups to construct PrefabPlaceholdersGroupAssets and add them to the dictionary by classId
Validate.IsFalse(randomPossibilitiesByClassId.IsEmpty);
- return Task.FromResult(result);
}
- private Dictionary CreateOrLoadPrefabCache(string nitroxCachePath)
+ private async Task CreateOrLoadPrefabCacheAsync(string nitroxCachePath)
{
- Dictionary prefabPlaceholdersGroupPaths = null;
+ Dictionary prefabPlaceholdersGroupPaths;
string cacheFilePath = Path.Combine(nitroxCachePath, CACHE_FILENAME);
Cache? cache = null;
try
{
- cache = Cache.Deserialize(serializer, cacheFilePath);
+ cache = await Cache.DeserializeAsync(serializer, cacheFilePath);
}
catch (Exception ex)
{
@@ -169,24 +155,33 @@ private Dictionary CreateOrLoadPrefabCache
}
prefabPlaceholdersGroupPaths = cache.Value.PrefabPlaceholdersGroupPaths;
randomPossibilitiesByClassId = cache.Value.RandomPossibilitiesByClassId;
+ groupsByClassId = cache.Value.GroupsByClassId;
+ placeholdersByClassId = cache.Value.PlaceholdersByClassId;
logger.ZLogDebug($"Successfully loaded cache with {prefabPlaceholdersGroupPaths.Count:@PrefabPlaceholdersCount} prefab placeholder groups and {randomPossibilitiesByClassId.Count:@RandomPossibilitiesCount} random spawn behaviours.");
}
// Fallback solution
- if (prefabPlaceholdersGroupPaths is null)
+ else
{
logger.ZLogInformation($"Building cache, this may take a while...");
- prefabPlaceholdersGroupPaths = new(GetPrefabPlaceholderGroupAssetsByGroupClassId(assetsManager, GetAllPrefabPlaceholdersGroupsFast(assetsManager)));
- Cache.Serialize(serializer, new Cache(CACHE_VERSION, prefabPlaceholdersGroupPaths, randomPossibilitiesByClassId), cacheFilePath);
+ // Get all prefab-classIds linked to the (partial) bundle path
+ string prefabDatabasePath = Path.Combine(options.Value.GetSubnauticaResourcesPath(), "StreamingAssets", "SNUnmanagedData", "prefabs.db");
+ Dictionary prefabDatabase = LoadPrefabDatabase(prefabDatabasePath);
+ (AddressableCatalogDictionary addressableCatalog, ClassIdByRuntimeKeyDictionary classIdByRuntimeKey) = LoadAddressableCatalog(options.Value.GetSubnauticaAaResourcePath(), prefabDatabase);
+ prefabPlaceholdersGroupPaths = new(GetPrefabPlaceholderGroupAssetsByGroupClassId(assetsManager, GetAllPrefabPlaceholdersGroupsFast(assetsManager, addressableCatalog, classIdByRuntimeKey), addressableCatalog, classIdByRuntimeKey));
+ await Cache.SerializeAsync(serializer, new Cache(CACHE_VERSION, prefabPlaceholdersGroupPaths, randomPossibilitiesByClassId, groupsByClassId, placeholdersByClassId), cacheFilePath);
logger.ZLogDebug(
$"Successfully built cache with {prefabPlaceholdersGroupPaths.Count:@PrefabPlaceholdersCount} prefab placeholder groups and {randomPossibilitiesByClassId.Count:@RandomPossibilitiesCount} random spawn behaviours. Future server starts will take less time.");
}
Validate.IsTrue(prefabPlaceholdersGroupPaths.Count > 0);
Validate.IsTrue(randomPossibilitiesByClassId.Count > 0);
- return prefabPlaceholdersGroupPaths;
+ Validate.IsTrue(groupsByClassId.Count > 0);
+ Validate.IsTrue(placeholdersByClassId.Count > 0);
}
- private void LoadAddressableCatalog(string aaRootPath, Dictionary prefabDatabase)
+ private (AddressableCatalogDictionary, ClassIdByRuntimeKeyDictionary) LoadAddressableCatalog(string aaRootPath, Dictionary prefabDatabase)
{
+ ClassIdByRuntimeKeyDictionary classIdByRuntimeKey = [];
+ AddressableCatalogDictionary addressableCatalog = [];
ContentCatalogData ccd = ContentCatalogData.FromJson(File.ReadAllText(Path.Combine(aaRootPath, "catalog.json")));
Dictionary classIdByPath = prefabDatabase.ToDictionary(m => m.Value, m => m.Key);
@@ -216,13 +211,15 @@ private void LoadAddressableCatalog(string aaRootPath, Dictionary
/// Gathers bundle paths by class id for prefab placeholder groups.
/// Also fills
///
- private ConcurrentDictionary GetAllPrefabPlaceholdersGroupsFast(SubnauticaAssetsManager am)
+ private ConcurrentDictionary GetAllPrefabPlaceholdersGroupsFast(SubnauticaAssetsManager am, AddressableCatalogDictionary addressableCatalog, ClassIdByRuntimeKeyDictionary classIdByRuntimeKey)
{
// First step is to find out about the hash of the types PrefabPlaceholdersGroup and SpawnRandom
// to be able to recognize them easily later on
@@ -313,7 +310,8 @@ private ConcurrentDictionary GetAllPrefabPlaceholdersGroupsFas
return prefabPlaceholdersGroupPaths;
}
- private ConcurrentDictionary GetPrefabPlaceholderGroupAssetsByGroupClassId(SubnauticaAssetsManager am, ConcurrentDictionary prefabPlaceholdersGroupPaths)
+ private ConcurrentDictionary GetPrefabPlaceholderGroupAssetsByGroupClassId(SubnauticaAssetsManager am, ConcurrentDictionary prefabPlaceholdersGroupPaths,
+ AddressableCatalogDictionary addressableCatalog, ClassIdByRuntimeKeyDictionary classIdByRuntimeKey)
{
ConcurrentDictionary prefabPlaceholderGroupsByGroupClassId = new();
@@ -323,7 +321,7 @@ private ConcurrentDictionary GetPrefabPlac
SubnauticaAssetsManager amInnerClone = amClone.Clone();
AssetsFileInstance assetFileInst = amInnerClone.LoadBundleWithDependencies(keyValuePair.Value);
- PrefabPlaceholdersGroupAsset prefabPlaceholderGroup = GetAndCachePrefabPlaceholdersGroupOfBundle(amInnerClone, assetFileInst, keyValuePair.Key);
+ PrefabPlaceholdersGroupAsset prefabPlaceholderGroup = GetAndCachePrefabPlaceholdersGroupOfBundle(amInnerClone, assetFileInst, keyValuePair.Key, addressableCatalog, classIdByRuntimeKey);
amInnerClone.UnloadAll();
if (!prefabPlaceholderGroupsByGroupClassId.TryAdd(keyValuePair.Key, prefabPlaceholderGroup))
@@ -334,13 +332,15 @@ private ConcurrentDictionary GetPrefabPlac
return prefabPlaceholderGroupsByGroupClassId;
}
- private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupOfBundle(SubnauticaAssetsManager amInst, AssetsFileInstance assetFileInst, string classId)
+ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupOfBundle(SubnauticaAssetsManager amInst, AssetsFileInstance assetFileInst, string classId, AddressableCatalogDictionary addressableCatalog,
+ ClassIdByRuntimeKeyDictionary classIdByRuntimeKey)
{
GetPrefabGameObjectInfoFromBundle(amInst, assetFileInst, out AssetFileInfo prefabGameObjectInfo);
- return GetAndCachePrefabPlaceholdersGroupGroup(amInst, assetFileInst, prefabGameObjectInfo, classId);
+ return GetAndCachePrefabPlaceholdersGroupGroup(amInst, assetFileInst, prefabGameObjectInfo, classId, addressableCatalog, classIdByRuntimeKey);
}
- private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(SubnauticaAssetsManager amInst, AssetsFileInstance assetFileInst, AssetFileInfo rootGameObjectInfo, string classId)
+ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(SubnauticaAssetsManager amInst, AssetsFileInstance assetFileInst, AssetFileInfo rootGameObjectInfo, string classId, AddressableCatalogDictionary addressableCatalog,
+ ClassIdByRuntimeKeyDictionary classIdByRuntimeKey)
{
if (!string.IsNullOrEmpty(classId) && groupsByClassId.TryGetValue(classId, out PrefabPlaceholdersGroupAsset cachedGroup))
{
@@ -368,7 +368,7 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
AssetTypeValueField gameObjectPtr = prefabPlaceholder["m_GameObject"];
AssetTypeValueField gameObjectField = amInst.GetExtAsset(assetFileInst, gameObjectPtr).baseField;
- IPrefabAsset asset = GetAndCacheAsset(amInst, prefabPlaceholder["prefabClassId"].AsString);
+ IPrefabAsset asset = GetAndCacheAsset(amInst, prefabPlaceholder["prefabClassId"].AsString, addressableCatalog, classIdByRuntimeKey);
bool isEntitySlotAsset = asset is PrefabPlaceholderAsset prefabPlaceholderAsset && prefabPlaceholderAsset.EntitySlot.HasValue;
NitroxTransform transform = amInst.GetTransformFromGameObject(assetFileInst, gameObjectField, rootGameObjectName, isEntitySlotAsset);
string prefabAssetClassId = prefabPlaceholder["prefabClassId"].AsString;
@@ -388,7 +388,7 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
return prefabPlaceholdersGroup;
}
- private IPrefabAsset? GetAndCacheAsset(SubnauticaAssetsManager am, string classId)
+ private IPrefabAsset? GetAndCacheAsset(SubnauticaAssetsManager am, string classId, AddressableCatalogDictionary addressableCatalog, ClassIdByRuntimeKeyDictionary classIdByRuntimeKey)
{
if (string.IsNullOrEmpty(classId))
{
@@ -415,7 +415,7 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
AssetFileInfo placeholdersGroupInfo = am.GetMonoBehaviourFromGameObject(assetFileInst, prefabGameObjectInfo, "PrefabPlaceholdersGroup");
if (placeholdersGroupInfo != null)
{
- PrefabPlaceholdersGroupAsset groupAsset = GetAndCachePrefabPlaceholdersGroupOfBundle(am, assetFileInst, classId);
+ PrefabPlaceholdersGroupAsset groupAsset = GetAndCachePrefabPlaceholdersGroupOfBundle(am, assetFileInst, classId, addressableCatalog, classIdByRuntimeKey);
groupsByClassId[classId] = groupAsset;
return groupAsset;
}
@@ -469,24 +469,30 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
return prefabPlaceholderAsset;
}
- private record struct Cache(int Version, Dictionary PrefabPlaceholdersGroupPaths, ConcurrentDictionary RandomPossibilitiesByClassId)
+ private record struct Cache(
+ int Version,
+ Dictionary PrefabPlaceholdersGroupPaths,
+ ConcurrentDictionary RandomPossibilitiesByClassId,
+ ConcurrentDictionary GroupsByClassId,
+ ConcurrentDictionary PlaceholdersByClassId
+ )
{
- public static void Serialize(JsonSerializer serializer, Cache cache, string filePath)
+ public static async Task SerializeAsync(JsonSerializer serializer, Cache cache, string filePath)
{
Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? throw new Exception("Failed to get directory path from cache file path"));
- using StreamWriter stream = File.CreateText(filePath);
+ await using StreamWriter stream = File.CreateText(filePath);
serializer.Serialize(stream, cache);
}
- public static Cache? Deserialize(JsonSerializer serializer, string filePath)
+ public static Task DeserializeAsync(JsonSerializer serializer, string filePath)
{
if (!File.Exists(filePath))
{
- return null;
+ return Task.FromResult(null);
}
Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? throw new Exception("Failed to get directory path from cache file path"));
using StreamReader reader = File.OpenText(filePath);
- return (Cache?)serializer.Deserialize(reader, typeof(Cache));
+ return Task.FromResult((Cache?)serializer.Deserialize(reader, typeof(Cache)));
}
}
}
From fbd5cafc69e90d029a0bd2e2b0d8cc379c03a01f Mon Sep 17 00:00:00 2001
From: Meas <1107063+Measurity@users.noreply.github.com>
Date: Mon, 16 Feb 2026 14:56:35 +0100
Subject: [PATCH 10/59] Asyncify command and packet handling (#2656)
Co-authored-by: Coding-Hen <8798074+Coding-Hen@users.noreply.github.com>
---
Nitrox.Launcher/Models/Design/ServerEntry.cs | 3 +-
.../Models/Design/StaticCommands.cs | 8 +-
.../FileCacheDelegatingHandler.cs | 2 +-
.../Models/Services/StorageService.cs | 6 +-
.../Styles/Theme/RadioButtonGroupStyle.axaml | 8 +-
.../ViewModels/ManageServerViewModel.cs | 2 +-
.../Entities/EscapePodWorldEntity.cs | 5 +-
.../GameLogic/Entities/GlobalRootEntity.cs | 2 +-
.../GameLogic/Entities/IUwePrefabFactory.cs | 3 +-
.../Entities/IUweWorldEntityFactory.cs | 6 -
.../GameLogic/Entities/InventoryItemEntity.cs | 2 +-
.../GameLogic/Entities/OxygenPipeEntity.cs | 2 +-
.../GameLogic/Entities/PrefabChildEntity.cs | 2 +-
.../Entities/SerializedWorldEntity.cs | 2 +-
.../GameLogic/Entities/WorldEntity.cs | 4 +-
.../DataStructures/GameLogic/Entity.cs | 2 +-
.../GameLogic/RandomStartGenerator.cs | 45 +-
.../Extensions/SunbeamEventExtensions.cs | 17 +
.../MultiplayerSession/PlayerContext.cs | 15 +-
.../Packets/AnimationChangeEvent.cs | 13 +-
.../Packets/BenchChanged.cs | 7 +-
.../Packets/BuildingResyncRequest.cs | 12 +-
.../Packets/CellVisibilityChanged.cs | 7 +-
.../Packets/ChatMessage.cs | 23 +-
.../Packets/DebugStartMapPacket.cs | 16 +-
Nitrox.Model.Subnautica/Packets/Disconnect.cs | 17 +-
.../Packets/EscapePodChanged.cs | 7 +-
.../Packets/FootstepPacket.cs | 7 +-
.../Packets/GameModeChanged.cs | 15 +-
.../Packets/KnownTechEntryAdd.cs | 4 +-
Nitrox.Model.Subnautica/Packets/Movement.cs | 3 +-
.../Packets/MultiplayerSessionReservation.cs | 38 +-
Nitrox.Model.Subnautica/Packets/MutePlayer.cs | 7 +-
.../Packets/PDAScanFinished.cs | 4 +-
.../Packets/PieceDeconstructed.cs | 2 +-
.../Packets/PlaySunbeamEvent.cs | 53 ++-
.../Packets/PlayerCinematicControllerCall.cs | 7 +-
.../Packets/PlayerDeathEvent.cs | 7 +-
.../Packets/PlayerHeldItemChanged.cs | 44 +-
.../Packets/PlayerInCyclopsMovement.cs | 7 +-
.../Packets/PlayerMovement.cs | 38 +-
.../Packets/PlayerStats.cs | 7 +-
Nitrox.Model.Subnautica/Packets/PvPAttack.cs | 7 +-
.../Packets/SetIntroCinematicMode.cs | 13 +-
.../Packets/SimulationOwnershipChange.cs | 5 +-
.../Packets/SimulationOwnershipRequest.cs | 26 +-
.../Packets/SpawnEntities.cs | 2 +-
.../Packets/StasisSphereHit.cs | 7 +-
.../Packets/StasisSphereShot.cs | 7 +-
.../Packets/SubRootChanged.cs | 22 +-
.../Packets/VehicleDocking.cs | 16 +-
.../Packets/VehicleOnPilotModeChanged.cs | 7 +-
.../Packets/VehicleUndocking.cs | 7 +-
.../Configuration/ServerStartOptions.cs | 1 -
.../Configuration/SubnauticaServerOptions.cs | 1 +
Nitrox.Model/Constants/NitroxConstants.cs | 1 +
.../Constants/SubnauticaServerConstants.cs | 9 +-
Nitrox.Model/Core/PeerId.cs | 27 ++
Nitrox.Model/Core/SessionId.cs | 40 ++
.../DataStructures/GameLogic/Perms.cs | 71 ++--
Nitrox.Model/DataStructures/NitroxId.cs | 6 +-
Nitrox.Model/DataStructures/Optional.cs | 6 +-
.../DataStructures/SimulatedEntity.cs | 48 +--
.../DataStructures/Unity/NitroxTransform.cs | 4 +-
Nitrox.Model/Extensions/StringExtensions.cs | 20 +-
Nitrox.Model/GlobalUsings.cs | 1 +
Nitrox.Model/Helper/AsyncBarrier.cs | 52 ---
Nitrox.Model/Helper/Validate.cs | 9 +-
.../Packets/Core/IPacketProcessContext.cs | 11 +
Nitrox.Model/Packets/Core/IPacketProcessor.cs | 20 +
.../Packets/Core/PacketProcessorsInvoker.cs | 139 ++++++
Nitrox.Model/Packets/CorrelatedPacket.cs | 17 +-
Nitrox.Model/Packets/Packet.cs | 20 +-
.../Processors/Abstract/IProcessorContext.cs | 6 -
.../Processors/Abstract/PacketProcessor.cs | 43 --
Nitrox.Model/Packets/TextAutoComplete.cs | 20 +
.../Extensions/LoggerExtensions.cs | 53 ++-
.../Extensions/LoggingBuilderExtensions.cs | 80 +++-
.../Extensions/ServiceCollectionExtensions.cs | 396 +++++++++---------
.../Administration/Core/IAdminFeature.cs | 5 +
.../Models/Administration/IKickPlayer.cs | 9 +
.../Models/AppEvents/ISaveState.cs | 15 +
.../Models/AppEvents/ISessionCleaner.cs | 12 +
.../Models/AppEvents/Triggers/AsyncTrigger.cs | 27 ++
.../Models/Commands/Abstract/CallArgs.cs | 63 ---
.../Models/Commands/Abstract/Command.cs | 139 ------
.../Models/Commands/Abstract/Parameter.cs | 41 --
.../Commands/Abstract/Type/TypeBoolean.cs | 39 --
.../Models/Commands/Abstract/Type/TypeEnum.cs | 26 --
.../Commands/Abstract/Type/TypeFloat.cs | 23 -
.../Models/Commands/Abstract/Type/TypeInt.cs | 23 -
.../Commands/Abstract/Type/TypeNitroxId.cs | 22 -
.../Commands/Abstract/Type/TypePlayer.cs | 26 --
.../Commands/Abstract/Type/TypeString.cs | 18 -
.../ArgConverters/Core/ConvertResult.cs | 17 +
.../ArgConverters/Core/IArgConverter.cs | 16 +
.../PlayerNameToPlayerArgConverter.cs | 21 +
.../SessionIdToPlayerArgConverter.cs | 26 ++
.../ArgConverters/WordToBoolArgConverter.cs | 17 +
.../Models/Commands/AuroraCommand.cs | 49 ++-
.../Models/Commands/AutosaveCommand.cs | 37 +-
.../Models/Commands/BackCommand.cs | 42 +-
.../Models/Commands/BroadcastCommand.cs | 31 +-
.../Commands/ChangeAdminPasswordCommand.cs | 39 +-
.../Commands/ChangeServerGamemodeCommand.cs | 47 +--
.../Commands/ChangeServerPasswordCommand.cs | 41 +-
.../Models/Commands/ConfigCommand.cs | 75 +---
.../Models/Commands/Core/AliasAttribute.cs | 7 +
.../Commands/Core/CommandHandlerEntry.cs | 166 ++++++++
.../Models/Commands/Core/CommandOrigin.cs | 10 +
.../Models/Commands/Core/CommandRegistry.cs | 261 ++++++++++++
.../Core/HostToServerCommandContext.cs | 85 ++++
.../Models/Commands/Core/ICommandContext.cs | 40 ++
.../Models/Commands/Core/ICommandHandler.cs | 28 ++
.../Models/Commands/Core/ICommandSubmit.cs | 12 +
.../Core/PlayerToServerCommandContext.cs | 80 ++++
.../Models/Commands/Core/RequiresOrigin.cs | 10 +
.../Core/RequiresPermissionAttribute.cs | 12 +
.../Models/Commands/DebugStartMapCommand.cs | 36 --
.../Debugging/DebugStartMapCommand.cs | 28 ++
.../Commands/Debugging/LoadBatchCommand.cs | 26 ++
.../Commands/Debugging/PlayerCommand.cs | 59 +++
.../Models/Commands/Debugging/QueryCommand.cs | 46 ++
.../Debugging/SeedNearPositionCommand.cs | 73 ++++
.../Models/Commands/DeopCommand.cs | 48 ++-
.../Models/Commands/DirectoryCommand.cs | 81 ++--
.../Models/Commands/FastCommand.cs | 73 ++--
.../Models/Commands/GameModeCommand.cs | 70 ++--
.../Models/Commands/HelpCommand.cs | 123 ++++--
.../Models/Commands/KickCommand.cs | 70 ++--
.../Models/Commands/ListCommand.cs | 34 +-
.../Models/Commands/LoadBatchCommand.cs | 34 --
.../Models/Commands/LoginCommand.cs | 51 ++-
.../Models/Commands/MuteCommand.cs | 63 ++-
.../Models/Commands/OpCommand.cs | 33 +-
.../Models/Commands/PlayerCommand.cs | 66 ---
.../Processor/TextCommandProcessor.cs | 78 ----
.../Models/Commands/PromoteCommand.cs | 56 ++-
.../Models/Commands/PvpCommand.cs | 43 +-
.../Models/Commands/QueryCommand.cs | 53 ---
.../Models/Commands/SaveCommand.cs | 30 +-
.../Commands/SetKeepInventoryCommand.cs | 38 +-
.../Models/Commands/StopCommand.cs | 13 +-
.../Models/Commands/SummaryCommand.cs | 46 +-
.../Models/Commands/SunbeamCommand.cs | 49 +--
.../Models/Commands/SwapSerializerCommand.cs | 38 --
.../Models/Commands/TeleportCommand.cs | 36 +-
.../Models/Commands/TimeCommand.cs | 35 +-
.../Models/Commands/UnmuteCommand.cs | 64 ++-
.../Models/Commands/WarpCommand.cs | 51 +--
.../Models/Commands/WhisperCommand.cs | 32 +-
.../Models/Commands/WhoisCommand.cs | 42 +-
.../Communication/LiteNetLibConnection.cs | 79 ----
.../Models/Communication/LiteNetLibServer.cs | 351 ++++++++++++----
.../Models/Communication/NitroxConnection.cs | 13 -
.../Models/Communication/SessionManager.cs | 125 ++++++
.../Models/Factories/RandomFactory.cs | 29 ++
.../Models/GameLogic/Bases/BuildingManager.cs | 10 +-
.../GameLogic/Entities/EntityRegistry.cs | 13 +-
.../GameLogic/Entities/EntitySimulation.cs | 112 ++---
.../Entities/Spawning/BatchEntitySpawner.cs | 120 +++---
.../Spawning/CrashHomeBootstrapper.cs | 2 +-
.../Entities/Spawning/GeyserBootstrapper.cs | 6 +-
.../Entities/Spawning/IEntityBootstrapper.cs | 2 +-
.../Spawning/IEntityBootstrapperManager.cs | 2 +-
.../Entities/Spawning/IEntitySpawner.cs | 11 -
.../Entities/Spawning/ReefbackBootstrapper.cs | 30 +-
.../StayAtLeashPositionBootstrapper.cs | 2 +-
.../SubnauticaEntityBootstrapperManager.cs | 18 +-
.../Entities/SubnauticaUwePrefabFactory.cs | 34 +-
.../SubnauticaUweWorldEntityFactory.cs | 16 +-
.../GameLogic/Entities/WorldEntityManager.cs | 15 +-
.../Models/GameLogic/EscapePodManager.cs | 57 +--
.../Models/GameLogic/GameData.cs | 2 +-
.../Models/GameLogic/JoiningManager.cs | 132 +++---
.../Models/GameLogic/PdaManager.cs | 184 ++++++--
.../Models/GameLogic/PlayerManager.cs | 177 ++++----
.../GameLogic/Players/PersistedPlayerData.cs | 7 +-
.../Models/GameLogic/Players/PlayerData.cs | 2 +-
.../Models/GameLogic/SimulationOwnership.cs | 11 +-
.../Models/GameLogic/SleepManager.cs | 80 ++--
.../Models/GameLogic/StoryManager.cs | 13 +-
.../Models/GameLogic/StoryScheduler.cs | 6 +-
.../Models/GameLogic/TimeService.cs | 16 +-
.../GameLogic/Unlockables/PdaStateData.cs | 39 +-
.../Models/Helper/DeterministicGenerator.cs | 48 +--
.../Models/Helper/EasyPool.cs | 19 +
.../Models/Helper/XorRandom.cs | 122 +++---
.../WriteRedactedLogLoggerMiddleware.cs | 2 +-
.../Logging/Redaction/Core/IRedactor.cs | 2 +-
.../Packets/Core/AnonProcessorContext.cs | 24 ++
.../Packets/Core/AuthProcessorContext.cs | 27 ++
.../Packets/Core/IAnonPacketProcessor.cs | 11 +
.../Packets/Core/IAuthPacketProcessor.cs | 11 +
.../Models/Packets/Core/IPacketSender.cs | 13 +
.../Models/Packets/Core/NopPacketSender.cs | 12 +
.../Models/Packets/PacketHandler.cs | 89 ----
...AggressiveWhenSeeTargetChangedProcessor.cs | 13 +-
.../AnimationChangeEventProcessor.cs | 19 +-
.../AttackCyclopsTargetChangedProcessor.cs | 13 +-
.../Processors/BaseDeconstructedProcessor.cs | 12 +-
.../Packets/Processors/BedEnterProcessor.cs | 16 +-
.../Packets/Processors/BedExitProcessor.cs | 16 +-
.../Packets/Processors/BuildingProcessor.cs | 35 +-
.../BuildingResyncRequestProcessor.cs | 18 +-
.../CellVisibilityChangedProcessor.cs | 30 +-
.../Processors/ChatMessageProcessor.cs | 34 +-
.../Processors/CheatCommandProcessor.cs | 12 +-
.../Processors/ClearPlanterProcessor.cs | 9 +-
.../Core/AuthenticatedPacketProcessor.cs | 14 -
.../Core/TransmitIfCanSeePacketProcessor.cs | 22 +-
.../Core/UnauthenticatedPacketProcessor.cs | 15 -
.../CreatureActionChangedProcessor.cs | 8 +-
.../CreaturePoopPerformedProcessor.cs | 8 +-
.../CyclopsDamagePointRepairedProcessor.cs | 21 +-
.../Processors/CyclopsDamageProcessor.cs | 33 +-
.../Processors/CyclopsFireCreatedProcessor.cs | 21 +-
.../DefaultServerPacketProcessor.cs | 52 +--
.../Processors/DiscordRequestIPProcessor.cs | 24 +-
.../EntityDestroyedPacketProcessor.cs | 23 +-
.../EntityMetadataUpdateProcessor.cs | 30 +-
.../Processors/EntityReparentedProcessor.cs | 10 +-
.../EntitySpawnedByClientProcessor.cs | 66 ++-
.../EntityTransformUpdatesProcessor.cs | 105 +++--
.../EscapePodChangedPacketProcessor.cs | 14 +-
.../Packets/Processors/FMODAssetProcessor.cs | 10 +-
.../Processors/FMODEventInstanceProcessor.cs | 25 +-
.../Packets/Processors/FireDousedProcessor.cs | 21 +-
.../Processors/FootstepPacketProcessor.cs | 24 +-
.../Processors/GoalCompletedProcessor.cs | 8 +-
.../Processors/KnownTechEntryAddProcessor.cs | 38 +-
.../LargeWaterParkDeconstructedProcessor.cs | 14 +-
.../Processors/LeakRepairedProcessor.cs | 18 +-
.../ModifyConstructedAmountProcessor.cs | 12 +-
...ultiplayerSessionPolicyRequestProcessor.cs | 31 +-
...layerSessionReservationRequestProcessor.cs | 48 +--
.../PDAEncyclopediaEntryAddProcessor.cs | 28 +-
.../Processors/PDALogEntryAddProcessor.cs | 33 +-
.../PDAScanFinishedPacketProcessor.cs | 20 +-
.../Processors/PickupItemPacketProcessor.cs | 32 +-
.../Processors/PieceDeconstructedProcessor.cs | 12 +-
.../Processors/PinnedRecipeMovedProcessor.cs | 11 +-
.../Packets/Processors/PlaceBaseProcessor.cs | 14 +-
.../Packets/Processors/PlaceGhostProcessor.cs | 12 +-
.../Processors/PlaceModuleProcessor.cs | 14 +-
.../Processors/PlayerDeathEventProcessor.cs | 46 +-
.../PlayerHeldItemChangedProcessor.cs | 25 +-
.../PlayerInCyclopsMovementProcessor.cs | 40 +-
...layerJoiningMultiplayerSessionProcessor.cs | 17 +-
.../Processors/PlayerMovementProcessor.cs | 37 +-
...PlayerQuickSlotsBindingChangedProcessor.cs | 14 +-
.../PlayerSeeOutOfCellEntityProcessor.cs | 16 +-
.../Processors/PlayerStatsProcessor.cs | 26 +-
.../Processors/PlayerSyncFinishedProcessor.cs | 37 +-
.../PlayerUnseeOutOfCellEntityProcessor.cs | 35 +-
.../Packets/Processors/PvPAttackProcessor.cs | 21 +-
.../RadioPlayPendingMessageProcessor.cs | 36 +-
.../RangedAttackLastTargetUpdateProcessor.cs | 7 +-
.../Processors/RecipePinnedProcessor.cs | 13 +-
.../RemoveCreatureCorpseProcessor.cs | 24 +-
.../Packets/Processors/ScheduleProcessor.cs | 27 +-
.../SeaDragonAttackTargetProcessor.cs | 3 +-
.../SeaDragonGrabExosuitProcessor.cs | 3 +-
.../SeaDragonSwatAttackProcessor.cs | 3 +-
.../SeaTreaderSpawnedChunkProcessor.cs | 3 +-
.../Processors/ServerCommandProcessor.cs | 41 +-
.../SetIntroCinematicModeProcessor.cs | 30 +-
.../SignalPingPreferenceChangedProcessor.cs | 9 +-
.../SimulationOwnershipRequestProcessor.cs | 26 +-
.../Processors/StoryGoalExecutedProcessor.cs | 30 +-
.../SubRootChangedPacketProcessor.cs | 29 +-
.../Processors/TextAutoCompleteProcessor.cs | 29 ++
.../Packets/Processors/UpdateBaseProcessor.cs | 14 +-
.../UpdateDisplaySurfaceWaterProcessor.cs | 9 +-
.../Processors/UpdateInPrecursorProcessor.cs | 9 +-
.../Processors/VehicleDockingProcessor.cs | 15 +-
.../VehicleMovementsPacketProcessor.cs | 36 +-
.../VehicleOnPilotModeChangedProcessor.cs | 19 +-
.../Processors/VehicleUndockingProcessor.cs | 21 +-
.../WaterParkDeconstructedProcessor.cs | 14 +-
.../Packets/Processors/WeldActionProcessor.cs | 34 +-
Nitrox.Server.Subnautica/Models/Player.cs | 59 ++-
.../Parsers/EntityDistributionsResource.cs | 11 +-
.../PrefabPlaceholderGroupsResource.cs | 6 +-
.../Resources/Parsers/RandomStartResource.cs | 13 +-
.../Parsers/WorldEntitiesResource.cs | 14 +-
.../Serialization/Json/PeerIdConverter.cs | 25 ++
.../Serialization/ServerJsonSerializer.cs | 3 +-
.../Serialization/ServerProtoBufSerializer.cs | 2 +-
.../Models/Serialization/World/WorldData.cs | 4 +-
.../Serialization/World/WorldService.cs | 39 +-
Nitrox.Server.Subnautica/Program.cs | 26 +-
.../Services/AutoSaveService.cs | 2 +-
.../Services/CommandService.cs | 302 +++++++++++--
.../Services/ConsoleInputService.cs | 10 +-
.../Services/Core/QueingBackgroundService.cs | 23 +
.../Services/FmodService.cs | 2 +-
.../Services/HibernateService.cs | 6 +-
.../Services/LanBroadcastService.cs | 2 +-
.../Services/MemoryService.cs | 30 +-
.../Services/PacketRegistryService.cs | 25 ++
.../Services/PacketSerializationService.cs | 86 ++++
.../PersistNitroxSerializableConfigService.cs | 25 ++
.../Services/SaveService.cs | 51 +--
.../Services/ServersManagementService.cs | 64 +--
.../Services/StatusService.cs | 49 ++-
.../SubnauticaResourceLoaderService.cs | 2 +-
.../DeferredPacketReceiverTest.cs | 2 +-
.../Communication/TestNonActionPacket.cs | 19 +-
.../Helper/Faker/NitroxAbstractFaker.cs | 9 +-
.../Helper/Faker/NitroxCollectionFaker.cs | 160 +++----
Nitrox.Test/Helper/Faker/NitroxFaker.cs | 68 ++-
.../Model/Packets/PacketsSerializableTest.cs | 2 +-
.../Packets/Processors/PacketProcessorTest.cs | 101 ++---
.../{XORRandomTest.cs => XorRandomTest.cs} | 6 +-
.../Server/Serialization/WorldServiceTest.cs | 1 -
NitroxClient/ClientAutoFacRegistrar.cs | 10 +-
NitroxClient/Communication/PacketReceiver.cs | 5 +-
.../Abstract/ClientPacketProcessor.cs | 16 -
.../Abstract/KeepInventoryChangedProcessor.cs | 20 -
...AggressiveWhenSeeTargetChangedProcessor.cs | 10 +-
.../AnimationChangeEventProcessor.cs | 21 +-
.../AttackCyclopsTargetChangedProcessor.cs | 10 +-
.../AuroraAndTimeUpdateProcessor.cs | 17 +-
.../Processors/BenchChangedProcessor.cs | 27 +-
.../Packets/Processors/BuildProcessor.cs | 27 +-
.../BuildingDesyncWarningProcessor.cs | 16 +-
.../Processors/BuildingResyncProcessor.cs | 93 ++--
.../Processors/ChatMessageProcessor.cs | 105 +++--
.../Processors/CoffeeMachineUseProcessor.cs | 22 +-
.../Processors/Core/ClientProcessorContext.cs | 17 +
.../Processors/Core/IClientPacketProcessor.cs | 8 +
.../Processors/CreatureActionProcessor.cs | 17 +-
.../CreaturePoopPerformedProcessor.cs | 10 +-
...yclopsDamagePointHealthChangedProcessor.cs | 32 +-
.../Processors/CyclopsDamageProcessor.cs | 280 ++++++-------
.../Processors/CyclopsDecoyLaunchProcessor.cs | 28 +-
.../Processors/CyclopsFireCreatedProcessor.cs | 28 +-
.../CyclopsFireSuppressionProcessor.cs | 28 +-
.../Processors/DebugStartMapProcessor.cs | 28 +-
.../DeconstructionBeginProcessor.cs | 50 +--
.../Packets/Processors/DisconnectProcessor.cs | 45 +-
.../Processors/DiscordRequestIPProcessor.cs | 10 +-
.../DropSimulationOwnershipProcessor.cs | 17 +-
.../Processors/EntityDestroyedProcessor.cs | 23 +-
.../EntityMetadataUpdateProcessor.cs | 24 +-
.../Processors/EntityReparentedProcessor.cs | 27 +-
.../EntityTransformUpdatesProcessor.cs | 21 +-
.../Processors/EscapePodChangedProcessor.cs | 40 +-
.../Processors/ExosuitArmActionProcessor.cs | 11 +-
.../Packets/Processors/FMODAssetProcessor.cs | 22 +-
.../Processors/FMODCustomEmitterProcessor.cs | 12 +-
.../FMODCustomLoopingEmitterProcessor.cs | 12 +-
.../Processors/FMODEventInstanceProcessor.cs | 12 +-
.../FMODStudioEventEmitterProcessor.cs | 12 +-
.../Processors/FastCheatChangedProcessor.cs | 7 +-
.../Packets/Processors/FireDousedProcessor.cs | 41 +-
.../Processors/FootstepPacketProcessor.cs | 20 +-
.../Processors/GameModeChangedProcessor.cs | 24 +-
.../GrapplingHookMovementProcessor.cs | 9 +-
.../Processors/InitialPlayerSyncProcessor.cs | 160 ++++---
.../Processors/ItemPositionProcessor.cs | 26 +-
.../Processors/JoinQueueInfoProcessor.cs | 11 +-
.../KeepInventoryChangedProcessor.cs | 16 +
.../Processors/KnownTechEntryAddProcessor.cs | 18 +-
.../Processors/LeakRepairedProcessor.cs | 11 +-
.../MedicalCabinetClickedProcessor.cs | 10 +-
.../MultiplayerSessionPolicyProcessor.cs | 30 +-
.../MultiplayerSessionReservationProcessor.cs | 28 +-
.../Packets/Processors/MutePlayerProcessor.cs | 26 +-
.../OpenableStateChangedProcessor.cs | 32 +-
.../PDAEncyclopediaEntryAddProcessor.cs | 17 +-
.../Processors/PDALogEntryAddProcessor.cs | 28 +-
.../Processors/PDAScanFinishedProcessor.cs | 15 +-
.../Processors/PermsChangedProcessor.cs | 19 +-
.../Processors/PlaySunbeamEventProcessor.cs | 9 +-
.../PlayerCinematicControllerCallProcessor.cs | 25 +-
.../Processors/PlayerDeathProcessor.cs | 21 +-
.../PlayerHeldItemChangedProcessor.cs | 25 +-
.../PlayerInCyclopsMovementProcessor.cs | 20 +-
...PlayerJoinedMultiplayerSessionProcessor.cs | 20 +-
.../Processors/PlayerKickedProcessor.cs | 36 +-
.../Processors/PlayerMovementProcessor.cs | 20 +-
.../Processors/PlayerStatsProcessor.cs | 19 +-
.../Processors/PlayerTeleportedProcessor.cs | 19 +-
.../Packets/Processors/PvPAttackProcessor.cs | 8 +-
.../RadioPlayPendingMessageProcessor.cs | 17 +-
.../RangedAttackLastTargetUpdateProcessor.cs | 10 +-
.../RemoveCreatureCorpseProcessor.cs | 70 ++--
.../Processors/RocketLaunchProcessor.cs | 18 +-
.../Packets/Processors/ScheduleProcessor.cs | 8 +-
.../SeaDragonAttackTargetProcessor.cs | 19 +-
.../SeaDragonGrabExosuitProcessor.cs | 12 +-
.../SeaDragonSwatAttackProcessor.cs | 12 +-
.../SeaTreaderChunkPickedUpProcessor.cs | 12 +-
.../SeaTreaderSpawnedChunkProcessor.cs | 11 +-
.../SeamothModuleActionProcessor.cs | 15 +-
.../Processors/ServerStoppedProcessor.cs | 19 +-
.../SetIntroCinematicModeProcessor.cs | 33 +-
.../SimulationOwnershipChangeProcessor.cs | 18 +-
.../SimulationOwnershipResponseProcessor.cs | 69 ++-
.../Processors/SleepCompleteProcessor.cs | 16 +-
.../Processors/SleepStatusUpdateProcessor.cs | 16 +-
.../Processors/SpawnEntitiesProcessor.cs | 33 +-
.../Processors/StasisSphereHitProcessor.cs | 20 +-
.../Processors/StasisSphereShotProcessor.cs | 20 +-
.../StoryGoalExecutedClientProcessor.cs | 18 +-
.../Processors/SubRootChangedProcessor.cs | 40 +-
.../Processors/TextAutoCompleteProcessor.cs | 25 ++
.../Packets/Processors/TimeChangeProcessor.cs | 17 +-
.../Processors/ToggleLightsProcessor.cs | 12 +-
.../Packets/Processors/TorpedoHitProcessor.cs | 18 +-
.../Processors/TorpedoShotProcessor.cs | 18 +-
.../TorpedoTargetAcquiredProcessor.cs | 18 +-
.../Processors/VehicleDockingProcessor.cs | 38 +-
.../Processors/VehicleMovementsProcessor.cs | 12 +-
.../VehicleOnPilotModeChangedProcessor.cs | 24 +-
.../Processors/VehicleUndockingProcessor.cs | 40 +-
.../Packets/Processors/WeldActionProcessor.cs | 48 +--
NitroxClient/GameLogic/AI.cs | 3 +-
NitroxClient/GameLogic/Bases/BuildUtils.cs | 1 -
.../GameLogic/Bases/BuildingHandler.cs | 1 -
.../GameLogic/Bases/GhostMetadataApplier.cs | 1 -
.../GameLogic/Bases/GhostMetadataRetriever.cs | 1 -
NitroxClient/GameLogic/BulletManager.cs | 35 +-
.../GameLogic/ChatUI/PlayerChatManager.cs | 15 +-
NitroxClient/GameLogic/Cyclops.cs | 2 -
NitroxClient/GameLogic/Entities.cs | 4 +-
NitroxClient/GameLogic/FMOD/FMODSystem.cs | 1 -
NitroxClient/GameLogic/Fires.cs | 1 -
.../HUD/PdaTabs/uGUI_PlayerListTab.cs | 15 +-
.../HUD/PdaTabs/uGUI_PlayerPingEntry.cs | 4 +-
.../GameLogic/HUD/PlayerVitalsManager.cs | 17 +-
.../Helper/TransientLocalObjectManager.cs | 77 ++--
.../Abstract/InitialSyncProcessor.cs | 1 -
.../EquippedItemInitialSyncProcessor.cs | 1 -
.../GlobalRootInitialSyncProcessor.cs | 6 +-
.../InitialSync/PdaInitialSyncProcessor.cs | 2 -
.../PlayerPositionInitialSyncProcessor.cs | 2 -
.../PlayerPreferencesInitialSyncProcessor.cs | 1 -
.../QuickSlotInitialSyncProcessor.cs | 1 -
.../RemotePlayerInitialSyncProcessor.cs | 2 -
...SimulationOwnershipInitialSyncProcessor.cs | 1 -
.../StoryGoalInitialSyncProcessor.cs | 1 -
NitroxClient/GameLogic/Interior.cs | 1 -
NitroxClient/GameLogic/Items.cs | 15 +-
NitroxClient/GameLogic/LocalPlayer.cs | 44 +-
NitroxClient/GameLogic/MedkitFabricator.cs | 1 -
NitroxClient/GameLogic/MobileVehicleBay.cs | 1 -
NitroxClient/GameLogic/NitroxConsole.cs | 1 -
.../GameLogic/PlayerLogic/PlayerCinematics.cs | 18 +-
.../PlayerModel/Abstract/INitroxPlayer.cs | 3 +-
.../ColorSwap/DiveSuitColorSwapManager.cs | 1 -
.../ColorSwap/FinColorSwapManager.cs | 1 -
.../RadiationHelmetColorSwapManager.cs | 1 -
.../RadiationSuitColorSwapManager.cs | 1 -
.../RadiationSuitVestColorSwapManager.cs | 1 -
.../RadiationTankColorSwapManager.cs | 1 -
.../ColorSwap/RebreatherColorSwapManager.cs | 1 -
.../ReinforcedSuitColorSwapManager.cs | 1 -
.../ColorSwap/ScubaTankColorSwapManager.cs | 1 -
.../ColorSwap/StillSuitColorSwapManager.cs | 1 -
.../PlayerModel/PlayerModelManager.cs | 1 -
.../PlayerPreferenceManager.cs | 1 -
NitroxClient/GameLogic/PlayerManager.cs | 38 +-
NitroxClient/GameLogic/RemotePlayer.cs | 12 +-
NitroxClient/GameLogic/Rockets.cs | 1 -
NitroxClient/GameLogic/SeamothModulesEvent.cs | 2 -
NitroxClient/GameLogic/SimulationOwnership.cs | 5 +-
.../Spawning/Bases/BaseLeakEntitySpawner.cs | 1 -
.../Spawning/Bases/BuildEntitySpawner.cs | 1 -
.../Bases/InteriorPieceEntitySpawner.cs | 1 -
.../Spawning/EscapePodEntitySpawner.cs | 2 -
.../Spawning/InstalledBatteryEntitySpawner.cs | 9 +-
.../Spawning/InstalledModuleEntitySpawner.cs | 1 -
.../Spawning/InventoryEntitySpawner.cs | 6 +-
.../Spawning/InventoryItemEntitySpawner.cs | 9 +-
.../Metadata/BeaconMetadataProcessor.cs | 1 -
.../Extractor/SeaTreaderMetadataExtractor.cs | 1 -
.../SubNameInputMetadataExtractor.cs | 1 -
.../Processor/ConstructorMetadataProcessor.cs | 1 -
.../Processor/CrafterMetadataProcessor.cs | 1 -
.../CyclopsLightingMetadataProcessor.cs | 1 -
.../Processor/CyclopsMetadataProcessor.cs | 1 -
.../Processor/ExosuitMetadataProcessor.cs | 1 -
.../Processor/FruitPlantMetadataProcessor.cs | 1 -
.../Processor/PlayerMetadataProcessor.cs | 2 +-
.../Processor/RadiationMetadataProcessor.cs | 1 -
.../Processor/RocketMetadataProcessor.cs | 1 -
.../Processor/SeaTreaderMetadataProcessor.cs | 1 -
.../Processor/SeamothMetadataProcessor.cs | 1 -
.../StayAtLeashPositionMetadataProcessor.cs | 1 -
.../SubNameInputMetadataProcessor.cs | 2 -
.../Spawning/PrefabChildEntitySpawner.cs | 6 +-
.../WorldEntities/CrashEntitySpawner.cs | 1 -
.../CreatureRespawnEntitySpawner.cs | 1 -
.../DefaultWorldEntitySpawner.cs | 1 -
.../WorldEntities/GeyserWorldEntitySpawner.cs | 1 -
.../WorldEntities/GlobalRootEntitySpawner.cs | 2 -
.../WorldEntities/PlacedWorldEntitySpawner.cs | 10 +-
.../PlaceholderGroupWorldEntitySpawner.cs | 1 -
.../PrefabPlaceholderEntitySpawner.cs | 1 -
.../ReefbackChildEntitySpawner.cs | 1 -
.../WorldEntities/ReefbackEntitySpawner.cs | 1 -
.../SerializedWorldEntitySpawner.cs | 2 -
.../WorldEntities/VehicleEntitySpawner.cs | 1 -
.../WorldEntitySpawnerResolver.cs | 3 +-
.../GameLogic/Spawning/WorldEntitySpawner.cs | 25 +-
NitroxClient/GameLogic/Terrain.cs | 4 +-
NitroxClient/GameLogic/TimeManager.cs | 1 -
NitroxClient/GameLogic/Vehicles.cs | 13 +-
NitroxClient/GlobalUsings.cs | 1 +
.../MultiplayerCinematicController.cs | 15 +-
.../MultiplayerCinematicReference.cs | 9 +-
.../MonoBehaviours/Cyclops/NitroxCyclops.cs | 4 -
.../MonoBehaviours/Gui/Chat/PlayerChat.cs | 79 +++-
.../Gui/HUD/RemotePlayerVitals.cs | 3 +-
.../ServerJoin/MainMenuJoinServerPanel.cs | 4 +-
.../MonoBehaviours/IntroCinematicUpdater.cs | 2 +-
NitroxClient/MonoBehaviours/Multiplayer.cs | 49 +--
NitroxClient/MonoBehaviours/NitroxEntity.cs | 6 +-
.../PlayerMovementBroadcaster.cs | 2 +-
...SeeTarget_ScanForAggressionTarget_Patch.cs | 1 -
.../AttackCyclops_UpdateAggression_Patch.cs | 1 -
...aseHullStrength_CrushDamageUpdate_Patch.cs | 1 -
.../Patches/Dynamic/Bed_Update_Patch.cs | 1 -
.../Dynamic/Bench_ExitSittingMode_Patch.cs | 1 -
.../Dynamic/Bench_OnHandClick_Patch.cs | 1 -
.../Dynamic/Bench_OnPlayerDeath_Patch.cs | 1 -
...CoffeeVendingMachine_OnMachineUse_Patch.cs | 1 -
.../Dynamic/Constructable_Construct_Patch.cs | 1 -
.../CrafterLogic_NotifyPickup_Patch.cs | 1 -
.../Dynamic/CrashHome_OnDestroy_Patch.cs | 1 -
.../Patches/Dynamic/CrashHome_Spawn_Patch.cs | 1 -
.../Patches/Dynamic/CrashHome_Update_Patch.cs | 2 +-
...oder_OnConsoleCommand_explodeship_Patch.cs | 1 -
.../CreatureDeath_OnKillAsync_Patch.cs | 2 -
.../CreatureDeath_SpawnRespawner_Patch.cs | 1 -
.../Dynamic/CreatureEgg_Hatch_Patch.cs | 1 -
...psDestructionEvent_SpawnLootAsync_Patch.cs | 2 -
...lopsSonarDisplay_NewEntityOnSonar_Patch.cs | 1 -
...ayNightCycle_OnConsoleCommand_day_Patch.cs | 1 -
...NightCycle_OnConsoleCommand_night_Patch.cs | 1 -
.../Dynamic/DevConsole_Submit_Patch.cs | 1 -
...ckedVehicleHandTarget_OnHandClick_Patch.cs | 3 +-
.../Dynamic/Eatable_IterateDespawn_Patch.cs | 1 -
.../Dynamic/FMODUWE_PlayOneShotImpl_Patch.cs | 1 -
.../FMOD_CustomEmitter_OnStop_Patch.cs | 1 -
...reExtinguisherHolder_TryStoreTank_Patch.cs | 1 -
.../Patches/Dynamic/Flare_OnDestroy_Patch.cs | 1 -
.../Dynamic/FootstepSounds_OnStep_Patch.cs | 8 +-
.../Dynamic/FruitPlant_Update_Patch.cs | 1 -
...eConsoleCommands_OnConsoleCommand_Patch.cs | 3 -
.../GhostCrafter_OnCraftingBegin_Patch.cs | 1 -
.../GoalManager_OnCompletedGoal_Patch.cs | 1 -
.../Dynamic/GrownPlant_OnKill_Patch.cs | 1 -
...Terminal_OnPlayerCinematicModeEnd_Patch.cs | 1 -
.../Dynamic/IncubatorEgg_HatchNow_Patch.cs | 1 -
.../Dynamic/Incubator_OnHatched_Patch.cs | 1 -
.../ItemsContainer_DestroyItem_Patch.cs | 1 -
.../Dynamic/KnownTech_NotifyAdd_Patch.cs | 2 -
.../Dynamic/KnownTech_NotifyAnalyze_Patch.cs | 2 -
.../Dynamic/LiveMixin_AddHealth_Patch.cs | 1 -
.../Patches/Dynamic/LiveMixin_Kill_Patch.cs | 1 -
.../Dynamic/LiveMixin_TakeDamage_Patch.cs | 3 +-
...Command_OnConsoleCommand_fastgrow_Patch.cs | 1 -
...ommand_OnConsoleCommand_fasthatch_Patch.cs | 1 -
.../Dynamic/PDAEncyclopedia_Add_Patch.cs | 1 -
.../Patches/Dynamic/PDALog_Add_Patch.cs | 1 -
.../PickPrefab_AddToContainerAsync_Patch.cs | 1 -
.../Dynamic/PickPrefab_SetPickedUp_Patch.cs | 1 -
.../Dynamic/PinManager_NotifyAdd_Patch.cs | 1 -
.../Dynamic/PinManager_NotifyRemove_Patch.cs | 1 -
.../Dynamic/PingInstance_Set_Patches.cs | 1 -
.../Patches/Dynamic/Poop_Perform_Patch.cs | 1 -
.../PrecursorKeyTerminal_DestroyKey_Patch.cs | 1 -
...Terminal_OnPlayerCinematicModeEnd_Patch.cs | 1 -
.../QuickSlots_DeselectInternal_Patch.cs | 1 -
.../QuickSlots_SelectInternal_Patch.cs | 2 -
.../Dynamic/Radio_PlayRadioMessage_Patch.cs | 1 -
...ngedAttackLastTarget_StartCasting_Patch.cs | 1 -
...gedAttackLastTarget_StartCharging_Patch.cs | 1 -
...SeaDragonMeleeAttack_OnTouchFront_Patch.cs | 1 -
.../SeaDragonMeleeAttack_SwatAttack_Patch.cs | 1 -
.../Dynamic/SeaDragon_GrabExosuit_Patch.cs | 1 -
.../SeaTreaderSounds_SpawnChunks_Patch.cs | 2 -
.../Dynamic/SeamothTorpedo_Explode_Patch.cs | 2 -
...SeamothTorpedo_RepeatingTargeting_Patch.cs | 2 -
.../Dynamic/SpawnOnKill_OnKill_Patch.cs | 1 -
.../Dynamic/StasisSphere_OnHit_Patch.cs | 9 +-
.../Dynamic/StasisSphere_Shoot_Patch.cs | 9 +-
...stomEventHandler_OnConsoleCommand_Patch.cs | 1 -
.../StoryGoalScheduler_Schedule_Patch.cs | 1 -
.../Dynamic/StoryGoal_Execute_Patch.cs | 1 -
.../Dynamic/SubNameInput_Awake_Patch.cs | 1 -
.../Dynamic/SubNameInput_SetTarget_Patch.cs | 1 -
.../Dynamic/SubRoot_OnTakeDamage_Patch.cs | 2 +-
.../Patches/Dynamic/Survival_Eat_Patch.cs | 1 -
.../Patches/Dynamic/Survival_Use_Patch.cs | 1 -
.../Patches/Dynamic/TimeCapsule_Open_Patch.cs | 1 -
.../Patches/Dynamic/Trashcan_Update_Patch.cs | 1 -
.../Dynamic/Utils_PlayFMODAsset_Patch.cs | 1 -
.../VehicleDockingBay_OnTriggerEnter.cs | 3 +-
...cleDockingBay_OnUndockingComplete_Patch.cs | 3 +-
.../Dynamic/Vehicle_TorpedoShot_Patch.cs | 2 -
.../WaterParkCreature_ManagedUpdate_Patch.cs | 1 -
.../WaterParkItem_ManagedUpdate_Patch.cs | 1 -
.../Dynamic/uGUI_ColorPicker_Awake_Patch.cs | 1 -
.../uGUI_SceneIntro_IntroSequence_Patch.cs | 4 +-
.../Persistent/uGUI_MainMenu_Start_Patch.cs | 2 +-
610 files changed, 7178 insertions(+), 6701 deletions(-)
delete mode 100644 Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUweWorldEntityFactory.cs
create mode 100644 Nitrox.Model.Subnautica/Extensions/SunbeamEventExtensions.cs
create mode 100644 Nitrox.Model/Core/PeerId.cs
create mode 100644 Nitrox.Model/Core/SessionId.cs
delete mode 100644 Nitrox.Model/Helper/AsyncBarrier.cs
create mode 100644 Nitrox.Model/Packets/Core/IPacketProcessContext.cs
create mode 100644 Nitrox.Model/Packets/Core/IPacketProcessor.cs
create mode 100644 Nitrox.Model/Packets/Core/PacketProcessorsInvoker.cs
delete mode 100644 Nitrox.Model/Packets/Processors/Abstract/IProcessorContext.cs
delete mode 100644 Nitrox.Model/Packets/Processors/Abstract/PacketProcessor.cs
create mode 100644 Nitrox.Model/Packets/TextAutoComplete.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Administration/Core/IAdminFeature.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Administration/IKickPlayer.cs
create mode 100644 Nitrox.Server.Subnautica/Models/AppEvents/ISaveState.cs
create mode 100644 Nitrox.Server.Subnautica/Models/AppEvents/ISessionCleaner.cs
create mode 100644 Nitrox.Server.Subnautica/Models/AppEvents/Triggers/AsyncTrigger.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/CallArgs.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Command.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Parameter.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypeBoolean.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypeEnum.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypeFloat.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypeInt.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypeNitroxId.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypePlayer.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Abstract/Type/TypeString.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/ArgConverters/Core/ConvertResult.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/ArgConverters/Core/IArgConverter.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/ArgConverters/PlayerNameToPlayerArgConverter.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/ArgConverters/SessionIdToPlayerArgConverter.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/ArgConverters/WordToBoolArgConverter.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/AliasAttribute.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/CommandHandlerEntry.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/CommandOrigin.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/CommandRegistry.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/HostToServerCommandContext.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/ICommandContext.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/ICommandHandler.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/ICommandSubmit.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/PlayerToServerCommandContext.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/RequiresOrigin.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Core/RequiresPermissionAttribute.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/DebugStartMapCommand.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Debugging/DebugStartMapCommand.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Debugging/LoadBatchCommand.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Debugging/PlayerCommand.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Debugging/QueryCommand.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Commands/Debugging/SeedNearPositionCommand.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/LoadBatchCommand.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/PlayerCommand.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/Processor/TextCommandProcessor.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/QueryCommand.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Commands/SwapSerializerCommand.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Communication/LiteNetLibConnection.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Communication/NitroxConnection.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Communication/SessionManager.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Factories/RandomFactory.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/GameLogic/Entities/Spawning/IEntitySpawner.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Helper/EasyPool.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Core/AnonProcessorContext.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Core/AuthProcessorContext.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Core/IAnonPacketProcessor.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Core/IAuthPacketProcessor.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Core/IPacketSender.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Core/NopPacketSender.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Packets/PacketHandler.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Packets/Processors/Core/AuthenticatedPacketProcessor.cs
delete mode 100644 Nitrox.Server.Subnautica/Models/Packets/Processors/Core/UnauthenticatedPacketProcessor.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Packets/Processors/TextAutoCompleteProcessor.cs
create mode 100644 Nitrox.Server.Subnautica/Models/Serialization/Json/PeerIdConverter.cs
create mode 100644 Nitrox.Server.Subnautica/Services/Core/QueingBackgroundService.cs
create mode 100644 Nitrox.Server.Subnautica/Services/PacketRegistryService.cs
create mode 100644 Nitrox.Server.Subnautica/Services/PacketSerializationService.cs
create mode 100644 Nitrox.Server.Subnautica/Services/PersistNitroxSerializableConfigService.cs
rename Nitrox.Test/Server/Helper/{XORRandomTest.cs => XorRandomTest.cs} (81%)
delete mode 100644 NitroxClient/Communication/Packets/Processors/Abstract/ClientPacketProcessor.cs
delete mode 100644 NitroxClient/Communication/Packets/Processors/Abstract/KeepInventoryChangedProcessor.cs
create mode 100644 NitroxClient/Communication/Packets/Processors/Core/ClientProcessorContext.cs
create mode 100644 NitroxClient/Communication/Packets/Processors/Core/IClientPacketProcessor.cs
create mode 100644 NitroxClient/Communication/Packets/Processors/KeepInventoryChangedProcessor.cs
create mode 100644 NitroxClient/Communication/Packets/Processors/TextAutoCompleteProcessor.cs
diff --git a/Nitrox.Launcher/Models/Design/ServerEntry.cs b/Nitrox.Launcher/Models/Design/ServerEntry.cs
index fd25ce95de..449e7fd739 100644
--- a/Nitrox.Launcher/Models/Design/ServerEntry.cs
+++ b/Nitrox.Launcher/Models/Design/ServerEntry.cs
@@ -411,7 +411,8 @@ private ServerProcess(string saveDir, CancellationTokenSource cts, bool isEmbedd
ArgumentList =
{
"--save",
- saveName
+ saveName,
+ $"--game-path \"{NitroxUser.GamePath}\"",
},
WindowStyle = isEmbeddedMode ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal,
CreateNoWindow = isEmbeddedMode
diff --git a/Nitrox.Launcher/Models/Design/StaticCommands.cs b/Nitrox.Launcher/Models/Design/StaticCommands.cs
index 2ea21de4ef..83cf6aeec3 100644
--- a/Nitrox.Launcher/Models/Design/StaticCommands.cs
+++ b/Nitrox.Launcher/Models/Design/StaticCommands.cs
@@ -23,7 +23,11 @@ private static async Task CopyToClipboard(object? controlOrText)
{
if (GetWindowOfObject(controlOrText) is not { } window)
{
- return;
+ window = App.Instance.AppWindow;
+ }
+ if (window == null)
+ {
+ throw new InvalidOperationException("A window instance must be provided");
}
string text = controlOrText switch
{
@@ -57,7 +61,7 @@ await Dispatcher.UIThread.InvokeAsync(async () =>
}
catch (Exception e)
{
- Log.Error(e, "Error trying to set clipboard");
+ Log.Error(e, "Error trying to copy to clipboard");
}
static Window? GetWindowOfObject(object? obj) =>
diff --git a/Nitrox.Launcher/Models/HttpDelegatingHandlers/FileCacheDelegatingHandler.cs b/Nitrox.Launcher/Models/HttpDelegatingHandlers/FileCacheDelegatingHandler.cs
index 62129f5800..cb30f916e1 100644
--- a/Nitrox.Launcher/Models/HttpDelegatingHandlers/FileCacheDelegatingHandler.cs
+++ b/Nitrox.Launcher/Models/HttpDelegatingHandlers/FileCacheDelegatingHandler.cs
@@ -61,7 +61,7 @@ protected override async Task SendAsync(HttpRequestMessage
try
{
Directory.CreateDirectory(NitroxUser.CachePath);
- return Path.Combine(NitroxUser.CachePath, $"nitrox_{string.Join('_', $"{uri.Host}{uri.LocalPath}".ReplaceInvalidFileNameCharacters('_').Split('_').Select(s => s[0]))}_{Convert.ToHexStringLower(uri.ToString().AsMd5Hash())}.cache");
+ return Path.Combine(NitroxUser.CachePath, $"nitrox_{string.Join('_', $"{uri.Host}{uri.LocalPath}".ReplaceInvalidFileNameCharacters('_').Split('_').Select(s => s[0]))}_{Convert.ToHexStringLower(uri.ToString().ToMd5Hash())}.cache");
}
catch (Exception ex)
{
diff --git a/Nitrox.Launcher/Models/Services/StorageService.cs b/Nitrox.Launcher/Models/Services/StorageService.cs
index fb5ef7ab49..60c8b014bd 100644
--- a/Nitrox.Launcher/Models/Services/StorageService.cs
+++ b/Nitrox.Launcher/Models/Services/StorageService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
@@ -10,10 +11,9 @@ namespace Nitrox.Launcher.Models.Services;
internal sealed class StorageService
{
- private IStorageProvider? storageProvider;
-
// TODO: Remove this hack when Avalonia allows IStorageProvider to be accessed without demanding a Window instance.
- private IStorageProvider StorageProvider => storageProvider ??= ((IClassicDesktopStyleApplicationLifetime)Application.Current?.ApplicationLifetime)?.Windows.FirstOrDefault()?.StorageProvider ?? throw new Exception($"{nameof(IStorageProvider)} not available!");
+ [field: MaybeNull, AllowNull]
+ private IStorageProvider StorageProvider => field ??= ((IClassicDesktopStyleApplicationLifetime)Application.Current?.ApplicationLifetime)?.Windows.FirstOrDefault()?.StorageProvider ?? throw new Exception($"{nameof(IStorageProvider)} not available!");
public async Task OpenFolderPickerAsync(string title, string? startingFolder = null)
{
diff --git a/Nitrox.Launcher/Models/Styles/Theme/RadioButtonGroupStyle.axaml b/Nitrox.Launcher/Models/Styles/Theme/RadioButtonGroupStyle.axaml
index 3cc64a75e1..5981e17c48 100644
--- a/Nitrox.Launcher/Models/Styles/Theme/RadioButtonGroupStyle.axaml
+++ b/Nitrox.Launcher/Models/Styles/Theme/RadioButtonGroupStyle.axaml
@@ -3,20 +3,20 @@
xmlns:controls="clr-namespace:Nitrox.Launcher.Models.Controls"
xmlns:converters="clr-namespace:Nitrox.Launcher.Models.Converters"
xmlns:design="clr-namespace:Nitrox.Launcher.Models.Design"
- xmlns:server="clr-namespace:Nitrox.Model.Server;assembly=Nitrox.Model"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:system="clr-namespace:System;assembly=System.Runtime">
+ xmlns:system="clr-namespace:System;assembly=System.Runtime"
+ xmlns:gameLogic="clr-namespace:Nitrox.Model.DataStructures.GameLogic;assembly=Nitrox.Model">
-
+
-
+
diff --git a/Nitrox.Launcher/ViewModels/ManageServerViewModel.cs b/Nitrox.Launcher/ViewModels/ManageServerViewModel.cs
index c3a90929a7..0c96c66679 100644
--- a/Nitrox.Launcher/ViewModels/ManageServerViewModel.cs
+++ b/Nitrox.Launcher/ViewModels/ManageServerViewModel.cs
@@ -474,7 +474,7 @@ partial void OnServerEmbeddedChanged(bool value)
private bool CanDeleteServer() => !ServerIsOnline;
- private Config LoadConfig() => NitroxConfig.Load(Path.Combine(SaveFolderDirectory, typeof(Config).GetCustomAttribute()?.FileName ?? throw new InvalidOperationException()));
+ private Config LoadConfig() => NitroxConfig.Load(SaveFolderDirectory);
private void StoreConfig(Config config)
{
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs
index 5917fb5fe3..ae8071f9af 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/EscapePodWorldEntity.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using BinaryPack.Attributes;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities.Metadata;
@@ -13,7 +14,7 @@ namespace Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities;
public class EscapePodEntity : GlobalRootEntity
{
[DataMember(Order = 1)]
- public List Players { get; set; } = [];
+ public List Players { get; set; } = [];
[IgnoreConstructor]
protected EscapePodEntity()
@@ -34,7 +35,7 @@ public EscapePodEntity(NitroxVector3 position, NitroxId id, EntityMetadata metad
}
/// Used for deserialization
- public EscapePodEntity(List players, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) :
+ public EscapePodEntity(List players, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) :
base(transform, level, classId, spawnedByServer, id, techType, metadata, parentId, childEntities)
{
Players = players;
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/GlobalRootEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/GlobalRootEntity.cs
index 6e21c85d9c..4a17a1d2b5 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/GlobalRootEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/GlobalRootEntity.cs
@@ -32,7 +32,7 @@ protected GlobalRootEntity()
}
/// Used for deserialization
- public GlobalRootEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities) :
+ public GlobalRootEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata? metadata, NitroxId? parentId, List childEntities) :
base(transform, level, classId, spawnedByServer, id, techType, metadata, parentId, childEntities)
{ }
}
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUwePrefabFactory.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUwePrefabFactory.cs
index 376f11da97..078f96baf4 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUwePrefabFactory.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUwePrefabFactory.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities;
public interface IUwePrefabFactory
{
- public bool TryGetPossiblePrefabs(string biomeType, out List prefabs);
+ Task> TryGetPossiblePrefabsAsync(string? biome);
}
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUweWorldEntityFactory.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUweWorldEntityFactory.cs
deleted file mode 100644
index f142ae84ff..0000000000
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/IUweWorldEntityFactory.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities;
-
-public interface IUweWorldEntityFactory
-{
- public bool TryFind(string classId, out UweWorldEntity uweWorldEntity);
-}
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/InventoryItemEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/InventoryItemEntity.cs
index edcf53d63b..98c2db2468 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/InventoryItemEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/InventoryItemEntity.cs
@@ -21,7 +21,7 @@ protected InventoryItemEntity()
}
/// Used for deserialization
- public InventoryItemEntity(NitroxId id, string classId, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities)
+ public InventoryItemEntity(NitroxId id, string classId, NitroxTechType techType, EntityMetadata? metadata, NitroxId parentId, List childEntities)
{
ClassId = classId;
Id = id;
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/OxygenPipeEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/OxygenPipeEntity.cs
index 644f5f13a0..e863edf167 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/OxygenPipeEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/OxygenPipeEntity.cs
@@ -27,7 +27,7 @@ protected OxygenPipeEntity()
}
/// Used for deserialization
- public OxygenPipeEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities, NitroxId parentPipeId, NitroxId rootPipeId, NitroxVector3 parentPosition) :
+ public OxygenPipeEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata? metadata, NitroxId? parentId, List childEntities, NitroxId parentPipeId, NitroxId rootPipeId, NitroxVector3 parentPosition) :
base(transform, level, classId, spawnedByServer, id, techType, metadata, parentId, childEntities)
{
ParentPipeId = parentPipeId;
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/PrefabChildEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/PrefabChildEntity.cs
index 5af1c0572f..54199e6304 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/PrefabChildEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/PrefabChildEntity.cs
@@ -8,7 +8,7 @@
namespace Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities
{
/*
- * A PrefabChildEntity is a gameobject that resides inside of a spawned prefab. Although the server knows about these,
+ * A PrefabChildEntity is a gameobject that resides inside a spawned prefab. Although the server knows about these,
* it is too cost prohibitive for it to send spawn data for all of these. Instead, we let the game spawn them and tag
* the entity after the fact. An example of this is a keypad in the aurora; there is an overarching Door prefab with
* the keypad baked in - we simply update the id of the keypad on spawn. Each PrefabChildEntity will always bubble up
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/SerializedWorldEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/SerializedWorldEntity.cs
index 71d78660d8..d2ee39868c 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/SerializedWorldEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/SerializedWorldEntity.cs
@@ -46,7 +46,7 @@ protected SerializedWorldEntity()
// Constructor for serialization. Has to be "protected" for json serialization.
}
- public SerializedWorldEntity(List components, int layer, NitroxTransform transform, NitroxId id, NitroxId parentId, AbsoluteEntityCell cell)
+ public SerializedWorldEntity(List components, int layer, NitroxTransform transform, NitroxId id, NitroxId? parentId, AbsoluteEntityCell cell)
{
Components = components;
Layer = layer;
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/WorldEntity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/WorldEntity.cs
index 7079aaeb64..6ed8bae3aa 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/WorldEntity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entities/WorldEntity.cs
@@ -57,7 +57,7 @@ protected WorldEntity()
// Constructor for serialization. Has to be "protected" for json serialization.
}
- public WorldEntity(NitroxVector3 localPosition, NitroxQuaternion localRotation, NitroxVector3 scale, NitroxTechType techType, int level, string classId, bool spawnedByServer, NitroxId id, Entity parentEntity)
+ public WorldEntity(NitroxVector3 localPosition, NitroxQuaternion localRotation, NitroxVector3 scale, NitroxTechType techType, int level, string classId, bool spawnedByServer, NitroxId id, Entity? parentEntity)
{
Transform = new NitroxTransform(localPosition, localRotation, scale);
TechType = techType;
@@ -78,7 +78,7 @@ public WorldEntity(NitroxVector3 localPosition, NitroxQuaternion localRotation,
}
/// Used for deserialization
- public WorldEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List childEntities)
+ public WorldEntity(NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata? metadata, NitroxId? parentId, List childEntities)
{
Id = id;
TechType = techType;
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entity.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entity.cs
index f41c14d5b1..af291185ec 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entity.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/Entity.cs
@@ -28,7 +28,7 @@ public abstract class Entity
public NitroxTechType TechType { get; set; }
[DataMember(Order = 3)]
- public EntityMetadata Metadata { get; set; }
+ public EntityMetadata? Metadata { get; set; }
[DataMember(Order = 4)]
public NitroxId? ParentId { get; set; }
diff --git a/Nitrox.Model.Subnautica/DataStructures/GameLogic/RandomStartGenerator.cs b/Nitrox.Model.Subnautica/DataStructures/GameLogic/RandomStartGenerator.cs
index e0636176b5..64ec6db2ba 100644
--- a/Nitrox.Model.Subnautica/DataStructures/GameLogic/RandomStartGenerator.cs
+++ b/Nitrox.Model.Subnautica/DataStructures/GameLogic/RandomStartGenerator.cs
@@ -4,52 +4,27 @@
namespace Nitrox.Model.Subnautica.DataStructures.GameLogic;
-public class RandomStartGenerator
+public sealed class RandomStartGenerator(RandomStartGenerator.IPixelProvider pixelProvider)
{
- private readonly IPixelProvider pixelProvider;
+ private readonly IPixelProvider pixelProvider = pixelProvider;
- public RandomStartGenerator(IPixelProvider pixelProvider)
- {
- this.pixelProvider = pixelProvider;
- }
-
- public NitroxVector3 GenerateRandomStartPosition(Random rnd)
- {
- for (int i = 0; i < 1000; i++)
- {
- float normalizedX = (float)rnd.NextDouble();
- float normalizedZ = (float)rnd.NextDouble();
-
- if (IsStartPointValid(normalizedX, normalizedZ))
- {
- float x = 4096f * normalizedX - 2048f; // normalizedX = (x + 2048) / 4096
- float z = 4096f * normalizedZ - 2048f;
- return new NitroxVector3(x, 0, z);
- }
- }
-
- return NitroxVector3.Zero;
- }
-
- public List GenerateRandomStartPositions(string seed)
+ ///
+ /// Generates all starts positions available for a given randomization. Only take as many positions as needed to avoid unnecessary compute.
+ ///
+ public IEnumerable GenerateAllStartPositions(Random random)
{
- Random rnd = new(seed.GetHashCode());
- List list = new();
-
- for (int i = 0; i < 1000; i++)
+ for (int i = 0; i < int.MaxValue; i++)
{
- float normalizedX = (float)rnd.NextDouble();
- float normalizedZ = (float)rnd.NextDouble();
+ float normalizedX = (float)random.NextDouble();
+ float normalizedZ = (float)random.NextDouble();
if (IsStartPointValid(normalizedX, normalizedZ))
{
float x = 4096f * normalizedX - 2048f; // normalizedX = (x + 2048) / 4096
float z = 4096f * normalizedZ - 2048f;
- list.Add(new NitroxVector3(x, 0, z));
+ yield return new NitroxVector3(x, 0, z);
}
}
-
- return list;
}
private bool IsStartPointValid(float normalizedX, float normalizedZ)
diff --git a/Nitrox.Model.Subnautica/Extensions/SunbeamEventExtensions.cs b/Nitrox.Model.Subnautica/Extensions/SunbeamEventExtensions.cs
new file mode 100644
index 0000000000..615a95a622
--- /dev/null
+++ b/Nitrox.Model.Subnautica/Extensions/SunbeamEventExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+using static Nitrox.Model.Subnautica.Packets.PlaySunbeamEvent;
+using static Nitrox.Model.Subnautica.Packets.PlaySunbeamEvent.SunbeamEvent;
+
+namespace Nitrox.Model.Subnautica.Extensions;
+
+public static class SunbeamEventExtensions
+{
+ public static string ToStoryKey(this SunbeamEvent storyEvent) =>
+ storyEvent switch
+ {
+ STORYSTART => "RadioSunbeamStart",
+ GUNAIM => "PrecursorGunAimCheck",
+ COUNTDOWN => "OnPlayRadioSunbeam4",
+ _ => throw new ArgumentOutOfRangeException(nameof(storyEvent), $"Unknown {nameof(SunbeamEvent)} with number {(int)storyEvent}.")
+ };
+}
diff --git a/Nitrox.Model.Subnautica/MultiplayerSession/PlayerContext.cs b/Nitrox.Model.Subnautica/MultiplayerSession/PlayerContext.cs
index 5e892ac66d..040f892a8d 100644
--- a/Nitrox.Model.Subnautica/MultiplayerSession/PlayerContext.cs
+++ b/Nitrox.Model.Subnautica/MultiplayerSession/PlayerContext.cs
@@ -1,8 +1,7 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.DataStructures.GameLogic;
-using Nitrox.Model.Server;
-using Nitrox.Model.Subnautica.DataStructures;
using Nitrox.Model.Subnautica.DataStructures.GameLogic;
namespace Nitrox.Model.Subnautica.MultiplayerSession;
@@ -11,7 +10,7 @@ namespace Nitrox.Model.Subnautica.MultiplayerSession;
public class PlayerContext
{
public string PlayerName { get; }
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxId PlayerNitroxId { get; }
public bool WasBrandNewPlayer { get; }
public PlayerSettings PlayerSettings { get; }
@@ -20,15 +19,15 @@ public class PlayerContext
///
/// Not null if the player is currently driving a vehicle.
///
- public NitroxId DrivingVehicle { get; set; }
+ public NitroxId? DrivingVehicle { get; set; }
public IntroCinematicMode IntroCinematicMode { get; set; }
public PlayerAnimation Animation { get; set; }
- public PlayerContext(string playerName, ushort playerId, NitroxId playerNitroxId, bool wasBrandNewPlayer, PlayerSettings playerSettings, bool isMuted,
- SubnauticaGameMode gameMode, NitroxId drivingVehicle, IntroCinematicMode introCinematicMode, PlayerAnimation animation)
+ public PlayerContext(string playerName, SessionId sessionId, NitroxId playerNitroxId, bool wasBrandNewPlayer, PlayerSettings playerSettings, bool isMuted,
+ SubnauticaGameMode gameMode, NitroxId? drivingVehicle, IntroCinematicMode introCinematicMode, PlayerAnimation animation)
{
PlayerName = playerName;
- PlayerId = playerId;
+ SessionId = sessionId;
PlayerNitroxId = playerNitroxId;
WasBrandNewPlayer = wasBrandNewPlayer;
PlayerSettings = playerSettings;
@@ -41,6 +40,6 @@ public PlayerContext(string playerName, ushort playerId, NitroxId playerNitroxId
public override string ToString()
{
- return $"[{nameof(PlayerContext)} PlayerName: {PlayerName}, PlayerId: {PlayerId}, PlayerNitroxId: {PlayerNitroxId}, WasBrandNewPlayer: {WasBrandNewPlayer}, PlayerSettings: {PlayerSettings}, GameMode: {GameMode}, DrivingVehicle: {DrivingVehicle}, IntroCinematicMode: {IntroCinematicMode}, Animation: {Animation}]";
+ return $"[{nameof(PlayerContext)} PlayerName: {PlayerName}, {nameof(SessionId)}: {SessionId}, PlayerNitroxId: {PlayerNitroxId}, WasBrandNewPlayer: {WasBrandNewPlayer}, PlayerSettings: {PlayerSettings}, GameMode: {GameMode}, DrivingVehicle: {DrivingVehicle}, IntroCinematicMode: {IntroCinematicMode}, Animation: {Animation}]";
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/AnimationChangeEvent.cs b/Nitrox.Model.Subnautica/Packets/AnimationChangeEvent.cs
index 0e18951f07..eaac32f28f 100644
--- a/Nitrox.Model.Subnautica/Packets/AnimationChangeEvent.cs
+++ b/Nitrox.Model.Subnautica/Packets/AnimationChangeEvent.cs
@@ -1,18 +1,13 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
using Nitrox.Model.Subnautica.DataStructures.GameLogic;
namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
-public class AnimationChangeEvent : Packet
+public class AnimationChangeEvent(SessionId sessionId, PlayerAnimation animation) : Packet
{
- public ushort PlayerId { get; }
- public PlayerAnimation Animation { get; }
-
- public AnimationChangeEvent(ushort playerId, PlayerAnimation animation)
- {
- PlayerId = playerId;
- Animation = animation;
- }
+ public SessionId SessionId { get; } = sessionId;
+ public PlayerAnimation Animation { get; } = animation;
}
diff --git a/Nitrox.Model.Subnautica/Packets/BenchChanged.cs b/Nitrox.Model.Subnautica/Packets/BenchChanged.cs
index 9414305665..be6dfb9303 100644
--- a/Nitrox.Model.Subnautica/Packets/BenchChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/BenchChanged.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
@@ -7,13 +8,13 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class BenchChanged : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxId BenchId { get; }
public BenchChangeState ChangeState { get; }
- public BenchChanged(ushort playerId, NitroxId benchId, BenchChangeState changeState)
+ public BenchChanged(SessionId sessionId, NitroxId benchId, BenchChangeState changeState)
{
- PlayerId = playerId;
+ SessionId = sessionId;
BenchId = benchId;
ChangeState = changeState;
}
diff --git a/Nitrox.Model.Subnautica/Packets/BuildingResyncRequest.cs b/Nitrox.Model.Subnautica/Packets/BuildingResyncRequest.cs
index cb57bae02d..92d236d427 100644
--- a/Nitrox.Model.Subnautica/Packets/BuildingResyncRequest.cs
+++ b/Nitrox.Model.Subnautica/Packets/BuildingResyncRequest.cs
@@ -5,14 +5,8 @@
namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
-public sealed class BuildingResyncRequest : Packet
+public sealed class BuildingResyncRequest(NitroxId? entityId = null, bool resyncEverything = true) : Packet
{
- public NitroxId EntityId { get; }
- public bool ResyncEverything { get; }
-
- public BuildingResyncRequest(NitroxId entityId = null, bool resyncEverything = true)
- {
- EntityId = entityId;
- ResyncEverything = resyncEverything;
- }
+ public NitroxId? EntityId { get; } = entityId;
+ public bool ResyncEverything { get; } = resyncEverything;
}
diff --git a/Nitrox.Model.Subnautica/Packets/CellVisibilityChanged.cs b/Nitrox.Model.Subnautica/Packets/CellVisibilityChanged.cs
index 6f4d61f4b8..7d9dbc841d 100644
--- a/Nitrox.Model.Subnautica/Packets/CellVisibilityChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/CellVisibilityChanged.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
using Nitrox.Model.Subnautica.DataStructures.GameLogic;
@@ -8,13 +9,13 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class CellVisibilityChanged : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public List Added { get; }
public List Removed { get; }
- public CellVisibilityChanged(ushort playerId, List added, List removed)
+ public CellVisibilityChanged(SessionId sessionId, List added, List removed)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Added = added;
Removed = removed;
}
diff --git a/Nitrox.Model.Subnautica/Packets/ChatMessage.cs b/Nitrox.Model.Subnautica/Packets/ChatMessage.cs
index 25f6beb4de..f6a80ec9b7 100644
--- a/Nitrox.Model.Subnautica/Packets/ChatMessage.cs
+++ b/Nitrox.Model.Subnautica/Packets/ChatMessage.cs
@@ -1,19 +1,18 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
-namespace Nitrox.Model.Subnautica.Packets
+namespace Nitrox.Model.Subnautica.Packets;
+
+[Serializable]
+public class ChatMessage : Packet
{
- [Serializable]
- public class ChatMessage : Packet
- {
- public ushort PlayerId { get; }
- public string Text { get; }
- public const ushort SERVER_ID = ushort.MaxValue;
+ public SessionId SessionId { get; }
+ public string Text { get; }
- public ChatMessage(ushort playerId, string text)
- {
- PlayerId = playerId;
- Text = text;
- }
+ public ChatMessage(SessionId sessionId, string text)
+ {
+ SessionId = sessionId;
+ Text = text;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/DebugStartMapPacket.cs b/Nitrox.Model.Subnautica/Packets/DebugStartMapPacket.cs
index ac100b2a64..3c68eb97e7 100644
--- a/Nitrox.Model.Subnautica/Packets/DebugStartMapPacket.cs
+++ b/Nitrox.Model.Subnautica/Packets/DebugStartMapPacket.cs
@@ -3,16 +3,10 @@
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Packets;
-namespace Nitrox.Model.Subnautica.Packets
-{
- [Serializable]
- public class DebugStartMapPacket : Packet
- {
- public List StartPositions { get; }
+namespace Nitrox.Model.Subnautica.Packets;
- public DebugStartMapPacket(List startPositions)
- {
- StartPositions = startPositions;
- }
- }
+[Serializable]
+public class DebugStartMapPacket(IList startPositions) : Packet
+{
+ public IList StartPositions { get; } = startPositions;
}
diff --git a/Nitrox.Model.Subnautica/Packets/Disconnect.cs b/Nitrox.Model.Subnautica/Packets/Disconnect.cs
index c6a34ea3e2..a959dc07cf 100644
--- a/Nitrox.Model.Subnautica/Packets/Disconnect.cs
+++ b/Nitrox.Model.Subnautica/Packets/Disconnect.cs
@@ -1,16 +1,11 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
-namespace Nitrox.Model.Subnautica.Packets
-{
- [Serializable]
- public class Disconnect : Packet
- {
- public ushort PlayerId { get; }
+namespace Nitrox.Model.Subnautica.Packets;
- public Disconnect(ushort playerId)
- {
- PlayerId = playerId;
- }
- }
+[Serializable]
+public class Disconnect(SessionId sessionId) : Packet
+{
+ public SessionId SessionId { get; } = sessionId;
}
diff --git a/Nitrox.Model.Subnautica/Packets/EscapePodChanged.cs b/Nitrox.Model.Subnautica/Packets/EscapePodChanged.cs
index e385304f0b..69a4ae1e5d 100644
--- a/Nitrox.Model.Subnautica/Packets/EscapePodChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/EscapePodChanged.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
@@ -7,12 +8,12 @@ namespace Nitrox.Model.Subnautica.Packets
[Serializable]
public class EscapePodChanged : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public Optional EscapePodId { get; }
- public EscapePodChanged(ushort playerId, Optional escapePodId)
+ public EscapePodChanged(SessionId sessionId, Optional escapePodId)
{
- PlayerId = playerId;
+ SessionId = sessionId;
EscapePodId = escapePodId;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/FootstepPacket.cs b/Nitrox.Model.Subnautica/Packets/FootstepPacket.cs
index ee85216e9d..0e3358f87e 100644
--- a/Nitrox.Model.Subnautica/Packets/FootstepPacket.cs
+++ b/Nitrox.Model.Subnautica/Packets/FootstepPacket.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
namespace Nitrox.Model.Subnautica.Packets;
@@ -6,12 +7,12 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class FootstepPacket : Packet
{
- public ushort PlayerID { get; }
+ public SessionId SessionId { get; }
public StepSounds AssetIndex { get; }
- public FootstepPacket(ushort playerID, StepSounds assetIndex)
+ public FootstepPacket(SessionId sessionId, StepSounds assetIndex)
{
- PlayerID = playerID;
+ SessionId = sessionId;
AssetIndex = assetIndex;
DeliveryMethod = Networking.NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED;
diff --git a/Nitrox.Model.Subnautica/Packets/GameModeChanged.cs b/Nitrox.Model.Subnautica/Packets/GameModeChanged.cs
index ec005b4516..926db1a8d9 100644
--- a/Nitrox.Model.Subnautica/Packets/GameModeChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/GameModeChanged.cs
@@ -1,28 +1,27 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.GameLogic;
using Nitrox.Model.Packets;
-using Nitrox.Model.Server;
-using Nitrox.Model.Subnautica.DataStructures;
namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
-public class GameModeChanged : Packet
+public sealed class GameModeChanged : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public bool AllPlayers { get; }
public SubnauticaGameMode GameMode { get; }
- public GameModeChanged(ushort playerId, bool allPlayers, SubnauticaGameMode gameMode)
+ public GameModeChanged(SessionId sessionId, bool allPlayers, SubnauticaGameMode gameMode)
{
- PlayerId = playerId;
+ SessionId = sessionId;
AllPlayers = allPlayers;
GameMode = gameMode;
}
- public static GameModeChanged ForPlayer(ushort playerId, SubnauticaGameMode gameMode)
+ public static GameModeChanged ForPlayer(SessionId sessionId, SubnauticaGameMode gameMode)
{
- return new(playerId, false, gameMode);
+ return new(sessionId, false, gameMode);
}
public static GameModeChanged ForAllPlayers(SubnauticaGameMode gameMode)
diff --git a/Nitrox.Model.Subnautica/Packets/KnownTechEntryAdd.cs b/Nitrox.Model.Subnautica/Packets/KnownTechEntryAdd.cs
index 0b03ef5ae2..98c5d889df 100644
--- a/Nitrox.Model.Subnautica/Packets/KnownTechEntryAdd.cs
+++ b/Nitrox.Model.Subnautica/Packets/KnownTechEntryAdd.cs
@@ -28,12 +28,12 @@ public enum EntryCategory
public EntryCategory Category { get; }
public List PartialTechTypesToRemove { get; }
- public KnownTechEntryAdd(EntryCategory category, NitroxTechType techType, bool verbose, List partialTechTypesToRemove = null)
+ public KnownTechEntryAdd(EntryCategory category, NitroxTechType techType, bool verbose, List? partialTechTypesToRemove = null)
{
Category = category;
TechType = techType;
Verbose = verbose;
- PartialTechTypesToRemove = partialTechTypesToRemove;
+ PartialTechTypesToRemove = partialTechTypesToRemove ?? [];
}
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/Movement.cs b/Nitrox.Model.Subnautica/Packets/Movement.cs
index c3389fbe36..c8a9fc27f3 100644
--- a/Nitrox.Model.Subnautica/Packets/Movement.cs
+++ b/Nitrox.Model.Subnautica/Packets/Movement.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Packets;
@@ -7,7 +8,7 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public abstract class Movement : Packet
{
- public abstract ushort PlayerId { get; }
+ public abstract SessionId SessionId { get; }
public abstract NitroxVector3 Position { get; }
public abstract NitroxVector3 Velocity { get; }
public abstract NitroxQuaternion BodyRotation { get; }
diff --git a/Nitrox.Model.Subnautica/Packets/MultiplayerSessionReservation.cs b/Nitrox.Model.Subnautica/Packets/MultiplayerSessionReservation.cs
index 906e9b49a1..c82e98c061 100644
--- a/Nitrox.Model.Subnautica/Packets/MultiplayerSessionReservation.cs
+++ b/Nitrox.Model.Subnautica/Packets/MultiplayerSessionReservation.cs
@@ -1,26 +1,30 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.MultiplayerSession;
using Nitrox.Model.Packets;
-namespace Nitrox.Model.Subnautica.Packets
+namespace Nitrox.Model.Subnautica.Packets;
+
+[Serializable]
+public class MultiplayerSessionReservation : CorrelatedPacket
{
- [Serializable]
- public class MultiplayerSessionReservation : CorrelatedPacket
+ public MultiplayerSessionReservation(string correlationId, MultiplayerSessionReservationState reservationState) : base(correlationId)
{
- public ushort PlayerId { get; }
- public string ReservationKey { get; }
- public MultiplayerSessionReservationState ReservationState { get; }
+ ReservationState = reservationState;
+ }
- public MultiplayerSessionReservation(string correlationId, MultiplayerSessionReservationState reservationState) : base(correlationId)
- {
- ReservationState = reservationState;
- }
-
- public MultiplayerSessionReservation(string correlationId, ushort playerId, string reservationKey,
- MultiplayerSessionReservationState reservationState = MultiplayerSessionReservationState.RESERVED) : this(correlationId, reservationState)
- {
- PlayerId = playerId;
- ReservationKey = reservationKey;
- }
+ public MultiplayerSessionReservation(string correlationId, SessionId sessionId, string reservationKey,
+ MultiplayerSessionReservationState reservationState = MultiplayerSessionReservationState.RESERVED) : this(correlationId, reservationState)
+ {
+ SessionId = sessionId;
+ ReservationKey = reservationKey;
}
+
+ ///
+ /// Gets the session id of the player.
+ ///
+ public SessionId SessionId { get; }
+
+ public string ReservationKey { get; }
+ public MultiplayerSessionReservationState ReservationState { get; }
}
diff --git a/Nitrox.Model.Subnautica/Packets/MutePlayer.cs b/Nitrox.Model.Subnautica/Packets/MutePlayer.cs
index 05ffefe403..7051842979 100644
--- a/Nitrox.Model.Subnautica/Packets/MutePlayer.cs
+++ b/Nitrox.Model.Subnautica/Packets/MutePlayer.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
namespace Nitrox.Model.Subnautica.Packets;
@@ -6,12 +7,12 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class MutePlayer : Packet
{
- public ushort PlayerId;
+ public SessionId SessionId;
public bool Muted;
- public MutePlayer(ushort playerId, bool muted)
+ public MutePlayer(SessionId sessionId, bool muted)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Muted = muted;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/PDAScanFinished.cs b/Nitrox.Model.Subnautica/Packets/PDAScanFinished.cs
index 1f663e1f02..7afeca35a1 100644
--- a/Nitrox.Model.Subnautica/Packets/PDAScanFinished.cs
+++ b/Nitrox.Model.Subnautica/Packets/PDAScanFinished.cs
@@ -8,14 +8,14 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class PDAScanFinished : Packet
{
- public NitroxId Id { get; }
+ public NitroxId? Id { get; }
public NitroxTechType TechType { get; }
public int UnlockedAmount { get; }
public bool FullyResearched { get; }
public bool Destroy { get; }
public bool WasAlreadyResearched { get; }
- public PDAScanFinished(NitroxId id, NitroxTechType techType, int unlockedAmount, bool fullyResearched, bool destroy, bool wasAlreadyResearched = false)
+ public PDAScanFinished(NitroxId? id, NitroxTechType techType, int unlockedAmount, bool fullyResearched, bool destroy, bool wasAlreadyResearched = false)
{
Id = id;
TechType = techType;
diff --git a/Nitrox.Model.Subnautica/Packets/PieceDeconstructed.cs b/Nitrox.Model.Subnautica/Packets/PieceDeconstructed.cs
index d195b1eeb3..39076f50ff 100644
--- a/Nitrox.Model.Subnautica/Packets/PieceDeconstructed.cs
+++ b/Nitrox.Model.Subnautica/Packets/PieceDeconstructed.cs
@@ -12,7 +12,7 @@ public class PieceDeconstructed : OrderedBuildPacket
public NitroxId PieceId { get; }
public BuildPieceIdentifier BuildPieceIdentifier { get; }
public GhostEntity ReplacerGhost { get; }
- public BaseData BaseData { get; set; }
+ public BaseData? BaseData { get; set; }
public PieceDeconstructed(NitroxId baseId, NitroxId pieceId, BuildPieceIdentifier buildPieceIdentifier, GhostEntity replacerGhost, BaseData baseData, int operationId) : base(operationId)
{
diff --git a/Nitrox.Model.Subnautica/Packets/PlaySunbeamEvent.cs b/Nitrox.Model.Subnautica/Packets/PlaySunbeamEvent.cs
index 2c46b17021..db77e8113b 100644
--- a/Nitrox.Model.Subnautica/Packets/PlaySunbeamEvent.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlaySunbeamEvent.cs
@@ -2,33 +2,44 @@
using Nitrox.Model.Packets;
namespace Nitrox.Model.Subnautica.Packets;
-
[Serializable]
-public class PlaySunbeamEvent : Packet
+public class PlaySunbeamEvent(string eventKey) : Packet
{
- public string EventKey { get; }
-
- public PlaySunbeamEvent(string eventKey)
- {
- EventKey = eventKey;
- }
-
- ///
- /// Associates an understandable event name and the associated goal from .
- ///
- public static class SunbeamEvent
- {
- public const string STORYSTART = "RadioSunbeamStart";
- public const string COUNTDOWN = "OnPlayRadioSunbeam4";
- public const string GUNAIM = "PrecursorGunAimCheck";
- }
+ public string EventKey { get; } = eventKey;
///
- /// An ordered list of the goals forming part of the whole Sunbeam story.
+ /// An ordered list of the goals forming part of the whole Sunbeam story.
///
///
- /// If you modify this list, make sure to accordingly modify .
+ /// If you modify this list, make sure to accordingly modify .
///
[NonSerialized]
- public static readonly string[] SunbeamGoals = new string[] { SunbeamEvent.STORYSTART, "OnPlayRadioSunbeamStart", "RadioSunbeam1", "OnPlayRadioSunbeam1", "RadioSunbeam2", "OnPlayRadioSunbeam2", "RadioSunbeam3", "OnPlayRadioSunbeam3", "RadioSunbeam4", SunbeamEvent.COUNTDOWN, SunbeamEvent.GUNAIM, "PrecursorGunAim", "SunbeamCheckPlayerRange", "PDASunbeamDestroyEventOutOfRange", "PDASunbeamDestroyEventInRange" };
+ public static readonly string[] SunbeamGoals =
+ [
+ SunbeamEvent.STORYSTART.ToStoryKey(),
+ "OnPlayRadioSunbeamStart",
+ "RadioSunbeam1",
+ "OnPlayRadioSunbeam1",
+ "RadioSunbeam2",
+ "OnPlayRadioSunbeam2",
+ "RadioSunbeam3",
+ "OnPlayRadioSunbeam3",
+ "RadioSunbeam4",
+ SunbeamEvent.COUNTDOWN.ToStoryKey(),
+ SunbeamEvent.GUNAIM.ToStoryKey(),
+ "PrecursorGunAim",
+ "SunbeamCheckPlayerRange",
+ "PDASunbeamDestroyEventOutOfRange",
+ "PDASunbeamDestroyEventInRange"
+ ];
+
+ ///
+ /// Associates an understandable event name and the associated goal from .
+ ///
+ public enum SunbeamEvent
+ {
+ STORYSTART,
+ COUNTDOWN,
+ GUNAIM
+ }
}
diff --git a/Nitrox.Model.Subnautica/Packets/PlayerCinematicControllerCall.cs b/Nitrox.Model.Subnautica/Packets/PlayerCinematicControllerCall.cs
index 47ca582b40..24313c2fde 100644
--- a/Nitrox.Model.Subnautica/Packets/PlayerCinematicControllerCall.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlayerCinematicControllerCall.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
@@ -7,15 +8,15 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class PlayerCinematicControllerCall : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxId ControllerID { get; }
public int ControllerNameHash { get; }
public string Key { get; }
public bool StartPlaying { get; }
- public PlayerCinematicControllerCall(ushort playerId, NitroxId controllerID, int controllerNameHash, string key, bool startPlaying)
+ public PlayerCinematicControllerCall(SessionId sessionId, NitroxId controllerID, int controllerNameHash, string key, bool startPlaying)
{
- PlayerId = playerId;
+ SessionId = sessionId;
ControllerID = controllerID;
ControllerNameHash = controllerNameHash;
Key = key;
diff --git a/Nitrox.Model.Subnautica/Packets/PlayerDeathEvent.cs b/Nitrox.Model.Subnautica/Packets/PlayerDeathEvent.cs
index f1f63a4343..4d9c7f84d6 100644
--- a/Nitrox.Model.Subnautica/Packets/PlayerDeathEvent.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlayerDeathEvent.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Packets;
@@ -7,12 +8,12 @@ namespace Nitrox.Model.Subnautica.Packets
[Serializable]
public class PlayerDeathEvent : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxVector3 DeathPosition { get; }
- public PlayerDeathEvent(ushort playerId, NitroxVector3 deathPosition)
+ public PlayerDeathEvent(SessionId sessionId, NitroxVector3 deathPosition)
{
- PlayerId = playerId;
+ SessionId = sessionId;
DeathPosition = deathPosition;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/PlayerHeldItemChanged.cs b/Nitrox.Model.Subnautica/Packets/PlayerHeldItemChanged.cs
index 20a839d7b8..69c1ada055 100644
--- a/Nitrox.Model.Subnautica/Packets/PlayerHeldItemChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlayerHeldItemChanged.cs
@@ -1,32 +1,32 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
using Nitrox.Model.Subnautica.DataStructures.GameLogic;
-namespace Nitrox.Model.Subnautica.Packets
+namespace Nitrox.Model.Subnautica.Packets;
+
+[Serializable]
+public class PlayerHeldItemChanged : Packet
{
- [Serializable]
- public class PlayerHeldItemChanged : Packet
- {
- public ushort PlayerId { get; }
- public NitroxId ItemId { get; }
- public ChangeType Type { get; }
- public NitroxTechType IsFirstTime { get; } // If it's the first time the player used that item type it send the techType, if not null.
+ public SessionId SessionId { get; }
+ public NitroxId ItemId { get; }
+ public ChangeType Type { get; }
+ public NitroxTechType? IsFirstTime { get; } // If it's the first time the player used that item type it send the techType, if not null.
- public PlayerHeldItemChanged(ushort playerId, NitroxId itemId, ChangeType type, NitroxTechType isFirstTime)
- {
- PlayerId = playerId;
- ItemId = itemId;
- Type = type;
- IsFirstTime = isFirstTime;
- }
+ public PlayerHeldItemChanged(SessionId sessionId, NitroxId itemId, ChangeType type, NitroxTechType? isFirstTime)
+ {
+ SessionId = sessionId;
+ ItemId = itemId;
+ Type = type;
+ IsFirstTime = isFirstTime;
+ }
- public enum ChangeType
- {
- DRAW_AS_TOOL,
- DRAW_AS_ITEM,
- HOLSTER_AS_TOOL,
- HOLSTER_AS_ITEM
- }
+ public enum ChangeType
+ {
+ DRAW_AS_TOOL,
+ DRAW_AS_ITEM,
+ HOLSTER_AS_TOOL,
+ HOLSTER_AS_ITEM
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/PlayerInCyclopsMovement.cs b/Nitrox.Model.Subnautica/Packets/PlayerInCyclopsMovement.cs
index 2bab1f4c93..3df4fd543e 100644
--- a/Nitrox.Model.Subnautica/Packets/PlayerInCyclopsMovement.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlayerInCyclopsMovement.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Networking;
using Nitrox.Model.Packets;
@@ -8,13 +9,13 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class PlayerInCyclopsMovement : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxVector3 LocalPosition { get; }
public NitroxQuaternion LocalRotation { get; }
- public PlayerInCyclopsMovement(ushort playerId, NitroxVector3 localPosition, NitroxQuaternion localRotation)
+ public PlayerInCyclopsMovement(SessionId sessionId, NitroxVector3 localPosition, NitroxQuaternion localRotation)
{
- PlayerId = playerId;
+ SessionId = sessionId;
LocalPosition = localPosition;
LocalRotation = localRotation;
DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED;
diff --git a/Nitrox.Model.Subnautica/Packets/PlayerMovement.cs b/Nitrox.Model.Subnautica/Packets/PlayerMovement.cs
index a5e90f78f0..de1ee24921 100644
--- a/Nitrox.Model.Subnautica/Packets/PlayerMovement.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlayerMovement.cs
@@ -1,27 +1,27 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Networking;
-namespace Nitrox.Model.Subnautica.Packets
+namespace Nitrox.Model.Subnautica.Packets;
+
+[Serializable]
+public class PlayerMovement : Movement
{
- [Serializable]
- public class PlayerMovement : Movement
+ public PlayerMovement(SessionId sessionId, NitroxVector3 position, NitroxVector3 velocity, NitroxQuaternion bodyRotation, NitroxQuaternion aimingRotation)
{
- public override ushort PlayerId { get; }
- public override NitroxVector3 Position { get; }
- public override NitroxVector3 Velocity { get; }
- public override NitroxQuaternion BodyRotation { get; }
- public override NitroxQuaternion AimingRotation { get; }
-
- public PlayerMovement(ushort playerId, NitroxVector3 position, NitroxVector3 velocity, NitroxQuaternion bodyRotation, NitroxQuaternion aimingRotation)
- {
- PlayerId = playerId;
- Position = position;
- Velocity = velocity;
- BodyRotation = bodyRotation;
- AimingRotation = aimingRotation;
- DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED;
- UdpChannel = UdpChannelId.MOVEMENTS;
- }
+ SessionId = sessionId;
+ Position = position;
+ Velocity = velocity;
+ BodyRotation = bodyRotation;
+ AimingRotation = aimingRotation;
+ DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED;
+ UdpChannel = UdpChannelId.MOVEMENTS;
}
+
+ public override SessionId SessionId { get; }
+ public override NitroxVector3 Position { get; }
+ public override NitroxVector3 Velocity { get; }
+ public override NitroxQuaternion BodyRotation { get; }
+ public override NitroxQuaternion AimingRotation { get; }
}
diff --git a/Nitrox.Model.Subnautica/Packets/PlayerStats.cs b/Nitrox.Model.Subnautica/Packets/PlayerStats.cs
index f7deea3bd5..90ca64e990 100644
--- a/Nitrox.Model.Subnautica/Packets/PlayerStats.cs
+++ b/Nitrox.Model.Subnautica/Packets/PlayerStats.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Networking;
using Nitrox.Model.Packets;
@@ -7,7 +8,7 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class PlayerStats : Packet
{
- public ushort PlayerId { get; set; }
+ public SessionId SessionId { get; set; }
public float Oxygen { get; }
public float MaxOxygen { get; }
public float Health { get; }
@@ -15,9 +16,9 @@ public class PlayerStats : Packet
public float Water { get; }
public float InfectionAmount { get; }
- public PlayerStats(ushort playerId, float oxygen, float maxOxygen, float health, float food, float water, float infectionAmount)
+ public PlayerStats(SessionId sessionId, float oxygen, float maxOxygen, float health, float food, float water, float infectionAmount)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Oxygen = oxygen;
MaxOxygen = maxOxygen;
Health = health;
diff --git a/Nitrox.Model.Subnautica/Packets/PvPAttack.cs b/Nitrox.Model.Subnautica/Packets/PvPAttack.cs
index de1b677e28..a398d37837 100644
--- a/Nitrox.Model.Subnautica/Packets/PvPAttack.cs
+++ b/Nitrox.Model.Subnautica/Packets/PvPAttack.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
namespace Nitrox.Model.Subnautica.Packets;
@@ -6,13 +7,13 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class PvPAttack : Packet
{
- public ushort TargetPlayerId { get; }
+ public SessionId TargetSessionId { get; }
public float Damage { get; set; }
public AttackType Type { get; }
- public PvPAttack(ushort targetPlayerId, float damage, AttackType type)
+ public PvPAttack(SessionId targetSessionId, float damage, AttackType type)
{
- TargetPlayerId = targetPlayerId;
+ TargetSessionId = targetSessionId;
Damage = damage;
Type = type;
}
diff --git a/Nitrox.Model.Subnautica/Packets/SetIntroCinematicMode.cs b/Nitrox.Model.Subnautica/Packets/SetIntroCinematicMode.cs
index fa13aebf82..2ca9819a8f 100644
--- a/Nitrox.Model.Subnautica/Packets/SetIntroCinematicMode.cs
+++ b/Nitrox.Model.Subnautica/Packets/SetIntroCinematicMode.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.Packets;
using Nitrox.Model.Subnautica.DataStructures.GameLogic;
@@ -7,20 +8,20 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class SetIntroCinematicMode : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public IntroCinematicMode Mode { get; }
- public ushort? PartnerId { get; set; }
+ public SessionId? PartnerId { get; set; }
- public SetIntroCinematicMode(ushort playerId, IntroCinematicMode mode)
+ public SetIntroCinematicMode(SessionId sessionId, IntroCinematicMode mode)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Mode = mode;
PartnerId = null;
}
- public SetIntroCinematicMode(ushort playerId, IntroCinematicMode mode, ushort? partnerId)
+ public SetIntroCinematicMode(SessionId sessionId, IntroCinematicMode mode, SessionId? partnerId)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Mode = mode;
PartnerId = partnerId;
}
diff --git a/Nitrox.Model.Subnautica/Packets/SimulationOwnershipChange.cs b/Nitrox.Model.Subnautica/Packets/SimulationOwnershipChange.cs
index f136094f92..23a25752ce 100644
--- a/Nitrox.Model.Subnautica/Packets/SimulationOwnershipChange.cs
+++ b/Nitrox.Model.Subnautica/Packets/SimulationOwnershipChange.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
@@ -10,11 +11,11 @@ public class SimulationOwnershipChange : Packet
{
public List Entities { get; }
- public SimulationOwnershipChange(NitroxId id, ushort owningPlayerId, SimulationLockType lockType, bool changesPosition = false)
+ public SimulationOwnershipChange(NitroxId id, SessionId owningSessionId, SimulationLockType lockType, bool changesPosition = false)
{
Entities = new List
{
- new(id, owningPlayerId, changesPosition, lockType)
+ new(id, owningSessionId, changesPosition, lockType)
};
}
diff --git a/Nitrox.Model.Subnautica/Packets/SimulationOwnershipRequest.cs b/Nitrox.Model.Subnautica/Packets/SimulationOwnershipRequest.cs
index bffb19fe92..884df30939 100644
--- a/Nitrox.Model.Subnautica/Packets/SimulationOwnershipRequest.cs
+++ b/Nitrox.Model.Subnautica/Packets/SimulationOwnershipRequest.cs
@@ -1,21 +1,21 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
-namespace Nitrox.Model.Subnautica.Packets
+namespace Nitrox.Model.Subnautica.Packets;
+
+[Serializable]
+public class SimulationOwnershipRequest : Packet
{
- [Serializable]
- public class SimulationOwnershipRequest : Packet
- {
- public ushort PlayerId { get; }
- public NitroxId Id { get; }
- public SimulationLockType LockType { get; }
+ public SessionId SessionId { get; }
+ public NitroxId Id { get; }
+ public SimulationLockType LockType { get; }
- public SimulationOwnershipRequest(ushort playerId, NitroxId id, SimulationLockType lockType)
- {
- PlayerId = playerId;
- Id = id;
- LockType = lockType;
- }
+ public SimulationOwnershipRequest(SessionId sessionId, NitroxId id, SimulationLockType lockType)
+ {
+ SessionId = sessionId;
+ Id = id;
+ LockType = lockType;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/SpawnEntities.cs b/Nitrox.Model.Subnautica/Packets/SpawnEntities.cs
index 1bded78973..437d3626eb 100644
--- a/Nitrox.Model.Subnautica/Packets/SpawnEntities.cs
+++ b/Nitrox.Model.Subnautica/Packets/SpawnEntities.cs
@@ -24,7 +24,7 @@ public SpawnEntities(List entities, List spawnedCell
ForceRespawn = forceRespawn;
}
- public SpawnEntities(Entity entity, SimulatedEntity simulatedEntity = null, bool forceRespawn = false)
+ public SpawnEntities(Entity entity, SimulatedEntity? simulatedEntity = null, bool forceRespawn = false)
{
Entities = [entity];
Simulations = [];
diff --git a/Nitrox.Model.Subnautica/Packets/StasisSphereHit.cs b/Nitrox.Model.Subnautica/Packets/StasisSphereHit.cs
index 45a505745b..256ded8881 100644
--- a/Nitrox.Model.Subnautica/Packets/StasisSphereHit.cs
+++ b/Nitrox.Model.Subnautica/Packets/StasisSphereHit.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Packets;
@@ -7,15 +8,15 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class StasisSphereHit : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxVector3 Position { get; }
public NitroxQuaternion Rotation { get; }
public float ChargeNormalized { get; }
public float Consumption { get; }
- public StasisSphereHit(ushort playerId, NitroxVector3 position, NitroxQuaternion rotation, float chargeNormalized, float consumption)
+ public StasisSphereHit(SessionId sessionId, NitroxVector3 position, NitroxQuaternion rotation, float chargeNormalized, float consumption)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Position = position;
Rotation = rotation;
ChargeNormalized = chargeNormalized;
diff --git a/Nitrox.Model.Subnautica/Packets/StasisSphereShot.cs b/Nitrox.Model.Subnautica/Packets/StasisSphereShot.cs
index 61dba4a9d6..ca2bfa8d60 100644
--- a/Nitrox.Model.Subnautica/Packets/StasisSphereShot.cs
+++ b/Nitrox.Model.Subnautica/Packets/StasisSphereShot.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures.Unity;
using Nitrox.Model.Packets;
@@ -7,16 +8,16 @@ namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
public class StasisSphereShot : Packet
{
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public NitroxVector3 Position { get; }
public NitroxQuaternion Rotation { get; }
public float Speed { get; }
public float LifeTime { get; }
public float ChargeNormalized { get; }
- public StasisSphereShot(ushort playerId, NitroxVector3 position, NitroxQuaternion rotation, float speed, float lifeTime, float chargeNormalized)
+ public StasisSphereShot(SessionId sessionId, NitroxVector3 position, NitroxQuaternion rotation, float speed, float lifeTime, float chargeNormalized)
{
- PlayerId = playerId;
+ SessionId = sessionId;
Position = position;
Rotation = rotation;
Speed = speed;
diff --git a/Nitrox.Model.Subnautica/Packets/SubRootChanged.cs b/Nitrox.Model.Subnautica/Packets/SubRootChanged.cs
index f864889e0a..61a8e89b28 100644
--- a/Nitrox.Model.Subnautica/Packets/SubRootChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/SubRootChanged.cs
@@ -1,19 +1,19 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
-namespace Nitrox.Model.Subnautica.Packets
+namespace Nitrox.Model.Subnautica.Packets;
+
+[Serializable]
+public class SubRootChanged : Packet
{
- [Serializable]
- public class SubRootChanged : Packet
- {
- public ushort PlayerId { get; }
- public Optional SubRootId { get; }
+ public SessionId SessionId { get; }
+ public Optional SubRootId { get; }
- public SubRootChanged(ushort playerId, Optional subRootId)
- {
- PlayerId = playerId;
- SubRootId = subRootId;
- }
+ public SubRootChanged(SessionId sessionId, Optional subRootId)
+ {
+ SessionId = sessionId;
+ SubRootId = subRootId;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/VehicleDocking.cs b/Nitrox.Model.Subnautica/Packets/VehicleDocking.cs
index 5ea4155cb6..16f2596318 100644
--- a/Nitrox.Model.Subnautica/Packets/VehicleDocking.cs
+++ b/Nitrox.Model.Subnautica/Packets/VehicleDocking.cs
@@ -1,20 +1,14 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
namespace Nitrox.Model.Subnautica.Packets;
[Serializable]
-public class VehicleDocking : Packet
+public sealed class VehicleDocking(NitroxId vehicleId, NitroxId dockId, SessionId sessionId) : Packet
{
- public NitroxId VehicleId { get; }
- public NitroxId DockId { get; }
- public ushort PlayerId { get; }
-
- public VehicleDocking(NitroxId vehicleId, NitroxId dockId, ushort playerId)
- {
- VehicleId = vehicleId;
- DockId = dockId;
- PlayerId = playerId;
- }
+ public NitroxId VehicleId { get; } = vehicleId;
+ public NitroxId DockId { get; } = dockId;
+ public SessionId SessionId { get; } = sessionId;
}
diff --git a/Nitrox.Model.Subnautica/Packets/VehicleOnPilotModeChanged.cs b/Nitrox.Model.Subnautica/Packets/VehicleOnPilotModeChanged.cs
index 426bd4b20a..0cf39ad68e 100644
--- a/Nitrox.Model.Subnautica/Packets/VehicleOnPilotModeChanged.cs
+++ b/Nitrox.Model.Subnautica/Packets/VehicleOnPilotModeChanged.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
@@ -8,13 +9,13 @@ namespace Nitrox.Model.Subnautica.Packets;
public class VehicleOnPilotModeChanged : Packet
{
public NitroxId VehicleId { get; }
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public bool IsPiloting { get; }
- public VehicleOnPilotModeChanged(NitroxId vehicleId, ushort playerId, bool isPiloting)
+ public VehicleOnPilotModeChanged(NitroxId vehicleId, SessionId sessionId, bool isPiloting)
{
VehicleId = vehicleId;
- PlayerId = playerId;
+ SessionId = sessionId;
IsPiloting = isPiloting;
}
}
diff --git a/Nitrox.Model.Subnautica/Packets/VehicleUndocking.cs b/Nitrox.Model.Subnautica/Packets/VehicleUndocking.cs
index c6823836f0..68a1735108 100644
--- a/Nitrox.Model.Subnautica/Packets/VehicleUndocking.cs
+++ b/Nitrox.Model.Subnautica/Packets/VehicleUndocking.cs
@@ -1,4 +1,5 @@
using System;
+using Nitrox.Model.Core;
using Nitrox.Model.DataStructures;
using Nitrox.Model.Packets;
@@ -9,14 +10,14 @@ public class VehicleUndocking : Packet
{
public NitroxId VehicleId { get; }
public NitroxId DockId { get; }
- public ushort PlayerId { get; }
+ public SessionId SessionId { get; }
public bool UndockingStart { get; }
- public VehicleUndocking(NitroxId vehicleId, NitroxId dockId, ushort playerId, bool undockingStart)
+ public VehicleUndocking(NitroxId vehicleId, NitroxId dockId, SessionId sessionId, bool undockingStart)
{
VehicleId = vehicleId;
DockId = dockId;
- PlayerId = playerId;
+ SessionId = sessionId;
UndockingStart = undockingStart;
}
}
diff --git a/Nitrox.Model/Configuration/ServerStartOptions.cs b/Nitrox.Model/Configuration/ServerStartOptions.cs
index 613b7b1677..c4d1c27bba 100644
--- a/Nitrox.Model/Configuration/ServerStartOptions.cs
+++ b/Nitrox.Model/Configuration/ServerStartOptions.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
diff --git a/Nitrox.Model/Configuration/SubnauticaServerOptions.cs b/Nitrox.Model/Configuration/SubnauticaServerOptions.cs
index 39ade10f8d..5cb1c90eb7 100644
--- a/Nitrox.Model/Configuration/SubnauticaServerOptions.cs
+++ b/Nitrox.Model/Configuration/SubnauticaServerOptions.cs
@@ -24,6 +24,7 @@ public sealed partial class SubnauticaServerOptions
[Range(1, ushort.MaxValue)]
public ushort ServerPort { get; set; } = SubnauticaServerConstants.DEFAULT_PORT;
+ [PropertyDescription("If not empty, users will be asked to enter a password to join the server")]
[RegularExpression(@"\w+")]
public string ServerPassword { get; set; } = "";
diff --git a/Nitrox.Model/Constants/NitroxConstants.cs b/Nitrox.Model/Constants/NitroxConstants.cs
index 6965b6e0b7..d1adc40cf6 100644
--- a/Nitrox.Model/Constants/NitroxConstants.cs
+++ b/Nitrox.Model/Constants/NitroxConstants.cs
@@ -3,4 +3,5 @@ namespace Nitrox.Model.Constants;
public static class NitroxConstants
{
public const string LAUNCHER_APP_NAME = "Nitrox.Launcher";
+ public const string PLAYER_NAME_VALID_REGEX = @"^[ a-zA-Z0-9._-]{3,25}$";
}
diff --git a/Nitrox.Model/Constants/SubnauticaServerConstants.cs b/Nitrox.Model/Constants/SubnauticaServerConstants.cs
index 710ef4c283..b109341b0c 100644
--- a/Nitrox.Model/Constants/SubnauticaServerConstants.cs
+++ b/Nitrox.Model/Constants/SubnauticaServerConstants.cs
@@ -1,11 +1,14 @@
namespace Nitrox.Model.Constants;
-public class SubnauticaServerConstants
+public static class SubnauticaServerConstants
{
public const int DEFAULT_PORT = 11000;
///
- /// Default seed in development so life pod spawn stays the same.
+ /// Default seed in development so starting spawn (escape pod) stays the same.
///
- public const string DEFAULT_DEVELOPMENT_SEED = "TCCBIBZXAB";
+ ///
+ /// This seed causes first escape pod spawn to be reasonably close to [-112, 0, -320] which we determined is optimal start position.
+ ///
+ public const string DEFAULT_DEVELOPMENT_SEED = "95311395";
}
diff --git a/Nitrox.Model/Core/PeerId.cs b/Nitrox.Model/Core/PeerId.cs
new file mode 100644
index 0000000000..4f845301cb
--- /dev/null
+++ b/Nitrox.Model/Core/PeerId.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Diagnostics;
+
+namespace Nitrox.Model.Core;
+
+///
+/// Globally unique ID of the networked entity. Is 0 for server. Starts from 1 if player.
+///
+[DebuggerDisplay($"{{{nameof(id)}}}")]
+public readonly record struct PeerId : IComparable
+{
+ public const uint SERVER_ID = 0;
+
+ private readonly uint id;
+
+ public bool IsServer => id == SERVER_ID;
+
+ private PeerId(uint id)
+ {
+ this.id = id;
+ }
+
+ public static implicit operator uint(PeerId id) => id.id;
+
+ public static implicit operator PeerId(uint id) => new(id);
+ public int CompareTo(PeerId other) => id.CompareTo(other.id);
+}
diff --git a/Nitrox.Model/Core/SessionId.cs b/Nitrox.Model/Core/SessionId.cs
new file mode 100644
index 0000000000..a9632cb30a
--- /dev/null
+++ b/Nitrox.Model/Core/SessionId.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Nitrox.Model.Core;
+
+///
+/// The session id (index) of a connection. The server uses 0, players will start from 1.
+///
+///
+/// It's important that, once a session id is assigned by the server, no other connection can impersonate by using the
+/// same id.
+/// Force a 10 minute "hands-off" time before which this session id can be reused.
+///
+public readonly record struct SessionId : IComparable
+{
+ public const int DELAY_REUSE_MINUTES = 10;
+ public const ushort SERVER_ID = (ushort)PeerId.SERVER_ID;
+
+ private readonly ushort id;
+
+ public bool IsPlayer => id != SERVER_ID;
+
+ private SessionId(ushort id)
+ {
+ this.id = id;
+ }
+
+ public static implicit operator ushort(SessionId id)
+ {
+ return id.id;
+ }
+
+ public static implicit operator SessionId(ushort id)
+ {
+ return new SessionId(id);
+ }
+
+ public int CompareTo(SessionId other) => id.CompareTo(other.id);
+
+ public override string ToString() => id.ToString();
+}
diff --git a/Nitrox.Model/DataStructures/GameLogic/Perms.cs b/Nitrox.Model/DataStructures/GameLogic/Perms.cs
index 89de6e5205..5b47ae2ee5 100644
--- a/Nitrox.Model/DataStructures/GameLogic/Perms.cs
+++ b/Nitrox.Model/DataStructures/GameLogic/Perms.cs
@@ -1,46 +1,39 @@
-using System;
+namespace Nitrox.Model.DataStructures.GameLogic;
-namespace Nitrox.Model.DataStructures.GameLogic
+///
+/// Should be sorted from least to most authoritative.
+///
+public enum Perms : byte
{
- ///
- /// Should be sorted from least to most authoritative.
- ///
- public enum Perms : byte
- {
- ///
- /// No permissions
- ///
- NONE,
-
- ///
- /// Default player permission, cannot use cheat and have access to basic server commands (e.g: help, list, whisper,
- /// whois, ...)
- ///
- PLAYER,
+ ///
+ /// No permissions
+ ///
+ NONE,
- ///
- /// Player that can manage other players in game. Can use vanilla cheat commands and some advanced server commands
- /// (e.g: mute, kick, broadcast, ...)
- ///
- MODERATOR,
+ ///
+ /// Default player permission, cannot use cheats and only have access to basic server commands (e.g: help, list, whisper,
+ /// whois, ...)
+ ///
+ PLAYER,
- ///
- /// Server administrator, can manage server settings and players. Can use vanilla cheat commands and all server
- /// commands (e.g: op, promote, server settings, ...)
- ///
- ADMIN,
+ ///
+ /// Player that can manage other players in game. Can use vanilla cheat commands and some advanced server commands
+ /// (e.g: mute, kick, broadcast, ...)
+ ///
+ MODERATOR,
- ///
- /// All permissions
- ///
- HOST,
- DEFAULT = PLAYER
- }
+ ///
+ /// Server administrator, can manage server settings and players. Can use vanilla cheat commands and almost all server
+ /// commands (e.g: op, promote, server settings, ...)
+ ///
+ ADMIN,
- [Flags]
- public enum PermsFlag : byte
- {
- NONE = 0x0,
- NO_CONSOLE = 0x1
- }
+ ///
+ /// All permissions, the owner of the server.
+ ///
+ ///
+ /// This is the permission used when using the server console.
+ ///
+ HOST,
+ DEFAULT = PLAYER
}
diff --git a/Nitrox.Model/DataStructures/NitroxId.cs b/Nitrox.Model/DataStructures/NitroxId.cs
index 1ffeeb0841..ea0a5cb2f3 100644
--- a/Nitrox.Model/DataStructures/NitroxId.cs
+++ b/Nitrox.Model/DataStructures/NitroxId.cs
@@ -52,7 +52,7 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
info.AddValue("id", guid.ToByteArray());
}
- public static bool operator ==(NitroxId id1, NitroxId id2)
+ public static bool operator ==(NitroxId? id1, NitroxId? id2)
{
if (id1 is null)
{
@@ -65,7 +65,7 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
return id1.Equals(id2);
}
- public static bool operator !=(NitroxId id1, NitroxId id2)
+ public static bool operator !=(NitroxId? id1, NitroxId? id2)
{
return !(id1 == id2);
}
@@ -86,7 +86,7 @@ public override bool Equals(object obj)
}
- public bool Equals(NitroxId other)
+ public bool Equals(NitroxId? other)
{
if (ReferenceEquals(null, other))
{
diff --git a/Nitrox.Model/DataStructures/Optional.cs b/Nitrox.Model/DataStructures/Optional.cs
index 8a8c6d9c9e..27456cf5f6 100644
--- a/Nitrox.Model/DataStructures/Optional.cs
+++ b/Nitrox.Model/DataStructures/Optional.cs
@@ -123,9 +123,9 @@ internal static Optional Of(T value)
return new Optional(value);
}
- internal static Optional OfNullable(T value)
+ internal static Optional OfNullable(T? value)
{
- return !valueChecksForT(value) ? Optional.Empty : new Optional(value);
+ return !valueChecksForT(value) ? Optional.Empty : new Optional(value!);
}
public override string ToString()
@@ -206,7 +206,7 @@ public static class Optional
#pragma warning restore CS0618
public static Optional Of(T value) where T : class => Optional.Of(value);
- public static Optional OfNullable(T value) where T : class => Optional.OfNullable(value);
+ public static Optional OfNullable(T? value) where T : class => Optional.OfNullable(value);
///
/// Adds a condition to the optional of the given type that is checked whenever is
diff --git a/Nitrox.Model/DataStructures/SimulatedEntity.cs b/Nitrox.Model/DataStructures/SimulatedEntity.cs
index f62bd9a253..900ec28b6a 100644
--- a/Nitrox.Model/DataStructures/SimulatedEntity.cs
+++ b/Nitrox.Model/DataStructures/SimulatedEntity.cs
@@ -1,33 +1,33 @@
using System;
+using Nitrox.Model.Core;
-namespace Nitrox.Model.DataStructures
+namespace Nitrox.Model.DataStructures;
+
+///
+/// A simulated entity that is tracked by the Nitrox server so that it knows which connected game client owns (and simulates) the entity.
+/// See for more information.
+///
+[Serializable]
+public class SimulatedEntity
{
///
- /// A simulated entity that is tracked by the Nitrox server so that it knows which connected game client owns (and simulates) the entity.
- /// See for more information.
+ /// True if entity isn't static (e.g. welded to world).
///
- [Serializable]
- public class SimulatedEntity
- {
- ///
- /// True if entity isn't static (e.g. welded to world).
- ///
- public bool ChangesPosition { get; }
- public NitroxId Id { get; }
- public ushort PlayerId { get; }
- public SimulationLockType LockType { get; }
+ public bool ChangesPosition { get; }
+ public NitroxId Id { get; }
+ public SessionId SessionId { get; }
+ public SimulationLockType LockType { get; }
- public SimulatedEntity(NitroxId id, ushort playerId, bool changesPosition, SimulationLockType lockType)
- {
- Id = id;
- PlayerId = playerId;
- ChangesPosition = changesPosition;
- LockType = lockType;
- }
+ public SimulatedEntity(NitroxId id, SessionId sessionId, bool changesPosition, SimulationLockType lockType)
+ {
+ Id = id;
+ SessionId = sessionId;
+ ChangesPosition = changesPosition;
+ LockType = lockType;
+ }
- public override string ToString()
- {
- return $"[SimulatedEntity Id: {Id}, PlayerId: {PlayerId}, ChangesPosition: {ChangesPosition}, LockType: {LockType}]";
- }
+ public override string ToString()
+ {
+ return $"[SimulatedEntity Id: {Id}, {nameof(SessionId)}: {SessionId}, ChangesPosition: {ChangesPosition}, LockType: {LockType}]";
}
}
diff --git a/Nitrox.Model/DataStructures/Unity/NitroxTransform.cs b/Nitrox.Model/DataStructures/Unity/NitroxTransform.cs
index a3b4ddf8d8..9ea2a02bbe 100644
--- a/Nitrox.Model/DataStructures/Unity/NitroxTransform.cs
+++ b/Nitrox.Model/DataStructures/Unity/NitroxTransform.cs
@@ -28,7 +28,7 @@ public Matrix4x4 LocalToWorldMatrix
}
}
- public NitroxTransform Parent;
+ public NitroxTransform? Parent;
[IgnoredMember]
public NitroxVector3 Position
@@ -64,7 +64,7 @@ public NitroxQuaternion Rotation
}
}
- public void SetParent(NitroxTransform parent, bool worldPositionStays = true)
+ public void SetParent(NitroxTransform? parent, bool worldPositionStays = true)
{
if (!worldPositionStays)
{
diff --git a/Nitrox.Model/Extensions/StringExtensions.cs b/Nitrox.Model/Extensions/StringExtensions.cs
index 62c47696c4..5497ad7e00 100644
--- a/Nitrox.Model/Extensions/StringExtensions.cs
+++ b/Nitrox.Model/Extensions/StringExtensions.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
@@ -6,13 +7,6 @@ namespace Nitrox.Model.Extensions;
public static class StringExtensions
{
- public static byte[] AsMd5Hash(this string input)
- {
- using MD5 md5 = MD5.Create();
- byte[] inputBytes = Encoding.ASCII.GetBytes(input);
- return md5.ComputeHash(inputBytes);
- }
-
///
/// Gets the arguments passed to a command, given its name.
///
@@ -44,4 +38,16 @@ public static IEnumerable GetCommandArgs(this string[] args, string name
}
}
}
+
+ extension(string self)
+ {
+ public byte[] ToMd5Hash()
+ {
+ using MD5 md5 = MD5.Create();
+ byte[] inputBytes = Encoding.ASCII.GetBytes(self);
+ return md5.ComputeHash(inputBytes);
+ }
+
+ public int ToMd5HashedInt32() => BitConverter.ToInt32(self.ToMd5Hash(), 0);
+ }
}
diff --git a/Nitrox.Model/GlobalUsings.cs b/Nitrox.Model/GlobalUsings.cs
index d7587de670..88c6919fbb 100644
--- a/Nitrox.Model/GlobalUsings.cs
+++ b/Nitrox.Model/GlobalUsings.cs
@@ -3,4 +3,5 @@
#else
global using LockObject = object;
#endif
+global using System.Runtime.CompilerServices;
global using Nitrox.Model.Extensions;
diff --git a/Nitrox.Model/Helper/AsyncBarrier.cs b/Nitrox.Model/Helper/AsyncBarrier.cs
deleted file mode 100644
index 38993e1d68..0000000000
--- a/Nitrox.Model/Helper/AsyncBarrier.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-#if NET
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Nitrox.Model.Helper;
-
-///
-/// Provides an automatically resetting asynchronous way to wait on a signal.
-///
-public sealed class AsyncBarrier
-{
- private readonly LockObject locker = new();
- private TaskCompletionSource signal = new();
-
- public void Signal()
- {
- lock (locker)
- {
- signal.TrySetResult();
- }
- }
-
- public async Task WaitForSignalAsync(CancellationToken cancellationToken) => await AtomicRefreshTcs(ref signal).WaitAsync(cancellationToken);
-
- public TaskAwaiter GetAwaiter()
- {
- lock (locker)
- {
- return signal.Task.GetAwaiter();
- }
- }
-
- private Task AtomicRefreshTcs(ref TaskCompletionSource tcs)
- {
- Task tcsTask;
- lock (locker)
- {
- tcsTask = tcs.Task;
- }
- if (tcsTask.IsCompletedSuccessfully)
- {
- lock (locker)
- {
- tcs = new TaskCompletionSource();
- return tcs.Task;
- }
- }
- return tcsTask;
- }
-}
-#endif
diff --git a/Nitrox.Model/Helper/Validate.cs b/Nitrox.Model/Helper/Validate.cs
index aa52d4f67a..2949e0328f 100644
--- a/Nitrox.Model/Helper/Validate.cs
+++ b/Nitrox.Model/Helper/Validate.cs
@@ -1,6 +1,5 @@
extern alias JB;
using System;
-using System.Runtime.CompilerServices;
using JB::JetBrains.Annotations;
using Nitrox.Model.DataStructures;
@@ -11,17 +10,17 @@ public static class Validate
// "where T : class" prevents non-nullable valuetypes from getting boxed to objects.
// In other words: Error when trying to assert non-null on something that can't be null in the first place.
[ContractAnnotation("o:null => halt")]
- public static void NotNull(T? o, [CallerArgumentExpression("o")] string argumentExpression = null) where T : class
+ public static void NotNull(T? o, [CallerArgumentExpression("o")] string? argumentExpression = null) where T : class
{
if (o != null)
{
return;
}
- throw new ArgumentNullException(argumentExpression);
+ throw new ArgumentNullException(nameof(o), argumentExpression);
}
- public static void IsTrue(bool b, [CallerArgumentExpression("b")] string argumentExpression = null)
+ public static void IsTrue(bool b, [CallerArgumentExpression("b")] string? argumentExpression = null)
{
if (!b)
{
@@ -29,7 +28,7 @@ public static void IsTrue(bool b, [CallerArgumentExpression("b")] string argumen
}
}
- public static void IsFalse(bool b, [CallerArgumentExpression("b")] string argumentExpression = null)
+ public static void IsFalse(bool b, [CallerArgumentExpression("b")] string? argumentExpression = null)
{
if (b)
{
diff --git a/Nitrox.Model/Packets/Core/IPacketProcessContext.cs b/Nitrox.Model/Packets/Core/IPacketProcessContext.cs
new file mode 100644
index 0000000000..6d31d3ee49
--- /dev/null
+++ b/Nitrox.Model/Packets/Core/IPacketProcessContext.cs
@@ -0,0 +1,11 @@
+namespace Nitrox.Model.Packets.Core;
+
+public interface IPacketProcessContext;
+
+public interface IPacketProcessContext : IPacketProcessContext
+{
+ ///
+ /// The sender of the packet.
+ ///
+ public TSenderRef Sender { get; set; }
+}
diff --git a/Nitrox.Model/Packets/Core/IPacketProcessor.cs b/Nitrox.Model/Packets/Core/IPacketProcessor.cs
new file mode 100644
index 0000000000..3b0183aea5
--- /dev/null
+++ b/Nitrox.Model/Packets/Core/IPacketProcessor.cs
@@ -0,0 +1,20 @@
+using System.Threading.Tasks;
+
+namespace Nitrox.Model.Packets.Core;
+
+public interface IPacketProcessor;
+
+///
+/// A packet processor. The method is called when a connection sends data.
+///
+public interface IPacketProcessor : IPacketProcessor
+ where TContext : IPacketProcessContext
+ where TPacket : Packet
+{
+ ///
+ /// Processes an incoming packet of type .
+ ///
+ /// The context provided to this processor containing data about its sender.
+ /// The incoming packet data that should be processed
+ Task Process(TContext context, TPacket packet);
+}
diff --git a/Nitrox.Model/Packets/Core/PacketProcessorsInvoker.cs b/Nitrox.Model/Packets/Core/PacketProcessorsInvoker.cs
new file mode 100644
index 0000000000..b1d16b1191
--- /dev/null
+++ b/Nitrox.Model/Packets/Core/PacketProcessorsInvoker.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+#if NET
+using System.Collections.Frozen;
+#endif
+
+namespace Nitrox.Model.Packets.Core;
+
+public sealed class PacketProcessorsInvoker
+{
+#if NET
+ private readonly FrozenDictionary packetTypeToProcessorEntry;
+#else
+ private readonly Dictionary packetTypeToProcessorEntry;
+#endif
+
+ public PacketProcessorsInvoker(IEnumerable packetProcessors)
+ {
+ if (packetProcessors is null)
+ {
+ throw new ArgumentOutOfRangeException(nameof(packetProcessors));
+ }
+
+ var entries = packetProcessors.SelectMany(pInstance => pInstance.GetType()
+ .GetInterfaces()
+ .Where(i => typeof(IPacketProcessor).IsAssignableFrom(i))
+ .Select(i => new
+ {
+ InterfaceType = i,
+ PacketType = i.GetGenericArguments().FirstOrDefault(t => typeof(Packet).IsAssignableFrom(t)),
+ Processor = pInstance
+ })
+ .Where(p => p.PacketType != null));
+ Dictionary lookup = [];
+ foreach (var entry in entries)
+ {
+ if (lookup.TryGetValue(entry.PacketType, out Entry value))
+ {
+ if (value.Processor != entry.Processor)
+ {
+ throw new Exception($"Packet type {value.PacketType} has multiple handlers (A: {value.Processor.GetType()}, B: {entry.Processor.GetType()}), which is not allowed.");
+ }
+ if (entry.InterfaceType.IsAssignableFrom(value.InterfaceType))
+ {
+ continue;
+ }
+ }
+
+ lookup[entry.PacketType] = new Entry(entry.Processor, entry.InterfaceType, entry.PacketType);
+ }
+#if NET
+ packetTypeToProcessorEntry = lookup.ToFrozenDictionary();
+#else
+ packetTypeToProcessorEntry = lookup;
+#endif
+ }
+
+ public Entry? GetProcessor(Type packetType)
+ {
+ Type current = packetType;
+ Type? prior = null;
+ while (current != prior)
+ {
+ if (packetTypeToProcessorEntry.TryGetValue(packetType, out Entry processor))
+ {
+ return processor;
+ }
+ prior = current;
+ current = packetType.BaseType;
+ }
+
+ return null;
+ }
+
+ [DebuggerDisplay($"{{{nameof(InterfaceType)}}}")]
+ public sealed class Entry
+ {
+ private static readonly Type[] expectedProcessorParameterTypes = [typeof(IPacketProcessContext), typeof(Packet)];
+ private readonly Func invoker;
+
+ public Type PacketType { get; }
+ public Type InterfaceType { get; }
+
+ public object Processor => invoker.Target;
+
+ internal Entry(IPacketProcessor processor, Type processorInterfaceType, Type packetType)
+ {
+ PacketType = packetType;
+ InterfaceType = processorInterfaceType;
+
+ MethodInfo method = processor.GetType().GetMethods().FirstOrDefault(m =>
+ {
+ if (!typeof(Task).IsAssignableFrom(m.ReturnType))
+ {
+ return false;
+ }
+ ParameterInfo[] parameterInfos = m.GetParameters();
+ if (parameterInfos.Length != expectedProcessorParameterTypes.Length)
+ {
+ return false;
+ }
+ for (int i = 0; i < parameterInfos.Length; i++)
+ {
+ Type expectedParamType = expectedProcessorParameterTypes[i];
+ // For packet parameter, we want the most specific method that can handle it.
+ if (expectedParamType == typeof(Packet))
+ {
+ expectedParamType = packetType;
+ }
+
+ if (!expectedParamType.IsAssignableFrom(parameterInfos[i].ParameterType))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ if (method == null)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(processor), $"Processor {processor.GetType()} implementing {processorInterfaceType} does not have a method that looks like 'Task M({string.Join(", ", expectedProcessorParameterTypes.Select(t => t.Name))})'");
+ }
+ ParameterInfo[] parameters = method.GetParameters();
+ Type funcType = typeof(Func<,,>).MakeGenericType(parameters[0].ParameterType, parameters[1].ParameterType, method.ReturnType);
+ Delegate processorDelegate = method.CreateDelegate(funcType, processor);
+ RuntimeHelpers.PrepareDelegate(processorDelegate);
+ invoker = Unsafe.As>(processorDelegate);
+ }
+
+ public Task Execute(IPacketProcessContext context, Packet packet) => invoker(context, packet);
+ public override string ToString() => $"Processor: {invoker.Target.GetType().Name}, {nameof(InterfaceType)}: {InterfaceType.Name}";
+ }
+}
diff --git a/Nitrox.Model/Packets/CorrelatedPacket.cs b/Nitrox.Model/Packets/CorrelatedPacket.cs
index 66dd6bebc6..a7c1d85b16 100644
--- a/Nitrox.Model/Packets/CorrelatedPacket.cs
+++ b/Nitrox.Model/Packets/CorrelatedPacket.cs
@@ -1,15 +1,10 @@
using System;
-namespace Nitrox.Model.Packets
-{
- [Serializable]
- public abstract class CorrelatedPacket : Packet
- {
- public string CorrelationId { get; protected set; }
+namespace Nitrox.Model.Packets;
- protected CorrelatedPacket(string correlationId)
- {
- CorrelationId = correlationId;
- }
- }
+// TODO: Refactor this away. Use SessionId instead of correlationId.
+[Serializable]
+public abstract class CorrelatedPacket(string correlationId) : Packet
+{
+ public string CorrelationId { get; protected set; } = correlationId;
}
diff --git a/Nitrox.Model/Packets/Packet.cs b/Nitrox.Model/Packets/Packet.cs
index 51a3ffb50b..1618291832 100644
--- a/Nitrox.Model/Packets/Packet.cs
+++ b/Nitrox.Model/Packets/Packet.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
@@ -14,12 +15,12 @@ namespace Nitrox.Model.Packets
public abstract class Packet
{
private static readonly Dictionary cachedPropertiesByType = new();
- private static readonly object cachedPropertiesByTypeLocker = new();
+ private static readonly LockObject cachedPropertiesByTypeLocker = new();
[ThreadStatic]
private static StringBuilder? toStringBuilder;
- private static readonly object lockObject = new();
+ private static readonly LockObject lockObject = new();
[IgnoredMember]
public NitroxDeliveryMethod.DeliveryMethod DeliveryMethod { get; protected set; } = NitroxDeliveryMethod.DeliveryMethod.RELIABLE_ORDERED;
@@ -83,11 +84,13 @@ public byte[] Serialize()
return BinaryConverter.Serialize(new Wrapper(this));
}
- public static Packet Deserialize(byte[] data)
+ public void SerializeInto(Stream stream)
{
- return BinaryConverter.Deserialize(data).Packet;
+ BinaryConverter.Serialize(new Wrapper(this), stream);
}
+ public static Packet? Deserialize(byte[] data) => BinaryConverter.Deserialize(data).Packet;
+
public override string ToString()
{
Type packetType = GetType();
@@ -134,14 +137,9 @@ public override string ToString()
///
/// This type solves both problems and only adds a single byte to the data.
///
- public readonly struct Wrapper
+ public readonly struct Wrapper(Packet packet)
{
- public Packet Packet { get; init; } = null;
-
- public Wrapper(Packet packet)
- {
- Packet = packet;
- }
+ public Packet? Packet { get; init; } = packet;
}
public enum UdpChannelId : byte
diff --git a/Nitrox.Model/Packets/Processors/Abstract/IProcessorContext.cs b/Nitrox.Model/Packets/Processors/Abstract/IProcessorContext.cs
deleted file mode 100644
index 7f2365b373..0000000000
--- a/Nitrox.Model/Packets/Processors/Abstract/IProcessorContext.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Nitrox.Model.Packets.Processors.Abstract
-{
- public interface IProcessorContext
- {
- }
-}
diff --git a/Nitrox.Model/Packets/Processors/Abstract/PacketProcessor.cs b/Nitrox.Model/Packets/Processors/Abstract/PacketProcessor.cs
deleted file mode 100644
index 28a8bf81e1..0000000000
--- a/Nitrox.Model/Packets/Processors/Abstract/PacketProcessor.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-
-namespace Nitrox.Model.Packets.Processors.Abstract
-{
- public abstract class PacketProcessor
- {
- public abstract void ProcessPacket(Packet packet, IProcessorContext context);
-
- public static Dictionary GetProcessors(Dictionary processorArguments, Func additionalConstraints)
- {
- return Assembly.GetCallingAssembly()
- .GetTypes()
- .Where(p => typeof(PacketProcessor).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
- .Where(additionalConstraints)
- .ToDictionary(proc => proc.BaseType.GetGenericArguments()[0], proc =>
- {
- ConstructorInfo[] ctors = proc.GetConstructors();
- if (ctors.Length > 1)
- {
- throw new NotSupportedException($"{proc.Name} has more than one constructor!");
- }
-
- ConstructorInfo ctor = ctors.First();
-
- // Prepare arguments for constructor (if applicable):
- object[] args = ctor.GetParameters().Select(pi =>
- {
- if (processorArguments.TryGetValue(pi.ParameterType, out object v))
- {
- return v;
- }
-
- throw new ArgumentException($"Argument value not defined for type {pi.ParameterType}! Used in {proc}");
- }).ToArray();
-
- return (PacketProcessor)ctor.Invoke(args);
- });
- }
- }
-}
diff --git a/Nitrox.Model/Packets/TextAutoComplete.cs b/Nitrox.Model/Packets/TextAutoComplete.cs
new file mode 100644
index 0000000000..2cf1ddad92
--- /dev/null
+++ b/Nitrox.Model/Packets/TextAutoComplete.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Nitrox.Model.Packets;
+
+[Serializable]
+public sealed class TextAutoComplete(string? text, TextAutoComplete.AutoCompleteContext context) : Packet
+{
+ ///
+ /// Text to send over as either a suggestion for auto complete (from client) or a reply to the suggestion (from
+ /// server).
+ ///
+ public string? Text { get; init; } = text;
+
+ public AutoCompleteContext Context { get; init; } = context;
+
+ public enum AutoCompleteContext
+ {
+ COMMAND_NAME
+ }
+}
diff --git a/Nitrox.Server.Subnautica/Extensions/LoggerExtensions.cs b/Nitrox.Server.Subnautica/Extensions/LoggerExtensions.cs
index a9df50ae5d..cdca9b3f07 100644
--- a/Nitrox.Server.Subnautica/Extensions/LoggerExtensions.cs
+++ b/Nitrox.Server.Subnautica/Extensions/LoggerExtensions.cs
@@ -1,5 +1,7 @@
using System.Net;
using System.Runtime.CompilerServices;
+using Nitrox.Model.Core;
+using Nitrox.Server.Subnautica.Models.Commands.Core;
using Nitrox.Server.Subnautica.Models.Logging;
using Nitrox.Server.Subnautica.Models.Logging.Scopes;
@@ -7,22 +9,6 @@ namespace Nitrox.Server.Subnautica.Extensions;
internal static partial class LoggerExtensions
{
- ///
- /// Sets the logger into "plain" mode. Text will be logged without the time, category or log level info.
- ///
- public static IDisposable? BeginPlainScope(this ILogger logger) => logger.BeginScope(new PlainScope());
-
- public static IDisposable? BeginPrefixScope(this ILogger logger, string prefix) => logger.BeginScope(new PrefixScope(prefix));
-
- ///
- public static CaptureScope BeginCaptureScope(this ILogger logger)
- {
- CaptureScope scope = new();
- IDisposable disposable = logger.BeginScope(scope);
- scope.InnerDisposable = disposable;
- return scope;
- }
-
public static void ZLogWarningOnce(this ILogger logger,
[InterpolatedStringHandlerArgument("logger")]
ref DeduplicateWarningInterpolatedStringHandler message,
@@ -96,6 +82,37 @@ public static void ZLogErrorOnce(this ILogger logger,
[ZLoggerMessage(Level = LogLevel.Error, Message = "Unable to open directory {Path} because it does not exist")]
public static partial void LogOpenDirectoryNotExists(this ILogger logger, string path);
- [ZLoggerMessage(Level = LogLevel.Information, Message = "Server password changed to '{Password}' by player '{PlayerName}'")]
- public static partial void LogServerPasswordChanged(this ILogger logger, string password, string playerName);
+ [ZLoggerMessage(Level = LogLevel.Information, Message = "Server password changed to '{Password}' by '{PlayerName}' on session #{SessionId}")]
+ public static partial void LogServerPasswordChanged(this ILogger logger, string password, string playerName, SessionId sessionId);
+
+ [ZLoggerMessage(Level = LogLevel.Trace, Message = "Adding {Handler}")]
+ public static partial void LogCommandHandlerAdded(this ILogger logger, CommandHandlerEntry handler);
+
+ ///
+ /// Logs a save request as being issued by the issuer.
+ ///
+ /// The logger instance to use.
+ /// Name of the issuer.
+ /// Session ID of the issuer.
+ [ZLoggerMessage(Level = LogLevel.Information, Message = "Save requested by '{Name}' #{SessionId}")]
+ public static partial void LogSaveRequest(this ILogger logger, string name, SessionId sessionId);
+
+ extension(ILogger logger)
+ {
+ ///
+ /// Sets the logger into "plain" mode. Text will be logged without the time, category or log level info.
+ ///
+ public IDisposable? BeginPlainScope() => logger.BeginScope(new PlainScope());
+
+ public IDisposable? BeginPrefixScope(string prefix) => logger.BeginScope(new PrefixScope(prefix));
+
+ ///
+ public CaptureScope BeginCaptureScope()
+ {
+ CaptureScope scope = new();
+ IDisposable disposable = logger.BeginScope(scope);
+ scope.InnerDisposable = disposable;
+ return scope;
+ }
+ }
}
diff --git a/Nitrox.Server.Subnautica/Extensions/LoggingBuilderExtensions.cs b/Nitrox.Server.Subnautica/Extensions/LoggingBuilderExtensions.cs
index 4151ed1bf8..2e3dea1e08 100644
--- a/Nitrox.Server.Subnautica/Extensions/LoggingBuilderExtensions.cs
+++ b/Nitrox.Server.Subnautica/Extensions/LoggingBuilderExtensions.cs
@@ -1,19 +1,83 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging.Console;
+using Nitrox.Server.Subnautica.Models.Logging.Redaction.Core;
+using Nitrox.Server.Subnautica.Models.Logging.Scopes;
using Nitrox.Server.Subnautica.Models.Logging.ZLogger;
+using Nitrox.Server.Subnautica.Services;
+using ZLogger.Providers;
namespace Nitrox.Server.Subnautica.Extensions;
internal static class LoggingBuilderExtensions
{
- public static ILoggingBuilder AddNitroxZLoggerPlain(this ILoggingBuilder builder, Action configure)
+ extension(ILoggingBuilder builder)
{
- builder.Services.AddSingleton(_ =>
+ private ILoggingBuilder AddNitroxZLoggerPlain(Action configure)
{
- PlainLogProcessor processor = new() { Options = new() };
- configure(processor.Options);
- processor.Formatter = processor.Options.CreateFormatter();
- return new ZLoggerPlainLoggerProvider(processor, processor.Options);
- });
- return builder;
+ builder.Services.AddSingleton(_ =>
+ {
+ PlainLogProcessor processor = new() { Options = new() };
+ configure(processor.Options);
+ processor.Formatter = processor.Options.CreateFormatter();
+ return new ZLoggerPlainLoggerProvider(processor, processor.Options);
+ });
+ return builder;
+ }
+
+ public ILoggingBuilder AddNitroxLogging()
+ {
+ builder.Services.AddRedactors();
+ return builder
+ .AddZLoggerConsole(static (options, provider) =>
+ {
+ options.IncludeScopes = true;
+ options.UseNitroxFormatter(formatterOptions =>
+ {
+ formatterOptions.OmitWhenCaptured = true;
+ bool isEmbedded = provider.GetRequiredService>().Value.IsEmbedded;
+ formatterOptions.ColorBehavior = isEmbedded ? LoggerColorBehavior.Disabled : LoggerColorBehavior.Enabled;
+ });
+ })
+ .AddNitroxZLoggerPlain(options =>
+ {
+ options.IncludeScopes = true;
+ options.UseNitroxFormatter(o =>
+ {
+ o.OmitWhenCaptured = true;
+ o.IsPlain = true;
+ }).OutputFunc = async (entry, formatter, generator, writer) => await ServersManagementService.LogQueue.Writer.WriteAsync(new ServersManagementService.LogEntry(entry, formatter, generator, writer));
+ })
+ .AddNitroxZLoggerPlain(options =>
+ {
+ options.IncludeScopes = true;
+ options.UseNitroxFormatter().OutputFunc = (entry, formatter, generator, writer) =>
+ {
+ if (entry.TryGetProperty(out CaptureScope scope))
+ {
+ scope.Capture(generator(entry, formatter, writer));
+ }
+ return Task.CompletedTask;
+ };
+ })
+ .AddZLoggerRollingFile(static (options, provider) =>
+ {
+ ServerStartOptions serverStartOptions = provider.GetRequiredService>().Value;
+ options.FilePathSelector = (timestamp, sequence) =>
+ {
+ string filename = $"{timestamp.ToLocalTime():yyyy-MM-dd}_server_{serverStartOptions.SaveName}_{sequence:000}.log";
+ return Path.Combine(serverStartOptions.GetServerLogsPath(), filename);
+ };
+ options.RollingInterval = RollingInterval.Day;
+ options.IncludeScopes = true;
+ options.UseNitroxFormatter(formatterOptions =>
+ {
+ formatterOptions.OmitWhenCaptured = true;
+ formatterOptions.Redactors = provider.GetRequiredService>()?.ToArray() ?? [];
+ });
+ });
+ }
}
}
diff --git a/Nitrox.Server.Subnautica/Extensions/ServiceCollectionExtensions.cs b/Nitrox.Server.Subnautica/Extensions/ServiceCollectionExtensions.cs
index 09abfe20f9..d5cf231979 100644
--- a/Nitrox.Server.Subnautica/Extensions/ServiceCollectionExtensions.cs
+++ b/Nitrox.Server.Subnautica/Extensions/ServiceCollectionExtensions.cs
@@ -1,34 +1,30 @@
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using AssetsTools.NET.Extra;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Logging.Console;
using Nitrox.Model.Constants;
+using Nitrox.Model.Packets.Core;
using Nitrox.Model.Subnautica.DataStructures.GameLogic;
using Nitrox.Model.Subnautica.DataStructures.GameLogic.Entities;
+using Nitrox.Server.Subnautica.Models.Administration.Core;
using Nitrox.Server.Subnautica.Models.AppEvents.Core;
-using Nitrox.Server.Subnautica.Models.AppEvents.Triggers;
-using Nitrox.Server.Subnautica.Models.Commands.Abstract;
-using Nitrox.Server.Subnautica.Models.Commands.Processor;
+using Nitrox.Server.Subnautica.Models.Commands.ArgConverters.Core;
+using Nitrox.Server.Subnautica.Models.Commands.Core;
using Nitrox.Server.Subnautica.Models.Communication;
using Nitrox.Server.Subnautica.Models.GameLogic;
using Nitrox.Server.Subnautica.Models.GameLogic.Bases;
using Nitrox.Server.Subnautica.Models.GameLogic.Entities;
using Nitrox.Server.Subnautica.Models.GameLogic.Entities.Spawning;
using Nitrox.Server.Subnautica.Models.Logging.Redaction.Core;
-using Nitrox.Server.Subnautica.Models.Logging.Scopes;
-using Nitrox.Server.Subnautica.Models.Packets;
+using Nitrox.Server.Subnautica.Models.Packets.Core;
using Nitrox.Server.Subnautica.Models.Packets.Processors;
-using Nitrox.Server.Subnautica.Models.Packets.Processors.Core;
using Nitrox.Server.Subnautica.Models.Resources.Core;
using Nitrox.Server.Subnautica.Models.Serialization;
using Nitrox.Server.Subnautica.Models.Serialization.SaveDataUpgrades;
using Nitrox.Server.Subnautica.Models.Serialization.World;
using Nitrox.Server.Subnautica.Services;
using ServiceScan.SourceGenerator;
-using ZLogger.Providers;
namespace Nitrox.Server.Subnautica.Extensions;
@@ -36,191 +32,11 @@ internal static partial class ServiceCollectionExtensions
{
private static readonly Lazy newWorldSeed = new(() => StringHelper.GenerateRandomString(10));
- ///
- /// Adds the fallback implementation for the interface if no other implementation is set.
- ///
- public static IServiceCollection AddFallback(this IServiceCollection services) where TInterface : class where TFallback : class, TInterface
- {
- services.TryAddSingleton();
- return services;
- }
-
- public static IServiceCollection AddHostedSingletonService(this IServiceCollection services) where T : class, IHostedService => services.AddSingleton().AddHostedService(provider => provider.GetRequiredService());
-
- public static IServiceCollection TryAddSingletonLazyArrayProvider(this IServiceCollection services)
- {
- services.TryAddSingleton>(provider => () => provider.GetRequiredService>().ToArray());
- return services;
- }
-
- public static IServiceCollection AddNitroxOptions(this IServiceCollection services)
- {
- services.AddOptionsWithValidateOnStart()
- .BindConfiguration("")
- .Configure(options =>
- {
- if (string.IsNullOrWhiteSpace(options.GamePath))
- {
- options.GamePath = NitroxUser.GamePath;
- }
- if (string.IsNullOrWhiteSpace(options.NitroxAssetsPath))
- {
- options.NitroxAssetsPath = NitroxUser.AssetsPath;
- }
- if (string.IsNullOrWhiteSpace(options.NitroxAppDataPath))
- {
- options.NitroxAppDataPath = NitroxUser.AppDataPath;
- }
- });
- services.AddOptionsWithValidateOnStart()
- .BindConfiguration(SubnauticaServerOptions.CONFIG_SECTION_PATH)
- .Configure((SubnauticaServerOptions options, IHostEnvironment environment) =>
- {
- options.Seed = options.Seed switch
- {
- null or "" when environment.IsDevelopment() => SubnauticaServerConstants.DEFAULT_DEVELOPMENT_SEED,
- null or "" => newWorldSeed.Value,
- _ => options.Seed
- };
- });
- return services;
- }
-
- public static ILoggingBuilder AddNitroxLogging(this ILoggingBuilder builder)
- {
- builder.Services.AddRedactors();
- return builder
- .AddZLoggerConsole(static (options, provider) =>
- {
- options.IncludeScopes = true;
- options.UseNitroxFormatter(formatterOptions =>
- {
- formatterOptions.OmitWhenCaptured = true;
- bool isEmbedded = provider.GetRequiredService>().Value.IsEmbedded;
- formatterOptions.ColorBehavior = isEmbedded ? LoggerColorBehavior.Disabled : LoggerColorBehavior.Enabled;
- });
- })
- .AddNitroxZLoggerPlain(options =>
- {
- options.IncludeScopes = true;
- options.UseNitroxFormatter(o =>
- {
- o.OmitWhenCaptured = true;
- o.IsPlain = true;
- }).OutputFunc = async (entry, formatter, generator, writer) => await ServersManagementService.LogQueue.Writer.WriteAsync(new ServersManagementService.LogEntry(entry, formatter, generator, writer));
- })
- .AddNitroxZLoggerPlain(options =>
- {
- options.IncludeScopes = true;
- options.UseNitroxFormatter().OutputFunc = (entry, formatter, generator, writer) =>
- {
- if (entry.TryGetProperty(out CaptureScope scope))
- {
- scope.Capture(generator(entry, formatter, writer));
- }
- return Task.CompletedTask;
- };
- })
- .AddZLoggerRollingFile(static (options, provider) =>
- {
- ServerStartOptions serverStartOptions = provider.GetRequiredService>().Value;
- options.FilePathSelector = (timestamp, sequence) =>
- {
- string filename = $"{timestamp.ToLocalTime():yyyy-MM-dd}_server_{serverStartOptions.SaveName}_{sequence:000}.log";
- return Path.Combine(serverStartOptions.GetServerLogsPath(), filename);
- };
- options.RollingInterval = RollingInterval.Day;
- options.IncludeScopes = true;
- options.UseNitroxFormatter(formatterOptions =>
- {
- formatterOptions.OmitWhenCaptured = true;
- formatterOptions.Redactors = provider.GetRequiredService>()?.ToArray() ?? [];
- });
- });
- }
-
- ///
- /// Provides a console reader, command registration and command handling to facilitate server administration.
- ///
- public static IServiceCollection AddCommands(this IServiceCollection services)
- {
- services.AddHostedSingletonService()
- .AddHostedSingletonService()
- .AddSingleton()
- .AddSingleton()
- .AddCommandHandlers();
- return services;
- }
-
- public static IServiceCollection AddWorld(this IServiceCollection services)
- {
- // Hack: Save service strongly depends on WorldService so it's a Func to prevent StackOverflow. TODO: Remove need for WorldService; each service should save / load its own data through a common interface.
- services.AddHostedSingletonService()
- .AddHostedSingletonService()
- .AddSingleton>(provider => provider.GetRequiredService)
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton();
-
- return services;
- }
-
- ///
- /// Provides packet type registration, processing and a listener for these packets on a configured UDP port.
- ///
- public static IServiceCollection AddPackets(this IServiceCollection services)
- {
- services.AddPacketProcessors()
- .AddSingleton()
- .AddSingleton()
- .AddHostedSingletonService();
- return services;
- }
+ [GenerateServiceRegistrations(AssignableTo = typeof(IRedactor), Lifetime = ServiceLifetime.Singleton)]
+ internal static partial IServiceCollection AddRedactors(this IServiceCollection services);
- ///
- /// Provides an API for local processes on the current machine to communicate and manage this server.
- ///
- public static IServiceCollection AddLocalServerManagement(this IServiceCollection services) =>
- services
- .AddHostedSingletonService();
-
- public static IServiceCollection AddSubnauticaResources(this IServiceCollection services) =>
- services
- .AddHostedSingletonService()
- .AddGameResources()
- .AddSingleton()
- .AddTransient()
- .AddSingleton()
- .AddTransient();
-
- public static IServiceCollection AddSaving(this IServiceCollection services) =>
- services
- .AddSaveUpgraders()
- .AddHostedSingletonService()
- .AddHostedSingletonService();
-
- public static IServiceCollection AddAppEvents(this IServiceCollection services) =>
- services
- .AddEvents()
- .AddEventTriggers();
-
- private static IServiceCollection AddEvent(this IServiceCollection services) =>
- services
- .TryAddSingletonLazyArrayProvider>()
- .AddSingleton(provider => (IEvent)provider.GetRequiredService());
+ [GenerateServiceRegistrations(AssignableTo = typeof(IAdminFeature<>), CustomHandler = nameof(AddImplementedAdminFeatures))]
+ internal static partial IServiceCollection AddAdminFeatures(this IServiceCollection services);
[GenerateServiceRegistrations(AssignableTo = typeof(IGameResource), Lifetime = ServiceLifetime.Singleton, AsSelf = true, AsImplementedInterfaces = true)]
private static partial IServiceCollection AddGameResources(this IServiceCollection services);
@@ -228,19 +44,197 @@ private static IServiceCollection AddEvent(this ISe
[GenerateServiceRegistrations(AssignableTo = typeof(SaveDataUpgrade), Lifetime = ServiceLifetime.Scoped)]
private static partial IServiceCollection AddSaveUpgraders(this IServiceCollection services);
- [GenerateServiceRegistrations(AssignableTo = typeof(AuthenticatedPacketProcessor<>), ExcludeAssignableTo = typeof(DefaultServerPacketProcessor), Lifetime = ServiceLifetime.Scoped)]
- [GenerateServiceRegistrations(AssignableTo = typeof(UnauthenticatedPacketProcessor<>), Lifetime = ServiceLifetime.Scoped)]
+ [GenerateServiceRegistrations(AssignableTo = typeof(IPacketProcessor), Lifetime = ServiceLifetime.Scoped)]
private static partial IServiceCollection AddPacketProcessors(this IServiceCollection services);
- [GenerateServiceRegistrations(AssignableTo = typeof(IRedactor), Lifetime = ServiceLifetime.Singleton)]
- private static partial IServiceCollection AddRedactors(this IServiceCollection services);
-
- [GenerateServiceRegistrations(AssignableTo = typeof(Command), Lifetime = ServiceLifetime.Scoped)]
- private static partial IServiceCollection AddCommandHandlers(this IServiceCollection services);
-
[GenerateServiceRegistrations(AssignableTo = typeof(EventTrigger<>), AsSelf = true, AsImplementedInterfaces = false, Lifetime = ServiceLifetime.Singleton)]
private static partial IServiceCollection AddEventTriggers(this IServiceCollection services);
[GenerateServiceRegistrations(AssignableTo = typeof(IEvent<>), Lifetime = ServiceLifetime.Singleton, CustomHandler = nameof(AddEvent))]
private static partial IServiceCollection AddEvents(this IServiceCollection services);
+
+ [GenerateServiceRegistrations(AssignableTo = typeof(ICommandHandlerBase), CustomHandler = nameof(AddCommandHandler))]
+ private static partial IServiceCollection AddCommandHandlers(this IServiceCollection services);
+
+ [GenerateServiceRegistrations(AssignableTo = typeof(IArgConverter), Lifetime = ServiceLifetime.Singleton, AsSelf = true, AsImplementedInterfaces = true)]
+ private static partial IServiceCollection AddCommandArgConverters(this IServiceCollection services);
+
+ ///
+ /// Registers a single command and all of its handlers as can be known by the implemented interfaces.
+ ///
+ private static void AddCommandHandler(this IServiceCollection services) where T : class, ICommandHandlerBase
+ {
+ Type[] handlerTypes = typeof(T).GetInterfaces().Where(t => t != typeof(ICommandHandlerBase) && typeof(ICommandHandlerBase).IsAssignableFrom(t)).ToArray();
+ if (handlerTypes.Length < 1)
+ {
+ return;
+ }
+ services.AddSingleton();
+
+ foreach (Type handlerType in handlerTypes)
+ {
+ services.AddSingleton(provider =>
+ {
+ T owner = provider.GetRequiredService();
+ return new CommandHandlerEntry(owner, handlerType);
+ });
+ }
+ }
+
+ private static void AddImplementedAdminFeatures(this IServiceCollection services) where TImplementation : class, IAdminFeature
+ {
+ foreach (Type featureInterfaceType in typeof(TImplementation).GetInterfaces()
+ .Where(i => typeof(IAdminFeature).IsAssignableFrom(i))
+ .Select(i => i.GetGenericArguments())
+ .Where(types => types.Length == 1)
+ .Select(types => types[0]))
+ {
+ services.AddSingleton(featureInterfaceType, provider => provider.GetRequiredService());
+ }
+ }
+
+ extension(IServiceCollection services)
+ {
+ ///
+ /// Adds the fallback implementation for the interface if no other implementation is set.
+ ///
+ public IServiceCollection AddFallback() where TInterface : class where TFallback : class, TInterface
+ {
+ services.TryAddSingleton();
+ return services;
+ }
+
+ public IServiceCollection AddHostedSingletonService() where T : class, IHostedService => services.AddSingleton().AddHostedService(provider => provider.GetRequiredService());
+
+ public IServiceCollection AddNitroxOptions()
+ {
+ services.AddOptionsWithValidateOnStart()
+ .BindConfiguration("")
+ .Configure(options =>
+ {
+ if (string.IsNullOrWhiteSpace(options.GamePath))
+ {
+ options.GamePath = NitroxUser.GamePath;
+ }
+ if (string.IsNullOrWhiteSpace(options.NitroxAssetsPath))
+ {
+ options.NitroxAssetsPath = NitroxUser.AssetsPath;
+ }
+ if (string.IsNullOrWhiteSpace(options.NitroxAppDataPath))
+ {
+ options.NitroxAppDataPath = NitroxUser.AppDataPath;
+ }
+ });
+ services.AddOptionsWithValidateOnStart()
+ .BindConfiguration(SubnauticaServerOptions.CONFIG_SECTION_PATH)
+ .Configure((SubnauticaServerOptions options, IHostEnvironment environment) =>
+ {
+ options.Seed = options.Seed switch
+ {
+ null or "" when environment.IsDevelopment() => SubnauticaServerConstants.DEFAULT_DEVELOPMENT_SEED,
+ null or "" => newWorldSeed.Value,
+ _ => options.Seed
+ };
+ });
+ services.AddHostedSingletonService();
+ return services;
+ }
+
+ ///
+ /// Provides a console reader, command registration and command handling to facilitate server administration.
+ ///
+ public IServiceCollection AddCommands() =>
+ services.AddHostedSingletonService()
+ .AddHostedSingletonService()
+ .AddSingleton()
+ .AddSingleton>(provider => provider.GetRequiredService)
+ .AddCommandHandlers()
+ .AddCommandArgConverters()
+ .AddSingleton();
+
+ ///
+ /// Adds all the services and managers necessary to simulate a Subnautica world.
+ ///
+ public IServiceCollection AddWorld()
+ {
+ // Hack: Save service strongly depends on WorldService so it's a Func to prevent StackOverflow. TODO: Remove need for WorldService; each service should save / load its own data through a common interface.
+ services.AddHostedSingletonService()
+ .AddHostedSingletonService()
+ .AddHostedSingletonService()
+ .AddSingleton>(provider => provider.GetRequiredService)
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton();
+
+ return services;
+ }
+
+ ///
+ /// Provides packet type registration, processing and a listener for these packets on a configured UDP port.
+ ///
+ public IServiceCollection AddPackets()
+ {
+ services.AddHostedSingletonService()
+ .AddHostedSingletonService()
+ .AddHostedSingletonService()
+ .AddPacketProcessors()
+ .TryAddSingletonLazyArrayProvider()
+ .AddSingleton()
+ .AddSingleton(provider => provider.GetRequiredService());
+ return services;
+ }
+
+ ///
+ /// Provides an API for local processes on the current machine to communicate and manage this server.
+ ///
+ public IServiceCollection AddLocalServerManagement() =>
+ services
+ .AddHostedSingletonService();
+
+ public IServiceCollection AddSubnauticaResources() =>
+ services
+ .AddHostedSingletonService()
+ .AddGameResources()
+ .AddSingleton()
+ .AddTransient()
+ .AddSingleton()
+ .AddTransient();
+
+ public IServiceCollection AddSaving() =>
+ services
+ .AddSaveUpgraders()
+ .AddHostedSingletonService()
+ .AddHostedSingletonService();
+
+ public IServiceCollection AddAppEvents() =>
+ services
+ .AddEvents()
+ .AddEventTriggers();
+
+ private IServiceCollection TryAddSingletonLazyArrayProvider()
+ {
+ services.TryAddSingleton>(provider => () => provider.GetRequiredService>().ToArray());
+ return services;
+ }
+
+ private IServiceCollection AddEvent() =>
+ services
+ .TryAddSingletonLazyArrayProvider>()
+ .AddSingleton(provider => (IEvent)provider.GetRequiredService());
+ }
}
diff --git a/Nitrox.Server.Subnautica/Models/Administration/Core/IAdminFeature.cs b/Nitrox.Server.Subnautica/Models/Administration/Core/IAdminFeature.cs
new file mode 100644
index 0000000000..122518366c
--- /dev/null
+++ b/Nitrox.Server.Subnautica/Models/Administration/Core/IAdminFeature.cs
@@ -0,0 +1,5 @@
+namespace Nitrox.Server.Subnautica.Models.Administration.Core;
+
+internal interface IAdminFeature;
+
+internal interface IAdminFeature : IAdminFeature where T : IAdminFeature;
diff --git a/Nitrox.Server.Subnautica/Models/Administration/IKickPlayer.cs b/Nitrox.Server.Subnautica/Models/Administration/IKickPlayer.cs
new file mode 100644
index 0000000000..631348cf31
--- /dev/null
+++ b/Nitrox.Server.Subnautica/Models/Administration/IKickPlayer.cs
@@ -0,0 +1,9 @@
+using Nitrox.Model.Core;
+using Nitrox.Server.Subnautica.Models.Administration.Core;
+
+namespace Nitrox.Server.Subnautica.Models.Administration;
+
+internal interface IKickPlayer : IAdminFeature
+{
+ Task KickPlayer(SessionId sessionId, string reason = "");
+}
diff --git a/Nitrox.Server.Subnautica/Models/AppEvents/ISaveState.cs b/Nitrox.Server.Subnautica/Models/AppEvents/ISaveState.cs
new file mode 100644
index 0000000000..251d738feb
--- /dev/null
+++ b/Nitrox.Server.Subnautica/Models/AppEvents/ISaveState.cs
@@ -0,0 +1,15 @@
+using Nitrox.Server.Subnautica.Models.AppEvents.Core;
+using Nitrox.Server.Subnautica.Models.AppEvents.Triggers;
+
+namespace Nitrox.Server.Subnautica.Models.AppEvents;
+
+///
+/// Event to let other services save their state to the same directory as the server save.
+///
+internal interface ISaveState : IEvent
+{
+ /// Path to the save directory of the current game server instance.
+ public record Args(string SavePath);
+
+ public class Trigger(Func[]> lazyHandlersProvider) : AsyncTrigger(lazyHandlersProvider);
+}
diff --git a/Nitrox.Server.Subnautica/Models/AppEvents/ISessionCleaner.cs b/Nitrox.Server.Subnautica/Models/AppEvents/ISessionCleaner.cs
new file mode 100644
index 0000000000..84a035417e
--- /dev/null
+++ b/Nitrox.Server.Subnautica/Models/AppEvents/ISessionCleaner.cs
@@ -0,0 +1,12 @@
+using Nitrox.Server.Subnautica.Models.AppEvents.Core;
+using Nitrox.Server.Subnautica.Models.AppEvents.Triggers;
+using Nitrox.Server.Subnautica.Models.Communication;
+
+namespace Nitrox.Server.Subnautica.Models.AppEvents;
+
+internal interface ISessionCleaner : IEvent
+{
+ public record Args(SessionManager.Session Session, int NewPlayerTotal);
+
+ public class Trigger(Func[]> lazyHandlersProvider) : AsyncTrigger(lazyHandlersProvider);
+}
diff --git a/Nitrox.Server.Subnautica/Models/AppEvents/Triggers/AsyncTrigger.cs b/Nitrox.Server.Subnautica/Models/AppEvents/Triggers/AsyncTrigger.cs
new file mode 100644
index 0000000000..45741017ef
--- /dev/null
+++ b/Nitrox.Server.Subnautica/Models/AppEvents/Triggers/AsyncTrigger.cs
@@ -0,0 +1,27 @@
+using System.Buffers;
+using Nitrox.Server.Subnautica.Models.AppEvents.Core;
+
+namespace Nitrox.Server.Subnautica.Models.AppEvents.Triggers;
+
+internal abstract class AsyncTrigger(Func[]> handlers) : EventTrigger(handlers)
+{
+ private static readonly ArrayPool pool = ArrayPool.Create();
+
+ public async Task InvokeAsync(TEventArgs args)
+ {
+ IEvent[] handlers = Handlers.Value;
+ Task[] tasks = pool.Rent(handlers.Length);
+ try
+ {
+ for (int i = 0; i < handlers.Length; i++)
+ {
+ tasks[i] = handlers[i].OnEventAsync(args);
+ }
+ await Task.WhenAll(tasks.AsSpan(0, handlers.Length));
+ }
+ finally
+ {
+ pool.Return(tasks);
+ }
+ }
+}
diff --git a/Nitrox.Server.Subnautica/Models/Commands/Abstract/CallArgs.cs b/Nitrox.Server.Subnautica/Models/Commands/Abstract/CallArgs.cs
deleted file mode 100644
index 3a43eed7fd..0000000000
--- a/Nitrox.Server.Subnautica/Models/Commands/Abstract/CallArgs.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Nitrox.Model.DataStructures;
-
-namespace Nitrox.Server.Subnautica.Models.Commands.Abstract
-{
- public abstract partial class Command
- {
- public ref struct CallArgs
- {
- public Command Command { get; }
- public Optional Sender { get; }
- public Span Args { get; }
-
- public bool IsConsole => !Sender.HasValue;
- public string SenderName => Sender.HasValue ? Sender.Value.Name : "SERVER";
-
- public CallArgs(Command command, Optional sender, Span args)
- {
- Command = command;
- Sender = sender;
- Args = args;
- }
-
- public bool IsValid(int index)
- {
- return index < Args.Length && index >= 0 && Args.Length != 0;
- }
-
- public string GetTillEnd(int startIndex = 0)
- {
- // TODO: Proper argument capture/parse instead of this argument join hack
- if (Args.Length > 0)
- {
- return string.Join(" ", Args.Slice(startIndex).ToArray());
- }
-
- return string.Empty;
- }
-
- public string Get(int index)
- {
- return Get(index);
- }
-
- public T Get(int index)
- {
- IParameter