Skip to content
5 changes: 5 additions & 0 deletions Client/mods/deathmatch/logic/CClientGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ class CClientGame
void EnablePacketRecorder(const char* szFilename);
void InitVoice(bool bEnabled, unsigned int uiServerSampleRate, unsigned char ucQuality, unsigned int uiBitrate);

void SetTimeOffsetFromServer(std::int64_t offset) noexcept { m_serverTimeOffset = offset; }
std::int64_t GetSyncedTime() const noexcept { return GetLocalTick() + m_serverTimeOffset; }

bool IsWindowFocused() const { return m_bFocused; }

// Accessors
Expand Down Expand Up @@ -930,6 +933,8 @@ class CClientGame
MultiCommandHandlerPolicy m_allowMultiCommandHandlers;

long long m_timeLastDiscordStateUpdate;

std::int64_t m_serverTimeOffset;
};

extern CClientGame* g_pClientGame;
56 changes: 35 additions & 21 deletions Client/mods/deathmatch/logic/CClientPed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2917,8 +2917,8 @@ void CClientPed::StreamedInPulse(bool bDoStandardPulses)
}

// Are we need to update anim speed & progress?
// We need to do it here because the anim starts on the next frame after calling RunNamedAnimation
if (m_pAnimationBlock && m_AnimationCache.progressWaitForStreamIn && IsAnimationInProgress())
// We need to do it here because the anim starts in the next frame after calling RunNamedAnimation
if (m_pAnimationBlock && m_AnimationCache.updateInNextFrame && IsAnimationInProgress())
UpdateAnimationProgressAndSpeed();

// Update our alpha
Expand Down Expand Up @@ -5784,7 +5784,7 @@ bool CClientPed::IsAnimationInProgress()
if (!m_pAnimationBlock)
return constAnim;

float elapsedTime = static_cast<float>(GetTimestamp() - m_AnimationCache.startTime) / 1000.0f;
float elapsedTime = static_cast<float>(g_pClientGame->GetSyncedTime() - m_AnimationCache.startTime) / 1000.0f;

auto animBlendHierarchy = g_pGame->GetAnimManager()->GetAnimation(m_AnimationCache.strName.c_str(), m_pAnimationBlock);
if (!animBlendHierarchy)
Expand Down Expand Up @@ -5871,6 +5871,9 @@ void CClientPed::RunNamedAnimation(std::unique_ptr<CAnimBlock>& pBlock, const ch
{
m_pAnimationBlock = g_pGame->GetAnimManager()->GetAnimBlock(pBlock->GetInterface());
}

m_AnimationCache = SAnimationCache{};

m_AnimationCache.strName = szAnimName;
m_AnimationCache.iTime = iTime;
m_AnimationCache.iBlend = iBlend;
Expand All @@ -5897,7 +5900,7 @@ void CClientPed::KillAnimation()
}
}
m_pAnimationBlock = NULL;
m_AnimationCache.strName = "";
m_AnimationCache = SAnimationCache{};
m_bRequestedAnimation = false;
SetNextAnimationNormal();
}
Expand All @@ -5916,46 +5919,57 @@ void CClientPed::RunAnimationFromCache()
if (!m_pAnimationBlock)
return;

// Copy our name incase it gets deleted
std::string animName = m_AnimationCache.strName;
// Copy our name & startTime incase it gets deleted
std::string animName = m_AnimationCache.strName;
std::int64_t startTime = m_AnimationCache.startTime;

// Run our animation
RunNamedAnimation(m_pAnimationBlock, animName.c_str(), m_AnimationCache.iTime, m_AnimationCache.iBlend, m_AnimationCache.bLoop,
m_AnimationCache.bUpdatePosition, m_AnimationCache.bInterruptible, m_AnimationCache.bFreezeLastFrame);

// Set anim progress & speed
m_AnimationCache.progressWaitForStreamIn = true;
// Restore our startTime
m_AnimationCache.startTime = startTime;

// Let's update animation progress & speed
m_AnimationCache.updateInNextFrame = true;
}

