diff --git a/Client/game_sa/CBuildingSA.cpp b/Client/game_sa/CBuildingSA.cpp new file mode 100644 index 0000000000..964506dd02 --- /dev/null +++ b/Client/game_sa/CBuildingSA.cpp @@ -0,0 +1,18 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CBuildingSA.cpp + * PURPOSE: Building entity + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CBuildingSA.h" + +CBuildingSA::CBuildingSA(CBuildingSAInterface* pInterface) +{ + SetInterface(pInterface); +} diff --git a/Client/game_sa/CBuildingSA.h b/Client/game_sa/CBuildingSA.h new file mode 100644 index 0000000000..6d6794ceb7 --- /dev/null +++ b/Client/game_sa/CBuildingSA.h @@ -0,0 +1,28 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CBuildingSA.h + * PURPOSE: Header file for game building class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include +#include "CEntitySA.h" + +class CBuildingSAInterface : public CEntitySAInterface +{ +}; +static_assert(sizeof(CBuildingSAInterface) == 0x38, "Invalid size CBuildingSAInterface"); + +class CBuildingSA final : public virtual CBuilding, public virtual CEntitySA +{ +public: + CBuildingSA(CBuildingSAInterface* pInterface); + + CBuildingSAInterface* GetBuildingInterface() { return reinterpret_cast(GetInterface()); }; +}; diff --git a/Client/game_sa/CFileLoaderSA.cpp b/Client/game_sa/CFileLoaderSA.cpp index 5bb8dd1fe0..67cb476cf5 100644 --- a/Client/game_sa/CFileLoaderSA.cpp +++ b/Client/game_sa/CFileLoaderSA.cpp @@ -28,6 +28,12 @@ void CFileLoaderSA::StaticSetHooks() HookInstall(0x538690, (DWORD)CFileLoader_LoadObjectInstance, 5); } +CEntitySAInterface* CFileLoaderSA::LoadObjectInstance(SFileObjectInstance* obj) +{ + // Second argument is model name. It's unused in the function + return ((CEntitySAInterface * (__cdecl*)(SFileObjectInstance*, const char*))0x538090)(obj, nullptr); +} + class CAtomicModelInfo { public: diff --git a/Client/game_sa/CFileLoaderSA.h b/Client/game_sa/CFileLoaderSA.h index f18c7e4633..260bc93cb2 100644 --- a/Client/game_sa/CFileLoaderSA.h +++ b/Client/game_sa/CFileLoaderSA.h @@ -29,6 +29,8 @@ class CFileLoaderSA CFileLoaderSA(); ~CFileLoaderSA(); + static CEntitySAInterface* LoadObjectInstance(SFileObjectInstance*); + static void StaticSetHooks(); }; diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index b46818d72b..329d2e3e50 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -465,6 +465,8 @@ class CModelInfoSA : public CModelInfo // Vehicle towing functions bool IsTowableBy(CModelInfo* towingModel) override; + bool IsDynamic() { return m_pInterface ? m_pInterface->usDynamicIndex != 0xffff : false; }; + private: void CopyStreamingInfoFromModel(ushort usCopyFromModelID); void RwSetSupportedUpgrades(RwFrame* parent, DWORD dwModel); diff --git a/Client/game_sa/CPoolsSA.cpp b/Client/game_sa/CPoolsSA.cpp index 9f3260029d..6680e82194 100644 --- a/Client/game_sa/CPoolsSA.cpp +++ b/Client/game_sa/CPoolsSA.cpp @@ -13,6 +13,7 @@ #include "CBikeSA.h" #include "CBmxSA.h" #include "CBoatSA.h" +#include "CBuildingSA.h" #include "CGameSA.h" #include "CHeliSA.h" #include "CMonsterTruckSA.h" @@ -24,6 +25,7 @@ #include "CTrainSA.h" #include "CWorldSA.h" #include "CKeyGenSA.h" +#include "CFileLoaderSA.h" extern CGameSA* pGame; @@ -33,6 +35,7 @@ CPoolsSA::CPoolsSA() m_ppObjectPoolInterface = (CPoolSAInterface**)0xB7449C; m_ppVehiclePoolInterface = (CPoolSAInterface**)0xB74494; m_ppTxdPoolInterface = (CPoolSAInterface**)0xC8800C; + m_ppBuildingPoolInterface = (CPoolSAInterface**)0xB74498; m_bGetVehicleEnabled = true; } @@ -329,6 +332,107 @@ 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 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 // ////////////////////////////////////////////////////////////////////////////////////////// @@ -676,7 +780,7 @@ DWORD CPoolsSA::GetPedPoolIndex(std::uint8_t* pInterface) { return MAX_PEDS; } - return ((pInterface - pTheObjects) / dwAlignedSize); + return ((pInterface - pTheObjects) / dwAlignedSize); } DWORD CPoolsSA::GetVehiclePoolIndex(std::uint8_t* pInterface) @@ -703,6 +807,18 @@ 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 60f5d549e8..106846b162 100644 --- a/Client/game_sa/CPoolsSA.h +++ b/Client/game_sa/CPoolsSA.h @@ -14,6 +14,7 @@ #include "CPedSA.h" #include "CVehicleSA.h" #include "CObjectSA.h" +#include "CBuildingSA.h" #include "CTextureDictonarySA.h" #define INVALID_POOL_ARRAY_ID 0xFFFFFFFF @@ -170,6 +171,15 @@ 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); @@ -195,6 +205,7 @@ 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); @@ -231,16 +242,16 @@ class CPoolsSA : public CPools }; // Pools - typedef SPoolData vehiclePool_t; - typedef SPoolData pedPool_t; - typedef SPoolData objectPool_t; - vehiclePool_t m_vehiclePool; - pedPool_t m_pedPool; - objectPool_t m_objectPool; - CPoolSAInterface** m_ppPedPoolInterface; - CPoolSAInterface** m_ppObjectPoolInterface; - CPoolSAInterface** m_ppVehiclePoolInterface; - CPoolSAInterface** m_ppTxdPoolInterface; + 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; bool m_bGetVehicleEnabled; }; diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index 0d7cc5168c..68b18a6ab5 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -143,6 +144,7 @@ #include #include #include +#include #include // Shared includes diff --git a/Client/mods/deathmatch/logic/CClientBuilding.cpp b/Client/mods/deathmatch/logic/CClientBuilding.cpp new file mode 100644 index 0000000000..393774c20b --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientBuilding.cpp @@ -0,0 +1,109 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientBuilding.cpp + * PURPOSE: Buildings handling class + * + *****************************************************************************/ + +#include "StdInc.h" + +CClientBuilding::CClientBuilding(class CClientManager* pManager, ElementID ID, uint16_t usModelId, const CVector &pos, const CVector &rot, uint8_t interior) + : ClassInit(this), + CClientEntity(ID), + m_pBuildingManager(pManager->GetBuildingManager()), + m_usModelId(usModelId), + m_vPos(pos), + m_vRot(rot), + m_interior(interior), + m_pBuilding(nullptr) +{ + m_pManager = pManager; + SetTypeName("building"); + m_pBuildingManager->AddToList(this); + Create(); +} + +CClientBuilding::~CClientBuilding() +{ + m_pBuildingManager->RemoveFromList(this); + Destroy(); +} + +void CClientBuilding::SetPosition(const CVector& vecPosition) +{ + if (!CClientBuildingManager::IsValidPosition(vecPosition)) + return; + + if (m_vPos == vecPosition) + return; + m_vPos = vecPosition; + Recreate(); +} + +void CClientBuilding::SetRotationRadians(const CVector& vecRadians) +{ + if (m_vRot == vecRadians) + return; + m_vRot = vecRadians; + Recreate(); +} + +bool CClientBuilding::SetMatrix(const CMatrix& matrix) +{ + if (!CClientBuildingManager::IsValidPosition(matrix.vPos)) + return false; + + m_vPos = matrix.vPos; + + CVector vecRotation; + g_pMultiplayer->ConvertMatrixToEulerAngles(matrix, vecRotation.fX, vecRotation.fY, vecRotation.fZ); + + ConvertRadiansToDegreesNoWrap(vecRotation); + vecRotation = ConvertEulerRotationOrder(vecRotation, EULER_MINUS_ZYX, EULER_ZXY); + ConvertDegreesToRadiansNoWrap(vecRotation); + + m_vRot = vecRotation; + + Recreate(); + return true; +} + +void CClientBuilding::SetInterior(uint8_t ucInterior) +{ + if (m_interior == ucInterior) + return; + m_interior = ucInterior; + Recreate(); +} + +void CClientBuilding::SetModel(uint16_t model) +{ + if (CClientBuildingManager::IsValidModel(model)) + { + m_usModelId = model; + Recreate(); + } +} + +void CClientBuilding::Create() +{ + if (m_pBuilding) + return; + + CVector4D vRot4D; + ConvertZXYEulersToQuaternion(m_vRot, vRot4D); + + m_pBuilding = g_pGame->GetPools()->AddBuilding(this, m_usModelId, &m_vPos, &vRot4D, m_interior); +} + +void CClientBuilding::Destroy() +{ + if (m_pBuilding) + { + g_pGame->GetPools()->RemoveBuilding(m_pBuilding); + m_pBuilding = nullptr; + } +} diff --git a/Client/mods/deathmatch/logic/CClientBuilding.h b/Client/mods/deathmatch/logic/CClientBuilding.h new file mode 100644 index 0000000000..f9f423f4a3 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientBuilding.h @@ -0,0 +1,63 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientBuilding.h + * PURPOSE: Physical object entity class + * + *****************************************************************************/ + +class CClientBuilding; + +#pragma once + +#include + +class CClientBuilding : public CClientEntity +{ + DECLARE_CLASS(CClientBuilding, CClientEntity) + friend class CClientBuildingManager; + +public: + CClientBuilding(class CClientManager* pManager, ElementID ID, uint16_t usModelId, const CVector &pos, const CVector &rot, uint8_t interior); + ~CClientBuilding(); + + void Unlink(){}; + void GetPosition(CVector& vecPosition) const override { vecPosition = m_vPos; }; + void SetPosition(const CVector& vecPosition) override; + + void GetRotationRadians(CVector& vecOutRadians) const override { vecOutRadians = m_vPos; }; + void SetRotationRadians(const CVector& vecRadians) override; + + CEntity* GetGameEntity() override { return m_pBuilding; }; + const CEntity* GetGameEntity() const override { return m_pBuilding; }; + + bool SetMatrix(const CMatrix& matrix) override; + + void SetInterior(uint8_t ucInterior) override; + + uint16_t GetModel() const noexcept { return m_usModelId; }; + void SetModel(uint16_t ulModel); + + eClientEntityType GetType() const noexcept { return CCLIENTBUILDING; } + +private: + void Create(); + void Destroy(); + + void Recreate() + { + Destroy(); + Create(); + }; + +private: + CClientBuildingManager* m_pBuildingManager; + + CBuilding* m_pBuilding; + uint16_t m_usModelId; + CVector m_vPos; + CVector m_vRot; + uint8_t m_interior; +}; diff --git a/Client/mods/deathmatch/logic/CClientBuildingManager.cpp b/Client/mods/deathmatch/logic/CClientBuildingManager.cpp new file mode 100644 index 0000000000..13a3246497 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientBuildingManager.cpp @@ -0,0 +1,87 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientBuildingManager.cpp + * PURPOSE: Building manager class + * + *****************************************************************************/ + +#include "StdInc.h" + +constexpr float WORLD_DISTANCE_FROM_CENTER = 3000.0f; + +CClientBuildingManager::CClientBuildingManager(CClientManager* pManager) +{ + // Init + m_bRemoveFromList = true; +} + +CClientBuildingManager::~CClientBuildingManager() +{ + // Delete all our buildings + RemoveAll(); +} + +void CClientBuildingManager::RemoveAll() +{ + // Make sure they don't remove themselves from our list + m_bRemoveFromList = false; + + // Run through our list deleting the buildings + for (CClientBuilding* building : m_List) + { + delete building; + } + + m_List.clear(); + + // Allow list removal again + m_bRemoveFromList = true; +} + +bool CClientBuildingManager::Exists(CClientBuilding* pBuilding) +{ + return std::find(m_List.begin(), m_List.end(), pBuilding) != m_List.end(); +} + +void CClientBuildingManager::RemoveFromList(CClientBuilding* pBuilding) +{ + // Can we remove anything from the list? + if (m_bRemoveFromList) + { + m_List.remove(pBuilding); + } +} + +bool CClientBuildingManager::IsValidModel(uint16_t modelId) +{ + if (modelId >= static_cast(g_pGame->GetBaseIDforTXD())) + return false; + + // Clothes and hands cause artefacts + if (384 <= modelId && modelId <= 397) + return false; + + CModelInfo* pModelInfo = g_pGame->GetModelInfo(modelId); + if (!pModelInfo || !pModelInfo->GetInterface()) + return false; + + if (!pModelInfo->IsAllocatedInArchive()) + return false; + + if (pModelInfo->IsDynamic()) + { + return false; + } + + eModelInfoType eType = pModelInfo->GetModelType(); + return (eType == eModelInfoType::CLUMP || eType == eModelInfoType::ATOMIC || eType == eModelInfoType::WEAPON || eType == eModelInfoType::TIME); +} + +bool CClientBuildingManager::IsValidPosition(const CVector& pos) noexcept +{ + return (pos.fX >= -WORLD_DISTANCE_FROM_CENTER && pos.fX <= WORLD_DISTANCE_FROM_CENTER && pos.fY >= -WORLD_DISTANCE_FROM_CENTER && + pos.fY <= WORLD_DISTANCE_FROM_CENTER); +} diff --git a/Client/mods/deathmatch/logic/CClientBuildingManager.h b/Client/mods/deathmatch/logic/CClientBuildingManager.h new file mode 100644 index 0000000000..e47ce1e0eb --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientBuildingManager.h @@ -0,0 +1,40 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientBuildingManager.h + * PURPOSE: Building manager class + * + *****************************************************************************/ + +class CClientBuildingManager; + +#pragma once + +#include +#include "CClientBuilding.h" + +class CClientBuildingManager +{ + friend class CClientBuilding; + +public: + CClientBuildingManager(class CClientManager* pManager); + ~CClientBuildingManager(); + + void RemoveAll(); + bool Exists(CClientBuilding* pBuilding); + + const std::list& GetBuildings() { return m_List; }; + + static bool IsValidModel(uint16_t modelId); + static bool IsValidPosition(const CVector& pos) noexcept; + +private: + void AddToList(CClientBuilding* pBuilding) { m_List.push_back(pBuilding); } + void RemoveFromList(CClientBuilding* pBuilding); + + std::list m_List; + bool m_bRemoveFromList; +}; diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index bd60dab3ba..0d84526d8b 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -79,6 +79,7 @@ enum eClientEntityType CCLIENTVECTORGRAPHIC, CCLIENTUNKNOWN, CCLIENTIMG, + CCLIENTBUILDING, }; class CEntity; @@ -143,6 +144,7 @@ enum eCClientEntityClassTypes CLASS_CClientPointLights, CLASS_CClientSearchLight, CLASS_CClientIMG, + CLASS_CClientBuilding, }; class CClientEntity : public CClientEntityBase diff --git a/Client/mods/deathmatch/logic/CClientManager.cpp b/Client/mods/deathmatch/logic/CClientManager.cpp index 21962cc8f3..e3a808555b 100644 --- a/Client/mods/deathmatch/logic/CClientManager.cpp +++ b/Client/mods/deathmatch/logic/CClientManager.cpp @@ -54,6 +54,7 @@ CClientManager::CClientManager() m_pModelManager = new CClientModelManager(); m_pPacketRecorder = new CClientPacketRecorder(this); m_pImgManager = new CClientIMGManager(this); + m_pBuildingManager = new CClientBuildingManager(this); m_bBeingDeleted = false; m_bGameUnloadedFlag = false; @@ -177,6 +178,9 @@ CClientManager::~CClientManager() delete m_pImgManager; m_pImgManager = nullptr; + + delete m_pBuildingManager; + m_pBuildingManager = nullptr; } // diff --git a/Client/mods/deathmatch/logic/CClientManager.h b/Client/mods/deathmatch/logic/CClientManager.h index e916c8eb73..0e9f0c7702 100644 --- a/Client/mods/deathmatch/logic/CClientManager.h +++ b/Client/mods/deathmatch/logic/CClientManager.h @@ -43,6 +43,7 @@ class CClientManager; #include "CClientPointLightsManager.h" #include "CClientModelManager.h" #include "CClientIMGManager.h" +#include "CClientBuildingManager.h" class CClientProjectileManager; class CClientExplosionManager; @@ -96,6 +97,7 @@ class CClientManager CClientEffectManager* GetEffectManager() { return m_pEffectManager; } CClientPointLightsManager* GetPointLightsManager() { return m_pPointLightsManager; } CClientIMGManager* GetIMGManager() { return m_pImgManager; } + CClientBuildingManager* GetBuildingManager() const noexcept { return m_pBuildingManager; } bool IsGameLoaded() { return g_pGame->GetSystemState() == 9 && !m_bGameUnloadedFlag && g_pCore->GetNetwork()->GetServerBitStreamVersion(); } bool IsBeingDeleted() { return m_bBeingDeleted; } @@ -148,6 +150,7 @@ class CClientManager CClientModelManager* m_pModelManager; CClientIMGManager* m_pImgManager; CClientPacketRecorder* m_pPacketRecorder; + CClientBuildingManager* m_pBuildingManager; bool m_bBeingDeleted; bool m_bGameUnloadedFlag; int m_iNumLowLODElements; diff --git a/Client/mods/deathmatch/logic/CClientModel.cpp b/Client/mods/deathmatch/logic/CClientModel.cpp index e42ee3a695..a80d637b8c 100644 --- a/Client/mods/deathmatch/logic/CClientModel.cpp +++ b/Client/mods/deathmatch/logic/CClientModel.cpp @@ -122,6 +122,13 @@ void CClientModel::RestoreEntitiesUsingThisModel() void CClientModel::RestoreDFF(CModelInfo* pModelInfo) { + auto callElementChangeEvent = [](auto &element, unsigned short usParentID, auto modelId) { + CLuaArguments Arguments; + Arguments.PushNumber(modelId); + Arguments.PushNumber(usParentID); + element.CallEvent("onClientElementModelChange", Arguments, true); + }; + auto unloadModelsAndCallEvents = [&](auto iterBegin, auto iterEnd, unsigned short usParentID, auto setElementModelLambda) { for (auto iter = iterBegin; iter != iterEnd; iter++) { @@ -135,10 +142,21 @@ void CClientModel::RestoreDFF(CModelInfo* pModelInfo) setElementModelLambda(element); - CLuaArguments Arguments; - Arguments.PushNumber(m_iModelID); - Arguments.PushNumber(usParentID); - element.CallEvent("onClientElementModelChange", Arguments, true); + callElementChangeEvent(element, usParentID, m_iModelID); + } + }; + + auto unloadModelsAndCallEventsNonStreamed = [&](auto iterBegin, auto iterEnd, unsigned short usParentID, auto setElementModelLambda) + { + for (auto iter = iterBegin; iter != iterEnd; iter++) + { + auto& element = **iter; + + if (element.GetModel() != m_iModelID) + continue; + + setElementModelLambda(element); + callElementChangeEvent(element, usParentID, m_iModelID); } }; @@ -166,6 +184,12 @@ void CClientModel::RestoreDFF(CModelInfo* pModelInfo) unloadModelsAndCallEvents(pPickupManager->IterBegin(), pPickupManager->IterEnd(), usParentID, [=](auto& element) { element.SetModel(usParentID); }); + // Restore buildings + CClientBuildingManager* pBuildingsManager = g_pClientGame->GetManager()->GetBuildingManager(); + auto& buildingsList = pBuildingsManager->GetBuildings(); + unloadModelsAndCallEventsNonStreamed(buildingsList.begin(), buildingsList.end(), usParentID, + [=](auto& element) { element.SetModel(usParentID); }); + // Restore COL g_pClientGame->GetManager()->GetColModelManager()->RestoreModel(m_iModelID); break; diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index ffc4466a33..be0e47a076 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -426,6 +426,16 @@ bool CStaticFunctionDefinitions::GetElementRotation(CClientEntity& Entity, CVect } break; } + case CCLIENTBUILDING: + { + CClientBuilding& pBuilding = static_cast(Entity); + pBuilding.GetRotationDegrees(vecRotation); + if (desiredRotOrder != EULER_DEFAULT && desiredRotOrder != EULER_ZXY) + { + vecRotation = ConvertEulerRotationOrder(vecRotation, EULER_ZXY, desiredRotOrder); + } + break; + } case CCLIENTPROJECTILE: { CClientProjectile& Projectile = static_cast(Entity); @@ -762,6 +772,12 @@ bool CStaticFunctionDefinitions::GetElementModel(CClientEntity& Entity, unsigned usModel = pPickup.GetModel(); break; } + case CCLIENTBUILDING: + { + CClientBuilding& pBuilding = static_cast(Entity); + usModel = pBuilding.GetModel(); + break; + } case CCLIENTPROJECTILE: { CClientProjectile& pProjectile = static_cast(Entity); @@ -1107,6 +1123,19 @@ bool CStaticFunctionDefinitions::SetElementRotation(CClientEntity& Entity, const break; } + case CCLIENTBUILDING: + { + CClientBuilding& pBuilding = static_cast(Entity); + if (argumentRotOrder == EULER_DEFAULT || argumentRotOrder == EULER_ZXY) + { + pBuilding.SetRotationDegrees(vecRotation); + } + else + { + pBuilding.SetRotationDegrees(ConvertEulerRotationOrder(vecRotation, argumentRotOrder, EULER_ZXY)); + } + break; + } case CCLIENTPROJECTILE: { // Didn't implement anything for projectiles since I couldn't really test (only non crashing element was satchel and its rotation is ugly) @@ -1461,6 +1490,26 @@ bool CStaticFunctionDefinitions::SetElementModel(CClientEntity& Entity, unsigned { RUN_CHILDREN(SetElementModel(**iter, usModel)) + auto callOnChangeEvent = [](auto &element, uint16_t usCurrentModel, uint16_t usModel) { + CLuaArguments Arguments; + Arguments.PushNumber(usCurrentModel); + Arguments.PushNumber(usModel); + bool bContinue = element.CallEvent("onClientElementModelChange", Arguments, true); + + // Check for another call to setElementModel + if (usModel != element.GetModel()) + return false; + + if (!bContinue) + { + // Change canceled + element.SetModel(usCurrentModel); + return false; + } + + return true; + }; + switch (Entity.GetType()) { case CCLIENTPED: @@ -1476,23 +1525,7 @@ bool CStaticFunctionDefinitions::SetElementModel(CClientEntity& Entity, unsigned if (!Ped.SetModel(usModel)) return false; - CLuaArguments Arguments; - Arguments.PushNumber(usCurrentModel); - Arguments.PushNumber(usModel); - bool bContinue = Ped.CallEvent("onClientElementModelChange", Arguments, true); - - // Check for another call to setElementModel - if (usModel != Ped.GetModel()) - return false; - - if (!bContinue) - { - // Change canceled - Ped.SetModel(usCurrentModel); - return false; - } - - break; + return callOnChangeEvent(Ped, usCurrentModel, usModel); } case CCLIENTVEHICLE: { @@ -1539,23 +1572,22 @@ bool CStaticFunctionDefinitions::SetElementModel(CClientEntity& Entity, unsigned Object.SetModel(usModel); - CLuaArguments Arguments; - Arguments.PushNumber(usCurrentModel); - Arguments.PushNumber(usModel); - bool bContinue = Object.CallEvent("onClientElementModelChange", Arguments, true); + return callOnChangeEvent(Object, usCurrentModel, usModel); + } + case CCLIENTBUILDING: + { + CClientBuilding& Object = static_cast(Entity); + const unsigned short usCurrentModel = Object.GetModel(); - // Check for another call to setElementModel - if (usModel != Object.GetModel()) + if (usCurrentModel == usModel) return false; - if (!bContinue) - { - // Change canceled - Object.SetModel(usCurrentModel); + if (!CClientObjectManager::IsValidModel(usModel)) return false; - } - break; + Object.SetModel(usModel); + + return callOnChangeEvent(Object, usCurrentModel, usModel); } case CCLIENTPROJECTILE: { @@ -1570,11 +1602,7 @@ bool CStaticFunctionDefinitions::SetElementModel(CClientEntity& Entity, unsigned Projectile.SetModel(usModel); - CLuaArguments Arguments; - Arguments.PushNumber(usCurrentModel); - Arguments.PushNumber(usModel); - Projectile.CallEvent("onClientElementModelChange", Arguments, true); - break; + return callOnChangeEvent(Projectile, usCurrentModel, usModel); } default: return false; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp index 912fd99f66..feb2bdf058 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp @@ -31,6 +31,7 @@ CClientDFFManager* CLuaFunctionDefs::m_pDFFManager; CClientColModelManager* CLuaFunctionDefs::m_pColModelManager; CRegisteredCommands* CLuaFunctionDefs::m_pRegisteredCommands; CClientIMGManager* CLuaFunctionDefs::m_pImgManager; +CClientBuildingManager* CLuaFunctionDefs::m_pBuildingManager; void CLuaFunctionDefs::Initialize(CLuaManager* pLuaManager, CScriptDebugging* pScriptDebugging, CClientGame* pClientGame) { @@ -55,4 +56,5 @@ void CLuaFunctionDefs::Initialize(CLuaManager* pLuaManager, CScriptDebugging* pS m_pColModelManager = m_pManager->GetColModelManager(); m_pRegisteredCommands = m_pClientGame->GetRegisteredCommands(); m_pImgManager = m_pManager->GetIMGManager(); + m_pBuildingManager = m_pManager->GetBuildingManager(); } diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h index 1681110616..105754152f 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h @@ -139,4 +139,5 @@ class CLuaFunctionDefs static CClientColModelManager* m_pColModelManager; static CRegisteredCommands* m_pRegisteredCommands; static CClientIMGManager* m_pImgManager; + static CClientBuildingManager* m_pBuildingManager; }; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index 5688c78d34..7f0b98c679 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -272,6 +272,10 @@ inline SString GetClassTypeName(CClientIMG*) { return "img"; } +inline SString GetClassTypeName(CClientBuilding*) +{ + return "building"; +} inline SString GetClassTypeName(CClientSound*) { return "sound"; diff --git a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp index 33388a5edc..c2a28809af 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp @@ -280,4 +280,5 @@ void CLuaManager::LoadCFunctions() CLuaXMLDefs::LoadFunctions(); CLuaClientDefs::LoadFunctions(); CLuaDiscordDefs::LoadFunctions(); + CLuaBuildingDefs::LoadFunctions(); } diff --git a/Client/mods/deathmatch/logic/lua/LuaCommon.h b/Client/mods/deathmatch/logic/lua/LuaCommon.h index 238cfeeed5..20dda7ff11 100644 --- a/Client/mods/deathmatch/logic/lua/LuaCommon.h +++ b/Client/mods/deathmatch/logic/lua/LuaCommon.h @@ -38,6 +38,7 @@ class CClientRadarMarker; class CClientTeam; class CClientTXD; class CClientIMG; +class CClientBuilding; class CClientVehicle; class CClientWater; class CClientWeapon; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp new file mode 100644 index 0000000000..205d571d80 --- /dev/null +++ b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.cpp @@ -0,0 +1,60 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include + +void CLuaBuildingDefs::LoadFunctions() +{ + // Backwards compatibility functions + constexpr static const std::pair functions[]{ + {"createBuilding", ArgumentParser}, + }; + + // Add functions + for (const auto& [name, func] : functions) + CLuaCFunctions::AddFunction(name, func); +} + +void CLuaBuildingDefs::AddClass(lua_State* luaVM) +{ + lua_newclass(luaVM); + + lua_classfunction(luaVM, "create", "createBuilding"); + + lua_registerclass(luaVM, "Building"); +} + +CClientBuilding* CLuaBuildingDefs::CreateBuilding(lua_State* const luaVM, uint16_t modelId, CVector pos, CVector rot, std::optional interior) +{ + CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM); + + // Get the resource we belong to + CResource* pResource = pLuaMain->GetResource(); + if (!pResource) + return false; + + if (!CClientBuildingManager::IsValidModel(modelId)) + throw std::invalid_argument("Invalid building model id"); + + if (!g_pGame->GetPools()->HasFreeBuildingSlot()) + throw std::invalid_argument("No free slot in buildings pool"); + + if (!CClientBuildingManager::IsValidPosition(pos)) + throw std::invalid_argument("Position is outside of game world"); + + ConvertDegreesToRadians(rot); + + CClientBuilding* pBuilding = new CClientBuilding(m_pManager, INVALID_ELEMENT_ID, modelId, pos, rot, interior.value_or(0)); + + CClientEntity* pRoot = pResource->GetResourceDynamicEntity(); + pBuilding->SetParent(pRoot); + + return pBuilding; +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h new file mode 100644 index 0000000000..5d4d69c65b --- /dev/null +++ b/Client/mods/deathmatch/logic/luadefs/CLuaBuildingDefs.h @@ -0,0 +1,23 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.x + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/luadefs/CLuaBuildingDefs.h + * PURPOSE: Lua building definitions class header + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once +#include "CLuaDefs.h" + +class CLuaBuildingDefs : public CLuaDefs +{ +public: + static void LoadFunctions(); + static void AddClass(lua_State* luaVM); + + // Buiding create funcs + static CClientBuilding* CreateBuilding(lua_State* const luaVM, uint16_t modelId, CVector pos, CVector rot, std::optional interior); +}; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp index de07ea84a4..457f72998f 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp @@ -31,6 +31,7 @@ CClientDFFManager* CLuaDefs::m_pDFFManager = NULL; CClientColModelManager* CLuaDefs::m_pColModelManager = NULL; CRegisteredCommands* CLuaDefs::m_pRegisteredCommands = NULL; CClientIMGManager* CLuaDefs::m_pImgManager = NULL; +CClientBuildingManager* CLuaDefs::m_pBuildingManager = nullptr; bool ms_bRegisterdPostCallHook = false; void CLuaDefs::Initialize(CClientGame* pClientGame, CLuaManager* pLuaManager, CScriptDebugging* pScriptDebugging) @@ -56,6 +57,7 @@ void CLuaDefs::Initialize(CClientGame* pClientGame, CLuaManager* pLuaManager, CS m_pColModelManager = m_pManager->GetColModelManager(); m_pRegisteredCommands = pClientGame->GetRegisteredCommands(); m_pImgManager = m_pManager->GetIMGManager(); + m_pBuildingManager = m_pManager->GetBuildingManager(); } int CLuaDefs::CanUseFunction(lua_CFunction f, lua_State* luaVM) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h index cf043b4572..3f3c18386f 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h @@ -65,6 +65,7 @@ class CLuaDefs static CClientColModelManager* m_pColModelManager; static CRegisteredCommands* m_pRegisteredCommands; static CClientIMGManager* m_pImgManager; + static CClientBuildingManager* m_pBuildingManager; protected: // Old style: Only warn on failure. This should diff --git a/Client/sdk/game/CBuilding.h b/Client/sdk/game/CBuilding.h new file mode 100644 index 0000000000..8cbaf6abb4 --- /dev/null +++ b/Client/sdk/game/CBuilding.h @@ -0,0 +1,24 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/CBuilding.h + * PURPOSE: Physical entity interface + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CEntity.h" + +class CBuildingSAInterface; + +class CBuilding : public virtual CEntity +{ +public: + virtual ~CBuilding(){}; + + virtual CBuildingSAInterface* GetBuildingInterface() = 0; +}; diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index ede6ca59cc..440f079be6 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -246,4 +246,5 @@ class CModelInfo virtual bool IsTowableBy(CModelInfo* towingModel) = 0; virtual unsigned int GetParentID() = 0; + virtual bool IsDynamic() = 0; }; diff --git a/Client/sdk/game/CPools.h b/Client/sdk/game/CPools.h index 76a39d9989..f758084639 100644 --- a/Client/sdk/game/CPools.h +++ b/Client/sdk/game/CPools.h @@ -18,6 +18,8 @@ class CObject; class CObjectSA; class CPed; class CPedSA; +class CBuilding; +class CBuildingSA; class CVector; class CVehicle; class CVehicleSA; @@ -82,6 +84,11 @@ 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; diff --git a/Client/sdk/game/CWorld.h b/Client/sdk/game/CWorld.h index 828873d762..a07d2dbd20 100644 --- a/Client/sdk/game/CWorld.h +++ b/Client/sdk/game/CWorld.h @@ -166,6 +166,8 @@ enum eDebugCaller CCivPed_Constructor, CCivPed_Destructor, CBuilding_Destructor, + CBuildingPool_Constructor, + CBuildingPool_Destructor, }; diff --git a/Shared/mods/deathmatch/logic/Utils.h b/Shared/mods/deathmatch/logic/Utils.h index 43f71aab54..3930514d80 100644 --- a/Shared/mods/deathmatch/logic/Utils.h +++ b/Shared/mods/deathmatch/logic/Utils.h @@ -246,6 +246,22 @@ inline float GetSmallestWrapUnsigned(float fValue, float fHigh) void RotateVector(CVector& vecLine, const CVector& vecRotation); +inline void ConvertZXYEulersToQuaternion(const CVector& vecFrom, CVector4D &vecTo) +{ + const float c1 = cos(vecFrom.fX * 0.5f); + const float c2 = cos(vecFrom.fY * 0.5f); + const float c3 = cos(vecFrom.fZ * 0.5f); + + const float s1 = sin(vecFrom.fX * 0.5f); + const float s2 = sin(vecFrom.fY * 0.5f); + const float s3 = sin(vecFrom.fZ * 0.5f); + + vecTo.fX = s1 * c2 * c3 - c1 * s2 * s3; + vecTo.fY = c1 * s2 * c3 + s1 * c2 * s3; + vecTo.fZ = c1 * c2 * s3 + s1 * s2 * c3; + vecTo.fW = c1 * c2 * c3 - s1 * s2 * s3; +} + #ifdef MTA_CLIENT // Misc utility functions unsigned int StripUnwantedCharacters(char* szText, unsigned char cReplace = ' '); diff --git a/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h b/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h index db8056763d..686a855deb 100644 --- a/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h +++ b/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h @@ -588,8 +588,6 @@ struct CLuaFunctionParserBase // A vector3 may also be filled from a vector4 if (CLuaVector4D* pVec4D = cast((CLuaVector4D*)0); pVec4D != nullptr) return *pVec4D; - if (CLuaMatrix* pMatrix = cast((CLuaMatrix*)0); pMatrix != nullptr) - return *pMatrix; // Subtract one from the index, as the call to lua::PopPrimitive above increments the index, even if the // underlying element is of a wrong type