diff --git a/Client/mods/deathmatch/logic/CClientPed.cpp b/Client/mods/deathmatch/logic/CClientPed.cpp index 59ce2f4cde..94a64d8c6a 100644 --- a/Client/mods/deathmatch/logic/CClientPed.cpp +++ b/Client/mods/deathmatch/logic/CClientPed.cpp @@ -2895,6 +2895,11 @@ 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()) + UpdateAnimationProgressAndSpeed(); + // Update our alpha unsigned char ucAlpha = m_ucAlpha; // Are we in a different interior to the camera? set our alpha to 0 @@ -3685,8 +3690,8 @@ void CClientPed::_CreateModel() Kill(WEAPONTYPE_UNARMED, 0, false, true); } - // Are we still playing animation? - if ((m_AnimationCache.bLoop || m_AnimationCache.bFreezeLastFrame || m_AnimationCache.progressWaitForStreamIn) && m_pAnimationBlock) + // Are we still playing a animation? + if (m_pAnimationBlock && IsAnimationInProgress()) { if (m_bisCurrentAnimationCustom) { @@ -3963,8 +3968,8 @@ void CClientPed::_ChangeModel() } m_bDontChangeRadio = false; - // Are we still playing a looped animation? - if ((m_AnimationCache.bLoop || m_AnimationCache.bFreezeLastFrame || m_AnimationCache.progressWaitForStreamIn) && m_pAnimationBlock) + // Are we still playing a animation? + if (m_pAnimationBlock && IsAnimationInProgress()) { if (m_bisCurrentAnimationCustom) { @@ -5728,7 +5733,23 @@ bool CClientPed::IsRunningAnimation() } return false; } - return (m_AnimationCache.bLoop && m_pAnimationBlock); + return (m_AnimationCache.bLoop || m_AnimationCache.bFreezeLastFrame) && m_pAnimationBlock; +} + +bool CClientPed::IsAnimationInProgress() +{ + bool constAnim = m_AnimationCache.bLoop || m_AnimationCache.bFreezeLastFrame; + + if (!m_pAnimationBlock) + return constAnim; + + float elapsedTime = static_cast(GetTimestamp() - m_AnimationCache.startTime) / 1000.0f; + + auto animBlendHierarchy = g_pGame->GetAnimManager()->GetAnimation(m_AnimationCache.strName.c_str(), m_pAnimationBlock); + if (!animBlendHierarchy) + return constAnim; + + return constAnim || elapsedTime < animBlendHierarchy->GetTotalTime(); } void CClientPed::RunNamedAnimation(std::unique_ptr& pBlock, const char* szAnimName, int iTime, int iBlend, bool bLoop, bool bUpdatePosition, @@ -5816,10 +5837,6 @@ void CClientPed::RunNamedAnimation(std::unique_ptr& pBlock, const ch m_AnimationCache.bUpdatePosition = bUpdatePosition; m_AnimationCache.bInterruptable = bInterruptable; m_AnimationCache.bFreezeLastFrame = bFreezeLastFrame; - m_AnimationCache.progress = 0.0f; - m_AnimationCache.speed = 1.0f; - m_AnimationCache.progressWaitForStreamIn = false; - m_AnimationCache.elapsedTime = 0.0f; } void CClientPed::KillAnimation() @@ -5858,39 +5875,45 @@ void CClientPed::RunAnimationFromCache() if (!m_pAnimationBlock) return; - bool needCalcProgress = m_AnimationCache.progressWaitForStreamIn; - float elapsedTime = m_AnimationCache.elapsedTime; - // Copy our name incase it gets deleted std::string animName = m_AnimationCache.strName; // 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); - auto animAssoc = g_pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(GetClump(), animName.c_str()); + // Set anim progress & speed + m_AnimationCache.progressWaitForStreamIn = 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; - // If the anim is synced from the server side, we need to calculate the progress - float progress = m_AnimationCache.progress; - if (needCalcProgress) - { - float animLength = animAssoc->GetLength(); + float animLength = animAssoc->GetLength(); + float progress = 0.0f; + float elapsedTime = static_cast(GetTimestamp() - 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 - { - 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; - } + // 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; } animAssoc->SetCurrentProgress(std::clamp(progress, 0.0f, 1.0f)); animAssoc->SetCurrentSpeed(m_AnimationCache.speed); + + m_AnimationCache.progressWaitForStreamIn = false; } void CClientPed::PostWeaponFire() diff --git a/Client/mods/deathmatch/logic/CClientPed.h b/Client/mods/deathmatch/logic/CClientPed.h index 6f2ba652fb..3a329499d7 100644 --- a/Client/mods/deathmatch/logic/CClientPed.h +++ b/Client/mods/deathmatch/logic/CClientPed.h @@ -129,17 +129,17 @@ struct SReplacedAnimation struct SAnimationCache { - 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 speed{1.0f}; - bool progressWaitForStreamIn{false}; // for sync anim only - float elapsedTime{0.0f}; // for sync anim only + 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 speed{1.0f}; + bool progressWaitForStreamIn{false}; + std::int64_t startTime{0}; }; class CClientObject; @@ -456,6 +456,10 @@ class CClientPed : public CClientStreamElement, public CAntiCheatModule bool GetRunningAnimationName(SString& strBlockName, SString& strAnimName); bool IsRunningAnimation(); + + // It checks whether the animation is still playing based on time, not on task execution. + bool IsAnimationInProgress(); + void RunNamedAnimation(std::unique_ptr& pBlock, const char* szAnimName, int iTime = -1, int iBlend = 250, bool bLoop = true, bool bUpdatePosition = true, bool bInterruptable = false, bool bFreezeLastFrame = true, bool bRunInSequence = false, bool bOffsetPed = false, bool bHoldLastFrame = false); @@ -463,6 +467,7 @@ class CClientPed : public CClientStreamElement, public CAntiCheatModule std::unique_ptr GetAnimationBlock(); const SAnimationCache& GetAnimationCache() const noexcept { return m_AnimationCache; } void RunAnimationFromCache(); + void UpdateAnimationProgressAndSpeed(); bool IsUsingGun(); diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index 1f50f7ac67..3e94d83bf5 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -4010,7 +4010,8 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream) std::string blockName, animName; int time, blendTime; bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore; - float elapsedTime, speed; + float speed; + double startTime; // Read data bitStream.ReadString(blockName); @@ -4022,15 +4023,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); // Run anim CStaticFunctionDefinitions::SetPedAnimation(*pPed, blockName, animName.c_str(), time, blendTime, looped, updatePosition, interruptable, freezeLastFrame); - pPed->m_AnimationCache.progressWaitForStreamIn = true; - pPed->m_AnimationCache.elapsedTime = elapsedTime; - - CStaticFunctionDefinitions::SetPedAnimationSpeed(*pPed, animName, speed); + pPed->m_AnimationCache.startTime = static_cast(startTime); + pPed->m_AnimationCache.speed = speed; + pPed->m_AnimationCache.progress = 0.0f; pPed->SetHasSyncedAnim(true); } diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 857aa91047..1bd697f2a0 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2247,6 +2247,8 @@ bool CStaticFunctionDefinitions::SetPedAnimation(CClientEntity& Entity, const SS } } } + + Ped.m_AnimationCache.startTime = GetTimestamp(); } else { diff --git a/Client/mods/deathmatch/logic/rpc/CPedRPCs.cpp b/Client/mods/deathmatch/logic/rpc/CPedRPCs.cpp index f01e91f09d..7e40474fb3 100644 --- a/Client/mods/deathmatch/logic/rpc/CPedRPCs.cpp +++ b/Client/mods/deathmatch/logic/rpc/CPedRPCs.cpp @@ -276,6 +276,12 @@ void CPedRPCs::SetPedAnimation(CClientEntity* pSource, NetBitStreamInterface& bi pPed->RunNamedAnimation(pBlock, animName.c_str(), iTime, iBlend, bLoop, bUpdatePosition, bInterruptable, bFreezeLastFrame); pPed->SetTaskToBeRestoredOnAnimEnd(bTaskToBeRestoredOnAnimEnd); pPed->SetTaskTypeToBeRestoredOnAnimEnd((eTaskType)TASK_SIMPLE_DUCK); + + pPed->m_AnimationCache.startTime = GetTimestamp(); + pPed->m_AnimationCache.speed = 1.0f; + pPed->m_AnimationCache.progress = 0.0f; + + pPed->SetHasSyncedAnim(true); } } } @@ -307,6 +313,7 @@ void CPedRPCs::SetPedAnimationProgress(CClientEntity* pSource, NetBitStreamInter if (pAnimAssociation) { pAnimAssociation->SetCurrentProgress(fProgress); + pPed->m_AnimationCache.progress = fProgress; } } } @@ -334,6 +341,7 @@ void CPedRPCs::SetPedAnimationSpeed(CClientEntity* pSource, NetBitStreamInterfac if (pAnimAssociation) { pAnimAssociation->SetCurrentSpeed(fSpeed); + pPed->m_AnimationCache.speed = fSpeed; } } } diff --git a/Server/mods/deathmatch/logic/CPed.h b/Server/mods/deathmatch/logic/CPed.h index 4e8440afa8..67ec905742 100644 --- a/Server/mods/deathmatch/logic/CPed.h +++ b/Server/mods/deathmatch/logic/CPed.h @@ -115,7 +115,7 @@ struct SPlayerAnimData int blendTime{250}; bool taskToBeRestoredOnAnimEnd{false}; - std::int64_t startedTick{0}; + std::int64_t startTime{0}; float progress{0.0f}; float speed{1.0f}; diff --git a/Server/mods/deathmatch/logic/CPedSync.cpp b/Server/mods/deathmatch/logic/CPedSync.cpp index 13dfad1c43..42952266cf 100644 --- a/Server/mods/deathmatch/logic/CPedSync.cpp +++ b/Server/mods/deathmatch/logic/CPedSync.cpp @@ -82,7 +82,7 @@ void CPedSync::OverrideSyncer(CPed* pPed, CPlayer* pPlayer, bool bPersist) void CPedSync::UpdateAllSyncer() { - auto currentTick = GetTickCount64_(); + auto currentTimestamp = GetTimestamp(); // Update all the ped's sync states for (auto iter = m_pPedManager->IterBegin(); iter != m_pPedManager->IterEnd(); iter++) @@ -91,7 +91,7 @@ void CPedSync::UpdateAllSyncer() const SPlayerAnimData& animData = (*iter)->GetAnimationData(); if (animData.IsAnimating()) { - float deltaTime = currentTick - animData.startedTick; + float deltaTime = static_cast(currentTimestamp - animData.startTime); if (!animData.freezeLastFrame && animData.time > 0 && deltaTime >= animData.time) (*iter)->SetAnimationData({}); } diff --git a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp index dd3038f741..4fcb00e84e 100644 --- a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp @@ -1005,9 +1005,8 @@ bool CEntityAddPacket::Write(NetBitStreamInterface& BitStream) const BitStream.Write(animData.blendTime); BitStream.WriteBit(animData.taskToBeRestoredOnAnimEnd); - // Write elapsed time & speed - float elapsedTime = GetTickCount64_() - animData.startedTick; - BitStream.Write(elapsedTime); + // Write start time & speed + BitStream.Write(static_cast(animData.startTime)); BitStream.Write(animData.speed); } } diff --git a/Shared/sdk/SharedUtil.Time.h b/Shared/sdk/SharedUtil.Time.h index 244a60f580..284a91a9af 100644 --- a/Shared/sdk/SharedUtil.Time.h +++ b/Shared/sdk/SharedUtil.Time.h @@ -41,6 +41,8 @@ namespace SharedUtil // double GetSecondCount(); + std::int64_t GetTimestamp(); + // // Get the time as a sortable string. // Set bDate to include the date, bMs to include milliseconds diff --git a/Shared/sdk/SharedUtil.Time.hpp b/Shared/sdk/SharedUtil.Time.hpp index f84ae8e1fe..7bcdc2859e 100644 --- a/Shared/sdk/SharedUtil.Time.hpp +++ b/Shared/sdk/SharedUtil.Time.hpp @@ -86,6 +86,15 @@ double SharedUtil::GetSecondCount() return GetTickCount64_() * (1 / 1000.0); } +// +// Returns a timestamp in ms +// +std::int64_t SharedUtil::GetTimestamp() +{ + auto now = std::chrono::system_clock::now(); + return std::chrono::duration_cast(now.time_since_epoch()).count(); +} + // // Get the time as a sortable string. // Set bDate to include the date, bMs to include milliseconds