void CClientPed::UpdateAnimationProgressAndSpeed()
{
if (!m_AnimationCache.progressWaitForStreamIn)
return;

// Get current anim
auto animAssoc = g_pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(GetClump(), m_AnimationCache.strName.c_str());
if (!animAssoc)
return;

float animLength = animAssoc->GetLength();
// Animation progress is calculated based on the animation duration
// The cached value is only set when setPedAnimationProgress is called by the client, and is then reset to NaN
float progress = 0.0f;
float elapsedTime = static_cast<float>(GetTimestamp() - m_AnimationCache.startTime) / 1000.0f;
if (std::isnan(m_AnimationCache.progress))
{
float animLength = animAssoc->GetLength();
float elapsedTime = static_cast<float>(g_pClientGame->GetSyncedTime() - m_AnimationCache.startTime) / 1000.0f;

if (m_AnimationCache.bFreezeLastFrame) // time and loop is ignored if freezeLastFrame is true
progress = (elapsedTime / animLength) * m_AnimationCache.speed;
if (m_AnimationCache.bFreezeLastFrame) // time and loop is ignored if freezeLastFrame is true
progress = (elapsedTime / animLength) * m_AnimationCache.speed;
else
{
if (m_AnimationCache.bLoop)
progress = std::fmod(elapsedTime * m_AnimationCache.speed, animLength) / animLength;
else
// For non-looped animations, limit duration to animLength if time exceeds it
progress = (elapsedTime / (m_AnimationCache.iTime <= animLength ? m_AnimationCache.iTime : animLength)) * m_AnimationCache.speed;
}
}
else
{
if (m_AnimationCache.bLoop)
progress = std::fmod(elapsedTime * m_AnimationCache.speed, animLength) / animLength;
else
// For non-looped animations, limit duration to animLength if time exceeds it
progress = (elapsedTime / (m_AnimationCache.iTime <= animLength ? m_AnimationCache.iTime : animLength)) * m_AnimationCache.speed;
progress = m_AnimationCache.progress;
m_AnimationCache.progress = std::numeric_limits<float>::quiet_NaN();
}

animAssoc->SetCurrentProgress(std::clamp(progress, 0.0f, 1.0f));
animAssoc->SetCurrentSpeed(m_AnimationCache.speed);

m_AnimationCache.progressWaitForStreamIn = false;
m_AnimationCache.updateInNextFrame = false;
}

void CClientPed::PostWeaponFire()
Expand Down
8 changes: 4 additions & 4 deletions Client/mods/deathmatch/logic/CClientPed.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,17 @@ struct SReplacedAnimation

struct SAnimationCache
{
std::string strName;
std::string strName{};
int iTime{-1};
bool bLoop{false};
bool bUpdatePosition{false};
bool bInterruptible{false};
bool bFreezeLastFrame{true};
int iBlend{250};
float progress{0.0f};
float progress{std::numeric_limits<float>::quiet_NaN()};
float speed{1.0f};
bool progressWaitForStreamIn{false};
std::int64_t startTime{0};
bool updateInNextFrame{false};
};

class CClientObject;
Expand Down Expand Up @@ -724,7 +724,7 @@ class CClientPed : public CClientStreamElement, public CAntiCheatModule
bool m_bDoingGangDriveby;
std::unique_ptr<CAnimBlock> m_pAnimationBlock;
bool m_bRequestedAnimation;
SAnimationCache m_AnimationCache;
SAnimationCache m_AnimationCache{};
bool m_bHeadless;
bool m_bFrozen;
bool m_bFrozenWaitingForGroundToLoad;
Expand Down
35 changes: 17 additions & 18 deletions Client/mods/deathmatch/logic/CPacketHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ void CPacketHandler::Packet_ServerJoined(NetBitStreamInterface& bitStream)
// unsigned short (2) - HTTP Download URL Size
// unsigned char (X) - HTTP Download URL
// unsigned char (X) - Server name
// int64 (X) - Server time

// Make sure any existing messageboxes are hided
g_pCore->RemoveMessageBox();
Expand Down Expand Up @@ -486,6 +487,10 @@ void CPacketHandler::Packet_ServerJoined(NetBitStreamInterface& bitStream)
discord->SetPresenceDetails(serverName.c_str(), false);
}
}

