From 9340c89bb130d854bf4d9cb71606d5b4113ce0e4 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Sun, 10 Aug 2025 17:05:31 -0400 Subject: [PATCH] Core/Players: Improve setting ActivePlayerData::TransportServerTime by including TIME_SYNC and CMSG_QUEUED_MESSAGES_END values in its calculation Port From (https://github.com/TrinityCore/TrinityCore/commit/821ecf8fa3b6b922ba178cde7593ef5d84ef734b) --- Source/Game/Entities/Player/Player.cs | 3 -- Source/Game/Handlers/CharacterHandler.cs | 9 ++++ Source/Game/Handlers/MovementHandler.cs | 50 ++++++++++++------- Source/Game/Maps/Map.cs | 3 ++ Source/Game/Networking/Packet.cs | 5 ++ .../Packets/AuthenticationPackets.cs | 12 +++++ Source/Game/Server/WorldSession.cs | 12 ++++- 7 files changed, 72 insertions(+), 22 deletions(-) diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 32445c70a..ba4e134ac 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -2195,9 +2195,6 @@ namespace Game.Entities } SendPacket(transferPending); - - RemovePlayerLocalFlag(PlayerLocalFlags.OverrideTransportServerTime); - SetTransportServerTime(0); } // remove from old map now diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index 6fb98319a..9383414d1 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -783,6 +783,9 @@ namespace Game SendPacket(new ResumeComms(ConnectionType.Instance)); + // client will respond to SMSG_RESUME_COMMS with CMSG_QUEUED_MESSAGES_END + RegisterTimeSync(SPECIAL_RESUME_COMMS_TIME_SYNC_COUNTER); + AddQueryHolderCallback(DB.Characters.DelayQueryHolder(holder)).AfterComplete(holder => HandlePlayerLogin((LoginQueryHolder)holder)); } @@ -799,6 +802,12 @@ namespace Game return; } + if (!_timeSyncClockDeltaQueue.IsEmpty) + { + pCurrChar.SetPlayerLocalFlag(PlayerLocalFlags.OverrideTransportServerTime); + pCurrChar.SetTransportServerTime((int)_timeSyncClockDelta); + } + pCurrChar.SetVirtualPlayerRealm(Global.WorldMgr.GetVirtualRealmAddress()); SendAccountDataTimes(ObjectGuid.Empty, AccountDataTypes.GlobalCacheMask); diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index 8e627f462..5620e9eb8 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -624,7 +624,7 @@ namespace Game } [WorldPacketHandler(ClientOpcodes.MoveSetAdvFlyingBankingRateAck)] - [WorldPacketHandler(ClientOpcodes.MoveSetAdvFlyingPitchingRateDownAck)] + [WorldPacketHandler(ClientOpcodes.MoveSetAdvFlyingPitchingRateDownAck)] [WorldPacketHandler(ClientOpcodes.MoveSetAdvFlyingPitchingRateUpAck)] [WorldPacketHandler(ClientOpcodes.MoveSetAdvFlyingTurnVelocityThresholdAck)] void HandleSetAdvFlyingSpeedRangeAck(MovementSpeedRangeAck speedRangeAck) @@ -854,21 +854,20 @@ namespace Game GetPlayer().CastSpell(GetPlayer(), 2479, true); } - [WorldPacketHandler(ClientOpcodes.TimeSyncResponse, Processing = PacketProcessing.ThreadSafe)] - void HandleTimeSyncResponse(TimeSyncResponse timeSyncResponse) + void HandleTimeSync(uint counter, long clientTime, DateTime responseReceiveTime) { - if (!_pendingTimeSyncRequests.ContainsKey(timeSyncResponse.SequenceIndex)) + var serverTimeAtSent = _pendingTimeSyncRequests.LookupByKey(counter); + if (serverTimeAtSent == 0) return; - uint serverTimeAtSent = _pendingTimeSyncRequests.LookupByKey(timeSyncResponse.SequenceIndex); - _pendingTimeSyncRequests.Remove(timeSyncResponse.SequenceIndex); + _pendingTimeSyncRequests.Remove(counter); // time it took for the request to travel to the client, for the client to process it and reply and for response to travel back to the server. // we are going to make 2 assumptions: // 1) we assume that the request processing time equals 0. // 2) we assume that the packet took as much time to travel from server to client than it took to travel from client to server. - uint roundTripDuration = Time.GetMSTimeDiff(serverTimeAtSent, timeSyncResponse.GetReceivedTime()); - uint lagDelay = roundTripDuration / 2; + uint roundTripDuration = Time.GetMSTimeDiff((uint)serverTimeAtSent, responseReceiveTime); + long lagDelay = roundTripDuration / 2; /* clockDelta = serverTime - clientTime @@ -880,11 +879,31 @@ namespace Game using the following relation: serverTime = clockDelta + clientTime */ - long clockDelta = (long)(serverTimeAtSent + lagDelay) - (long)timeSyncResponse.ClientTime; + long clockDelta = serverTimeAtSent + lagDelay - clientTime; _timeSyncClockDeltaQueue.PushFront(Tuple.Create(clockDelta, roundTripDuration)); ComputeNewClockDelta(); } + [WorldPacketHandler(ClientOpcodes.TimeSyncResponse, Processing = PacketProcessing.ThreadSafe)] + void HandleTimeSyncResponse(TimeSyncResponse timeSyncResponse) + { + HandleTimeSync(timeSyncResponse.SequenceIndex, timeSyncResponse.ClientTime, timeSyncResponse.GetReceivedTime()); + } + + [WorldPacketHandler(ClientOpcodes.QueuedMessagesEnd, Status = SessionStatus.Authed, Processing = PacketProcessing.Inplace)] + void HandleQueuedMessagesEnd(QueuedMessagesEnd queuedMessagesEnd) + { + HandleTimeSync(SPECIAL_RESUME_COMMS_TIME_SYNC_COUNTER, queuedMessagesEnd.Timestamp, queuedMessagesEnd.GetWorldPacket().GetReceivedTime()); + } + + [WorldPacketHandler(ClientOpcodes.MoveInitActiveMoverComplete, Processing = PacketProcessing.ThreadSafe)] + void HandleMoveInitActiveMoverComplete(MoveInitActiveMoverComplete moveInitActiveMoverComplete) + { + HandleTimeSync(SPECIAL_INIT_ACTIVE_MOVER_TIME_SYNC_COUNTER, moveInitActiveMoverComplete.Ticks, moveInitActiveMoverComplete.GetWorldPacket().GetReceivedTime()); + + _player.UpdateObjectVisibility(false); + } + void ComputeNewClockDelta() { // implementation of the technique described here: https://web.archive.org/web/20180430214420/http://www.mine-control.com/zack/timesync/timesync.html @@ -921,15 +940,12 @@ namespace Game var back = _timeSyncClockDeltaQueue.Back(); _timeSyncClockDelta = back.Item1; } - } - [WorldPacketHandler(ClientOpcodes.MoveInitActiveMoverComplete, Processing = PacketProcessing.ThreadSafe)] - void HandleMoveInitActiveMoverComplete(MoveInitActiveMoverComplete moveInitActiveMoverComplete) - { - _player.SetPlayerLocalFlag(PlayerLocalFlags.OverrideTransportServerTime); - _player.SetTransportServerTime((int)(GameTime.GetGameTimeMS() - moveInitActiveMoverComplete.Ticks)); - - _player.UpdateObjectVisibility(false); + if (_player != null) + { + _player.SetPlayerLocalFlag(PlayerLocalFlags.OverrideTransportServerTime); + _player.SetTransportServerTime((int)_timeSyncClockDelta); + } } } } diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index cb67c1e03..3ad846b58 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -1767,6 +1767,9 @@ namespace Game.Maps UpdateObject packet; data.BuildPacket(out packet); player.SendPacket(packet); + + // client will respond to SMSG_UPDATE_OBJECT that contains ThisIsYou = true with CMSG_MOVE_INIT_ACTIVE_MOVER_COMPLETE + player.GetSession().RegisterTimeSync(WorldSession.SPECIAL_INIT_ACTIVE_MOVER_TIME_SYNC_COUNTER); } void SendInitTransports(Player player) diff --git a/Source/Game/Networking/Packet.cs b/Source/Game/Networking/Packet.cs index 40d01dc85..184b5bc3c 100644 --- a/Source/Game/Networking/Packet.cs +++ b/Source/Game/Networking/Packet.cs @@ -29,6 +29,11 @@ namespace Game.Networking Log.outDebug(LogFilter.Network, "Received ClientOpcode: {0} From: {1}", GetOpcode(), session != null ? session.GetPlayerInfo() : "Unknown IP"); } + public WorldPacket GetWorldPacket() + { + return _worldPacket; + } + protected WorldPacket _worldPacket; } diff --git a/Source/Game/Networking/Packets/AuthenticationPackets.cs b/Source/Game/Networking/Packets/AuthenticationPackets.cs index c41801ff2..a8487fe12 100644 --- a/Source/Game/Networking/Packets/AuthenticationPackets.cs +++ b/Source/Game/Networking/Packets/AuthenticationPackets.cs @@ -411,6 +411,18 @@ namespace Game.Networking.Packets } } + class QueuedMessagesEnd : ClientPacket + { + public uint Timestamp; + + public QueuedMessagesEnd(WorldPacket packet) : base(packet) { } + + public override void Read() + { + Timestamp = _worldPacket.ReadUInt32(); + } + } + //Structs public struct AuthWaitInfo { diff --git a/Source/Game/Server/WorldSession.cs b/Source/Game/Server/WorldSession.cs index ebcff6430..cff2668d6 100644 --- a/Source/Game/Server/WorldSession.cs +++ b/Source/Game/Server/WorldSession.cs @@ -25,6 +25,9 @@ namespace Game { public partial class WorldSession : IDisposable { + public static uint SPECIAL_INIT_ACTIVE_MOVER_TIME_SYNC_COUNTER = 0xFFFFFFFF; + public static uint SPECIAL_RESUME_COMMS_TIME_SYNC_COUNTER = 0xFFFFFFFE; + public WorldSession(uint id, string name, uint battlenetAccountId, WorldSocket sock, AccountTypes sec, Expansion expansion, long mute_time, string os, TimeSpan timezoneOffset, uint build, Framework.ClientBuild.ClientBuildVariantId clientBuildVariant, Locale locale, uint recruiter, bool isARecruiter) { m_muteTime = mute_time; @@ -888,13 +891,18 @@ namespace Game timeSyncRequest.SequenceIndex = _timeSyncNextCounter; SendPacket(timeSyncRequest); - _pendingTimeSyncRequests[_timeSyncNextCounter] = Time.GetMSTime(); + RegisterTimeSync(_timeSyncNextCounter); // Schedule next sync in 10 sec (except for the 2 first packets, which are spaced by only 5s) _timeSyncTimer = _timeSyncNextCounter == 0 ? 5000 : 10000u; _timeSyncNextCounter++; } + public void RegisterTimeSync(uint counter) + { + _pendingTimeSyncRequests[counter] = Time.GetMSTime(); + } + uint AdjustClientMovementTime(uint time) { long movementTime = (long)time + _timeSyncClockDelta; @@ -998,7 +1006,7 @@ namespace Game CircularBuffer> _timeSyncClockDeltaQueue = new(6); // first member: clockDelta. Second member: latency of the packet exchange that was used to compute that clockDelta. long _timeSyncClockDelta; - Dictionary _pendingTimeSyncRequests = new(); // key: counter. value: server time when packet with that counter was sent. + Dictionary _pendingTimeSyncRequests = new(); // key: counter. value: server time when packet with that counter was sent. uint _timeSyncNextCounter; uint _timeSyncTimer;