diff --git a/Client/mods/deathmatch/logic/CClientPed.h b/Client/mods/deathmatch/logic/CClientPed.h index 2934990e05..65df08efff 100644 --- a/Client/mods/deathmatch/logic/CClientPed.h +++ b/Client/mods/deathmatch/logic/CClientPed.h @@ -552,6 +552,9 @@ class CClientPed : public CClientStreamElement, public CAntiCheatModule std::unique_ptr GetAnimAssociation(CAnimBlendHierarchySAInterface* pHierarchyInterface); + void SetHasSyncedAnim(bool synced) noexcept { m_hasSyncedAnim = synced; } + bool HasSyncedAnim() const noexcept { return m_hasSyncedAnim; } + protected: // This constructor is for peds managed by a player. These are unknown to the ped manager. CClientPed(CClientManager* pManager, unsigned long ulModelID, ElementID ID, bool bIsLocalPlayer); @@ -789,4 +792,7 @@ class CClientPed : public CClientStreamElement, public CAntiCheatModule CClientPed* m_pGettingJackedBy; // The ped that is jacking us std::shared_ptr m_clientModel; + + bool m_hasSyncedAnim{}; + bool m_animationOverridedByClient{}; }; diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index 787cd4c239..ea5443f176 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -3995,6 +3995,39 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream) // Collisions pPed->SetUsesCollision(bCollisonsEnabled); + // Animation + if (bitStream.Can(eBitStreamVersion::AnimationsSync)) + { + // Contains animation data? + if (bitStream.ReadBit()) + { + std::string blockName, animName; + int time, blendTime; + bool looped, updatePosition, interruptable, freezeLastFrame, taskRestore; + float progress, speed; + + // Read data + bitStream.ReadString(blockName); + bitStream.ReadString(animName); + bitStream.Read(time); + bitStream.ReadBit(looped); + bitStream.ReadBit(updatePosition); + bitStream.ReadBit(interruptable); + bitStream.ReadBit(freezeLastFrame); + bitStream.Read(blendTime); + bitStream.ReadBit(taskRestore); + bitStream.Read(progress); + bitStream.Read(speed); + + // Run anim + CStaticFunctionDefinitions::SetPedAnimation(*pPed, blockName, animName.c_str(), time, blendTime, looped, updatePosition, interruptable, freezeLastFrame); + CStaticFunctionDefinitions::SetPedAnimationProgress(*pPed, animName, progress); + CStaticFunctionDefinitions::SetPedAnimationSpeed(*pPed, animName, speed); + + pPed->SetHasSyncedAnim(true); + } + } + break; } diff --git a/Client/mods/deathmatch/logic/CPedSync.cpp b/Client/mods/deathmatch/logic/CPedSync.cpp index 8461ac1a87..a0e3b6b9ca 100644 --- a/Client/mods/deathmatch/logic/CPedSync.cpp +++ b/Client/mods/deathmatch/logic/CPedSync.cpp @@ -307,6 +307,8 @@ void CPedSync::WritePedInformation(NetBitStreamInterface* pBitStream, CClientPed ucFlags |= 0x20; if (pPed->IsInWater() != pPed->m_LastSyncedData->bIsInWater) ucFlags |= 0x40; + if (pPed->HasSyncedAnim() && (!pPed->IsRunningAnimation() || pPed->m_animationOverridedByClient)) + ucFlags |= 0x80; // Do we really have to sync this ped? if (ucFlags == 0) @@ -395,4 +397,11 @@ void CPedSync::WritePedInformation(NetBitStreamInterface* pBitStream, CClientPed pBitStream->WriteBit(pPed->IsInWater()); pPed->m_LastSyncedData->bIsInWater = pPed->IsInWater(); } + + // The animation has been overwritten or interrupted by the client + if (ucFlags & 0x80 && pBitStream->Can(eBitStreamVersion::AnimationsSync)) + { + pPed->SetHasSyncedAnim(false); + pPed->m_animationOverridedByClient = false; + } } diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp index 6caf245618..8aa0d5d1f6 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp @@ -2248,6 +2248,10 @@ int CLuaPedDefs::SetPedAnimation(lua_State* luaVM) } pPed->SetTaskToBeRestoredOnAnimEnd(bTaskToBeRestoredOnAnimEnd); + + if (pPed->HasSyncedAnim()) + pPed->m_animationOverridedByClient = true; + lua_pushboolean(luaVM, true); return 1; } diff --git a/Server/mods/deathmatch/logic/CPed.h b/Server/mods/deathmatch/logic/CPed.h index bf2f4901b7..daa5c8e0c0 100644 --- a/Server/mods/deathmatch/logic/CPed.h +++ b/Server/mods/deathmatch/logic/CPed.h @@ -103,6 +103,26 @@ enum eBone BONE_RIGHTFOOT }; +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::int64_t startedTick{0}; + + float progress{0.0f}; + float speed{1.0f}; + + bool IsAnimating() const noexcept { return !blockName.empty() && !animName.empty(); } +}; + class CWeapon { public: @@ -278,6 +298,11 @@ class CPed : public CElement std::vector::const_iterator NearPlayersIterBegin() { return m_nearPlayersList.begin(); } std::vector::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; }; + void SetAnimationSpeed(float speed) { m_animData.speed = speed; }; + protected: bool ReadSpecialData(const int iLine) override; @@ -316,6 +341,7 @@ class CPed : public CElement bool m_bFrozen; bool m_bStealthAiming; CVehicle* m_pJackingVehicle; + SPlayerAnimData m_animData{}; CVehicle* m_pVehicle; unsigned int m_uiVehicleSeat; diff --git a/Server/mods/deathmatch/logic/CPedSync.cpp b/Server/mods/deathmatch/logic/CPedSync.cpp index cb2f9e417b..f911ab027c 100644 --- a/Server/mods/deathmatch/logic/CPedSync.cpp +++ b/Server/mods/deathmatch/logic/CPedSync.cpp @@ -82,9 +82,20 @@ void CPedSync::OverrideSyncer(CPed* pPed, CPlayer* pPlayer, bool bPersist) void CPedSync::UpdateAllSyncer() { + auto currentTick = GetTickCount64_(); + // Update all the ped's sync states for (auto iter = m_pPedManager->IterBegin(); iter != m_pPedManager->IterEnd(); iter++) { + // Has the duration of the ped's animation already elapsed? + const SPlayerAnimData& animData = (*iter)->GetAnimationData(); + if (animData.IsAnimating()) + { + float deltaTime = currentTick - animData.startedTick; + if (!animData.freezeLastFrame && animData.time > 0 && deltaTime >= animData.time) + (*iter)->SetAnimationData({}); + } + // It is a ped, yet not a player if (IS_PED(*iter) && !IS_PLAYER(*iter)) UpdateSyncer(*iter); @@ -272,6 +283,9 @@ void CPedSync::Packet_PedSync(CPedSyncPacket& Packet) if (Data.ucFlags & 0x40) pPed->SetInWater(Data.bIsInWater); + if (Data.ucFlags & 0x80) + pPed->SetAnimationData({}); + // Is it time to sync to everyone bool bDoFarSync = llTickCountNow - pPed->GetLastFarSyncTick() >= g_TickRateSettings.iPedFarSync; diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index c3cebfdce8..e80745b184 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -4353,8 +4353,6 @@ bool CStaticFunctionDefinitions::SetPedAnimation(CElement* pElement, const SStri CPed* pPed = static_cast(pElement); if (pPed->IsSpawned()) { - // TODO: save their animation? - // Tell the players CBitStream BitStream; if (!blockName.empty() && !animName.empty()) @@ -4367,6 +4365,9 @@ bool CStaticFunctionDefinitions::SetPedAnimation(CElement* pElement, const SStri if (pPed->IsChoking()) pPed->SetChoking(false); + // Store anim data + pPed->SetAnimationData(SPlayerAnimData{blockName, animName, iTime, bLoop, bUpdatePosition, bInterruptable, bFreezeLastFrame, iBlend, bTaskToBeRestoredOnAnimEnd, GetTickCount64_()}); + BitStream.pBitStream->WriteString(blockName); BitStream.pBitStream->WriteString(animName); BitStream.pBitStream->Write(iTime); @@ -4381,9 +4382,12 @@ bool CStaticFunctionDefinitions::SetPedAnimation(CElement* pElement, const SStri { // Inform them to kill the current animation instead BitStream.pBitStream->Write((unsigned char)0); + + // Clear anim data + pPed->SetAnimationData({}); } - m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, SET_PED_ANIMATION, *BitStream.pBitStream)); + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, SET_PED_ANIMATION, *BitStream.pBitStream)); return true; } } @@ -4405,14 +4409,17 @@ bool CStaticFunctionDefinitions::SetPedAnimationProgress(CElement* pElement, con { BitStream.pBitStream->WriteString(animName); BitStream.pBitStream->Write(fProgress); + + pPed->SetAnimationProgress(fProgress); } else { // Inform them to kill the current animation instead BitStream.pBitStream->Write((unsigned char)0); + pPed->SetAnimationData({}); } - m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, SET_PED_ANIMATION_PROGRESS, *BitStream.pBitStream)); + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, SET_PED_ANIMATION_PROGRESS, *BitStream.pBitStream)); return true; } } @@ -4433,6 +4440,7 @@ bool CStaticFunctionDefinitions::SetPedAnimationSpeed(CElement* pElement, const BitStream.pBitStream->WriteString(animName); BitStream.pBitStream->Write(fSpeed); + pPed->SetAnimationSpeed(fSpeed); m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, SET_PED_ANIMATION_SPEED, *BitStream.pBitStream)); return true; diff --git a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp index 8973abc6b1..6a77404f59 100644 --- a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp @@ -977,6 +977,34 @@ bool CEntityAddPacket::Write(NetBitStreamInterface& BitStream) const BitStream.Write(currentWeaponSlot); } + // Animation + if (BitStream.Can(eBitStreamVersion::AnimationsSync)) + { + const SPlayerAnimData& animData = pPed->GetAnimationData(); + + // Contains animation data? + bool animRunning = animData.IsAnimating(); + BitStream.WriteBit(animRunning); + + if (animRunning) + { + BitStream.WriteString(animData.blockName); + BitStream.WriteString(animData.animName); + BitStream.Write(animData.time); + BitStream.WriteBit(animData.loop); + BitStream.WriteBit(animData.updatePosition); + BitStream.WriteBit(animData.interruptable); + BitStream.WriteBit(animData.freezeLastFrame); + BitStream.Write(animData.blendTime); + BitStream.WriteBit(animData.taskToBeRestoredOnAnimEnd); + + // Write progress & speed + float deltaTime = GetTickCount64_() - animData.startedTick; + BitStream.Write((deltaTime / animData.time) * animData.speed); + BitStream.Write(animData.speed); + } + } + break; } diff --git a/Shared/sdk/net/bitstream.h b/Shared/sdk/net/bitstream.h index 7a2d3f10d7..ea7fbcc230 100644 --- a/Shared/sdk/net/bitstream.h +++ b/Shared/sdk/net/bitstream.h @@ -595,7 +595,11 @@ enum class eBitStreamVersion : unsigned short // Add "spawnFlyingComponent" to setVehiclePanelState // 2024-12-31 SetVehiclePanelState_SpawnFlyingComponent, - + + // Ped animations synchronization + // 2025-01-01 + AnimationsSync, + // This allows us to automatically increment the BitStreamVersion when things are added to this enum. // Make sure you only add things above this comment. Next,