Core/Movement: Add time synchronisation (Needs tested)

Port From (https://github.com/TrinityCore/TrinityCore/commit/c19a4db1c12b8864d6c486ee8e2f0e058fb4155a)
This commit is contained in:
hondacrx
2021-12-02 22:34:04 -05:00
parent f5695f0cf2
commit a96fe1e47f
11 changed files with 586 additions and 75 deletions
@@ -0,0 +1,424 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Framework.Collections
{
/// <inheritdoc/>
/// <summary>
/// Circular buffer.
///
/// When writing to a full buffer:
/// PushBack -> removes this[0] / Front()
/// PushFront -> removes this[Size-1] / Back()
///
/// this implementation is inspired by
/// http://www.boost.org/doc/libs/1_53_0/libs/circular_buffer/doc/circular_buffer.html
/// because I liked their interface.
/// </summary>
public class CircularBuffer<T> : IEnumerable<T>
{
private readonly T[] _buffer;
/// <summary>
/// The _start. Index of the first element in buffer.
/// </summary>
private int _start;
/// <summary>
/// The _end. Index after the last element in the buffer.
/// </summary>
private int _end;
/// <summary>
/// The _size. Buffer size.
/// </summary>
private int _size;
/// <summary>
/// Initializes a new instance of the <see cref="CircularBuffer{T}"/> class.
///
/// </summary>
/// <param name='capacity'>
/// Buffer capacity. Must be positive.
/// </param>
public CircularBuffer(int capacity)
: this(capacity, new T[] { })
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CircularBuffer{T}"/> class.
///
/// </summary>
/// <param name='capacity'>
/// Buffer capacity. Must be positive.
/// </param>
/// <param name='items'>
/// Items to fill buffer with. Items length must be less than capacity.
/// Suggestion: use Skip(x).Take(y).ToArray() to build this argument from
/// any enumerable.
/// </param>
public CircularBuffer(int capacity, T[] items)
{
if (capacity < 1)
{
throw new ArgumentException(
"Circular buffer cannot have negative or zero capacity.", nameof(capacity));
}
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (items.Length > capacity)
{
throw new ArgumentException(
"Too many items to fit circular buffer", nameof(items));
}
_buffer = new T[capacity];
Array.Copy(items, _buffer, items.Length);
_size = items.Length;
_start = 0;
_end = _size == capacity ? 0 : _size;
}
/// <summary>
/// Maximum capacity of the buffer. Elements pushed into the buffer after
/// maximum capacity is reached (IsFull = true), will remove an element.
/// </summary>
public int Capacity { get { return _buffer.Length; } }
/// <summary>
/// Boolean indicating if Circular is at full capacity.
/// Adding more elements when the buffer is full will
/// cause elements to be removed from the other end
/// of the buffer.
/// </summary>
public bool IsFull
{
get
{
return Size == Capacity;
}
}
/// <summary>
/// True if has no elements.
/// </summary>
public bool IsEmpty
{
get
{
return Size == 0;
}
}
/// <summary>
/// Current buffer size (the number of elements that the buffer has).
/// </summary>
public int Size { get { return _size; } }
/// <summary>
/// Element at the front of the buffer - this[0].
/// </summary>
/// <returns>The value of the element of type T at the front of the buffer.</returns>
public T Front()
{
ThrowIfEmpty();
return _buffer[_start];
}
/// <summary>
/// Element at the back of the buffer - this[Size - 1].
/// </summary>
/// <returns>The value of the element of type T at the back of the buffer.</returns>
public T Back()
{
ThrowIfEmpty();
return _buffer[(_end != 0 ? _end : Capacity) - 1];
}
/// <summary>
/// Index access to elements in buffer.
/// Index does not loop around like when adding elements,
/// valid interval is [0;Size[
/// </summary>
/// <param name="index">Index of element to access.</param>
/// <exception cref="IndexOutOfRangeException">Thrown when index is outside of [; Size[ interval.</exception>
public T this[int index]
{
get
{
if (IsEmpty)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
}
if (index >= _size)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
}
int actualIndex = InternalIndex(index);
return _buffer[actualIndex];
}
set
{
if (IsEmpty)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
}
if (index >= _size)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
}
int actualIndex = InternalIndex(index);
_buffer[actualIndex] = value;
}
}
/// <summary>
/// Pushes a new element to the back of the buffer. Back()/this[Size-1]
/// will now return this element.
///
/// When the buffer is full, the element at Front()/this[0] will be
/// popped to allow for this new element to fit.
/// </summary>
/// <param name="item">Item to push to the back of the buffer</param>
public void PushBack(T item)
{
if (IsFull)
{
_buffer[_end] = item;
Increment(ref _end);
_start = _end;
}
else
{
_buffer[_end] = item;
Increment(ref _end);
++_size;
}
}
/// <summary>
/// Pushes a new element to the front of the buffer. Front()/this[0]
/// will now return this element.
///
/// When the buffer is full, the element at Back()/this[Size-1] will be
/// popped to allow for this new element to fit.
/// </summary>
/// <param name="item">Item to push to the front of the buffer</param>
public void PushFront(T item)
{
if (IsFull)
{
Decrement(ref _start);
_end = _start;
_buffer[_start] = item;
}
else
{
Decrement(ref _start);
_buffer[_start] = item;
++_size;
}
}
/// <summary>
/// Removes the element at the back of the buffer. Decreasing the
/// Buffer size by 1.
/// </summary>
public void PopBack()
{
ThrowIfEmpty("Cannot take elements from an empty buffer.");
Decrement(ref _end);
_buffer[_end] = default(T);
--_size;
}
/// <summary>
/// Removes the element at the front of the buffer. Decreasing the
/// Buffer size by 1.
/// </summary>
public void PopFront()
{
ThrowIfEmpty("Cannot take elements from an empty buffer.");
_buffer[_start] = default(T);
Increment(ref _start);
--_size;
}
/// <summary>
/// Copies the buffer contents to an array, according to the logical
/// contents of the buffer (i.e. independent of the internal
/// order/contents)
/// </summary>
/// <returns>A new array with a copy of the buffer contents.</returns>
public T[] ToArray()
{
T[] newArray = new T[Size];
int newArrayOffset = 0;
var segments = ToArraySegments();
foreach (ArraySegment<T> segment in segments)
{
Array.Copy(segment.Array, segment.Offset, newArray, newArrayOffset, segment.Count);
newArrayOffset += segment.Count;
}
return newArray;
}
/// <summary>
/// Get the contents of the buffer as 2 ArraySegments.
/// Respects the logical contents of the buffer, where
/// each segment and items in each segment are ordered
/// according to insertion.
///
/// Fast: does not copy the array elements.
/// Useful for methods like <c>Send(IList&lt;ArraySegment&lt;Byte&gt;&gt;)</c>.
///
/// <remarks>Segments may be empty.</remarks>
/// </summary>
/// <returns>An IList with 2 segments corresponding to the buffer content.</returns>
public IList<ArraySegment<T>> ToArraySegments()
{
return new[] { ArrayOne(), ArrayTwo() };
}
#region IEnumerable<T> implementation
/// <summary>
/// Returns an enumerator that iterates through this buffer.
/// </summary>
/// <returns>An enumerator that can be used to iterate this collection.</returns>
public IEnumerator<T> GetEnumerator()
{
var segments = ToArraySegments();
foreach (ArraySegment<T> segment in segments)
{
for (int i = 0; i < segment.Count; i++)
{
yield return segment.Array[segment.Offset + i];
}
}
}
#endregion
#region IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
{
if (IsEmpty)
{
throw new InvalidOperationException(message);
}
}
/// <summary>
/// Increments the provided index variable by one, wrapping
/// around if necessary.
/// </summary>
/// <param name="index"></param>
private void Increment(ref int index)
{
if (++index == Capacity)
{
index = 0;
}
}
/// <summary>
/// Decrements the provided index variable by one, wrapping
/// around if necessary.
/// </summary>
/// <param name="index"></param>
private void Decrement(ref int index)
{
if (index == 0)
{
index = Capacity;
}
index--;
}
/// <summary>
/// Converts the index in the argument to an index in <code>_buffer</code>
/// </summary>
/// <returns>
/// The transformed index.
/// </returns>
/// <param name='index'>
/// External index.
/// </param>
private int InternalIndex(int index)
{
return _start + (index < (Capacity - _start) ? index : index - Capacity);
}
// doing ArrayOne and ArrayTwo methods returning ArraySegment<T> as seen here:
// http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1957cccdcb0c4ef7d80a34a990065818d
// http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1f5081a54afbc2dfc1a7fb20329df7d5b
// should help a lot with the code.
#region Array items easy access.
// The array is composed by at most two non-contiguous segments,
// the next two methods allow easy access to those.
private ArraySegment<T> ArrayOne()
{
if (IsEmpty)
{
return new ArraySegment<T>(new T[0]);
}
else if (_start < _end)
{
return new ArraySegment<T>(_buffer, _start, _end - _start);
}
else
{
return new ArraySegment<T>(_buffer, _start, _buffer.Length - _start);
}
}
private ArraySegment<T> ArrayTwo()
{
if (IsEmpty)
{
return new ArraySegment<T>(new T[0]);
}
else if (_start < _end)
{
return new ArraySegment<T>(_buffer, _end, 0);
}
else
{
return new ArraySegment<T>(_buffer, 0, _end);
}
}
#endregion
}
}
+17
View File
@@ -18,6 +18,7 @@
using Framework.Constants;
using Framework.GameMath;
using System;
using System.Collections.Generic;
using System.Numerics;
public static class MathFunctions
@@ -269,6 +270,22 @@ public static class MathFunctions
return (ushort)((byte)l | (ushort)h << 8);
}
public static double Variance(this IEnumerable<uint> source)
{
int n = 0;
double mean = 0;
double M2 = 0;
foreach (var x in source)
{
n = n + 1;
double delta = x - mean;
mean = mean + delta / n;
M2 += delta * (x - mean);
}
return M2 / (n - 1);
}
//3d math
public static Box toWorldSpace(Matrix4x4 rotation, Vector3 translation, Box box)
{
+6
View File
@@ -82,6 +82,12 @@ public static class Time
return newMSTime - oldMSTime;
}
public static uint GetMSTimeDiff(uint oldMSTime, DateTime newTime)
{
uint newMSTime = (uint)(newTime - ApplicationStartTime).TotalMilliseconds;
return GetMSTimeDiff(oldMSTime, newMSTime);
}
public static uint GetMSTimeDiffToNow(uint oldMSTime)
{
var newMSTime = GetMSTime();
@@ -212,10 +212,6 @@ namespace Game.Entities
// only changed for direct client control (possess, vehicle etc.), not stuff you control using pet commands
public Unit m_unitMovedByMe;
Team m_team;
public Stack<uint> m_timeSyncQueue = new();
uint m_timeSyncTimer;
public uint m_timeSyncClient;
public uint m_timeSyncServer;
ReputationMgr reputationMgr;
QuestObjectiveCriteriaManager m_questObjectiveCriteriaMgr;
public AtLoginFlags atLoginFlags;
+2 -34
View File
@@ -74,8 +74,6 @@ namespace Game.Entities
m_logintime = GameTime.GetGameTime();
m_Last_tick = m_logintime;
m_timeSyncServer = GameTime.GetGameTimeMS();
m_dungeonDifficulty = Difficulty.Normal;
m_raidDifficulty = Difficulty.NormalRaid;
m_legacyRaidDifficulty = Difficulty.Raid10N;
@@ -533,13 +531,6 @@ namespace Game.Entities
else
m_zoneUpdateTimer -= diff;
}
if (m_timeSyncTimer > 0 && !IsBeingTeleportedFar())
{
if (diff >= m_timeSyncTimer)
SendTimeSync();
else
m_timeSyncTimer -= diff;
}
if (IsAlive())
{
@@ -848,29 +839,6 @@ namespace Game.Entities
Session.SendPacket(data);
}
//Time
void ResetTimeSync()
{
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = GameTime.GetGameTimeMS();
}
void SendTimeSync()
{
m_timeSyncQueue.Push(m_movementCounter++);
TimeSyncRequest packet = new();
packet.SequenceIndex = m_timeSyncQueue.Last();
SendPacket(packet);
// Schedule next sync in 10 sec
m_timeSyncTimer = 10000;
m_timeSyncServer = GameTime.GetGameTimeMS();
if (m_timeSyncQueue.Count > 3)
Log.outError(LogFilter.Network, "Not received CMSG_TIME_SYNC_RESP for over 30 seconds from player {0} ({1}), possible cheater", GetGUID().ToString(), GetName());
}
public DeclinedName GetDeclinedNames() { return _declinedname; }
public void CreateGarrison(uint garrSiteId)
@@ -5460,10 +5428,10 @@ namespace Game.Entities
if (!m_teleport_options.HasAnyFlag(TeleportToOptions.Seamless))
{
m_movementCounter = 0;
ResetTimeSync();
GetSession().ResetTimeSync();
}
SendTimeSync();
GetSession().SendTimeSync();
GetSocial().SendSocialList(this, SocialFlag.All);
+76 -12
View File
@@ -26,6 +26,7 @@ using Game.Networking;
using Game.Networking.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Game
{
@@ -140,14 +141,8 @@ namespace Game
if (opcode == ClientOpcodes.MoveFallLand || opcode == ClientOpcodes.MoveStartSwim || opcode == ClientOpcodes.MoveSetFly)
mover.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LandingOrFlight); // Parachutes
uint mstime = GameTime.GetGameTimeMS();
if (m_clientTimeDelay == 0)
m_clientTimeDelay = mstime - movementInfo.Time;
movementInfo.Time = movementInfo.Time + m_clientTimeDelay;
movementInfo.Guid = mover.GetGUID();
movementInfo.Time = AdjustClientMovementTime(movementInfo.Time);
mover.m_movementInfo = movementInfo;
// Some vehicles allow the passenger to turn by himself
@@ -205,7 +200,7 @@ namespace Game
plrMover.RemovePlayerFlag(PlayerFlags.IsOutOfBounds);
if (opcode == ClientOpcodes.MoveJump)
{
{
plrMover.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2.Jump); // Mind Control
Unit.ProcSkillsAndAuras(plrMover, null, ProcFlags.Jump, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null);
}
@@ -347,7 +342,7 @@ namespace Game
// resurrect character at enter into instance where his corpse exist after add to map
if (mapEntry.IsDungeon() && !player.IsAlive())
{
{
if (player.GetCorpseLocation().GetMapId() == mapEntry.Id)
{
player.ResurrectPlayer(0.5f, false);
@@ -619,7 +614,7 @@ namespace Game
return;
}
moveApplyMovementForceAck.Ack.Status.Time += m_clientTimeDelay;
moveApplyMovementForceAck.Ack.Status.Time = AdjustClientMovementTime(moveApplyMovementForceAck.Ack.Status.Time);
MoveUpdateApplyMovementForce updateApplyMovementForce = new();
updateApplyMovementForce.Status = moveApplyMovementForceAck.Ack.Status;
@@ -641,7 +636,7 @@ namespace Game
return;
}
moveRemoveMovementForceAck.Ack.Status.Time += m_clientTimeDelay;
moveRemoveMovementForceAck.Ack.Status.Time = AdjustClientMovementTime(moveRemoveMovementForceAck.Ack.Status.Time);
MoveUpdateRemoveMovementForce updateRemoveMovementForce = new();
updateRemoveMovementForce.Status = moveRemoveMovementForceAck.Ack.Status;
@@ -683,7 +678,7 @@ namespace Game
}
}
setModMovementForceMagnitudeAck.Ack.Status.Time += m_clientTimeDelay;
setModMovementForceMagnitudeAck.Ack.Status.Time = AdjustClientMovementTime(setModMovementForceMagnitudeAck.Ack.Status.Time);
MoveUpdateSpeed updateModMovementForceMagnitude = new(ServerOpcodes.MoveUpdateModMovementForceMagnitude);
updateModMovementForceMagnitude.Status = setModMovementForceMagnitudeAck.Ack.Status;
@@ -759,5 +754,74 @@ namespace Game
if (GetPlayer().pvpInfo.IsHostile)
GetPlayer().CastSpell(GetPlayer(), 2479, true);
}
[WorldPacketHandler(ClientOpcodes.TimeSyncResponse, Processing = PacketProcessing.ThreadSafe)]
void HandleTimeSyncResponse(TimeSyncResponse timeSyncResponse)
{
if (!_pendingTimeSyncRequests.ContainsKey(timeSyncResponse.SequenceIndex))
return;
uint serverTimeAtSent = _pendingTimeSyncRequests.LookupByKey(timeSyncResponse.SequenceIndex);
_pendingTimeSyncRequests.Remove(timeSyncResponse.SequenceIndex);
// 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;
/*
clockDelta = serverTime - clientTime
where
serverTime: time that was displayed on the clock of the SERVER at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
clientTime: time that was displayed on the clock of the CLIENT at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
Once clockDelta has been computed, we can compute the time of an event on server clock when we know the time of that same event on the client clock,
using the following relation:
serverTime = clockDelta + clientTime
*/
long clockDelta = (long)(serverTimeAtSent + lagDelay) - (long)timeSyncResponse.ClientTime;
_timeSyncClockDeltaQueue.PushFront(Tuple.Create(clockDelta, roundTripDuration));
ComputeNewClockDelta();
}
void ComputeNewClockDelta()
{
// implementation of the technique described here: https://web.archive.org/web/20180430214420/http://www.mine-control.com/zack/timesync/timesync.html
// to reduce the skew induced by dropped TCP packets that get resent.
//accumulator_set < uint32, features < tag::mean, tag::median, tag::variance(lazy) > > latencyAccumulator;
List<uint> latencyList = new();
foreach (var pair in _timeSyncClockDeltaQueue)
latencyList.Add(pair.Item2);
uint latencyMedian = (uint)Math.Round(latencyList.Average(p => p));//median(latencyAccumulator));
uint latencyStandardDeviation = (uint)Math.Round(Math.Sqrt(latencyList.Variance()));//variance(latencyAccumulator)));
//accumulator_set<long, features<tag::mean>> clockDeltasAfterFiltering;
List<long> clockDeltasAfterFiltering = new();
uint sampleSizeAfterFiltering = 0;
foreach (var pair in _timeSyncClockDeltaQueue)
{
if (pair.Item2 < latencyStandardDeviation + latencyMedian)
{
clockDeltasAfterFiltering.Add(pair.Item1);
sampleSizeAfterFiltering++;
}
}
if (sampleSizeAfterFiltering != 0)
{
long meanClockDelta = (long)(Math.Round(clockDeltasAfterFiltering.Average()));
if (Math.Abs(meanClockDelta - _timeSyncClockDelta) > 25)
_timeSyncClockDelta = meanClockDelta;
}
else if (_timeSyncClockDelta == 0)
{
var back = _timeSyncClockDeltaQueue.Back();
_timeSyncClockDelta = back.Item1;
}
}
}
}
-19
View File
@@ -31,24 +31,5 @@ namespace Game
response.Time = GameTime.GetGameTime();
SendPacket(response);
}
[WorldPacketHandler(ClientOpcodes.TimeSyncResponse, Processing = PacketProcessing.Inplace)]
void HandleTimeSyncResponse(TimeSyncResponse packet)
{
Log.outDebug(LogFilter.Network, "CMSG_TIME_SYNC_RESP");
if (packet.SequenceIndex != _player.m_timeSyncQueue.FirstOrDefault())
Log.outError(LogFilter.Network, "Wrong time sync counter from player {0} (cheater?)", _player.GetName());
Log.outDebug(LogFilter.Network, "Time sync received: counter {0}, client ticks {1}, time since last sync {2}", packet.SequenceIndex, packet.ClientTime, packet.ClientTime - _player.m_timeSyncClient);
uint ourTicks = packet.ClientTime + (GameTime.GetGameTimeMS() - _player.m_timeSyncServer);
// diff should be small
Log.outDebug(LogFilter.Network, "Our ticks: {0}, diff {1}, latency {2}", ourTicks, ourTicks - packet.ClientTime, GetLatency());
_player.m_timeSyncClient = packet.ClientTime;
_player.m_timeSyncQueue.Pop();
}
}
}
+4
View File
@@ -220,7 +220,11 @@ namespace Game.Networking
public uint GetOpcode() { return opcode; }
public DateTime GetReceivedTime() { return m_receivedTime; }
public void SetReceiveTime(DateTime receivedTime) { m_receivedTime = receivedTime; }
uint opcode;
DateTime m_receivedTime; // only set for a specific set of opcodes, for performance reasons.
}
public class PacketHeader
@@ -275,6 +275,8 @@ namespace Game.Networking.Packets
ClientTime = _worldPacket.ReadUInt32();
}
public DateTime GetReceivedTime() { return _worldPacket.GetReceivedTime(); }
public uint ClientTime; // Client ticks in ms
public uint SequenceIndex; // Same index as in request
}
+3 -3
View File
@@ -340,6 +340,9 @@ namespace Game.Networking
break;
}
if (opcode == ClientOpcodes.TimeSyncResponse)
packet.SetReceiveTime(DateTime.Now);
// Our Idle timer will reset on any non PING opcodes on login screen, allowing us to catch people idling.
_worldSession.ResetTimeOutTime(false);
@@ -812,10 +815,7 @@ namespace Game.Networking
lock (_worldSessionLock)
{
if (_worldSession != null)
{
_worldSession.SetLatency(ping.Latency);
_worldSession.ResetClientTimeDelay();
}
else
{
Log.outError(LogFilter.Network, "WorldSocket:HandlePing: peer sent CMSG_PING, but is not authenticated or got recently kicked, address = {0}", GetRemoteIpAddress());
+52 -3
View File
@@ -15,8 +15,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Collections;
using Framework.Constants;
using Framework.Database;
using Framework.Realm;
using Game.Accounts;
using Game.BattleGrounds;
using Game.BattlePets;
@@ -32,7 +34,6 @@ using System.IO;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Framework.Realm;
namespace Game
{
@@ -331,6 +332,18 @@ namespace Game
if (m_Socket[(int)ConnectionType.Realm] != null && m_Socket[(int)ConnectionType.Realm].IsOpen() && _warden != null)
_warden.Update();
if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
{
// Send time sync packet every 10s.
if (_timeSyncTimer > 0)
{
if (diff >= _timeSyncTimer)
SendTimeSync();
else
_timeSyncTimer -= diff;
}
}
ProcessQueryCallbacks();
if (updater.ProcessUnsafe())
@@ -828,12 +841,42 @@ namespace Game
}
}
public void ResetTimeSync()
{
_timeSyncNextCounter = 0;
_pendingTimeSyncRequests.Clear();
}
public void SendTimeSync()
{
TimeSyncRequest timeSyncRequest = new();
timeSyncRequest.SequenceIndex = _timeSyncNextCounter;
SendPacket(timeSyncRequest);
_pendingTimeSyncRequests[_timeSyncNextCounter] = Time.GetMSTime();
// Schedule next sync in 10 sec (except for the 2 first packets, which are spaced by only 5s)
_timeSyncTimer = _timeSyncNextCounter == 0 ? 5000 : 10000u;
_timeSyncNextCounter++;
}
uint AdjustClientMovementTime(uint time)
{
long movementTime = (long)time + _timeSyncClockDelta;
if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF)
{
Log.outWarn(LogFilter.Misc, "The computed movement time using clockDelta is erronous. Using fallback instead");
return GameTime.GetGameTimeMS();
}
else
return (uint)movementTime;
}
public Locale GetSessionDbcLocale() { return m_sessionDbcLocale; }
public Locale GetSessionDbLocaleIndex() { return m_sessionDbLocaleIndex; }
public uint GetLatency() { return m_latency; }
public void SetLatency(uint latency) { m_latency = latency; }
public void ResetClientTimeDelay() { m_clientTimeDelay = 0; }
public void ResetTimeOutTime(bool onlyActive)
{
if (GetPlayer())
@@ -893,7 +936,6 @@ namespace Game
Locale m_sessionDbcLocale;
Locale m_sessionDbLocaleIndex;
uint m_latency;
uint m_clientTimeDelay;
AccountData[] _accountData = new AccountData[(int)AccountDataTypes.Max];
uint[] tutorials = new uint[SharedConst.MaxAccountTutorialValues];
TutorialsFlag tutorialsChanged;
@@ -916,6 +958,13 @@ namespace Game
ObjectGuid m_currentBankerGUID;
CircularBuffer<Tuple<long, uint>> _timeSyncClockDeltaQueue = new(6); // first member: clockDelta. Second member: latency of the packet exchange that was used to compute that clockDelta.
long _timeSyncClockDelta;
Dictionary<uint, uint> _pendingTimeSyncRequests = new(); // key: counter. value: server time when packet with that counter was sent.
uint _timeSyncNextCounter;
uint _timeSyncTimer;
CollectionMgr _collectionMgr;
ConnectToKey _instanceConnectKey;