From f0a87484c3b3949d387288cb1e8b36ff80469450 Mon Sep 17 00:00:00 2001 From: FileEX Date: Sat, 4 Jan 2025 17:18:36 +0100 Subject: [PATCH] Fix synced animations --- Client/mods/deathmatch/logic/CClientPed.cpp | 125 +++++++++++------- Client/mods/deathmatch/logic/CClientPed.h | 29 ++-- .../mods/deathmatch/logic/CPacketHandler.cpp | 8 +- .../logic/CStaticFunctionDefinitions.cpp | 6 + .../logic/packets/CEntityAddPacket.cpp | 6 +- 5 files changed, 104 insertions(+), 70 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientPed.cpp b/Client/mods/deathmatch/logic/CClientPed.cpp index adf37d0224..3e57ff789a 100644 --- a/Client/mods/deathmatch/logic/CClientPed.cpp +++ b/Client/mods/deathmatch/logic/CClientPed.cpp @@ -38,10 +38,10 @@ using std::vector; extern CClientGame* g_pClientGame; #ifndef M_PI -#define M_PI 3.14159265358979323846 + #define M_PI 3.14159265358979323846 #endif -#define INVALID_VALUE 0xFFFFFFFF +#define INVALID_VALUE 0xFFFFFFFF #define PED_INTERPOLATION_WARP_THRESHOLD 5 // Minimal threshold #define PED_INTERPOLATION_WARP_THRESHOLD_FOR_SPEED 5 // Units to increment the threshold per speed unit @@ -1166,7 +1166,7 @@ CClientVehicle* CClientPed::GetClosestEnterableVehicle(bool bGetPositionFromClos for (; iter != listEnd; iter++) { pTempVehicle = *iter; - + if (pTempVehicle->IsLocalEntity() != localVehicles) continue; @@ -2885,11 +2885,7 @@ void CClientPed::StreamedInPulse(bool bDoStandardPulses) m_bRequestedAnimation = false; - // Copy our name incase it gets deleted - SString strAnimName = m_AnimationCache.strName; - // Run our animation - RunNamedAnimation(m_pAnimationBlock, strAnimName, m_AnimationCache.iTime, m_AnimationCache.iBlend, m_AnimationCache.bLoop, - m_AnimationCache.bUpdatePosition, m_AnimationCache.bInterruptable, m_AnimationCache.bFreezeLastFrame); + RunAnimationFromCache(); } } @@ -3400,8 +3396,8 @@ void CClientPed::Interpolate() { // We're not at the end? if (ulCurrentTime < m_ulEndRotationTime) - { - const float fDelta = GetOffsetRadians(m_fBeginRotation, m_fTargetRotationA); + { + const float fDelta = GetOffsetRadians(m_fBeginRotation, m_fTargetRotationA); // Hack for the wrap-around (the edge seems to be varying...) if (fDelta < -M_PI || fDelta > M_PI) @@ -3411,7 +3407,7 @@ void CClientPed::Interpolate() } else { - // Interpolate the player rotation + // Interpolate the player rotation const float fDeltaTime = float(m_ulEndRotationTime - m_ulBeginRotationTime); const float fCameraDelta = GetOffsetRadians(m_fBeginCameraRotation, m_fTargetCameraRotation); const float fProgress = float(ulCurrentTime - m_ulBeginRotationTime); @@ -3676,18 +3672,15 @@ void CClientPed::_CreateModel() Kill(WEAPONTYPE_UNARMED, 0, false, true); } - // Are we still playing a looped animation? - if (m_AnimationCache.bLoop && m_pAnimationBlock) + // Are we still playing animation? + if ((m_AnimationCache.bLoop || m_AnimationCache.bFreezeLastFrame || m_AnimationCache.progressWaitForStreamIn) && m_pAnimationBlock) { if (m_bisCurrentAnimationCustom) { m_bisNextAnimationCustom = true; } - // Copy our anim name incase it gets deleted - SString strAnimName = m_AnimationCache.strName; - // Run our animation - RunNamedAnimation(m_pAnimationBlock, strAnimName, m_AnimationCache.iTime, m_AnimationCache.iBlend, m_AnimationCache.bLoop, - m_AnimationCache.bUpdatePosition, m_AnimationCache.bInterruptable, m_AnimationCache.bFreezeLastFrame); + + RunAnimationFromCache(); } // Set the voice that corresponds to our model @@ -3939,7 +3932,7 @@ void CClientPed::_ChangeModel() // So make sure clothes geometry is built now... m_pClothes->AddAllToModel(); m_pPlayerPed->RebuildPlayer(); - } + } // Remove reference to the old model we used (Flag extra GTA reference to be removed as well) pLoadedModel->RemoveRef(true); @@ -3958,18 +3951,14 @@ void CClientPed::_ChangeModel() m_bDontChangeRadio = false; // Are we still playing a looped animation? - if (m_AnimationCache.bLoop && m_pAnimationBlock) + if ((m_AnimationCache.bLoop || m_AnimationCache.bFreezeLastFrame || m_AnimationCache.progressWaitForStreamIn) && m_pAnimationBlock) { if (m_bisCurrentAnimationCustom) { m_bisNextAnimationCustom = true; } - // Copy our anim name incase it gets deleted - SString strAnimName = m_AnimationCache.strName; - // Run our animation - RunNamedAnimation(m_pAnimationBlock, strAnimName, m_AnimationCache.iTime, m_AnimationCache.iBlend, m_AnimationCache.bLoop, - m_AnimationCache.bUpdatePosition, m_AnimationCache.bInterruptable, m_AnimationCache.bFreezeLastFrame); + RunAnimationFromCache(); } // Set the voice that corresponds to the new model @@ -5260,7 +5249,7 @@ void CClientPed::Respawn(CVector* pvecPosition, bool bRestoreState, bool bCamera // Restore the camera's interior whether we're restoring player states or not g_pGame->GetWorld()->SetCurrentArea(ucCameraInterior); - // Reset goggle effect + // Reset goggle effect g_pMultiplayer->SetNightVisionEnabled(bOldNightVision, false); g_pMultiplayer->SetThermalVisionEnabled(bOldThermalVision, false); @@ -5794,6 +5783,10 @@ 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() @@ -5827,6 +5820,45 @@ std::unique_ptr CClientPed::GetAnimationBlock() return nullptr; } +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()); + 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(); + + 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; + } + } + + animAssoc->SetCurrentSpeed(m_AnimationCache.speed); +} + void CClientPed::PostWeaponFire() { m_ulLastTimeFired = CClientTime::GetTime(); @@ -5919,7 +5951,7 @@ bool CClientPed::SetOnFire(bool bIsOnFire) { if (m_pPlayerPed) return m_pPlayerPed->SetOnFire(bIsOnFire); - + m_bIsOnFire = bIsOnFire; return true; } @@ -6296,9 +6328,9 @@ void CClientPed::HandleWaitingForGroundToLoad() { // If not near any MTA objects, then don't bother waiting SetFrozenWaitingForGroundToLoad(false); - #ifdef ASYNC_LOADING_DEBUG_OUTPUTA +#ifdef ASYNC_LOADING_DEBUG_OUTPUTA OutputDebugLine("[AsyncLoading] FreezeUntilCollisionLoaded - Early stop"); - #endif +#endif return; } @@ -6319,29 +6351,29 @@ void CClientPed::HandleWaitingForGroundToLoad() bool bASync = g_pGame->IsASyncLoadingEnabled(); bool bMTAObjLimit = pObjectManager->IsObjectLimitReached(); bool bHasModel = GetModelInfo() != NULL; - #ifndef ASYNC_LOADING_DEBUG_OUTPUTA +#ifndef ASYNC_LOADING_DEBUG_OUTPUTA bool bMTALoaded = pObjectManager->ObjectsAroundPointLoaded(vecPosition, fUseRadius, m_usDimension); - #else +#else SString strAround; bool bMTALoaded = pObjectManager->ObjectsAroundPointLoaded(vecPosition, fUseRadius, m_usDimension, &strAround); - #endif +#endif - #ifdef ASYNC_LOADING_DEBUG_OUTPUTA +#ifdef ASYNC_LOADING_DEBUG_OUTPUTA SString status = SString( "%2.2f,%2.2f,%2.2f bASync:%d bHasModel:%d bMTALoaded:%d bMTAObjLimit:%d m_fGroundCheckTolerance:%2.2f m_fObjectsAroundTolerance:%2.2f " "fUseRadius:%2.1f", vecPosition.fX, vecPosition.fY, vecPosition.fZ, bASync, bHasModel, bMTALoaded, bMTAObjLimit, m_fGroundCheckTolerance, m_fObjectsAroundTolerance, fUseRadius); - #endif +#endif // See if ground is ready if ((!bHasModel || !bMTALoaded) && m_fObjectsAroundTolerance < 1.f) { m_fGroundCheckTolerance = 0.f; m_fObjectsAroundTolerance = std::min(1.f, m_fObjectsAroundTolerance + 0.01f); - #ifdef ASYNC_LOADING_DEBUG_OUTPUTA +#ifdef ASYNC_LOADING_DEBUG_OUTPUTA status += (" FreezeUntilCollisionLoaded - wait"); - #endif +#endif } else { @@ -6354,16 +6386,16 @@ void CClientPed::HandleWaitingForGroundToLoad() if (fUseDist > -0.2f && fUseDist < 1.5f) SetFrozenWaitingForGroundToLoad(false); - #ifdef ASYNC_LOADING_DEBUG_OUTPUTA +#ifdef ASYNC_LOADING_DEBUG_OUTPUTA status += (SString(" GetDistanceFromGround: fDist:%2.2f fUseDist:%2.2f", fDist, fUseDist)); - #endif +#endif // Stop waiting after 3 frames, if the object limit has not been reached. (bASync should always be false here) if (m_fGroundCheckTolerance > 0.03f && !bMTAObjLimit && !bASync) SetFrozenWaitingForGroundToLoad(false); } - #ifdef ASYNC_LOADING_DEBUG_OUTPUTA +#ifdef ASYNC_LOADING_DEBUG_OUTPUTA OutputDebugLine(SStringX("[AsyncLoading] ")++ status); g_pCore->GetGraphics()->DrawString(10, 220, -1, 1, status); @@ -6371,7 +6403,7 @@ void CClientPed::HandleWaitingForGroundToLoad() strAround.Split("\n", lineList); for (unsigned int i = 0; i < lineList.size(); i++) g_pCore->GetGraphics()->DrawString(10, 230 + i * 10, -1, 1, lineList[i]); - #endif +#endif } // @@ -6658,7 +6690,6 @@ bool CClientPed::ExitVehicle() return false; } - // Check the server is compatible if we are a ped if (!IsLocalPlayer() && !g_pNet->CanServerBitStream(eBitStreamVersion::PedEnterExit)) { @@ -6789,7 +6820,7 @@ void CClientPed::UpdateVehicleInOut() CClientVehicle* vehicle = GetRealOccupiedVehicle(); if (!vehicle) return; - + // Call the onClientVehicleEnter event for the ped // Check if it is cancelled before allowing the ped to enter the vehicle CLuaArguments arguments; @@ -6816,7 +6847,7 @@ void CClientPed::UpdateVehicleInOut() if (realVehicle) return; - + // Call the onClientVehicleExit event for the ped CLuaArguments arguments; arguments.PushElement(this); // player / ped @@ -6843,7 +6874,7 @@ void CClientPed::UpdateVehicleInOut() // If we aren't working on leaving the car (he's eiter finished or cancelled/failed leaving) if (IsLeavingVehicle()) return; - + // Are we outside the car? CClientVehicle* pVehicle = GetRealOccupiedVehicle(); if (pVehicle) @@ -7047,7 +7078,7 @@ void CClientPed::UpdateVehicleInOut() // If we aren't getting jacked if (m_bIsGettingJacked) return; - + CClientVehicle* pVehicle = GetRealOccupiedVehicle(); CClientVehicle* pOccupiedVehicle = GetOccupiedVehicle(); @@ -7062,7 +7093,7 @@ void CClientPed::UpdateVehicleInOut() // Are we supposed to be in a vehicle? But aren't? if (!pOccupiedVehicle || pVehicle || IsWarpInToVehicleRequired()) return; - + // Jax: this happens when we try to warp into a streamed out vehicle, including when we use CClientVehicle::StreamInNow // ..maybe we need a different way to detect bike falls? @@ -7070,7 +7101,7 @@ void CClientPed::UpdateVehicleInOut() NetBitStreamInterface* pBitStream = g_pNet->AllocateNetBitStream(); if (!pBitStream) return; - + // Write the ped or player ID to it if (g_pNet->CanServerBitStream(eBitStreamVersion::PedEnterExit)) { diff --git a/Client/mods/deathmatch/logic/CClientPed.h b/Client/mods/deathmatch/logic/CClientPed.h index 65df08efff..4286d64e91 100644 --- a/Client/mods/deathmatch/logic/CClientPed.h +++ b/Client/mods/deathmatch/logic/CClientPed.h @@ -127,23 +127,17 @@ struct SReplacedAnimation struct SAnimationCache { - SString strName; - int iTime; - bool bLoop; - bool bUpdatePosition; - bool bInterruptable; - bool bFreezeLastFrame; - int iBlend; - - SAnimationCache() - { - iTime = -1; - bLoop = false; - bUpdatePosition = false; - bInterruptable = false; - bFreezeLastFrame = true; - iBlend = 250; - } + 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 }; class CClientObject; @@ -466,6 +460,7 @@ class CClientPed : public CClientStreamElement, public CAntiCheatModule void KillAnimation(); std::unique_ptr GetAnimationBlock(); const SAnimationCache& GetAnimationCache() const noexcept { return m_AnimationCache; } + void RunAnimationFromCache(); bool IsUsingGun(); diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index ea5443f176..9001f6ffde 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -4004,7 +4004,7 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream) std::string blockName, animName; int time, blendTime; bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore; - float progress, speed; + float elapsedTime, speed; // Read data bitStream.ReadString(blockName); @@ -4016,12 +4016,14 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream) bitStream.ReadBit(freezeLastFrame); bitStream.Read(blendTime); bitStream.ReadBit(taskRestore); - bitStream.Read(progress); + bitStream.Read(elapsedTime); bitStream.Read(speed); // Run anim CStaticFunctionDefinitions::SetPedAnimation(*pPed, blockName, animName.c_str(), time, blendTime, looped, updatePosition, interruptable, freezeLastFrame); - CStaticFunctionDefinitions::SetPedAnimationProgress(*pPed, animName, progress); + pPed->m_AnimationCache.progressWaitForStreamIn = true; + pPed->m_AnimationCache.elapsedTime = elapsedTime; + CStaticFunctionDefinitions::SetPedAnimationSpeed(*pPed, animName, speed); pPed->SetHasSyncedAnim(true); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 7098e7fdbd..259bd7951c 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2274,10 +2274,14 @@ bool CStaticFunctionDefinitions::SetPedAnimationProgress(CClientEntity& Entity, pAnimAssociation->SetCurrentProgress(fProgress); return true; } + + Ped.m_AnimationCache.progress = fProgress; } else { Ped.KillAnimation(); + Ped.m_AnimationCache.progress = 0.0f; + return true; } } @@ -2300,6 +2304,8 @@ bool CStaticFunctionDefinitions::SetPedAnimationSpeed(CClientEntity& Entity, con pAnimAssociation->SetCurrentSpeed(fSpeed); return true; } + + Ped.m_AnimationCache.speed = fSpeed; } } diff --git a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp index 6a77404f59..5521512417 100644 --- a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp @@ -998,9 +998,9 @@ bool CEntityAddPacket::Write(NetBitStreamInterface& BitStream) const BitStream.Write(animData.blendTime); BitStream.WriteBit(animData.taskToBeRestoredOnAnimEnd); - // Write progress & speed - float deltaTime = GetTickCount64_() - animData.startedTick; - BitStream.Write((deltaTime / animData.time) * animData.speed); + // Write elapsed time & speed + float elapsedTime = GetTickCount64_() - animData.startedTick; + BitStream.Write(elapsedTime); BitStream.Write(animData.speed); } }