diff --git a/Client/game_sa/CAnimBlendAssociationSA.h b/Client/game_sa/CAnimBlendAssociationSA.h index 871fc177cf3..2d33ea49de0 100644 --- a/Client/game_sa/CAnimBlendAssociationSA.h +++ b/Client/game_sa/CAnimBlendAssociationSA.h @@ -171,6 +171,8 @@ class CAnimBlendAssociationSA : public CAnimBlendAssociation float GetLength() const noexcept { return GetAnimHierarchy()->GetTotalTime(); } void SetAnimID(short sAnimID) { m_pInterface->sAnimID = sAnimID; } void SetAnimGroup(short sAnimGroup) { m_pInterface->sAnimGroup = sAnimGroup; } + + short GetFlags() const override { return m_pInterface->m_nFlags; } void SetFlags(short sFlags) { m_pInterface->m_nFlags = sFlags; } protected: diff --git a/Client/game_sa/CAnimManagerSA.cpp b/Client/game_sa/CAnimManagerSA.cpp index 92b295f7a93..9afcc8f2dd0 100644 --- a/Client/game_sa/CAnimManagerSA.cpp +++ b/Client/game_sa/CAnimManagerSA.cpp @@ -418,13 +418,17 @@ std::unique_ptr CAnimManagerSA::BlendAnimation(RpClump* p } std::unique_ptr CAnimManagerSA::BlendAnimation(RpClump* pClump, CAnimBlendHierarchy* pHierarchy, int ID, float fBlendDelta) +{ + return BlendAnimation(pClump, pHierarchy->GetInterface(), ID, fBlendDelta); +} + +std::unique_ptr CAnimManagerSA::BlendAnimation(RpClump* pClump, CAnimBlendHierarchySAInterface* pHierarchyInterface, int ID, float fBlendDelta) { if (!pClump) - return NULL; + return nullptr; CAnimBlendAssociationSAInterface* pInterface = nullptr; DWORD dwFunc = FUNC_CAnimManager_BlendAnimation_hier; - CAnimBlendHierarchySAInterface* pHierarchyInterface = pHierarchy->GetInterface(); __asm { push fBlendDelta @@ -435,10 +439,10 @@ std::unique_ptr CAnimManagerSA::BlendAnimation(RpClump* p mov pInterface, eax add esp, 0x10 } + if (pInterface) - { return std::make_unique(pInterface); - } + return nullptr; } diff --git a/Client/game_sa/CAnimManagerSA.h b/Client/game_sa/CAnimManagerSA.h index 2268270044a..64b053ac734 100644 --- a/Client/game_sa/CAnimManagerSA.h +++ b/Client/game_sa/CAnimManagerSA.h @@ -109,6 +109,7 @@ class CAnimManagerSA : public CAnimManager AnimationId animID); std::unique_ptr BlendAnimation(RpClump* pClump, AssocGroupId animGroup, AnimationId animID, float fBlendDelta); std::unique_ptr BlendAnimation(RpClump* pClump, CAnimBlendHierarchy* pHierarchy, int ID, float fBlendDelta); + std::unique_ptr BlendAnimation(RpClump* pClump, CAnimBlendHierarchySAInterface* pHierarchyInterface, int ID, float fBlendDelta); void AddAnimBlockRef(int ID); void RemoveAnimBlockRef(int ID); diff --git a/Client/game_sa/CBuildingSA.cpp b/Client/game_sa/CBuildingSA.cpp index 28164b59fe5..1507a7ca61b 100644 --- a/Client/game_sa/CBuildingSA.cpp +++ b/Client/game_sa/CBuildingSA.cpp @@ -15,6 +15,8 @@ #include "CGameSA.h" #include "CMatrixLinkSA.h" #include "CDynamicPool.h" +#include "CAnimManagerSA.h" +#include "gamesa_renderware.h" extern CGameSA* pGame; @@ -70,6 +72,33 @@ void CBuildingSA::SetLod(CBuilding* pLod) } } +void CBuildingSA::SetAnimation(CAnimBlendHierarchySAInterface* animation, eAnimationFlags flags) +{ + if (!m_pInterface || !m_pInterface->m_pRwObject) + return; + + RpClump* clump = reinterpret_cast(m_pInterface->m_pRwObject); + + if (!RpAnimBlendClumpIsInitialized(clump) && animation) + RpAnimBlendClumpInit(clump); + + if (animation) + pGame->GetAnimManager()->BlendAnimation(clump, animation, flags, 1.0f); +} + +bool CBuildingSA::SetAnimationSpeed(float speed) +{ + if (!m_pInterface || !m_pInterface->m_pRwObject) + return false; + + auto assoc = pGame->GetAnimManager()->RpAnimBlendClumpGetFirstAssociation(GetRpClump()); + if (!assoc) + return false; + + assoc->SetCurrentSpeed(speed); + return true; +} + void CBuildingSA::AllocateMatrix() { auto* newMatrix = g_matrixPool.AllocateItem(); diff --git a/Client/game_sa/CBuildingSA.h b/Client/game_sa/CBuildingSA.h index fd1ae87ccc7..7ba5e76e4e7 100644 --- a/Client/game_sa/CBuildingSA.h +++ b/Client/game_sa/CBuildingSA.h @@ -27,6 +27,8 @@ class CBuildingSA final : public virtual CBuilding, public virtual CEntitySA CBuildingSAInterface* GetBuildingInterface() { return static_cast(GetInterface()); }; void SetLod(CBuilding* pLod) override; + void SetAnimation(class CAnimBlendHierarchySAInterface* animation, eAnimationFlags flags) override; + bool SetAnimationSpeed(float speed) override; void AllocateMatrix(); void ReallocateMatrix(); diff --git a/Client/game_sa/CBuildingsPoolSA.cpp b/Client/game_sa/CBuildingsPoolSA.cpp index b73355bf9da..dfcea599b0d 100644 --- a/Client/game_sa/CBuildingsPoolSA.cpp +++ b/Client/game_sa/CBuildingsPoolSA.cpp @@ -60,7 +60,7 @@ CClientEntity* CBuildingsPoolSA::GetClientBuilding(CBuildingSAInterface* pGameIn return m_buildingPool.entities[poolIndex].pClientEntity; } -CBuilding* CBuildingsPoolSA::AddBuilding(CClientBuilding* pClientBuilding, uint16_t modelId, CVector* vPos, CVector* vRot, uint8_t interior) +CBuilding* CBuildingsPoolSA::AddBuilding(CClientBuilding* pClientBuilding, uint16_t modelId, CVector* vPos, CVector* vRot, uint8_t interior, bool anim) { if (!HasFreeBuildingSlot()) return nullptr; @@ -80,7 +80,13 @@ CBuilding* CBuildingsPoolSA::AddBuilding(CClientBuilding* pClientBuilding, uint1 instance.position = *vPos; instance.rotation = {}; + // Trick to create CAnimatedBuilding + bool hasAnimBlend = modelInfo->GetInterface()->bHasAnimBlend; + if (anim) + modelInfo->GetInterface()->bHasAnimBlend = true; + auto pBuilding = static_cast(CFileLoaderSA::LoadObjectInstance(&instance)); + modelInfo->GetInterface()->bHasAnimBlend = hasAnimBlend; // Disable lod and ipl pBuilding->m_pLod = nullptr; diff --git a/Client/game_sa/CBuildingsPoolSA.h b/Client/game_sa/CBuildingsPoolSA.h index 033b39b870c..02f1a467166 100644 --- a/Client/game_sa/CBuildingsPoolSA.h +++ b/Client/game_sa/CBuildingsPoolSA.h @@ -22,7 +22,7 @@ class CBuildingsPoolSA : public CBuildingsPool CBuildingsPoolSA(); ~CBuildingsPoolSA() = default; - CBuilding* AddBuilding(CClientBuilding*, uint16_t modelId, CVector* vPos, CVector* vRot, uint8_t interior); + CBuilding* AddBuilding(CClientBuilding*, uint16_t modelId, CVector* vPos, CVector* vRot, uint8_t interior, bool anim); void RemoveBuilding(CBuilding* pBuilding); bool HasFreeBuildingSlot(); diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index e1b11579dd1..ddd8ca6a776 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -372,6 +372,28 @@ CModelInfo* CGameSA::GetModelInfo(DWORD dwModelID, bool bCanBeInvalid) return nullptr; } +CModelInfo* CGameSA::GetModelInfo(CBaseModelInfoSAInterface* baseModelInfo) +{ + static std::unordered_map s_cache; + + auto it = s_cache.find(baseModelInfo); + if (it != s_cache.end()) + return it->second; + + const std::size_t count = GetCountOfAllFileIDs(); + for (std::size_t i = 0; i < count; i++) + { + CModelInfoSA* modelInfo = &ModelInfo[i]; + if (modelInfo && modelInfo->IsValid() && modelInfo->GetInterface() == baseModelInfo) + { + s_cache[baseModelInfo] = modelInfo; + return modelInfo; + } + } + + return nullptr; +} + /** * Starts the game * \todo make addresses into constants @@ -1155,6 +1177,11 @@ void CGameSA::ResetModelTimes() CModelInfoSA::StaticResetModelTimes(); } +void CGameSA::ResetModelAnimations() +{ + CModelInfoSA::StaticResetModelAnimations(); +} + void CGameSA::ResetAlphaTransparencies() { CModelInfoSA::StaticResetAlphaTransparencies(); diff --git a/Client/game_sa/CGameSA.h b/Client/game_sa/CGameSA.h index 7ef64b44c48..88c5f3b97b4 100644 --- a/Client/game_sa/CGameSA.h +++ b/Client/game_sa/CGameSA.h @@ -183,6 +183,7 @@ class CGameSA : public CGame CWeaponInfo* GetWeaponInfo(eWeaponType weapon, eWeaponSkill skill = WEAPONSKILL_STD); CModelInfo* GetModelInfo(DWORD dwModelID, bool bCanBeInvalid = false); + CModelInfo* GetModelInfo(CBaseModelInfoSAInterface* baseModelInfo); CObjectGroupPhysicalProperties* GetObjectGroupPhysicalProperties(unsigned char ucObjectGroup); uint32_t GetBaseIDforDFF() override { return 0; } @@ -302,6 +303,7 @@ class CGameSA : public CGame void ResetAlphaTransparencies(); void DisableVSync(); void ResetModelTimes(); + void ResetModelAnimations() override; void OnPedContextChange(CPed* pPedContext); CPed* GetPedContext(); diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 927d645be99..a6274bb407c 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -20,6 +20,7 @@ #include "CPedSA.h" #include "CWorldSA.h" #include "gamesa_renderware.h" +#include "CAnimManagerSA.h" extern CCoreInterface* g_pCore; extern CGameSA* pGame; @@ -36,6 +37,7 @@ std::map CModelInfo std::unordered_map CModelInfoSA::ms_OriginalObjectPropertiesGroups; std::unordered_map> CModelInfoSA::ms_VehicleModelDefaultWheelSizes; std::map CModelInfoSA::ms_DefaultTxdIDMap; +std::unordered_set CModelInfoSA::ms_modelsModifiedAnim; union tIdeFlags { @@ -1517,6 +1519,28 @@ void CModelInfoSA::ResetAllVehiclesWheelSizes() ms_VehicleModelDefaultWheelSizes.clear(); } +void CModelInfoSA::StaticResetModelAnimations() +{ + for (auto& it : ms_modelsModifiedAnim) + { + CModelInfo* modelInfo = pGame->GetModelInfo(it); + if (modelInfo) + { + modelInfo->DisableObjectAnimation(false); + modelInfo->SetObjectAnimation(nullptr, 0, 0); + } + + CBaseModelInfoSAInterface* modelInfoInterface = modelInfo->GetInterface(); + if (modelInfoInterface) + { + modelInfoInterface->bHasAnimBlend = static_cast(modelInfoInterface)->m_nAnimFileIndex != -1; + modelInfoInterface->bAnimSomething = modelInfoInterface->bHasAnimBlend; + } + } + + ms_modelsModifiedAnim.clear(); +} + bool CModelInfoSA::SetCustomModel(RpClump* pClump) { if (!pClump) @@ -1938,6 +1962,8 @@ static void __declspec(naked) HOOK_NodeNameStreamRead() void CModelInfoSA::StaticSetHooks() { EZHookInstall(NodeNameStreamRead); + + HookInstallVTBLCall((void*)0x85BD5C, (std::uintptr_t)CClumpModelInfoSAInterface::CreateInstance); } // Recursive RwFrame children searching function @@ -2183,3 +2209,56 @@ bool CVehicleModelInfoSAInterface::IsComponentDamageable(int componentIndex) con { return pVisualInfo->m_maskComponentDamagable & (1 << componentIndex); } + +RpClump* __fastcall CClumpModelInfoSAInterface::CreateInstance(CClumpModelInfoSAInterface* clumpModelInfo) +{ + if (!clumpModelInfo->pRwObject) + return nullptr; + + clumpModelInfo->AddRef(); + + RpClump* clump = RpClumpClone(reinterpret_cast(clumpModelInfo->pRwObject)); + + if (IsClumpSkinned(clump) && !clumpModelInfo->bHasComplexHierarchy) + { + RpHAnimHierarchy* hier = GetAnimHierarchyFromClump(clump); + RpClumpForAllAtomics(clump, reinterpret_cast(0x4C4EF0), hier); + + RtAnimAnimation* animForHierarchy = RpAnimBlendCreateAnimationForHierarchy(hier); + RtAnimInterpolatorSetCurrentAnim(hier->currentAnim, animForHierarchy); + hier->flags = rpHANIMHIERARCHYUPDATEMODELLINGMATRICES | rpHANIMHIERARCHYUPDATELTMS; + } + + if (clumpModelInfo->bHasAnimBlend) + { + CAnimBlendHierarchySAInterface* anim = nullptr; + auto* modelInfo = pGame->GetModelInfo(static_cast(clumpModelInfo)); + eAnimationFlags flags = ANIMATION_IS_LOOPED; + + if (clumpModelInfo->m_nAnimFileIndex != -1) + { + if (!modelInfo || !modelInfo->IsObjectAnimationDisabled()) + { + if (auto block = pGame->GetAnimManager()->GetAnimationBlock(clumpModelInfo->m_nAnimFileIndex)) + { + if (auto animation = pGame->GetAnimManager()->GetAnimation(clumpModelInfo->ulHashKey, block)) + anim = animation->GetInterface(); + } + } + } + else if (modelInfo) + { + anim = modelInfo->GetObjectAnimation(); + flags = modelInfo->GetObjectAnimationFlags(); + } + + if (anim) + { + RpAnimBlendClumpInit(clump); + pGame->GetAnimManager()->BlendAnimation(clump, anim, flags, 1.0f); + } + } + + clumpModelInfo->RemoveRef(); + return clump; +} diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index b778f808be1..484057f9c38 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -15,6 +15,7 @@ #include #include "CRenderWareSA.h" #include "game/RenderWare.h" +#include class CPedModelInfoSA; class CPedModelInfoSAInterface; @@ -168,19 +169,47 @@ class CBaseModelInfoSAInterface { struct { - unsigned char bHasBeenPreRendered : 1; // we use this because we need to apply changes only once - unsigned char bAlphaTransparency : 1; // bDrawLast - unsigned char bAdditiveRender : 1; - unsigned char bDontWriteZBuffer : 1; - unsigned char bDontCastShadowsOn : 1; - unsigned char bDoWeOwnTheColModel : 1; - unsigned char bIsBackfaceCulled : 1; - unsigned char bIsColLoaded : 1; - unsigned char bIsRoad : 1; - unsigned char bHasComplexHierarchy : 1; - unsigned char bDontCollideWithFlyer : 1; - eModelSpecialType eSpecialModelType : 4; - unsigned char bWetRoadReflection : 1; // Used for tags + unsigned char bHasBeenPreRendered : 1; // we use this because we need to apply changes only once + unsigned char bAlphaTransparency : 1; // bDrawLast + unsigned char bAdditiveRender : 1; + unsigned char bDontWriteZBuffer : 1; + unsigned char bDontCastShadowsOn : 1; + unsigned char bDoWeOwnTheColModel : 1; + unsigned char bIsBackfaceCulled : 1; + unsigned char bIsColLoaded : 1; // isLod + + union + { + // Atomic flags + struct + { + unsigned char bIsRoad : 1; + unsigned char bAtomicFlag0x200 : 1; + unsigned char bDontCollideWithFlyer : 1; + eModelSpecialType eSpecialModelType : 4; + unsigned char bWetRoadReflection : 1; // Used for tags + }; + + // Vehicle flags + struct + { + unsigned char bUsesVehDummy : 1; + unsigned char unkVehFlag : 1; + unsigned char carMod : 5; + unsigned char bUseCommonVehicleDictionary : 1; + }; + + // Clump flags + struct + { + unsigned char bHasAnimBlend : 1; + unsigned char bHasComplexHierarchy : 1; + unsigned char bAnimSomething : 1; + unsigned char bOwnsCollisionModel : 1; + unsigned char unknownClumpFlag : 3; + unsigned char bTagDisabled : 1; + }; + }; }; unsigned short usFlags; @@ -232,6 +261,9 @@ class CBaseModelInfoSAInterface // +726 = Word array as referenced in CVehicleModelInfo::GetVehicleUpgrade(int) // +762 = Array of WORD containing something relative to paintjobs // +772 = Anim file index + + void AddRef() { ((void(__thiscall*)(CBaseModelInfoSAInterface*))0x4C4BA0)(this); } + void RemoveRef() { ((void(__thiscall*)(CBaseModelInfoSAInterface*))0x4C4BB0)(this); } }; static_assert(sizeof(CBaseModelInfoSAInterface) == 0x20, "Invalid size for CBaseModelInfoSAInterface"); @@ -252,6 +284,8 @@ class CClumpModelInfoSAInterface : public CBaseModelInfoSAInterface char* m_animFileName; uint32_t m_nAnimFileIndex; }; + + static RpClump* __fastcall CreateInstance(CClumpModelInfoSAInterface* clumpModelInfo); }; class CTimeModelInfoSAInterface : public CBaseModelInfoSAInterface @@ -355,6 +389,11 @@ class CModelInfoSA : public CModelInfo static std::unordered_map> ms_VehicleModelDefaultWheelSizes; static std::map ms_DefaultTxdIDMap; SVehicleSupportedUpgrades m_ModelSupportedUpgrades; + static std::unordered_set ms_modelsModifiedAnim; + CAnimBlendHierarchySAInterface* m_objectAimation{}; + bool m_objectAnimationDisabled{false}; + unsigned int m_objectAnimationBlockNameHash{0}; + eAnimationFlags m_animationFlags{}; public: CModelInfoSA(); @@ -449,6 +488,10 @@ class CModelInfoSA : public CModelInfo void ResetVehicleWheelSizes(std::pair* defaultSizes = nullptr) override; static void ResetAllVehiclesWheelSizes(); + static void InsertModelIntoModifiedAnimList(std::uint32_t modelId) { ms_modelsModifiedAnim.insert(modelId); } + static void RemoveModelFromModifiedAnimList(std::uint32_t modelId) { ms_modelsModifiedAnim.erase(modelId); } + static void StaticResetModelAnimations(); + // ONLY use for peds void GetVoice(short* psVoiceType, short* psVoice); void GetVoice(const char** pszVoiceType, const char** szVoice); @@ -492,6 +535,14 @@ class CModelInfoSA : public CModelInfo void RestoreObjectPropertiesGroup(); static void RestoreAllObjectsPropertiesGroups(); + void SetObjectAnimation(CAnimBlendHierarchySAInterface* anim, unsigned int blockNameHash, std::uint16_t flags) noexcept override { m_objectAimation = anim; InsertModelIntoModifiedAnimList(m_dwModelID); m_objectAnimationBlockNameHash = blockNameHash; m_animationFlags = static_cast(flags); } + CAnimBlendHierarchySAInterface* GetObjectAnimation() const noexcept override { return m_objectAimation; } + unsigned int GetObjectAnimationBlockNameHash() const noexcept override { return m_objectAnimationBlockNameHash; } + eAnimationFlags GetObjectAnimationFlags() const noexcept override { return m_animationFlags; } + + void DisableObjectAnimation(bool disable) noexcept override { m_objectAnimationDisabled = disable; InsertModelIntoModifiedAnimList(m_dwModelID); } + bool IsObjectAnimationDisabled() const noexcept override { return m_objectAnimationDisabled; } + // Vehicle towing functions bool IsTowableBy(CModelInfo* towingModel) override; diff --git a/Client/game_sa/CObjectSA.cpp b/Client/game_sa/CObjectSA.cpp index 556c4fa6c03..f0f248898c8 100644 --- a/Client/game_sa/CObjectSA.cpp +++ b/Client/game_sa/CObjectSA.cpp @@ -16,6 +16,8 @@ #include "CRopesSA.h" #include "CWorldSA.h" #include "CFireManagerSA.h" +#include "CAnimManagerSA.h" +#include "gamesa_renderware.h" extern CGameSA* pGame; @@ -322,3 +324,41 @@ bool CObjectSA::SetOnFire(bool onFire) return true; } + +void CObjectSA::SetAnimation(CAnimBlendHierarchySAInterface* animation, eAnimationFlags flags) +{ + if (!m_pInterface || !m_pInterface->m_pRwObject) + return; + + RpClump* clump = GetRpClump(); + + if (!RpAnimBlendClumpIsInitialized(clump) && animation) + RpAnimBlendClumpInit(clump); + + if (animation) + pGame->GetAnimManager()->BlendAnimation(clump, animation, flags, 1.0f); + else + { + for (auto assoc = pGame->GetAnimManager()->RpAnimBlendClumpGetFirstAssociation(clump); assoc; assoc = pGame->GetAnimManager()->RpAnimBlendGetNextAssociation(assoc)) + { + // Disable playing flag + short flags = assoc->GetFlags(); + flags &= ~ANIMATION_IS_PLAYING; + + assoc->SetFlags(flags); + } + } +} + +bool CObjectSA::SetAnimationSpeed(float speed) +{ + if (!m_pInterface || !m_pInterface->m_pRwObject) + return false; + + auto assoc = pGame->GetAnimManager()->RpAnimBlendClumpGetFirstAssociation(GetRpClump()); + if (!assoc) + return false; + + assoc->SetCurrentSpeed(speed); + return true; +} diff --git a/Client/game_sa/CObjectSA.h b/Client/game_sa/CObjectSA.h index 8b1d18b35e6..be7eb2bbcd6 100644 --- a/Client/game_sa/CObjectSA.h +++ b/Client/game_sa/CObjectSA.h @@ -156,6 +156,9 @@ class CObjectSA : public virtual CObject, public virtual CPhysicalSA bool IsOnFire() override { return GetObjectInterface()->pFire != nullptr; } bool SetOnFire(bool onFire) override; + void SetAnimation(class CAnimBlendHierarchySAInterface* animation, eAnimationFlags flags) override; + bool SetAnimationSpeed(float speed) override; + private: void CheckForGangTag(); }; diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index 774860f9ec2..ccde429cbf6 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -265,6 +265,81 @@ RwTexDictionary* CRenderWareSA::ReadTXD(const SString& strFilename, const SStrin return pTex; } +bool CRenderWareSA::DoContainHAnimPLG(const SString& filename, const SString& buffer) +{ + // Open the stream + RwStream* streamModel; + RwBuffer streamBuffer; + if (!buffer.empty()) + { + streamBuffer.ptr = (void*)buffer.data(); + streamBuffer.size = buffer.size(); + streamModel = RwStreamOpen(STREAM_TYPE_BUFFER, STREAM_MODE_READ, &streamBuffer); + } + else + streamModel = RwStreamOpen(STREAM_TYPE_FILENAME, STREAM_MODE_READ, *filename); + + std::size_t sizeToSkip = 0; + // Skip Clump header + sizeToSkip += 0xC; + // Skip first struct (header + data, it's always 0x18 bytes) + sizeToSkip += 0x18; + // Skip frame list header + sizeToSkip += 0xC; + // Skip frame list struct header + sizeToSkip += 0xC; + + RwStreamSkip(streamModel, sizeToSkip); + sizeToSkip = 0; + + // Read number of frames (4 bytes) + int frames = 0; + RwStreamRead(streamModel, &frames, 4); + // Skip all frames data (56 bytes each) + sizeToSkip += (56 * frames); + // Skip extension header + sizeToSkip += 0xC; + + RwStreamSkip(streamModel, sizeToSkip); + + int animChunks = 0; + int chunkType = 0; + int chunkLength = 0; + + // Read extension chunks + while (RwStreamReadChunkHeader(streamModel, &chunkType, &chunkLength, nullptr, nullptr)) + { + // If this is an extension chunk + if (chunkType == 0x3) + { + // Read chunks inside extension chunk + int extensionChunkSize = chunkLength; + while (extensionChunkSize > 0) + { + int subChunkType = 0; + int subChunkLength = 0; + RwStreamReadChunkHeader(streamModel, &subChunkType, &subChunkLength, nullptr, nullptr); + + // Is this a HAnimPLG chunk? + if (subChunkType == 0x11E) + animChunks++; + + RwStreamSkip(streamModel, subChunkLength); + extensionChunkSize -= (subChunkLength + 12); // 12 bytes for chunk header + } + } + + // Break if we reached the end of extensions + if (chunkType == 0x1A) + break; + } + + // Close the stream + RwStreamClose(streamModel, nullptr); + return animChunks > 0; +} + + // Reads and parses a DFF file specified by a path (szDFF) into a CModelInfo identified by the object id (usModelID) // bLoadEmbeddedCollisions should be true for vehicles // Any custom TXD should be imported before this call @@ -304,11 +379,12 @@ RpClump* CRenderWareSA::ReadDFF(const SString& strFilename, const SString& buffe return NULL; } + CModelInfo* modelInfo = pGame->GetModelInfo(usModelID); + if (bLoadEmbeddedCollisions) { // Vehicles have their collision loaded through the CollisionModel plugin, so we need to remove the current collision to prevent a memory leak. // This needs to be done here before reading the stream data, because plugins are read in RpClumpStreamRead. - CModelInfo* modelInfo = pGame->GetModelInfo(usModelID); if (modelInfo) { if (auto* modelInfoInterface = modelInfo->GetInterface()) @@ -339,6 +415,25 @@ RpClump* CRenderWareSA::ReadDFF(const SString& strFilename, const SString& buffe // close the stream RwStreamClose(streamModel, NULL); + // Set animation flags if needed + // Functions SetAnimFile and ConvertAnimFileIndex are useless for us as we have our custom implementation of objects animation loading + if (modelInfo) + { + auto* modelInfoInterface = modelInfo->GetInterface(); + if (!modelInfoInterface) + return pClump; + + // We need to parse the file again to check for HAnimPLG presence + bool hasAnim = DoContainHAnimPLG(strFilename, buffer); + bool defaultAnimFlag = modelInfoInterface->bHasAnimBlend; + + modelInfoInterface->bHasAnimBlend = hasAnim; + modelInfoInterface->bAnimSomething = hasAnim; + + if (hasAnim != defaultAnimFlag) + CModelInfoSA::InsertModelIntoModifiedAnimList(usModelID); + } + return pClump; } diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index dfab3585cab..3143875d755 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -42,6 +42,9 @@ class CRenderWareSA : public CRenderWare // Reads and parses a TXD file specified by a path (szTXD) RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer); + // Checks whether a DFF file contains an HAnimPLG chunk + bool DoContainHAnimPLG(const SString& filename, const SString& buffer); + // Reads and parses a DFF file specified by a path (szDFF) into a CModelInfo identified by the object id (usModelID) RpClump* ReadDFF(const SString& strFilename, const SString& buffer, unsigned short usModelID, bool bLoadEmbeddedCollisions); diff --git a/Client/game_sa/HookSystem.cpp b/Client/game_sa/HookSystem.cpp index 963647064e0..0fe1abd2e9c 100644 --- a/Client/game_sa/HookSystem.cpp +++ b/Client/game_sa/HookSystem.cpp @@ -25,3 +25,12 @@ void HookInstallCall(DWORD dwInstallAddress, DWORD dwHookFunction) MemPut(dwInstallAddress, 0xE8); MemPut(dwInstallAddress + 1, dwOffset); } + +void HookInstallVTBLCall(void* vtblMethodAddress, std::uintptr_t hookFunction) +{ + // We need to change the protection of the memory page to be able to write to it as it's in the .rdata section + DWORD op; + VirtualProtect(vtblMethodAddress, 4, PAGE_EXECUTE_READWRITE, &op); + *static_cast(vtblMethodAddress) = hookFunction; + VirtualProtect(vtblMethodAddress, 4, op, &op); +} diff --git a/Client/game_sa/HookSystem.h b/Client/game_sa/HookSystem.h index 01d39be2c09..e8058a30b08 100644 --- a/Client/game_sa/HookSystem.h +++ b/Client/game_sa/HookSystem.h @@ -52,6 +52,7 @@ void* FunctionPointerToVoidP(T func) BYTE* CreateJump(DWORD dwFrom, DWORD dwTo, BYTE* ByteArray); void HookInstallCall(DWORD dwInstallAddress, DWORD dwHookFunction); +void HookInstallVTBLCall(void* vtblMethodAddress, std::uintptr_t hookFunction); template bool HookInstall(DWORD dwInstallAddress, T dwHookHandler, int iJmpCodeSize = 5) diff --git a/Client/game_sa/gamesa_renderware.h b/Client/game_sa/gamesa_renderware.h index 4bfb5b43327..d72b6ee27df 100644 --- a/Client/game_sa/gamesa_renderware.h +++ b/Client/game_sa/gamesa_renderware.h @@ -106,6 +106,12 @@ typedef RpHAnimHierarchy*(__cdecl* GetAnimHierarchyFromSkinClump_t)(RpClump*); typedef int(__cdecl* RpHAnimIDGetIndex_t)(RpHAnimHierarchy*, int); typedef RwMatrix*(__cdecl* RpHAnimHierarchyGetMatrixArray_t)(RpHAnimHierarchy*); typedef RtQuat*(__cdecl* RtQuatRotate_t)(RtQuat* quat, const RwV3d* axis, float angle, RwOpCombineType combineOp); +typedef bool(__cdecl* RwStreamReadChunkHeader_t)(RwStream* stream, int* chunkType, int* chunkLength, int* version, int* buildNum); +typedef bool(__cdecl* IsClumpSkinned_t)(RpClump* clump); +typedef RtAnimAnimation*(__cdecl* RpAnimBlendCreateAnimationForHierarchy_t)(RpHAnimHierarchy* hierarchy); +typedef void(__cdecl* RtAnimInterpolatorSetCurrentAnim_t)(RtAnimInterpolator* interpolator, RtAnimAnimation* animation); +typedef void(__cdecl* RpAnimBlendClumpInit_t)(RpClump* clump); +typedef bool(__cdecl* RpAnimBlendClumpIsInitialized_t)(RpClump* clump); /*****************************************************************************/ /** Renderware function mappings **/ @@ -196,6 +202,12 @@ RWFUNC(GetAnimHierarchyFromSkinClump_t GetAnimHierarchyFromSkinClump, (GetAnimHi RWFUNC(RpHAnimIDGetIndex_t RpHAnimIDGetIndex, (RpHAnimIDGetIndex_t)0xDEAD) RWFUNC(RpHAnimHierarchyGetMatrixArray_t RpHAnimHierarchyGetMatrixArray, (RpHAnimHierarchyGetMatrixArray_t)0xDEAD) RWFUNC(RtQuatRotate_t RtQuatRotate, (RtQuatRotate_t)0xDEAD) +RWFUNC(RwStreamReadChunkHeader_t RwStreamReadChunkHeader, (RwStreamReadChunkHeader_t)0xDEAD) +RWFUNC(IsClumpSkinned_t IsClumpSkinned, (IsClumpSkinned_t)0xDEAD) +RWFUNC(RpAnimBlendCreateAnimationForHierarchy_t RpAnimBlendCreateAnimationForHierarchy, (RpAnimBlendCreateAnimationForHierarchy_t)0xDEAD) +RWFUNC(RtAnimInterpolatorSetCurrentAnim_t RtAnimInterpolatorSetCurrentAnim, (RtAnimInterpolatorSetCurrentAnim_t)0xDEAD) +RWFUNC(RpAnimBlendClumpInit_t RpAnimBlendClumpInit, (RpAnimBlendClumpInit_t)0xDEAD) +RWFUNC(RpAnimBlendClumpIsInitialized_t RpAnimBlendClumpIsInitialized, (RpAnimBlendClumpIsInitialized_t)0xDEAD) /*****************************************************************************/ /** GTA function definitions and mappings **/ diff --git a/Client/game_sa/gamesa_renderware.hpp b/Client/game_sa/gamesa_renderware.hpp index 1561333a140..66f0a94b84b 100644 --- a/Client/game_sa/gamesa_renderware.hpp +++ b/Client/game_sa/gamesa_renderware.hpp @@ -89,6 +89,12 @@ void InitRwFunctions() RpHAnimIDGetIndex = (RpHAnimIDGetIndex_t)0x7C51A0; RpHAnimHierarchyGetMatrixArray = (RpHAnimHierarchyGetMatrixArray_t)0x7C5120; RtQuatRotate = (RtQuatRotate_t)0x7EB7C0; + RwStreamReadChunkHeader = reinterpret_cast(0x7ED0F0); + IsClumpSkinned = reinterpret_cast(0x4C4DC0); + RpAnimBlendCreateAnimationForHierarchy = reinterpret_cast(0x4D60E0); + RtAnimInterpolatorSetCurrentAnim = reinterpret_cast(0x7CD5A0); + RpAnimBlendClumpInit = reinterpret_cast(0x4D6720); + RpAnimBlendClumpIsInitialized = reinterpret_cast(0x4D6760); SetTextureDict = (SetTextureDict_t)0x007319C0; LoadClumpFile = (LoadClumpFile_t)0x005371F0; diff --git a/Client/mods/deathmatch/logic/CClientBuilding.cpp b/Client/mods/deathmatch/logic/CClientBuilding.cpp index ebf4452fdf4..99d31571ed4 100644 --- a/Client/mods/deathmatch/logic/CClientBuilding.cpp +++ b/Client/mods/deathmatch/logic/CClientBuilding.cpp @@ -36,6 +36,22 @@ CClientBuilding::~CClientBuilding() m_pBuildingManager->RemoveFromList(this); } +void CClientBuilding::DoPulse() +{ + if (m_animation && !m_animationPlaying) + RunAnimation(); + + // Custom anim speed? + if (!m_animationSpeedUpdated) + { + if (m_pBuilding && m_pBuilding->GetRpClump()) + { + m_pBuilding->SetAnimationSpeed(m_animationSpeed); + m_animationSpeedUpdated = true; + } + } +} + void CClientBuilding::Unlink() { if (m_pHighBuilding) @@ -47,6 +63,14 @@ void CClientBuilding::Unlink() SetLowLodBuilding(); } Destroy(); + + // Remove from ifp list if needed + if (m_animationBlockNameHash > 0) + { + auto ifp = g_pClientGame->GetIFPPointerFromMap(m_animationBlockNameHash); + if (ifp) + ifp->RemoveEntityUsingThisIFP(this); + } } void CClientBuilding::SetPosition(const CVector& vecPosition) @@ -130,7 +154,7 @@ void CClientBuilding::Create() if (m_bBeingDeleted) return; - m_pBuilding = g_pGame->GetPools()->GetBuildingsPool().AddBuilding(this, m_usModelId, &m_vPos, &m_vRot, m_interior); + m_pBuilding = g_pGame->GetPools()->GetBuildingsPool().AddBuilding(this, m_usModelId, &m_vPos, &m_vRot, m_interior, m_animation != nullptr); if (!m_pBuilding) return; @@ -146,6 +170,9 @@ void CClientBuilding::Create() { m_pHighBuilding->GetBuildingEntity()->SetLod(m_pBuilding); } + + m_animationSpeedUpdated = false; + m_animationPlaying = false; } void CClientBuilding::Destroy() @@ -198,3 +225,37 @@ float CClientBuilding::GetDistanceFromCentreOfMassToBaseOfModel() { return m_pBuilding ? m_pBuilding->GetDistanceFromCentreOfMassToBaseOfModel() : 0.0f; } + +void CClientBuilding::SetAnimation(CAnimBlendHierarchySAInterface* animation, unsigned int blockNameHash, std::uint16_t flags) +{ + m_animation = animation; + m_animationBlockNameHash = blockNameHash; + m_animationFlags = static_cast(flags); + + Recreate(); + RunAnimation(); +} + +bool CClientBuilding::SetAnimationSpeed(float speed) +{ + m_animationSpeed = speed; + m_animationSpeedUpdated = false; + + if (m_pBuilding && m_pBuilding->GetRpClump()) + { + m_animationSpeedUpdated = true; + return m_pBuilding->SetAnimationSpeed(speed); + } + + return true; +} + +void CClientBuilding::RunAnimation() +{ + if (!m_pBuilding) + return; + + m_pBuilding->SetAnimation(m_animation, m_animationFlags); + m_pBuilding->SetAnimationSpeed(m_animationSpeed); + m_animationPlaying = m_animation != nullptr && m_pBuilding->GetRpClump(); +} diff --git a/Client/mods/deathmatch/logic/CClientBuilding.h b/Client/mods/deathmatch/logic/CClientBuilding.h index aadef38f7ea..96a5de1d544 100644 --- a/Client/mods/deathmatch/logic/CClientBuilding.h +++ b/Client/mods/deathmatch/logic/CClientBuilding.h @@ -23,6 +23,8 @@ class CClientBuilding : public CClientEntity CClientBuilding(class CClientManager* pManager, ElementID ID, uint16_t usModelId, const CVector &pos, const CVector &rot, uint8_t interior); ~CClientBuilding(); + void DoPulse(); + void Unlink(); void GetPosition(CVector& vecPosition) const override { vecPosition = m_vPos; }; void SetPosition(const CVector& vecPosition) override; @@ -50,13 +52,17 @@ class CClientBuilding : public CClientEntity bool IsValid() const noexcept { return m_pBuilding != nullptr; }; - CClientBuilding* GetLowLodBuilding() const noexcept { return m_pLowBuilding; }; bool SetLowLodBuilding(CClientBuilding* pLod = nullptr); bool IsLod() const noexcept { return m_pHighBuilding != nullptr; }; float GetDistanceFromCentreOfMassToBaseOfModel(); + void SetAnimation(class CAnimBlendHierarchySAInterface* animation, unsigned int blockNameHash, std::uint16_t flags); + bool SetAnimationSpeed(float speed); + + unsigned int GetAnimationBlockNameHash() const noexcept { return m_animationBlockNameHash; } + private: CClientBuilding* GetHighLodBuilding() const { return m_pHighBuilding; }; void SetHighLodBuilding(CClientBuilding* pHighBuilding = nullptr) { m_pHighBuilding = pHighBuilding; }; @@ -67,6 +73,8 @@ class CClientBuilding : public CClientEntity Create(); }; + void RunAnimation(); + private: CClientBuildingManager* m_pBuildingManager; @@ -79,4 +87,11 @@ class CClientBuilding : public CClientEntity CClientBuilding* m_pHighBuilding; CClientBuilding* m_pLowBuilding; + + CAnimBlendHierarchySAInterface* m_animation{}; + unsigned int m_animationBlockNameHash{0}; + bool m_animationPlaying{false}; + float m_animationSpeed{1.0f}; + eAnimationFlags m_animationFlags{}; + bool m_animationSpeedUpdated{false}; }; diff --git a/Client/mods/deathmatch/logic/CClientBuildingManager.cpp b/Client/mods/deathmatch/logic/CClientBuildingManager.cpp index 627f432e094..e7478940038 100644 --- a/Client/mods/deathmatch/logic/CClientBuildingManager.cpp +++ b/Client/mods/deathmatch/logic/CClientBuildingManager.cpp @@ -26,6 +26,12 @@ CClientBuildingManager::~CClientBuildingManager() RemoveAll(); } +void CClientBuildingManager::DoPulse() +{ + for (CClientBuilding* building : m_List) + building->DoPulse(); +} + void CClientBuildingManager::RemoveAll() { // Make sure they don't remove themselves from our list @@ -91,6 +97,15 @@ void CClientBuildingManager::DestroyAllForABit() } } +void CClientBuildingManager::RestreamBuildings(std::uint32_t modelId) +{ + for (CClientBuilding* building : m_List) + { + if (building->GetModel() == modelId) + building->Recreate(); + } +} + void CClientBuildingManager::RestoreDestroyed() { bool hasInvalidLods = true; diff --git a/Client/mods/deathmatch/logic/CClientBuildingManager.h b/Client/mods/deathmatch/logic/CClientBuildingManager.h index 11cc759e8d6..392d4391ec4 100644 --- a/Client/mods/deathmatch/logic/CClientBuildingManager.h +++ b/Client/mods/deathmatch/logic/CClientBuildingManager.h @@ -23,6 +23,8 @@ class CClientBuildingManager CClientBuildingManager(class CClientManager* pManager); ~CClientBuildingManager(); + void DoPulse(); + void RemoveAll(); bool Exists(CClientBuilding* pBuilding); @@ -35,6 +37,8 @@ class CClientBuildingManager bool SetPoolCapacity(size_t newCapacity); void DestroyAllForABit(); + void RestreamBuildings(std::uint32_t modelId); + void RestoreDestroyed(); void RestoreDestroyedSafe(); diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index 3d345a9fd28..5608fb8a79e 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -30,6 +30,7 @@ class CClientManager; #define IS_RADARMARKER(entity) ((entity)->GetType()==CCLIENTRADARMARKER) #define IS_VEHICLE(entity) ((entity)->GetType()==CCLIENTVEHICLE) #define IS_OBJECT(entity) ((entity)->GetType()==CCLIENTOBJECT) +#define IS_BUILDING(entity) ((entity)->GetType()==CCLIENTBUILDING) #define IS_MARKER(entity) ((entity)->GetType()==CCLIENTMARKER) #define IS_PICKUP(entity) ((entity)->GetType()==CCLIENTPICKUP) #define IS_RADAR_AREA(entity) ((entity)->GetType()==CCLIENTRADARAREA) diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 7632881badd..995d8723112 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -3462,6 +3462,7 @@ void CClientGame::Event_OnIngame() g_pGame->ResetModelFlags(); g_pGame->ResetAlphaTransparencies(); g_pGame->ResetModelTimes(); + g_pGame->ResetModelAnimations(); // Reset weapon render g_pGame->SetWeaponRenderEnabled(true); @@ -3900,6 +3901,7 @@ void CClientGame::PostWorldProcessHandler() m_pManager->GetMarkerManager()->DoPulse(); m_pManager->GetPointLightsManager()->DoPulse(); m_pManager->GetObjectManager()->DoPulse(); + m_pManager->GetBuildingManager()->DoPulse(); double dTimeSlice = m_TimeSliceTimer.Get(); m_TimeSliceTimer.Reset(); diff --git a/Client/mods/deathmatch/logic/CClientIFP.cpp b/Client/mods/deathmatch/logic/CClientIFP.cpp index 5694d2c3379..10b2dce5d46 100644 --- a/Client/mods/deathmatch/logic/CClientIFP.cpp +++ b/Client/mods/deathmatch/logic/CClientIFP.cpp @@ -36,6 +36,24 @@ void CClientIFP::Unlink() // Remove IFP animations from replaced animations of peds/players g_pClientGame->OnClientIFPUnload(pIFP); + + for (auto model : m_modelsUsingThisIFP) + { + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (modelInfo) + modelInfo->SetObjectAnimation(nullptr, 0, 0); + + m_pManager->GetObjectManager()->RestreamObjects(model); + m_pManager->GetBuildingManager()->RestreamBuildings(model); + } + + for (auto entity : m_entitiesUsingThisIFP) + { + if (entity->GetType() == eEntityType::ENTITY_TYPE_OBJECT) + static_cast(entity)->SetAnimation(nullptr, 0, 0); + else if (entity->GetType() == eEntityType::ENTITY_TYPE_BUILDING) + static_cast(entity)->SetAnimation(nullptr, 0, 0); + } } } diff --git a/Client/mods/deathmatch/logic/CClientIFP.h b/Client/mods/deathmatch/logic/CClientIFP.h index 233030f1016..f5ec6422ab7 100644 --- a/Client/mods/deathmatch/logic/CClientIFP.h +++ b/Client/mods/deathmatch/logic/CClientIFP.h @@ -218,6 +218,17 @@ class CClientIFP final : public CClientEntity, CFileReader void GetPosition(CVector& vecPosition) const {}; void SetPosition(const CVector& vecPosition){}; + void InsertModelUsingThisIFP(std::uint32_t modelId) { m_modelsUsingThisIFP.insert(modelId); } + void RemoveModelUsingThisIFP(std::uint32_t modelId) { m_modelsUsingThisIFP.erase(modelId); } + + void InsertEntityUsingThisIFP(CClientEntity* entity) { m_entitiesUsingThisIFP.push_back(entity); } + void RemoveEntityUsingThisIFP(CClientEntity* entity) + { + auto it = std::find(m_entitiesUsingThisIFP.begin(), m_entitiesUsingThisIFP.end(), entity); + if (it != m_entitiesUsingThisIFP.end()) + m_entitiesUsingThisIFP.erase(it); + } + private: bool ReadIFPByVersion(); void ReadIFPVersion1(); @@ -279,6 +290,9 @@ class CClientIFP final : public CClientEntity, CFileReader bool m_bUnloading; CAnimManager* m_pAnimManager; + std::unordered_set m_modelsUsingThisIFP{}; + std::vector m_entitiesUsingThisIFP{}; + // 32 because there are 32 bones in a ped model const unsigned short m_kcIFPSequences = 32; // We'll keep all key frames compressed by default. GTA:SA will decompress diff --git a/Client/mods/deathmatch/logic/CClientObject.cpp b/Client/mods/deathmatch/logic/CClientObject.cpp index 8137a191a2e..12b9ba82524 100644 --- a/Client/mods/deathmatch/logic/CClientObject.cpp +++ b/Client/mods/deathmatch/logic/CClientObject.cpp @@ -84,6 +84,14 @@ void CClientObject::Unlink() SetLowLodObject(NULL); while (!m_HighLodObjectList.empty()) m_HighLodObjectList[0]->SetLowLodObject(NULL); + + // Remove from ifp list if needed + if (m_animationBlockNameHash > 0) + { + auto ifp = g_pClientGame->GetIFPPointerFromMap(m_animationBlockNameHash); + if (ifp) + ifp->RemoveEntityUsingThisIFP(this); + } } void CClientObject::GetPosition(CVector& vecPosition) const @@ -436,6 +444,29 @@ void CClientObject::SetHealth(float fHealth) m_fHealth = fHealth; } +void CClientObject::SetAnimation(CAnimBlendHierarchySAInterface* animation, unsigned int blockNameHash, std::uint16_t flags) +{ + m_animation = animation; + m_animationBlockNameHash = blockNameHash; + m_animationFlags = static_cast(flags); + + RunAnimation(); +} + +bool CClientObject::SetAnimationSpeed(float speed) +{ + m_animationSpeed = speed; + m_animationSpeedUpdated = false; + + if (m_pObject && m_pObject->GetRpClump()) + { + m_animationSpeedUpdated = true; + return m_pObject->SetAnimationSpeed(speed); + } + + return true; +} + void CClientObject::StreamIn(bool bInstantly) { // Don't stream the object in, if respawn is disabled and the object is broken @@ -555,6 +586,9 @@ void CClientObject::Create() if (m_vecCenterOfMass.fX != 0.0f || m_vecCenterOfMass.fY != 0.0f || m_vecCenterOfMass.fZ != 0.0f) m_pObject->SetCenterOfMass(m_vecCenterOfMass); + m_animationPlaying = false; + m_animationSpeedUpdated = false; + // Reattach to an entity + any entities attached to this ReattachEntities(); @@ -657,6 +691,29 @@ void CClientObject::StreamedInPulse() UpdateStreamPosition(m_vecPosition); } } + + if (m_animation && !m_animationPlaying) + RunAnimation(); + + if (!m_animationSpeedUpdated) + { + if (m_pObject && m_pObject->GetRpClump()) + { + m_pObject->SetAnimationSpeed(m_animationSpeed); + m_animationSpeedUpdated = true; + } + } +} + +void CClientObject::RunAnimation() +{ + if (!m_pObject) + return; + + m_pObject->SetAnimation(m_animation, m_animationFlags); + m_pObject->SetAnimationSpeed(m_animationSpeed); + + m_animationPlaying = m_animation != nullptr && m_pObject->GetRpClump(); } void CClientObject::GetMoveSpeed(CVector& vecMoveSpeed) const diff --git a/Client/mods/deathmatch/logic/CClientObject.h b/Client/mods/deathmatch/logic/CClientObject.h index 03b37fbfbcc..867ae737a72 100644 --- a/Client/mods/deathmatch/logic/CClientObject.h +++ b/Client/mods/deathmatch/logic/CClientObject.h @@ -122,6 +122,11 @@ class CClientObject : public CClientStreamElement bool IsOnFire() override { return m_pObject ? m_pObject->IsOnFire() : false; } bool SetOnFire(bool onFire) override { return m_pObject ? m_pObject->SetOnFire(onFire) : false; }; + void SetAnimation(CAnimBlendHierarchySAInterface* animation, unsigned int blockNameHash, std::uint16_t flags); + bool SetAnimationSpeed(float speed); + + unsigned int GetAnimationBlockNameHash() const noexcept { return m_animationBlockNameHash; } + protected: void StreamIn(bool bInstantly); void StreamOut(); @@ -134,6 +139,8 @@ class CClientObject : public CClientStreamElement void StreamedInPulse(); + void RunAnimation(); + class CClientObjectManager* m_pObjectManager; class CClientModelRequestManager* m_pModelRequester; @@ -157,6 +164,9 @@ class CClientObject : public CClientStreamElement float m_fBuoyancyConstant; CVector m_vecCenterOfMass; bool m_bVisibleInAllDimensions = false; + bool m_animationPlaying{false}; + float m_animationSpeed{1.0f}; + bool m_animationSpeedUpdated{false}; CVector m_vecMoveSpeed; CVector m_vecTurnSpeed; @@ -167,6 +177,10 @@ class CClientObject : public CClientStreamElement bool m_IsHiddenLowLod; // true if this object is low LOD and should not be drawn std::shared_ptr m_clientModel; + CAnimBlendHierarchySAInterface* m_animation{}; + unsigned int m_animationBlockNameHash{0}; + eAnimationFlags m_animationFlags{}; + public: CObject* m_pObject; SLastSyncedObjectData m_LastSyncedData; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp index c763e1d3f4a..1b6f27e0ca1 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp @@ -1,4 +1,4 @@ -/***************************************************************************** +/***************************************************************************** * * PROJECT: Multi Theft Auto * (Shared logic for modifications) @@ -100,6 +100,8 @@ void CLuaElementDefs::LoadFunctions() {"setElementCallPropagationEnabled", SetElementCallPropagationEnabled}, {"setElementLighting", ArgumentParser}, {"setElementOnFire", ArgumentParser}, + {"setElementAnimation", ArgumentParser}, + {"setElementAnimationSpeed", ArgumentParser}, }; // Add functions @@ -195,6 +197,8 @@ void CLuaElementDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setStreamable", "setElementStreamable"); lua_classfunction(luaVM, "setLighting", "setElementLighting"); lua_classfunction(luaVM, "setOnFire", "setElementOnFire"); + lua_classfunction(luaVM, "setAnimation", "setElementAnimation"); + lua_classfunction(luaVM, "setAnimationSpeed", "setElementAnimationSpeed"); lua_classvariable(luaVM, "callPropagationEnabled", "setElementCallPropagationEnabled", "isElementCallPropagationEnabled"); lua_classvariable(luaVM, "waitingForGroundToLoad", NULL, "isElementWaitingForGroundToLoad"); @@ -2522,6 +2526,65 @@ bool CLuaElementDefs::SetElementOnFire(CClientEntity* entity, bool onFire) noexc return entity->SetOnFire(onFire); } +bool CLuaElementDefs::SetElementAnimation(CClientEntity* entity, std::optional> ifpOrNil, std::optional animationName, std::optional flags) +{ + if (!IS_OBJECT(entity) && !IS_BUILDING(entity)) + return false; + + auto getAnimBlockHash = [&](CClientEntity* ent) -> std::uint32_t + { + return IS_OBJECT(ent) ? static_cast(ent)->GetAnimationBlockNameHash() : static_cast(ent)->GetAnimationBlockNameHash(); + }; + + auto setEntityAnimation = [&](CClientEntity* ent, CAnimBlendHierarchySAInterface* anim, std::uint32_t blockHash, std::uint16_t flags) + { + if (IS_OBJECT(ent)) + static_cast(ent)->SetAnimation(anim, blockHash, flags); + else + static_cast(ent)->SetAnimation(anim, blockHash, flags); + }; + + // Clean up old IFP association before applying a new animation + if (auto hash = getAnimBlockHash(entity)) + { + auto ifp = g_pClientGame->GetIFPPointerFromMap(hash); + if (ifp) + ifp->RemoveEntityUsingThisIFP(entity); + } + + if (!ifpOrNil.has_value() || std::holds_alternative(ifpOrNil.value())) + { + setEntityAnimation(entity, nullptr, 0, 0); + return true; + } + + CClientIFP* ifp = std::get(*ifpOrNil); + auto animHierarchy = ifp->GetAnimationHierarchy(animationName.value_or("")); + if (!animHierarchy) + throw std::invalid_argument("Invalid animation name"); + + setEntityAnimation(entity, animHierarchy, ifp->GetBlockNameHash(), flags.value_or(eAnimationFlags::ANIMATION_IS_LOOPED)); + ifp->InsertEntityUsingThisIFP(entity); + + return true; +} + +bool CLuaElementDefs::SetElementAnimationSpeed(CClientEntity* entity, float speed) +{ + if (!IS_OBJECT(entity) && !IS_BUILDING(entity)) + return false; + + switch (entity->GetType()) + { + case CCLIENTOBJECT: + return static_cast(entity)->SetAnimationSpeed(speed); + case CCLIENTBUILDING: + return static_cast(entity)->SetAnimationSpeed(speed); + } + + return false; +} + int CLuaElementDefs::IsElementLowLod(lua_State* luaVM) { // bool isElementLowLOD ( element theElement ) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h index 3d82ddcb145..e26fe41e816 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h @@ -102,4 +102,7 @@ class CLuaElementDefs : public CLuaDefs LUA_DECLARE(SetElementCallPropagationEnabled); static bool SetElementLighting(CClientEntity* entity, float lighting); static bool SetElementOnFire(CClientEntity* entity, bool onFire) noexcept; + + static bool SetElementAnimation(CClientEntity* entity, std::optional> ifpOrNil, std::optional animationName, std::optional flags); + static bool SetElementAnimationSpeed(CClientEntity* entity, float speed); }; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index be075cb4c40..cedd45c1cd8 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -17,6 +17,7 @@ #include #include "CLuaEngineDefs.h" #include +#include "game/CAnimManager.h" //! Set the CModelCacheManager limits //! By passing `nil`/no value the original values are restored @@ -156,7 +157,8 @@ void CLuaEngineDefs::LoadFunctions() {"enginePreloadWorldArea", ArgumentParser}, {"engineRestreamModel", ArgumentParser}, {"engineRestream", ArgumentParser}, - + {"engineSetModelAnimation", ArgumentParser}, + {"engineRestoreModelAnimation", ArgumentParser}, // CLuaCFunctions::AddFunction ( "engineReplaceMatchingAtomics", EngineReplaceMatchingAtomics ); // CLuaCFunctions::AddFunction ( "engineReplaceWheelAtomics", EngineReplaceWheelAtomics ); @@ -206,6 +208,9 @@ void CLuaEngineDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setModelTXDID", "engineSetModelTXDID"); lua_classfunction(luaVM, "resetModelTXDID", "engineResetModelTXDID"); + lua_classfunction(luaVM, "setModelAnimation", "engineSetModelAnimation"); + lua_classfunction(luaVM, "resetModelAnimation", "engineRestoreModelAnimation"); + lua_registerstaticclass(luaVM, "Engine"); // `EngineStreaming` class @@ -2619,3 +2624,65 @@ void CLuaEngineDefs::EngineRestream(std::optional option) { g_pClientGame->Restream(option); } + +bool CLuaEngineDefs::EngineSetModelAnimation(std::uint16_t modelId, std::optional> ifpOrNil, std::optional animationName, std::optional flags) +{ + if (!CClientObjectManager::IsValidModel(modelId) && !CClientBuildingManager::IsValidModel(modelId)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(modelId); + if (!modelInfo) + return false; + + // Clean up old IFP association before applying a new animation + if (auto anim = modelInfo->GetObjectAnimation()) + { + auto ifp = g_pClientGame->GetIFPPointerFromMap(modelInfo->GetObjectAnimationBlockNameHash()); + if (ifp) + ifp->RemoveModelUsingThisIFP(modelId); + } + + if (!ifpOrNil.has_value() || std::holds_alternative(ifpOrNil.value())) + { + modelInfo->DisableObjectAnimation(true); + modelInfo->SetObjectAnimation(nullptr, 0, 0); + } + else if (std::holds_alternative(ifpOrNil.value())) + { + CClientIFP* ifp = std::get(ifpOrNil.value()); + auto animHierarchy = ifp->GetAnimationHierarchy(animationName.value_or("")); + if (!animHierarchy) + throw std::invalid_argument("Invalid animation name"); + + ifp->InsertModelUsingThisIFP(modelId); + modelInfo->SetObjectAnimation(animHierarchy, ifp->GetBlockNameHash(), flags.value_or(eAnimationFlags::ANIMATION_IS_LOOPED)); + } + + m_pManager->GetObjectManager()->RestreamObjects(modelId); + m_pManager->GetBuildingManager()->RestreamBuildings(modelId); + return true; +} + +void CLuaEngineDefs::EngineRestoreModelAnimation(std::uint16_t modelId) +{ + if (!CClientObjectManager::IsValidModel(modelId) && !CClientBuildingManager::IsValidModel(modelId)) + throw LuaFunctionError("Invalid model"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(modelId); + if (!modelInfo) + return; + + modelInfo->DisableObjectAnimation(false); + + if (auto anim = modelInfo->GetObjectAnimation()) + { + auto ifp = g_pClientGame->GetIFPPointerFromMap(modelInfo->GetObjectAnimationBlockNameHash()); + if (ifp) + ifp->RemoveModelUsingThisIFP(modelId); + } + + modelInfo->SetObjectAnimation(nullptr, 0, 0); + + m_pManager->GetObjectManager()->RestreamObjects(modelId); + m_pManager->GetBuildingManager()->RestreamBuildings(modelId); +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index 6a107c0ded0..0f30090c1dd 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -99,6 +99,9 @@ class CLuaEngineDefs : public CLuaDefs static bool EngineRestreamModel(std::uint16_t modelId); static void EngineRestream(std::optional option); + static bool EngineSetModelAnimation(std::uint16_t modelId, std::optional> ifpOrNil, std::optional animationName, std::optional flags); + static void EngineRestoreModelAnimation(std::uint16_t modelId); + private: static void AddEngineColClass(lua_State* luaVM); static void AddEngineTxdClass(lua_State* luaVM); diff --git a/Client/sdk/game/CAnimBlendAssociation.h b/Client/sdk/game/CAnimBlendAssociation.h index ce2c0fec9ee..1d3e3d21a94 100644 --- a/Client/sdk/game/CAnimBlendAssociation.h +++ b/Client/sdk/game/CAnimBlendAssociation.h @@ -24,6 +24,31 @@ enum class eAnimGroup; enum class eAnimID; struct Rpclump; +enum eAnimationFlags +{ + ANIMATION_DEFAULT = 0, // 0x0, + ANIMATION_IS_PLAYING = 1 << 0, // 0x1, + ANIMATION_IS_LOOPED = 1 << 1, // 0x2, + ANIMATION_IS_BLEND_AUTO_REMOVE = 1 << 2, //!< (0x4) Automatically `delete this` once faded out (`m_BlendAmount <= 0 && m_BlendDelta <= 0`) + ANIMATION_IS_FINISH_AUTO_REMOVE = 1 << 3, // 0x8, // Animation will be stuck on last frame, if not set + ANIMATION_IS_PARTIAL = 1 << 4, // 0x10, + ANIMATION_IS_SYNCRONISED = 1 << 5, // 0x20, + ANIMATION_CAN_EXTRACT_VELOCITY = 1 << 6, // 0x40, + ANIMATION_CAN_EXTRACT_X_VELOCITY = 1 << 7, // 0x80, + + // ** User defined flags ** + ANIMATION_WALK = 1 << 8, // 0x100, + ANIMATION_200 = 1 << 9, // 0x200, + ANIMATION_DONT_ADD_TO_PARTIAL_BLEND = 1 << 10, // 0x400, + ANIMATION_IS_FRONT = 1 << 11, // 0x800, + ANIMATION_SECONDARY_TASK_ANIM = 1 << 12, // 0x1000, + // ** + + ANIMATION_IGNORE_ROOT_TRANSLATION = 1 << 13, // 0x2000, + ANIMATION_REFERENCE_BLOCK = 1 << 14, // 0x4000, + ANIMATION_FACIAL = 1 << 15, // 0x8000 // The animation is never destroyed if this flag is set, NO MATTER WHAT +}; + class CAnimBlendAssociation { public: @@ -46,5 +71,6 @@ class CAnimBlendAssociation virtual float GetLength() const noexcept = 0; virtual void SetAnimID(short sAnimID) = 0; virtual void SetAnimGroup(short sAnimGroup) = 0; + virtual short GetFlags() const = 0; virtual void SetFlags(short sFlags) = 0; }; diff --git a/Client/sdk/game/CAnimManager.h b/Client/sdk/game/CAnimManager.h index 31e4c88e00e..da0476af230 100644 --- a/Client/sdk/game/CAnimManager.h +++ b/Client/sdk/game/CAnimManager.h @@ -80,6 +80,7 @@ class CAnimManager virtual AnimBlendAssoc_type AddAnimationAndSync(RpClump* pClump, CAnimBlendAssociation* pAssociation, AssocGroupId animGroup, AnimationId animID) = 0; virtual AnimBlendAssoc_type BlendAnimation(RpClump* pClump, AssocGroupId animGroup, AnimationId animID, float fBlendDelta) = 0; virtual AnimBlendAssoc_type BlendAnimation(RpClump* pClump, CAnimBlendHierarchy* pHierarchy, int ID, float fBlendDelta) = 0; + virtual AnimBlendAssoc_type BlendAnimation(RpClump* pClump, CAnimBlendHierarchySAInterface* pHierarchyInterface, int ID, float fBlendDelta) = 0; virtual void AddAnimBlockRef(int ID) = 0; virtual void RemoveAnimBlockRef(int ID) = 0; diff --git a/Client/sdk/game/CBuilding.h b/Client/sdk/game/CBuilding.h index 8969df3daab..f506952ad14 100644 --- a/Client/sdk/game/CBuilding.h +++ b/Client/sdk/game/CBuilding.h @@ -22,4 +22,6 @@ class CBuilding : public virtual CEntity virtual CBuildingSAInterface* GetBuildingInterface() = 0; virtual void SetLod(CBuilding* pLod) = 0; + virtual void SetAnimation(class CAnimBlendHierarchySAInterface* animation, enum eAnimationFlags flags) = 0; + virtual bool SetAnimationSpeed(float speed) = 0; }; diff --git a/Client/sdk/game/CBuildingsPool.h b/Client/sdk/game/CBuildingsPool.h index 85ad4b87391..3b64b61bece 100644 --- a/Client/sdk/game/CBuildingsPool.h +++ b/Client/sdk/game/CBuildingsPool.h @@ -22,7 +22,7 @@ class CBuildingsPool public: // Buildings pool - virtual CBuilding* AddBuilding(class CClientBuilding*, std::uint16_t modelId, CVector* vPos, CVector* vRot, std::uint8_t interior) = 0; + virtual CBuilding* AddBuilding(class CClientBuilding*, std::uint16_t modelId, CVector* vPos, CVector* vRot, std::uint8_t interior, bool anim) = 0; virtual void RemoveBuilding(CBuilding* pObject) = 0; virtual bool HasFreeBuildingSlot() = 0; virtual bool Resize(int size) = 0; diff --git a/Client/sdk/game/CGame.h b/Client/sdk/game/CGame.h index fbaba31d4b8..f6b4b041d2e 100644 --- a/Client/sdk/game/CGame.h +++ b/Client/sdk/game/CGame.h @@ -263,6 +263,7 @@ class __declspec(novtable) CGame virtual void ResetAlphaTransparencies() = 0; virtual void DisableVSync() = 0; virtual void ResetModelTimes() = 0; + virtual void ResetModelAnimations() = 0; virtual void OnPedContextChange(CPed* pPedContext) = 0; virtual CPed* GetPedContext() = 0; diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 7e5cc8ec65e..5976fa39d49 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -25,6 +25,7 @@ class CColModel; class CPedModelInfo; struct RpClump; struct RwObject; +enum eAnimationFlags; class CBoundingBox { @@ -254,6 +255,14 @@ class CModelInfo virtual unsigned short GetObjectPropertiesGroup() = 0; virtual void RestoreObjectPropertiesGroup() = 0; + virtual void SetObjectAnimation(CAnimBlendHierarchySAInterface* anim, unsigned int blockNameHash, std::uint16_t flags) noexcept = 0; + virtual CAnimBlendHierarchySAInterface* GetObjectAnimation() const noexcept = 0; + virtual unsigned int GetObjectAnimationBlockNameHash() const noexcept = 0; + virtual eAnimationFlags GetObjectAnimationFlags() const noexcept = 0; + + virtual void DisableObjectAnimation(bool disable) noexcept = 0; + virtual bool IsObjectAnimationDisabled() const noexcept = 0; + // Vehicle towing functions virtual bool IsTowableBy(CModelInfo* towingModel) = 0; diff --git a/Client/sdk/game/CObject.h b/Client/sdk/game/CObject.h index 1d65e4fc77f..0dfd978c965 100644 --- a/Client/sdk/game/CObject.h +++ b/Client/sdk/game/CObject.h @@ -42,4 +42,6 @@ class CObject : public virtual CPhysical virtual CVector* GetScale() = 0; virtual void ResetScale() = 0; + virtual void SetAnimation(class CAnimBlendHierarchySAInterface* animation, enum eAnimationFlags flags) = 0; + virtual bool SetAnimationSpeed(float speed) = 0; }; diff --git a/Client/sdk/game/RenderWare.h b/Client/sdk/game/RenderWare.h index d43099d0cc2..70d11317408 100644 --- a/Client/sdk/game/RenderWare.h +++ b/Client/sdk/game/RenderWare.h @@ -51,6 +51,7 @@ typedef struct RwObjectFrame RwObjectFrame; typedef struct RpAtomic RpAtomic; typedef struct RwCamera RwCamera; typedef struct RpLight RpLight; +typedef struct RtAnimAnimation RtAnimAnimation; typedef RwCamera* (*RwCameraPreCallback)(RwCamera* camera); typedef RwCamera* (*RwCameraPostCallback)(RwCamera* camera); @@ -200,6 +201,21 @@ enum RpLightFlags LIGHT_FLAGS_LAST = RW_STRUCT_ALIGN }; +enum RpHAnimHierarchyFlag +{ + /* creation flags */ + rpHANIMHIERARCHYSUBHIERARCHY = 0x01, /**< This hierarchy is a sub-hierarchy */ + rpHANIMHIERARCHYNOMATRICES = 0x02, /**< This hierarchy has no local matrices */ + + /* update flags */ + rpHANIMHIERARCHYUPDATEMODELLINGMATRICES = 0x1000, /**< This hierarchy updates modeling matrices */ + rpHANIMHIERARCHYUPDATELTMS = 0x2000, /**< This hierarchy updates LTMs */ + rpHANIMHIERARCHYLOCALSPACEMATRICES = 0x4000, /**< This hierarchy calculates matrices in a space + relative to its root */ + + rpHANIMHIERARCHYFLAGFORCEENUMSIZEINT = RWFORCEENUMSIZEINT +}; + // RenderWare/plugin base types struct RwObject {