std::int64_t serverLocalTick;
bitStream.Read(serverLocalTick);
g_pClientGame->SetTimeOffsetFromServer(serverLocalTick - GetLocalTick());
}

void CPacketHandler::Packet_ServerDisconnected(NetBitStreamInterface& bitStream)
Expand Down Expand Up @@ -1014,11 +1019,11 @@ void CPacketHandler::Packet_PlayerList(NetBitStreamInterface& bitStream)
// Animation
if (bitStream.ReadBit())
{
std::string blockName, animName;
int time, blendTime;
bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore;
float speed;
double startTime;
std::string blockName, animName;
int time, blendTime;
bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore;
float speed;
std::int64_t startTime;

// Read data
bitStream.ReadString(blockName);
Expand All @@ -1036,9 +1041,8 @@ void CPacketHandler::Packet_PlayerList(NetBitStreamInterface& bitStream)
// Run anim
CStaticFunctionDefinitions::SetPedAnimation(*pPlayer, blockName, animName.c_str(), time, blendTime, looped, updatePosition, interruptable,
freezeLastFrame);
pPlayer->m_AnimationCache.startTime = static_cast<std::int64_t>(startTime);
pPlayer->m_AnimationCache.startTime = startTime;
pPlayer->m_AnimationCache.speed = speed;
pPlayer->m_AnimationCache.progress = 0.0f;

pPlayer->SetHasSyncedAnim(true);
}
Expand Down Expand Up @@ -3970,11 +3974,11 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream)
// Animation
if (bitStream.ReadBit())
{
std::string blockName, animName;
int time, blendTime;
bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore;
float speed;
float elapsedTime;
std::string blockName, animName;
int time, blendTime;
bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore;
float speed;
std::int64_t startTime;

// Read data
bitStream.ReadString(blockName);
Expand All @@ -3986,19 +3990,14 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream)
bitStream.ReadBit(freezeLastFrame);
bitStream.Read(blendTime);
bitStream.ReadBit(taskRestore);
bitStream.Read(elapsedTime);
bitStream.Read(startTime);
bitStream.Read(speed);

// Server sends elapsed time rather than start time due to bitstream limitations regarding 64 bit integers.
const uint64_t nowTick = GetTickCount64_();
const int64_t startTime = nowTick - elapsedTime;

// Run anim
CStaticFunctionDefinitions::SetPedAnimation(*pPed, blockName, animName.c_str(), time, blendTime, looped, updatePosition, interruptable,
freezeLastFrame);
pPed->m_AnimationCache.startTime = startTime;
pPed->m_AnimationCache.speed = speed;
pPed->m_AnimationCache.progress = 0.0f;

