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
@@ -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;