From f15d0d6c00520e852ce0b646ee8205d00436424e Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 27 Dec 2023 11:03:46 +0100 Subject: [PATCH] Fix time incrementing on client-side while it's paused on server-side (for first player joining during queue) --- .../StoryGoalInitialSyncProcessor.cs | 2 +- NitroxClient/GameLogic/TimeManager.cs | 42 +++++++++++++++++-- .../DataStructures/GameLogic/TimeData.cs | 4 +- NitroxModel/Packets/InitialPlayerSync.cs | 5 +++ NitroxModel/Packets/TimeChange.cs | 7 +++- .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 3 +- ...layerJoiningMultiplayerSessionProcessor.cs | 2 + NitroxServer/GameLogic/StoryManager.cs | 2 +- NitroxServer/GameLogic/TimeKeeper.cs | 2 +- 9 files changed, 57 insertions(+), 12 deletions(-) diff --git a/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs index 3e2e349193..a1c6d71585 100644 --- a/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs @@ -163,7 +163,7 @@ private static IEnumerator RefreshStoryWithLatestData() private void SetTimeData(InitialPlayerSync packet) { timeManager.ProcessUpdate(packet.TimeData.TimePacket); - timeManager.InitRealTimeElapsed(packet.TimeData.RealTimeElapsed, packet.TimeData.TimePacket.UpdateTime); + timeManager.InitRealTimeElapsed(packet.TimeData.TimePacket.RealTimeElapsed, packet.TimeData.TimePacket.UpdateTime, packet.IsFirstPlayer); timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime; } } diff --git a/NitroxClient/GameLogic/TimeManager.cs b/NitroxClient/GameLogic/TimeManager.cs index f080b095d5..1ca0eb334e 100644 --- a/NitroxClient/GameLogic/TimeManager.cs +++ b/NitroxClient/GameLogic/TimeManager.cs @@ -1,4 +1,5 @@ using System; +using NitroxClient.MonoBehaviours; using NitroxModel.Packets; using UnityEngine; @@ -6,6 +7,12 @@ namespace NitroxClient.GameLogic; public class TimeManager { + /// + /// When first player connects to the server, time will resume when time will be resumed on server-side. + /// According to this, we need to freeze time on first player connecting before it has fully loaded. + /// + private bool freezeTime = true; + /// /// Latest moment at which we updated the time /// @@ -26,16 +33,31 @@ public class TimeManager public float AuroraRealExplosionTime { get; set; } + private const double DEFAULT_REAL_TIME = 0; + /// /// Calculates the exact real time elapsed from an offset () and the delta time between /// and the offset's exact (). /// public double RealTimeElapsed { - get => (DateTimeOffset.UtcNow - realTimeElapsedRegistrationTime).TotalMilliseconds * 0.001 + realTimeElapsed; + get + { + // Unitialized state + if (realTimeElapsedRegistrationTime == default) + { + return DEFAULT_REAL_TIME; + } + if (freezeTime) + { + return realTimeElapsed; + } + + return (DateTimeOffset.UtcNow - realTimeElapsedRegistrationTime).TotalMilliseconds * 0.001 + realTimeElapsed; + } } - private const double DEFAULT_TIME = 480; + private const double DEFAULT_SUBNAUTICA_TIME = 480; /// /// Calculates the current exact time from an offset () and the delta time between @@ -52,7 +74,11 @@ public double CurrentTime // Unitialized state if (latestRegisteredTime == 0) { - return DEFAULT_TIME; + return DEFAULT_SUBNAUTICA_TIME; + } + if (freezeTime) + { + return latestRegisteredTime; } return (DateTimeOffset.UtcNow - latestRegistrationTime).TotalMilliseconds * 0.001 + latestRegisteredTime; } @@ -76,6 +102,13 @@ public double CurrentTime public void ProcessUpdate(TimeChange packet) { + if (freezeTime && Multiplayer.Main && Multiplayer.Main.InitialSyncCompleted) + { + freezeTime = false; + } + realTimeElapsedRegistrationTime = DateTimeOffset.FromUnixTimeMilliseconds(packet.UpdateTime); + realTimeElapsed = packet.RealTimeElapsed; + latestRegistrationTime = DateTimeOffset.FromUnixTimeMilliseconds(packet.UpdateTime); latestRegisteredTime = packet.CurrentTime; @@ -97,9 +130,10 @@ public double CalculateCurrentTime() return currentTime; } - public void InitRealTimeElapsed(double realTimeElapsed, long registrationTime) + public void InitRealTimeElapsed(double realTimeElapsed, long registrationTime, bool isFirstPlayer) { this.realTimeElapsed = realTimeElapsed; realTimeElapsedRegistrationTime = DateTimeOffset.FromUnixTimeMilliseconds(registrationTime); + freezeTime = isFirstPlayer; } } diff --git a/NitroxModel/DataStructures/GameLogic/TimeData.cs b/NitroxModel/DataStructures/GameLogic/TimeData.cs index 85fe5ab454..824c118843 100644 --- a/NitroxModel/DataStructures/GameLogic/TimeData.cs +++ b/NitroxModel/DataStructures/GameLogic/TimeData.cs @@ -8,12 +8,10 @@ public class TimeData { public TimeChange TimePacket; public AuroraEventData AuroraEventData; - public double RealTimeElapsed; - public TimeData(TimeChange timePacket, AuroraEventData auroraEventData, double realTimeElapsed) + public TimeData(TimeChange timePacket, AuroraEventData auroraEventData) { TimePacket = timePacket; AuroraEventData = auroraEventData; - RealTimeElapsed = realTimeElapsed; } } diff --git a/NitroxModel/Packets/InitialPlayerSync.cs b/NitroxModel/Packets/InitialPlayerSync.cs index 6a7d6fc739..030b6cc4f9 100644 --- a/NitroxModel/Packets/InitialPlayerSync.cs +++ b/NitroxModel/Packets/InitialPlayerSync.cs @@ -32,6 +32,7 @@ public class InitialPlayerSync : Packet public Perms Permissions { get; } public SubnauticaPlayerPreferences Preferences { get; } public TimeData TimeData { get; } + public bool IsFirstPlayer { get; } public Dictionary BuildOperationIds { get; } public InitialPlayerSync(NitroxId playerGameObjectId, @@ -53,6 +54,7 @@ public InitialPlayerSync(NitroxId playerGameObjectId, Perms perms, SubnauticaPlayerPreferences preferences, TimeData timeData, + bool isFirstPlayer, Dictionary buildOperationIds) { AssignedEscapePodId = assignedEscapePodId; @@ -74,6 +76,7 @@ public InitialPlayerSync(NitroxId playerGameObjectId, Permissions = perms; Preferences = preferences; TimeData = timeData; + IsFirstPlayer = isFirstPlayer; BuildOperationIds = buildOperationIds; } @@ -98,6 +101,7 @@ public InitialPlayerSync( Perms permissions, SubnauticaPlayerPreferences preferences, TimeData timeData, + bool isFirstPlayer, Dictionary buildOperationIds) { AssignedEscapePodId = assignedEscapePodId; @@ -119,6 +123,7 @@ public InitialPlayerSync( Permissions = permissions; Preferences = preferences; TimeData = timeData; + IsFirstPlayer = isFirstPlayer; BuildOperationIds = buildOperationIds; } } diff --git a/NitroxModel/Packets/TimeChange.cs b/NitroxModel/Packets/TimeChange.cs index b8ff7a1d8f..89904388f6 100644 --- a/NitroxModel/Packets/TimeChange.cs +++ b/NitroxModel/Packets/TimeChange.cs @@ -13,10 +13,15 @@ public class TimeChange : Packet /// Real time at which the CurrentTime was observed /// public long UpdateTime { get; } + /// + /// Real time elapsed in seconds + /// + public double RealTimeElapsed; - public TimeChange(double currentTime, long updateTime) + public TimeChange(double currentTime, long updateTime, double realTimeElapsed) { CurrentTime = currentTime; UpdateTime = updateTime; + RealTimeElapsed = realTimeElapsed; } } diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index ce27fa6a5f..33b51f6e15 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -12,11 +12,12 @@ public sealed partial class FPSCounter_UpdateDisplay_Patch : NitroxPatch, IDynam public static void Postfix(FPSCounter __instance) { - if (!Multiplayer.Main.InitialSyncCompleted) + if (!Multiplayer.Active) { return; } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); + __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); __instance.text.SetText(__instance.strBuffer); } } diff --git a/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs b/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs index 2257ea095e..c1f015c759 100644 --- a/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/PlayerJoiningMultiplayerSessionProcessor.cs @@ -70,6 +70,7 @@ public override void Process(PlayerJoiningMultiplayerSession packet, NitroxConne RespawnExistingEntity(player); } List globalRootEntities = world.WorldEntityManager.GetGlobalRootEntities(true); + bool isFirstPlayer = playerManager.GetConnectedPlayers().Count == 1; InitialPlayerSync initialPlayerSync = new(player.GameObjectId, wasBrandNewPlayer, @@ -90,6 +91,7 @@ public override void Process(PlayerJoiningMultiplayerSession packet, NitroxConne player.Permissions, new(new(player.PingInstancePreferences), player.PinnedRecipePreferences.ToList()), storyManager.GetTimeData(), + isFirstPlayer, BuildingManager.GetEntitiesOperations(globalRootEntities) ); diff --git a/NitroxServer/GameLogic/StoryManager.cs b/NitroxServer/GameLogic/StoryManager.cs index 3cb9beb9d6..6705341b4e 100644 --- a/NitroxServer/GameLogic/StoryManager.cs +++ b/NitroxServer/GameLogic/StoryManager.cs @@ -192,7 +192,7 @@ public AuroraEventData MakeAuroraData() public TimeData GetTimeData() { - return new(timeKeeper.MakeTimePacket(), MakeAuroraData(), timeKeeper.RealTimeElapsed); + return new(timeKeeper.MakeTimePacket(), MakeAuroraData()); } public enum TimeModification diff --git a/NitroxServer/GameLogic/TimeKeeper.cs b/NitroxServer/GameLogic/TimeKeeper.cs index 20593545d2..309b2ef3c3 100644 --- a/NitroxServer/GameLogic/TimeKeeper.cs +++ b/NitroxServer/GameLogic/TimeKeeper.cs @@ -142,7 +142,7 @@ public void ChangeTime(TimeModification type) public TimeChange MakeTimePacket() { - return new(ElapsedSeconds, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); + return new(ElapsedSeconds, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), RealTimeElapsed); } public delegate void TimeSkipped(double skipAmount);