pPed->SetHasSyncedAnim(true);
}
Expand Down
27 changes: 18 additions & 9 deletions Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2258,13 +2258,16 @@ bool CStaticFunctionDefinitions::SetPedAnimation(CClientEntity& Entity, const SS
CClientPed& Ped = static_cast<CClientPed&>(Entity);
if (strBlockName && szAnimName)
{
bool success = false;

std::unique_ptr<CAnimBlock> pBlock = g_pGame->GetAnimManager()->GetAnimationBlock(strBlockName);
if (pBlock)
{
Ped.SetCurrentAnimationCustom(false);
Ped.SetNextAnimationNormal();
Ped.RunNamedAnimation(pBlock, szAnimName, iTime, iBlend, bLoop, bUpdatePosition, bInterruptible, bFreezeLastFrame);
return true;

success = true;
}
else
{
Expand All @@ -2283,12 +2286,16 @@ bool CStaticFunctionDefinitions::SetPedAnimation(CClientEntity& Entity, const SS

const char* szGateWayAnimationName = g_pGame->GetAnimManager()->GetGateWayAnimationName();
Ped.RunNamedAnimation(pBlock, szGateWayAnimationName, iTime, iBlend, bLoop, bUpdatePosition, bInterruptible, bFreezeLastFrame);
return true;

success = true;
}
}
}

Ped.m_AnimationCache.startTime = GetTimestamp();
if (success)
Ped.m_AnimationCache.startTime = g_pClientGame->GetSyncedTime();

return success;
}
else
{
Expand All @@ -2311,12 +2318,14 @@ bool CStaticFunctionDefinitions::SetPedAnimationProgress(CClientEntity& Entity,
{
auto pAnimAssociation = g_pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(Ped.GetClump(), strAnimName);
if (pAnimAssociation)
{
pAnimAssociation->SetCurrentProgress(fProgress);
return true;
else
{
Ped.m_AnimationCache.progress = fProgress;
Ped.m_AnimationCache.updateInNextFrame = true;
}

Ped.m_AnimationCache.progress = fProgress;
return true;
}
else
{
Expand All @@ -2340,12 +2349,12 @@ bool CStaticFunctionDefinitions::SetPedAnimationSpeed(CClientEntity& Entity, con
{
auto pAnimAssociation = g_pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(Ped.GetClump(), strAnimName);
if (pAnimAssociation)
{
pAnimAssociation->SetCurrentSpeed(fSpeed);
return true;
}
else
Ped.m_AnimationCache.updateInNextFrame = true;

Ped.m_AnimationCache.speed = fSpeed;
return true;
}
}

Expand Down
7 changes: 4 additions & 3 deletions Client/mods/deathmatch/logic/rpc/CPedRPCs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,8 @@ void CPedRPCs::SetPedAnimation(CClientEntity* pSource, NetBitStreamInterface& bi
pPed->SetTaskToBeRestoredOnAnimEnd(bTaskToBeRestoredOnAnimEnd);
pPed->SetTaskTypeToBeRestoredOnAnimEnd((eTaskType)TASK_SIMPLE_DUCK);

pPed->m_AnimationCache.startTime = GetTimestamp();
pPed->m_AnimationCache.startTime = g_pClientGame->GetSyncedTime();
pPed->m_AnimationCache.speed = 1.0f;
pPed->m_AnimationCache.progress = 0.0f;

pPed->SetHasSyncedAnim(true);
}
Expand Down Expand Up @@ -308,9 +307,11 @@ void CPedRPCs::SetPedAnimationProgress(CClientEntity* pSource, NetBitStreamInter
{
auto pAnimAssociation = g_pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(pPed->GetClump(), animName.c_str());
if (pAnimAssociation)
{
pAnimAssociation->SetCurrentProgress(fProgress);
else
{
pPed->m_AnimationCache.progress = fProgress;
pPed->m_AnimationCache.updateInNextFrame = true;
}
}
}
Expand Down
29 changes: 13 additions & 16 deletions Server/mods/deathmatch/logic/CPed.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,17 @@ enum eBone

struct SPlayerAnimData
{
std::string blockName{};
std::string animName{};
int time{-1};
bool loop{true};
bool updatePosition{true};
bool interruptable{true};
bool freezeLastFrame{true};
int blendTime{250};
bool taskToBeRestoredOnAnimEnd{false};

std::string blockName{};
std::string animName{};
int time{-1};
bool loop{true};
bool updatePosition{true};
bool interruptable{true};
bool freezeLastFrame{true};
int blendTime{250};
bool taskToBeRestoredOnAnimEnd{false};
std::int64_t startTime{0};

float progress{0.0f};
float speed{1.0f};
float speed{1.0f};

bool IsAnimating() const noexcept { return !blockName.empty() && !animName.empty(); }
};
Expand Down Expand Up @@ -304,9 +301,9 @@ class CPed : public CElement
std::vector<CPlayer*>::const_iterator NearPlayersIterBegin() { return m_nearPlayersList.begin(); }
std::vector<CPlayer*>::const_iterator NearPlayersIterEnd() { return m_nearPlayersList.end(); }

const SPlayerAnimData& GetAnimationData() const noexcept { return m_animData; };
void SetAnimationData(const SPlayerAnimData& animData) { m_animData = animData; };
void SetAnimationProgress(float progress) { m_animData.progress = progress; };
const SPlayerAnimData& GetAnimationData() const noexcept { return m_animData; }
SPlayerAnimData GetAnimationData() noexcept { return m_animData; }
void SetAnimationData(const SPlayerAnimData& animData) { m_animData = animData; }
void SetAnimationSpeed(float speed) { m_animData.speed = speed; };

void SetHanging(bool hanging) noexcept { m_hanging = hanging; }
Expand Down
Loading
Loading