Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -928,6 +931,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.bInterruptable, 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 bInterruptable{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, bInterruptable, 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, bInterruptable, 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