From eb6b18a5d49a7f0f34bdbf42b15f933e42876cf8 Mon Sep 17 00:00:00 2001 From: Uladzislau Nikalayevich Date: Fri, 5 Apr 2024 06:07:05 +0300 Subject: [PATCH] Add removeAllGameBuildings / restoreAllGameBuildings API (#3275) --- Client/game_sa/CBuildingsPoolSA.cpp | 178 ++++++++++++++++++ Client/game_sa/CBuildingsPoolSA.h | 38 ++++ Client/game_sa/CGameSA.cpp | 20 ++ Client/game_sa/CGameSA.h | 5 + Client/game_sa/CIplSA.h | 37 ++++ Client/game_sa/CIplStoreSA.cpp | 103 ++++++++++ Client/game_sa/CIplStoreSA.h | 36 ++++ Client/game_sa/CPoolSAInterface.h | 151 +++++++++++++++ Client/game_sa/CPoolsSA.cpp | 122 ------------ Client/game_sa/CPoolsSA.h | 151 +-------------- Client/game_sa/CQuadTreeNodeSA.h | 56 ++++++ .../mods/deathmatch/logic/CClientBuilding.cpp | 4 +- .../mods/deathmatch/logic/CClientBuilding.h | 4 +- .../logic/luadefs/CLuaBuildingDefs.cpp | 48 ++++- .../logic/luadefs/CLuaBuildingDefs.h | 2 + Client/sdk/game/CBuildingsPool.h | 31 +++ Client/sdk/game/CGame.h | 4 + Client/sdk/game/CIplStore.h | 19 ++ Client/sdk/game/CPools.h | 10 +- Client/sdk/game/CWorld.h | 1 + 20 files changed, 743 insertions(+), 277 deletions(-) create mode 100644 Client/game_sa/CBuildingsPoolSA.cpp create mode 100644 Client/game_sa/CBuildingsPoolSA.h create mode 100644 Client/game_sa/CIplSA.h create mode 100644 Client/game_sa/CIplStoreSA.cpp create mode 100644 Client/game_sa/CIplStoreSA.h create mode 100644 Client/game_sa/CPoolSAInterface.h create mode 100644 Client/game_sa/CQuadTreeNodeSA.h create mode 100644 Client/sdk/game/CBuildingsPool.h create mode 100644 Client/sdk/game/CIplStore.h diff --git a/Client/game_sa/CBuildingsPoolSA.cpp b/Client/game_sa/CBuildingsPoolSA.cpp new file mode 100644 index 0000000000..76e14cd34e --- /dev/null +++ b/Client/game_sa/CBuildingsPoolSA.cpp @@ -0,0 +1,178 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CBuildingsPoolSA.cpp + * PURPOSE: Buildings pool class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CBuildingsPoolSA.h" + +#include "CFileLoaderSA.h" +#include +#include "CGameSA.h" +#include "CPtrNodeSingleListSA.h" + +extern CGameSA* pGame; + +class CClientEntity; + +CBuildingsPoolSA::CBuildingsPoolSA() : m_pOriginalBuildingsBackup(nullptr) +{ + m_ppBuildingPoolInterface = (CPoolSAInterface**)0xB74498; +} + +inline bool CBuildingsPoolSA::AddBuildingToPool(CClientBuilding* pClientBuilding, CBuildingSA* pBuilding) +{ + // Grab the new object interface + CBuildingSAInterface* pInterface = pBuilding->GetBuildingInterface(); + + if (!pInterface) + return false; + + uint32_t dwElementIndexInPool = (*m_ppBuildingPoolInterface)->GetObjectIndexSafe(pInterface); + if (dwElementIndexInPool == UINT_MAX) + return false; + + m_buildingPool.arrayOfClientEntities[dwElementIndexInPool] = {pBuilding, (CClientEntity*)pClientBuilding}; + + // Increase the count of objects + ++m_buildingPool.ulCount; + + return true; +} + +CBuilding* CBuildingsPoolSA::AddBuilding(CClientBuilding* pClientBuilding, uint16_t modelId, CVector* vPos, CVector4D* vRot, uint8_t interior) +{ + if (!HasFreeBuildingSlot()) + return nullptr; + + // Load building + SFileObjectInstance instance; + instance.modelID = modelId; + instance.lod = -1; + instance.interiorID = interior; + instance.position = *vPos; + instance.rotation = *vRot; + + // Fix strange SA rotation + instance.rotation.fW = -instance.rotation.fW; + + auto pBuilding = static_cast(CFileLoaderSA::LoadObjectInstance(&instance)); + + // Disable lod and ipl + pBuilding->m_pLod = nullptr; + pBuilding->m_iplIndex = 0; + + // Always stream model collosion + // TODO We can setup collison bounding box and use GTA streamer for it + auto modelInfo = pGame->GetModelInfo(modelId); + modelInfo->AddColRef(); + + // Add building in world + auto pBuildingSA = new CBuildingSA(pBuilding); + pGame->GetWorld()->Add(pBuildingSA, CBuildingPool_Constructor); + + // Add CBuildingSA object in pool + AddBuildingToPool(pClientBuilding, pBuildingSA); + + return pBuildingSA; +} + +void CBuildingsPoolSA::RemoveBuilding(CBuilding* pBuilding) +{ + assert(NULL != pBuilding); + + CBuildingSAInterface* pInterface = pBuilding->GetBuildingInterface(); + + uint32_t dwElementIndexInPool = (*m_ppBuildingPoolInterface)->GetObjectIndexSafe(pInterface); + if (dwElementIndexInPool == UINT_MAX) + return; + + // Remove building from world + pGame->GetWorld()->Remove(pInterface, CBuildingPool_Destructor); + + // Remove building from cover list + CPtrNodeSingleListSAInterface* coverList = reinterpret_cast*>(0xC1A2B8); + coverList->RemoveItem(pInterface); + + // Remove plant + using CPlantColEntry_Remove = CEntitySAInterface* (*)(CEntitySAInterface*); + ((CPlantColEntry_Remove)0x5DBEF0)(pInterface); + + // Remove col reference + auto modelInfo = pGame->GetModelInfo(pBuilding->GetModelIndex()); + modelInfo->RemoveColRef(); + + // Remove from BuildingSA pool + auto* pBuildingSA = m_buildingPool.arrayOfClientEntities[dwElementIndexInPool].pEntity; + m_buildingPool.arrayOfClientEntities[dwElementIndexInPool] = {nullptr, nullptr}; + + // Delete it from memory + delete pBuildingSA; + + // Remove building from SA pool + (*m_ppBuildingPoolInterface)->Release(dwElementIndexInPool); + + // Decrease the count of elements in the pool + --m_buildingPool.ulCount; +} + +void CBuildingsPoolSA::RemoveAllBuildings() +{ + if (m_pOriginalBuildingsBackup) + return; + + m_pOriginalBuildingsBackup = std::make_unique, MAX_BUILDINGS>>(); + + auto pBuildsingsPool = (*m_ppBuildingPoolInterface); + for (size_t i = 0; i < MAX_BUILDINGS; i++) + { + if (pBuildsingsPool->IsContains(i)) + { + auto building = pBuildsingsPool->GetObject(i); + + pGame->GetWorld()->Remove(building, CBuildingPool_Destructor); + + pBuildsingsPool->Release(i); + + (*m_pOriginalBuildingsBackup)[i].first = true; + (*m_pOriginalBuildingsBackup)[i].second = *building; + } + else + { + (*m_pOriginalBuildingsBackup)[i].first = false; + } + } +} + +void CBuildingsPoolSA::RestoreAllBuildings() +{ + if (!m_pOriginalBuildingsBackup) + return; + + auto& originalData = *m_pOriginalBuildingsBackup; + auto pBuildsingsPool = (*m_ppBuildingPoolInterface); + for (size_t i = 0; i < MAX_BUILDINGS; i++) + { + if (originalData[i].first) + { + pBuildsingsPool->AllocateAt(i); + auto building = pBuildsingsPool->GetObject(i); + *building = originalData[i].second; + + pGame->GetWorld()->Add(building, CBuildingPool_Constructor); + } + } + + m_pOriginalBuildingsBackup.release(); +} + +bool CBuildingsPoolSA::HasFreeBuildingSlot() +{ + return (*m_ppBuildingPoolInterface)->GetFreeSlot() != -1; +} diff --git a/Client/game_sa/CBuildingsPoolSA.h b/Client/game_sa/CBuildingsPoolSA.h new file mode 100644 index 0000000000..25854e3b2e --- /dev/null +++ b/Client/game_sa/CBuildingsPoolSA.h @@ -0,0 +1,38 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CBuildingsPoolSA.h + * PURPOSE: Buildings pool class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include +#include +#include "CPoolSAInterface.h" +#include "CBuildingSA.h" + +class CBuildingsPoolSA : public CBuildingsPool +{ +public: + CBuildingsPoolSA(); + ~CBuildingsPoolSA() = default; + + CBuilding* AddBuilding(CClientBuilding*, uint16_t modelId, CVector* vPos, CVector4D* vRot, uint8_t interior); + void RemoveBuilding(CBuilding* pBuilding); + bool HasFreeBuildingSlot(); + + void RemoveAllBuildings(); + void RestoreAllBuildings(); + +private: + bool AddBuildingToPool(CClientBuilding* pClientBuilding, CBuildingSA* pBuilding); + +private: + SPoolData m_buildingPool; + CPoolSAInterface** m_ppBuildingPoolInterface; + + std::unique_ptr, MAX_BUILDINGS>> m_pOriginalBuildingsBackup; +}; diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index 51fc34e1ba..236fad9fd7 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -56,6 +56,7 @@ #include "CWeatherSA.h" #include "CWorldSA.h" #include "D3DResourceSystemSA.h" +#include "CIplStoreSA.h" extern CGameSA* pGame; @@ -137,6 +138,7 @@ CGameSA::CGameSA() m_pWeaponStatsManager = new CWeaponStatManagerSA(); m_pPointLights = new CPointLightsSA(); m_collisionStore = new CColStoreSA(); + m_pIplStore = new CIplStoreSA(); // Normal weapon types (WEAPONSKILL_STD) for (int i = 0; i < NUM_WeaponInfosStdSkill; i++) @@ -273,6 +275,7 @@ CGameSA::~CGameSA() delete reinterpret_cast(m_pAudioContainer); delete reinterpret_cast(m_pPointLights); delete static_cast(m_collisionStore); + delete static_cast(m_pIplStore); delete[] ModelInfo; delete[] ObjectGroupsInfo; @@ -425,6 +428,9 @@ void CGameSA::Reset() // Restore changed TXD IDs CModelInfoSA::StaticResetTextureDictionaries(); + + // Restore default world state + RestoreGameBuildings(); } } @@ -882,6 +888,20 @@ void CGameSA::GetShaderReplacementStats(SShaderReplacementStats& outStats) m_pRenderWare->GetShaderReplacementStats(outStats); } +void CGameSA::RemoveAllBuildings() +{ + m_pIplStore->SetDynamicIplStreamingEnabled(false); + + m_pPools->GetBuildingsPool().RemoveAllBuildings(); +} + +void CGameSA::RestoreGameBuildings() +{ + m_pPools->GetBuildingsPool().RestoreAllBuildings(); + + m_pIplStore->SetDynamicIplStreamingEnabled(true, [](CIplSAInterface* ipl) { return memcmp("barriers", ipl->name, 8) != 0; }); +} + // Ensure models have the default lod distances void CGameSA::ResetModelLodDistances() { diff --git a/Client/game_sa/CGameSA.h b/Client/game_sa/CGameSA.h index e7aa9c3cfd..5a2be9f3ae 100644 --- a/Client/game_sa/CGameSA.h +++ b/Client/game_sa/CGameSA.h @@ -152,6 +152,7 @@ class CGameSA : public CGame CColStore* GetCollisionStore() override { return m_collisionStore; } CRenderWareSA* GetRenderWareSA() { return m_pRenderWare; } CFxManagerSA* GetFxManagerSA() { return m_pFxManager; } + CIplStore* GetIplStore() { return m_pIplStore; }; CWeaponInfo* GetWeaponInfo(eWeaponType weapon, eWeaponSkill skill = WEAPONSKILL_STD); CModelInfo* GetModelInfo(DWORD dwModelID, bool bCanBeInvalid = false); @@ -273,6 +274,9 @@ class CGameSA : public CGame PostWeaponFireHandler* m_pPostWeaponFireHandler; TaskSimpleBeHitHandler* m_pTaskSimpleBeHitHandler; + void RemoveAllBuildings(); + void RestoreGameBuildings(); + private: CPools* m_pPools; CPlayerInfo* m_pPlayerInfo; @@ -320,6 +324,7 @@ class CGameSA : public CGame CGameSettings* m_pSettings; CCarEnterExit* m_pCarEnterExit; CControllerConfigManager* m_pControllerConfigManager; + CIplStore* m_pIplStore; eGameVersion m_eGameVersion; bool m_bAsyncScriptEnabled; diff --git a/Client/game_sa/CIplSA.h b/Client/game_sa/CIplSA.h new file mode 100644 index 0000000000..e230d1caab --- /dev/null +++ b/Client/game_sa/CIplSA.h @@ -0,0 +1,37 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CIplSA.h + * PURPOSE: Header file for game IPL class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include +#include "CRect.h" + +class CIplSAInterface +{ +public: + CRect rect; + char name[16]; + uint16_t unk; + uint16_t minBuildId; + uint16_t maxBuildId; + uint16_t minBummyId; + uint16_t maxDummyId; + uint16_t relatedIpl; + uint8_t interior; + uint8_t unk2; + uint8_t bLoadReq; + uint8_t bDisabledStreaming; + uint8_t unk3; + uint8_t unk4; + uint8_t unk5; + uint8_t unk6; +}; +static_assert(sizeof(CIplSAInterface) == 0x34, "Wrong CIplSAInterface size"); diff --git a/Client/game_sa/CIplStoreSA.cpp b/Client/game_sa/CIplStoreSA.cpp new file mode 100644 index 0000000000..2069b40c4e --- /dev/null +++ b/Client/game_sa/CIplStoreSA.cpp @@ -0,0 +1,103 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CIplStore.cpp + * PURPOSE: IPL store class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +#include "CIplStoreSA.h" +#include "CQuadTreeNodeSA.h" + +static auto gIplQuadTree = (CQuadTreeNodesSAInterface**)0x8E3FAC; + +CIplStoreSA::CIplStoreSA() : m_isStreamingEnabled(true), m_ppIplPoolInterface((CPoolSAInterface**)0x8E3FB0) +{ +} + +void CIplStoreSA::UnloadAndDisableStreaming(int iplId) +{ + typedef void*(__cdecl * Function_EnableStreaming)(int); + ((Function_EnableStreaming)(0x405890))(iplId); +} + +void CIplStoreSA::EnableStreaming(int iplId) +{ + auto ipl = (*m_ppIplPoolInterface)->GetObject(iplId); + ipl->bDisabledStreaming = false; + + (*gIplQuadTree)->AddItem(ipl, &ipl->rect); +} + +void CIplStoreSA::SetDynamicIplStreamingEnabled(bool state) +{ + if (m_isStreamingEnabled == state) + return; + + // Ipl with 0 index is generic + // We don't unload this IPL + + auto pPool = *m_ppIplPoolInterface; + if (!state) + { + for (int i = 1; i < pPool->m_nSize; i++) + { + if (pPool->IsContains(i)) + { + UnloadAndDisableStreaming(i); + } + } + (*gIplQuadTree)->RemoveAllItems(); + } + else + { + for (int i = 1; i < pPool->m_nSize; i++) + { + if (pPool->IsContains(i)) + { + EnableStreaming(i); + } + } + } + + m_isStreamingEnabled = state; +} + +void CIplStoreSA::SetDynamicIplStreamingEnabled(bool state, std::function filter) +{ + if (m_isStreamingEnabled == state) + return; + + // Ipl with 0 index is generic + // We don't unload this IPL + + auto pPool = *m_ppIplPoolInterface; + if (!state) + { + for (int i = 1; i < pPool->m_nSize; i++) + { + if (pPool->IsContains(i) && filter(pPool->GetObject(i))) + { + UnloadAndDisableStreaming(i); + } + } + (*gIplQuadTree)->RemoveAllItems(); + } + else + { + for (int i = 1; i < pPool->m_nSize; i++) + { + if (pPool->IsContains(i) && filter(pPool->GetObject(i))) + { + EnableStreaming(i); + } + } + } + + m_isStreamingEnabled = state; +} diff --git a/Client/game_sa/CIplStoreSA.h b/Client/game_sa/CIplStoreSA.h new file mode 100644 index 0000000000..1751ecd4f7 --- /dev/null +++ b/Client/game_sa/CIplStoreSA.h @@ -0,0 +1,36 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CIplStore.h + * PURPOSE: IPL store class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CIplSA.h" +#include "CPoolsSA.h" +#include +#include + +class CIplStoreSA : public CIplStore +{ +public: + CIplStoreSA(); + ~CIplStoreSA() = default; + + void SetDynamicIplStreamingEnabled(bool state); + void SetDynamicIplStreamingEnabled(bool state, std::function filter); + +private: + void UnloadAndDisableStreaming(int iplId); + void EnableStreaming(int iplId); + +private: + CPoolSAInterface** m_ppIplPoolInterface; + + bool m_isStreamingEnabled; +}; diff --git a/Client/game_sa/CPoolSAInterface.h b/Client/game_sa/CPoolSAInterface.h new file mode 100644 index 0000000000..8b653aefc1 --- /dev/null +++ b/Client/game_sa/CPoolSAInterface.h @@ -0,0 +1,151 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CPoolsSA.h + * PURPOSE: Header file for pools interface + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include + +// size of tPoolObjectFlags is 1 byte only +union tPoolObjectFlags +{ + struct + { + unsigned char nId : 7; + bool bEmpty : 1; + }; + +private: + unsigned char nValue; +}; + +template +class CPoolSAInterface +{ +public: + // m_pObjects contains all interfaces. 140 maximum for ped objects. + B* m_pObjects; + tPoolObjectFlags* m_byteMap; + int m_nSize; + int m_nFirstFree; + bool m_bOwnsAllocations; + bool field_11; + + // Default constructor for statically allocated pools + CPoolSAInterface() + { + // Remember to call CPool::Init to fill in the fields! + m_pObjects = nullptr; + m_byteMap = nullptr; + m_nSize = 0; + m_bOwnsAllocations = false; + } + + uint GetFreeSlot() + { + bool bLooped = false; + uint index = m_nFirstFree + 1; + + while (true) + { + if (index >= m_nSize) + { + if (bLooped) + return -1; + + index = 0; + bLooped = true; + } + + if (m_byteMap[index].bEmpty) + { + m_nFirstFree = index; + return index; + } + index++; + } + + return -1; + }; + + B* Allocate() + { + m_nFirstFree++; // Continue after the last allocated slot + const auto sz = m_nSize; // Storing size to avoid reloads from memory - should help out the optimizer + for (auto i = 0u; i < sz; i++) + { + const auto slot = (m_nFirstFree + i) % sz; + const auto e = &m_byteMap[slot]; + if (!e->bEmpty) + { + continue; + } + m_nFirstFree = slot; + e->bEmpty = false; + e->nId++; + return &m_pObjects[slot]; + } + return nullptr; + } + + B* AllocateAt(uint uiSlot) + { + m_pObjects[uiSlot] = B(); + m_byteMap[uiSlot].bEmpty = false; + m_byteMap[uiSlot].nId ^= uiSlot ^ (uiSlot + 1); + + return &m_pObjects[uiSlot]; + } + + void Release(uint index) + { + m_byteMap[index].bEmpty = true; + m_byteMap[index].nId = 0; + if (index == m_nFirstFree) + --m_nFirstFree; + } + + void Delete(uint index) { Release(index); } + + bool IsEmpty(std::int32_t objectIndex) { return m_byteMap[objectIndex].bEmpty; } + bool IsContains(uint index) + { + if (m_nSize <= index) + return false; + return !IsEmpty(index); + } + + B* GetObject(std::int32_t objectIndex) { return &m_pObjects[objectIndex]; } + + uint GetObjectIndex(B* pObject) { return ((DWORD)pObject - (DWORD)m_pObjects) / sizeof(B); } + + uint32_t GetObjectIndexSafe(B* pObject) + { + uint32_t index = GetObjectIndex(pObject); + return index > m_nSize ? UINT_MAX : index; + } +}; + +// Generic container for pools +template +struct SPoolData +{ + std::array, MAX> arrayOfClientEntities; + unsigned long ulCount; + +public: + SPoolData() : ulCount(0UL) + { + for (unsigned int i = 0; i < MAX; ++i) + { + arrayOfClientEntities[i] = {nullptr, nullptr}; + } + } +}; diff --git a/Client/game_sa/CPoolsSA.cpp b/Client/game_sa/CPoolsSA.cpp index e3810ec443..2495cfcdc4 100644 --- a/Client/game_sa/CPoolsSA.cpp +++ b/Client/game_sa/CPoolsSA.cpp @@ -36,7 +36,6 @@ CPoolsSA::CPoolsSA() m_ppObjectPoolInterface = (CPoolSAInterface**)0xB7449C; m_ppVehiclePoolInterface = (CPoolSAInterface**)0xB74494; m_ppTxdPoolInterface = (CPoolSAInterface**)0xC8800C; - m_ppBuildingPoolInterface = (CPoolSAInterface**)0xB74498; m_bGetVehicleEnabled = true; } @@ -333,115 +332,6 @@ void CPoolsSA::DeleteAllObjects() } } -////////////////////////////////////////////////////////////////////////////////////////// -// BUILDINGS POOL // -////////////////////////////////////////////////////////////////////////////////////////// - -inline bool CPoolsSA::AddBuildingToPool(CClientBuilding* pClientBuilding, CBuildingSA* pBuilding) -{ - // Grab the new object interface - CBuildingSAInterface* pInterface = pBuilding->GetBuildingInterface(); - - if (!pInterface) - return false; - - DWORD dwElementIndexInPool = GetBuildingPoolIndex((std::uint8_t*)pInterface); - if (dwElementIndexInPool >= MAX_BUILDINGS) - { - return false; - } - - m_buildingPool.arrayOfClientEntities[dwElementIndexInPool] = {pBuilding, (CClientEntity*)pClientBuilding}; - - // Increase the count of objects - ++m_buildingPool.ulCount; - - return true; -} - -CBuilding* CPoolsSA::AddBuilding(CClientBuilding* pClientBuilding, uint16_t modelId, CVector *vPos, CVector4D *vRot, uint8_t interior) -{ - if (!HasFreeBuildingSlot()) - return nullptr; - - // Load building - SFileObjectInstance instance; - instance.modelID = modelId; - instance.lod = -1; - instance.interiorID = interior; - instance.position = *vPos; - instance.rotation = *vRot; - - // Fix strange SA rotation - instance.rotation.fW = -instance.rotation.fW; - - auto pBuilding = static_cast(CFileLoaderSA::LoadObjectInstance(&instance)); - - // Disable lod and ipl - pBuilding->m_pLod = nullptr; - pBuilding->m_iplIndex = 0; - - // Always stream model collosion - // TODO We can setup collison bounding box and use GTA streamer for it - auto modelInfo = pGame->GetModelInfo(modelId); - modelInfo->AddColRef(); - - // Add building in world - auto pBuildingSA = new CBuildingSA(pBuilding); - pGame->GetWorld()->Add(pBuildingSA, CBuildingPool_Constructor); - - // Add CBuildingSA object in pool - AddBuildingToPool(pClientBuilding, pBuildingSA); - - return pBuildingSA; -} - -void CPoolsSA::RemoveBuilding(CBuilding* pBuilding) -{ - assert(NULL != pBuilding); - - CBuildingSAInterface* pInterface = pBuilding->GetBuildingInterface(); - - DWORD dwElementIndexInPool = GetBuildingPoolIndex((std::uint8_t*)pInterface); - if (dwElementIndexInPool >= MAX_BUILDINGS) - { - return; - } - - // Remove building from world - pGame->GetWorld()->Remove(pInterface, CBuildingPool_Destructor); - - // Remove building from cover list - CPtrNodeSingleListSAInterface* coverList = reinterpret_cast*>(0xC1A2B8); - coverList->RemoveItem(pInterface); - - // Remove plant - using CPlantColEntry_Remove = CEntitySAInterface* (*)(CEntitySAInterface*); - ((CPlantColEntry_Remove)0x5DBEF0)(pInterface); - - // Remove col reference - auto modelInfo = pGame->GetModelInfo(pBuilding->GetModelIndex()); - modelInfo->RemoveColRef(); - - // Remove from BuildingSA pool - auto* pBuildingSA = m_buildingPool.arrayOfClientEntities[dwElementIndexInPool].pEntity; - m_buildingPool.arrayOfClientEntities[dwElementIndexInPool] = {nullptr, nullptr}; - - // Delete it from memory - delete pBuildingSA; - - // Remove building from SA pool - (*m_ppBuildingPoolInterface)->Release(dwElementIndexInPool); - - // Decrease the count of elements in the pool - --m_buildingPool.ulCount; -} - -bool CPoolsSA::HasFreeBuildingSlot() -{ - return (*m_ppBuildingPoolInterface)->GetFreeSlot() != -1; -} - ////////////////////////////////////////////////////////////////////////////////////////// // PEDS POOL // ////////////////////////////////////////////////////////////////////////////////////////// @@ -816,18 +706,6 @@ DWORD CPoolsSA::GetObjectPoolIndex(std::uint8_t* pInterface) return ((pInterface - pTheObjects) / dwAlignedSize); } -DWORD CPoolsSA::GetBuildingPoolIndex(std::uint8_t* pInterface) -{ - DWORD dwAlignedSize = sizeof(CBuildingSAInterface); - std::uint8_t* pTheObjects = (std::uint8_t*)(*m_ppBuildingPoolInterface)->m_pObjects; - DWORD dwMaxIndex = MAX_BUILDINGS - 1; - if (pInterface < pTheObjects || pInterface > pTheObjects + (dwMaxIndex * dwAlignedSize)) - { - return MAX_BUILDINGS; - } - return ((pInterface - pTheObjects) / dwAlignedSize); -} - uint CPoolsSA::GetModelIdFromClump(RpClump* pRpClump) { // Search our pools for a match diff --git a/Client/game_sa/CPoolsSA.h b/Client/game_sa/CPoolsSA.h index 106846b162..f3b09a4101 100644 --- a/Client/game_sa/CPoolsSA.h +++ b/Client/game_sa/CPoolsSA.h @@ -16,126 +16,12 @@ #include "CObjectSA.h" #include "CBuildingSA.h" #include "CTextureDictonarySA.h" +#include "CBuildingsPoolSA.h" #define INVALID_POOL_ARRAY_ID 0xFFFFFFFF class CClientEntity; -// size of tPoolObjectFlags is 1 byte only -union tPoolObjectFlags -{ - struct - { - unsigned char nId : 7; - bool bEmpty : 1; - }; - -private: - unsigned char nValue; -}; - -template -class CPoolSAInterface -{ -public: - // m_pObjects contains all interfaces. 140 maximum for ped objects. - B* m_pObjects; - tPoolObjectFlags* m_byteMap; - int m_nSize; - int m_nFirstFree; - bool m_bOwnsAllocations; - bool field_11; - - // Default constructor for statically allocated pools - CPoolSAInterface() - { - // Remember to call CPool::Init to fill in the fields! - m_pObjects = nullptr; - m_byteMap = nullptr; - m_nSize = 0; - m_bOwnsAllocations = false; - } - - uint GetFreeSlot() - { - bool bLooped = false; - uint index = m_nFirstFree + 1; - - while (true) - { - if (index >= m_nSize) - { - if (bLooped) - return -1; - - index = 0; - bLooped = true; - } - - if (m_byteMap[index].bEmpty) - { - m_nFirstFree = index; - return index; - } - index++; - } - - return -1; - }; - - B* Allocate() - { - m_nFirstFree++; // Continue after the last allocated slot - const auto sz = m_nSize; // Storing size to avoid reloads from memory - should help out the optimizer - for (auto i = 0u; i < sz; i++) { - const auto slot = (m_nFirstFree + i) % sz; - const auto e = &m_byteMap[slot]; - if (!e->bEmpty) { - continue; - } - m_nFirstFree = slot; - e->bEmpty = false; - e->nId++; - return &m_pObjects[slot]; - } - return nullptr; - } - - B* AllocateAt(uint uiSlot) - { - m_pObjects[uiSlot] = B(); - m_byteMap[uiSlot].bEmpty = false; - m_byteMap[uiSlot].nId ^= uiSlot ^ (uiSlot + 1); - - return &m_pObjects[uiSlot]; - } - - void Release(uint index) - { - m_byteMap[index].bEmpty = true; - m_byteMap[index].nId = 0; - if (index == m_nFirstFree) - --m_nFirstFree; - } - - void Delete(uint index) - { - Release(index); - } - - bool IsEmpty(std::int32_t objectIndex) { return m_byteMap[objectIndex].bEmpty; } - bool IsContains(uint index) - { - if (m_nSize <= index) - return false; - return !IsEmpty(index); - } - - B* GetObject(std::int32_t objectIndex) { return &m_pObjects[objectIndex]; } - - uint GetObjectIndex(B* pObject) { return ((DWORD)pObject - (DWORD)m_pObjects) / sizeof(B); } -}; - class CPoolsSA : public CPools { public: @@ -171,15 +57,6 @@ class CPoolsSA : public CPools unsigned long GetObjectCount() { return m_objectPool.ulCount; } void DeleteAllObjects(); - // Buildings pool -private: - bool AddBuildingToPool(CClientBuilding* pClientBuilding, CBuildingSA* pBuilding); - -public: - CBuilding* AddBuilding(CClientBuilding*, uint16_t modelId, CVector *vPos, CVector4D *vRot, uint8_t interior); - void RemoveBuilding(CBuilding* pBuilding); - bool HasFreeBuildingSlot(); - // Peds pool CPed* AddPed(CClientPed* pClientPed, unsigned int nModelIndex); CPed* AddPed(CClientPed* pClientPed, DWORD* pGameInterface); @@ -205,7 +82,6 @@ class CPoolsSA : public CPools DWORD GetPedPoolIndex(std::uint8_t* pInterface); DWORD GetVehiclePoolIndex(std::uint8_t* pInterfacee); DWORD GetObjectPoolIndex(std::uint8_t* pInterface); - DWORD GetBuildingPoolIndex(std::uint8_t* pInterface); int GetNumberOfUsedSpaces(ePools pools); int GetPoolDefaultCapacity(ePools pool); @@ -221,37 +97,20 @@ class CPoolsSA : public CPools ushort GetFreeTextureDictonarySlot(); -private: - // Generic container for pools - template - struct SPoolData - { - std::array, MAX> arrayOfClientEntities; - unsigned long ulCount; - - private: - friend class CPoolsSA; - - SPoolData() : ulCount(0UL) - { - for (unsigned int i = 0; i < MAX; ++i) - { - arrayOfClientEntities[i] = {nullptr, nullptr}; - } - } - }; + CBuildingsPool& GetBuildingsPool() noexcept override { return m_BuildingsPool; }; +private: // Pools SPoolData m_vehiclePool; SPoolData m_pedPool; SPoolData m_objectPool; - SPoolData m_buildingPool; CPoolSAInterface** m_ppPedPoolInterface; CPoolSAInterface** m_ppObjectPoolInterface; CPoolSAInterface** m_ppVehiclePoolInterface; CPoolSAInterface** m_ppTxdPoolInterface; - CPoolSAInterface** m_ppBuildingPoolInterface; + + CBuildingsPoolSA m_BuildingsPool; bool m_bGetVehicleEnabled; }; diff --git a/Client/game_sa/CQuadTreeNodeSA.h b/Client/game_sa/CQuadTreeNodeSA.h new file mode 100644 index 0000000000..dabb9bdfe5 --- /dev/null +++ b/Client/game_sa/CQuadTreeNodeSA.h @@ -0,0 +1,56 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CQuadTreeNodeSA.h + * PURPOSE: Quad tree node class interface + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CPtrNodeSingleListSA.h" +#include "CRect.h" + +template +class CQuadTreeNodesSAInterface +{ +public: + void RemoveAllItems(); + char AddItem(T* item, CRect* boudingBox); + +private: + float m_fX; + float m_fY; + float m_fW; + float m_fH; + CPtrNodeSingleListSAInterface m_pItemList; + CQuadTreeNodesSAInterface* m_childrens[4]; + uint32_t m_level; +}; +static_assert(sizeof(CQuadTreeNodesSAInterface) == 0x28, "Wrong CQuadTreeNodesSAInterface size"); + +template +void CQuadTreeNodesSAInterface::RemoveAllItems() +{ + if (m_level) + { + for (size_t i = 0; i < 4; i++) + { + m_childrens[i]->RemoveAllItems(); + } + } + else + { + m_pItemList.RemoveAllItems(); + } +}; + +template +char CQuadTreeNodesSAInterface::AddItem(T* item, CRect* boudingBox) +{ + typedef char(__thiscall * CQuadTreeNode_AddItem_t)(CQuadTreeNodesSAInterface*, void*, CRect*); + return ((CQuadTreeNode_AddItem_t)(0x552CD0))(this, item, boudingBox); +}; diff --git a/Client/mods/deathmatch/logic/CClientBuilding.cpp b/Client/mods/deathmatch/logic/CClientBuilding.cpp index 393774c20b..6b50476138 100644 --- a/Client/mods/deathmatch/logic/CClientBuilding.cpp +++ b/Client/mods/deathmatch/logic/CClientBuilding.cpp @@ -96,14 +96,14 @@ void CClientBuilding::Create() CVector4D vRot4D; ConvertZXYEulersToQuaternion(m_vRot, vRot4D); - m_pBuilding = g_pGame->GetPools()->AddBuilding(this, m_usModelId, &m_vPos, &vRot4D, m_interior); + m_pBuilding = g_pGame->GetPools()->GetBuildingsPool().AddBuilding(this, m_usModelId, &m_vPos, &vRot4D, m_interior); } void CClientBuilding::Destroy() { if (m_pBuilding) { - g_pGame->GetPools()->RemoveBuilding(m_pBuilding); + g_pGame->GetPools()->GetBuildingsPool().RemoveBuilding(m_pBuilding); m_pBuilding = nullptr; } } diff --git a/Client/mods/deathmatch/logic/CClientBuilding.h b/Client/mods/deathmatch/logic/CClientBuilding.h index 70041d0aa6..dec521272a 100644 --- a/Client/mods/deathmatch/logic/CClientBuilding.h +++ b/Client/mods/deathmatch/logic/CClientBuilding.h @@ -42,9 +42,11 @@ class CClientBuilding : public CClientEntity eClientEntityType GetType() const noexcept { return CCLIENTBUILDING; } -private: void Create(); void Destroy(); + bool IsValid() const noexcept { return m_pBuilding != nullptr; }; + +private: void Recreate() { diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp index 205d571d80..7c0c31509b 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp @@ -15,6 +15,8 @@ void CLuaBuildingDefs::LoadFunctions() // Backwards compatibility functions constexpr static const std::pair functions[]{ {"createBuilding", ArgumentParser}, + {"removeAllGameBuildings", ArgumentParser}, + {"restoreAllGameBuildings", ArgumentParser}, }; // Add functions @@ -43,7 +45,7 @@ CClientBuilding* CLuaBuildingDefs::CreateBuilding(lua_State* const luaVM, uint16 if (!CClientBuildingManager::IsValidModel(modelId)) throw std::invalid_argument("Invalid building model id"); - if (!g_pGame->GetPools()->HasFreeBuildingSlot()) + if (!g_pGame->GetPools()->GetBuildingsPool().HasFreeBuildingSlot()) throw std::invalid_argument("No free slot in buildings pool"); if (!CClientBuildingManager::IsValidPosition(pos)) @@ -58,3 +60,47 @@ CClientBuilding* CLuaBuildingDefs::CreateBuilding(lua_State* const luaVM, uint16 return pBuilding; } + +void CLuaBuildingDefs::RemoveAllGameBuildings() +{ + // We do not want to remove scripted buildings + // But we need remove them from the buildings pool for a bit... + for (CClientBuilding* building : m_pBuildingManager->GetBuildings()) + { + building->Destroy(); + } + + // This function makes buildings backup without scripted buildings + g_pGame->RemoveAllBuildings(); + + // ... And restore here + for (CClientBuilding* building : m_pBuildingManager->GetBuildings()) + { + building->Create(); + } +} + +void CLuaBuildingDefs::RestoreGameBuildings() +{ + // We want to restore the game buildings to the same positions as they were before the backup. + // Remove scripted buildings for a bit + for (CClientBuilding* building : m_pBuildingManager->GetBuildings()) + { + building->Destroy(); + } + + g_pGame->RestoreGameBuildings(); + + // Restore what we can + for (CClientBuilding* building : m_pBuildingManager->GetBuildings()) + { + building->Create(); + + if (!building->IsValid()) + { + // User creates too much buildings + // We can't restore them all + delete building; + } + } +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h index 5d4d69c65b..628d320844 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h @@ -20,4 +20,6 @@ class CLuaBuildingDefs : public CLuaDefs // Buiding create funcs static CClientBuilding* CreateBuilding(lua_State* const luaVM, uint16_t modelId, CVector pos, CVector rot, std::optional interior); + static void RemoveAllGameBuildings(); + static void RestoreGameBuildings(); }; diff --git a/Client/sdk/game/CBuildingsPool.h b/Client/sdk/game/CBuildingsPool.h new file mode 100644 index 0000000000..6a08ee5372 --- /dev/null +++ b/Client/sdk/game/CBuildingsPool.h @@ -0,0 +1,31 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/CBuildingsPool.h + * PURPOSE: Buildings pool interface + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "Common.h" +#include + +class CBuilding; +class CBuildingSA; + +class CBuildingsPool +{ +public: + + // Buildings pool + virtual CBuilding* AddBuilding(class CClientBuilding*, uint16_t modelId, CVector* vPos, CVector4D* vRot, uint8_t interior) = 0; + virtual void RemoveBuilding(CBuilding* pObject) = 0; + virtual bool HasFreeBuildingSlot() = 0; + + virtual void RemoveAllBuildings() = 0; + virtual void RestoreAllBuildings() = 0; +}; diff --git a/Client/sdk/game/CGame.h b/Client/sdk/game/CGame.h index dee2eaeae8..6df946ad3c 100644 --- a/Client/sdk/game/CGame.h +++ b/Client/sdk/game/CGame.h @@ -66,6 +66,7 @@ class CWeaponStat; class CWeaponStatManager; class CWeather; class CWorld; +class CIplStore; enum eEntityType; enum ePedPieceTypes; @@ -255,4 +256,7 @@ class __declspec(novtable) CGame virtual int32_t GetBaseIDforRRR() = 0; virtual int32_t GetBaseIDforSCM() = 0; virtual int32_t GetCountOfAllFileIDs() = 0; + + virtual void RemoveAllBuildings() = 0; + virtual void RestoreGameBuildings() = 0; }; diff --git a/Client/sdk/game/CIplStore.h b/Client/sdk/game/CIplStore.h new file mode 100644 index 0000000000..c3c58f2c95 --- /dev/null +++ b/Client/sdk/game/CIplStore.h @@ -0,0 +1,19 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/CIplStore.h + * PURPOSE: Game IPL store interface + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +class CIplStore +{ +public: + virtual void SetDynamicIplStreamingEnabled(bool state) = 0; + virtual void SetDynamicIplStreamingEnabled(bool state, std::function filter) = 0; +}; diff --git a/Client/sdk/game/CPools.h b/Client/sdk/game/CPools.h index f758084639..b6374b6f75 100644 --- a/Client/sdk/game/CPools.h +++ b/Client/sdk/game/CPools.h @@ -11,6 +11,9 @@ #pragma once +#include "Common.h" +#include "CBuildingsPool.h" + class CClientEntity; class CEntity; class CEntitySAInterface; @@ -84,11 +87,6 @@ class CPools virtual CPed* GetPedFromRef(DWORD dwGameRef) = 0; virtual unsigned long GetPedCount() = 0; - // Buildings pool - virtual CBuilding* AddBuilding(class CClientBuilding*, uint16_t modelId, CVector *vPos, CVector4D *vRot, uint8_t interior) = 0; - virtual void RemoveBuilding(CBuilding* pObject) = 0; - virtual bool HasFreeBuildingSlot() = 0; - // Others virtual CVehicle* AddTrain(class CClientVehicle* pClientVehicle, CVector* vecPosition, DWORD dwModels[], int iSize, bool iDirection, uchar ucTrackId = 0xFF) = 0; @@ -110,4 +108,6 @@ class CPools virtual bool IsFreeTextureDictonarySlot(uint uiTxdID) = 0; virtual ushort GetFreeTextureDictonarySlot() = 0; + + virtual CBuildingsPool& GetBuildingsPool() noexcept = 0; }; diff --git a/Client/sdk/game/CWorld.h b/Client/sdk/game/CWorld.h index a07d2dbd20..807fa09a36 100644 --- a/Client/sdk/game/CWorld.h +++ b/Client/sdk/game/CWorld.h @@ -327,6 +327,7 @@ class CWorld { public: virtual void Add(CEntity* entity, eDebugCaller CallerId) = 0; + virtual void Add(CEntitySAInterface* entity, eDebugCaller CallerId) = 0; virtual void Remove(CEntity* entity, eDebugCaller CallerId) = 0; virtual void Remove(CEntitySAInterface* entityInterface, eDebugCaller CallerId) = 0; virtual auto ProcessLineAgainstMesh(CEntitySAInterface* e, CVector start, CVector end) -> SProcessLineOfSightMaterialInfoResult = 0;