From 77ab3e64a3c6dacdcee02a223b67aec6c5b97ec2 Mon Sep 17 00:00:00 2001 From: Uladzislau Nikalayevich Date: Sun, 26 May 2024 02:27:20 +0300 Subject: [PATCH] Add LOD support for buildings (#3371) --- Client/game_sa/CBuildingSA.cpp | 49 ++++++++++++++ Client/game_sa/CBuildingSA.h | 4 +- .../mods/deathmatch/logic/CClientBuilding.cpp | 67 +++++++++++++++++-- .../mods/deathmatch/logic/CClientBuilding.h | 14 +++- .../logic/CStaticFunctionDefinitions.cpp | 27 +++++++- .../logic/lua/CLuaFunctionParseHelpers.cpp | 25 +++++++ .../logic/lua/CLuaFunctionParseHelpers.h | 1 + .../logic/luadefs/CLuaElementDefs.cpp | 29 +++----- .../logic/luadefs/CLuaElementDefs.h | 2 +- Client/sdk/game/CBuilding.h | 1 + Client/sdk/game/CWorld.h | 2 +- 11 files changed, 191 insertions(+), 30 deletions(-) diff --git a/Client/game_sa/CBuildingSA.cpp b/Client/game_sa/CBuildingSA.cpp index 964506dd02..32c1818309 100644 --- a/Client/game_sa/CBuildingSA.cpp +++ b/Client/game_sa/CBuildingSA.cpp @@ -11,8 +11,57 @@ #include "StdInc.h" #include "CBuildingSA.h" +#include +#include "CGameSA.h" + +extern CGameSA* pGame; CBuildingSA::CBuildingSA(CBuildingSAInterface* pInterface) { SetInterface(pInterface); } + +void CBuildingSA::SetLod(CBuilding* pLod) +{ + if (pLod) + { + if (m_pInterface->m_pLod) + { + SetLod(nullptr); + } + + CBuildingSAInterface* pLodInterface = dynamic_cast(pLod)->GetBuildingInterface(); + assert(pLodInterface); + + // We should recreate buildings... + pGame->GetWorld()->Remove(pLodInterface, CBuilding_SetLod); + pGame->GetWorld()->Remove(m_pInterface, CBuilding_SetLod); + + m_pInterface->m_pLod = pLodInterface; + pLodInterface->bUsesCollision = 0; + pLodInterface->numLodChildren = 1; + + if (pGame->GetModelInfo(pLodInterface->m_nModelIndex)->GetLODDistance() > 300) + { + pLodInterface->bIsBIGBuilding = 1; + } + + // Only this specific order works + pGame->GetWorld()->Add(m_pInterface, CBuilding_SetLod); + pGame->GetWorld()->Add(pLodInterface, CBuilding_SetLod); + } + else + { + CEntitySAInterface* pCurrentLod = m_pInterface->m_pLod; + if (pCurrentLod) + { + pGame->GetWorld()->Remove(pCurrentLod, CBuilding_SetLod); + + m_pInterface->m_pLod = nullptr; + pCurrentLod->bIsBIGBuilding = false; + pCurrentLod->numLodChildren = 0; + + pGame->GetWorld()->Add(pCurrentLod, CBuilding_SetLod); + } + } +} diff --git a/Client/game_sa/CBuildingSA.h b/Client/game_sa/CBuildingSA.h index 6d6794ceb7..6aae552470 100644 --- a/Client/game_sa/CBuildingSA.h +++ b/Client/game_sa/CBuildingSA.h @@ -24,5 +24,7 @@ class CBuildingSA final : public virtual CBuilding, public virtual CEntitySA public: CBuildingSA(CBuildingSAInterface* pInterface); - CBuildingSAInterface* GetBuildingInterface() { return reinterpret_cast(GetInterface()); }; + CBuildingSAInterface* GetBuildingInterface() { return static_cast(GetInterface()); }; + + void SetLod(CBuilding* pLod) override; }; diff --git a/Client/mods/deathmatch/logic/CClientBuilding.cpp b/Client/mods/deathmatch/logic/CClientBuilding.cpp index 6b50476138..df8006f620 100644 --- a/Client/mods/deathmatch/logic/CClientBuilding.cpp +++ b/Client/mods/deathmatch/logic/CClientBuilding.cpp @@ -18,7 +18,9 @@ CClientBuilding::CClientBuilding(class CClientManager* pManager, ElementID ID, u m_vPos(pos), m_vRot(rot), m_interior(interior), - m_pBuilding(nullptr) + m_pBuilding(nullptr), + m_pHighBuilding(nullptr), + m_pLowBuilding(nullptr) { m_pManager = pManager; SetTypeName("building"); @@ -29,6 +31,18 @@ CClientBuilding::CClientBuilding(class CClientManager* pManager, ElementID ID, u CClientBuilding::~CClientBuilding() { m_pBuildingManager->RemoveFromList(this); +} + +void CClientBuilding::Unlink() +{ + if (m_pHighBuilding) + { + m_pHighBuilding->SetLowLodBuilding(); + } + if (m_pLowBuilding) + { + SetLowLodBuilding(); + } Destroy(); } @@ -93,17 +107,62 @@ void CClientBuilding::Create() if (m_pBuilding) return; + if (m_bBeingDeleted) + return; + CVector4D vRot4D; ConvertZXYEulersToQuaternion(m_vRot, vRot4D); m_pBuilding = g_pGame->GetPools()->GetBuildingsPool().AddBuilding(this, m_usModelId, &m_vPos, &vRot4D, m_interior); + + if (m_pHighBuilding) + { + m_pHighBuilding->GetBuildingEntity()->SetLod(m_pBuilding); + } } void CClientBuilding::Destroy() { - if (m_pBuilding) + if (!m_pBuilding) + return; + + if (m_pHighBuilding) { - g_pGame->GetPools()->GetBuildingsPool().RemoveBuilding(m_pBuilding); - m_pBuilding = nullptr; + m_pHighBuilding->GetBuildingEntity()->SetLod(nullptr); } + g_pGame->GetPools()->GetBuildingsPool().RemoveBuilding(m_pBuilding); + m_pBuilding = nullptr; +} + +bool CClientBuilding::SetLowLodBuilding(CClientBuilding* pLod) +{ + if (pLod) + { + // Remove prev LOD + SetLowLodBuilding(); + + // Unlink old high lod element + CClientBuilding* pOveridedBuilding = pLod->GetHighLodBuilding(); + if (pOveridedBuilding && pOveridedBuilding != this) + { + pOveridedBuilding->SetLowLodBuilding(); + } + + // Add new LOD + m_pLowBuilding = pLod; + m_pBuilding->SetLod(pLod->GetBuildingEntity()); + + pLod->SetHighLodBuilding(this); + } + else + { + // Remove LOD + if (m_pLowBuilding) + { + m_pLowBuilding->SetHighLodBuilding(); + } + m_pBuilding->SetLod(nullptr); + m_pLowBuilding = nullptr; + } + return true; } diff --git a/Client/mods/deathmatch/logic/CClientBuilding.h b/Client/mods/deathmatch/logic/CClientBuilding.h index dec521272a..47587c77b9 100644 --- a/Client/mods/deathmatch/logic/CClientBuilding.h +++ b/Client/mods/deathmatch/logic/CClientBuilding.h @@ -23,13 +23,14 @@ class CClientBuilding : public CClientEntity CClientBuilding(class CClientManager* pManager, ElementID ID, uint16_t usModelId, const CVector &pos, const CVector &rot, uint8_t interior); ~CClientBuilding(); - void Unlink(){}; + 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_vRot; }; void SetRotationRadians(const CVector& vecRadians) override; + CBuilding* GetBuildingEntity() const { return m_pBuilding; }; CEntity* GetGameEntity() override { return m_pBuilding; }; const CEntity* GetGameEntity() const override { return m_pBuilding; }; @@ -46,7 +47,15 @@ class CClientBuilding : public CClientEntity void Destroy(); 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; }; + + private: + CClientBuilding* GetHighLodBuilding() const { return m_pHighBuilding; }; + void SetHighLodBuilding(CClientBuilding* pHighBuilding = nullptr) { m_pHighBuilding = pHighBuilding; }; void Recreate() { @@ -62,4 +71,7 @@ class CClientBuilding : public CClientEntity CVector m_vPos; CVector m_vRot; uint8_t m_interior; + + CClientBuilding* m_pHighBuilding; + CClientBuilding* m_pLowBuilding; }; diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index be0e47a076..cacd3290ad 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -3830,7 +3830,12 @@ bool CStaticFunctionDefinitions::SetLowLodElement(CClientEntity& Entity, CClient { RUN_CHILDREN(SetLowLodElement(**iter, pLowLodEntity)) - switch (Entity.GetType()) + eClientEntityType entityType = Entity.GetType(); + + if (pLowLodEntity != nullptr && entityType != pLowLodEntity->GetType()) + return false; + + switch (entityType) { case CCLIENTOBJECT: { @@ -3840,6 +3845,14 @@ bool CStaticFunctionDefinitions::SetLowLodElement(CClientEntity& Entity, CClient return false; break; } + case CCLIENTBUILDING: + { + CClientBuilding& Building = static_cast(Entity); + CClientBuilding* pLowLodBuilding = static_cast(pLowLodEntity); + if (!Building.SetLowLodBuilding(pLowLodBuilding)) + return false; + break; + } default: return false; } @@ -3859,6 +3872,12 @@ bool CStaticFunctionDefinitions::GetLowLodElement(CClientEntity& Entity, CClient pOutLowLodEntity = Object.GetLowLodObject(); break; } + case CCLIENTBUILDING: + { + CClientBuilding& Building = static_cast(Entity); + pOutLowLodEntity = Building.GetLowLodBuilding(); + break; + } default: return false; } @@ -3878,6 +3897,12 @@ bool CStaticFunctionDefinitions::IsElementLowLod(CClientEntity& Entity, bool& bO bOutIsLowLod = Object.IsLowLod(); break; } + case CCLIENTBUILDING: + { + CClientBuilding& Building = static_cast(Entity); + bOutIsLowLod = Building.IsLod(); + break; + } default: return false; } diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index ccaa3264fc..5ee0b062b1 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -1099,6 +1099,31 @@ bool MinClientReqCheck(CScriptArgReader& argStream, const char* szVersionReq, co return true; } +// +// Check min client is correct +// Thrown a error if below required +// +void MinClientReqCheck(lua_State* luaVM, const char* szVersionReq, const char* szReason) +{ + CLuaMain* pLuaMain = g_pClientGame->GetLuaManager()->GetVirtualMachine(luaVM); + if (!pLuaMain) + return; + + CResource* pResource = pLuaMain->GetResource(); + if (!pResource) + return; + + if (pResource->GetMinClientReq() < szVersionReq) + { + #if MTASA_VERSION_TYPE == VERSION_TYPE_RELEASE + SString err(" section in the meta.xml is incorrect or missing (expected at least client %s because %s)", + szVersionReq, szReason); + throw std::invalid_argument(err); + #endif + } + +} + // // Read next as preg option flags // diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index d54388d8be..078c798934 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -575,6 +575,7 @@ void MixedReadDxFontString(CScriptArgReader& argStream, eFontType& outFontType, void MixedReadGuiFontString(CScriptArgReader& argStream, SString& strFontName, const char* szDefaultFontName, CClientGuiFont*& poutGuiFontElement); void MixedReadMaterialString(CScriptArgReader& argStream, CClientMaterial*& pMaterialElement); bool ReadMatrix(lua_State* luaVM, uint uiArgIndex, CMatrix& outMatrix); +void MinClientReqCheck(lua_State* luaVM, const char* szVersionReq, const char* szReason); bool MinClientReqCheck(CScriptArgReader& argStream, const char* szVersionReq, const char* szReason = nullptr); void ReadPregFlags(CScriptArgReader& argStream, pcrecpp::RE_Options& pOptions); diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp index 0e0175e6ce..621cc1c5a3 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp @@ -14,6 +14,8 @@ #include using std::list; +#define MIN_CLIENT_REQ_LOD_FOR_BUILDING "1.6.0-0.20000" + void CLuaElementDefs::LoadFunctions() { constexpr static const std::pair functions[]{ @@ -95,7 +97,7 @@ void CLuaElementDefs::LoadFunctions() {"setElementCollidableWith", SetElementCollidableWith}, {"setElementDoubleSided", SetElementDoubleSided}, {"setElementFrozen", SetElementFrozen}, - {"setLowLODElement", SetLowLodElement}, + {"setLowLODElement", ArgumentParser}, {"setElementCallPropagationEnabled", SetElementCallPropagationEnabled}, }; @@ -2497,29 +2499,14 @@ int CLuaElementDefs::GetLowLodElement(lua_State* luaVM) return 1; } -int CLuaElementDefs::SetLowLodElement(lua_State* luaVM) +bool CLuaElementDefs::SetLowLodElement(lua_State* luaVM, CClientEntity* pEntity, std::optional pLowLodEntity) { - // bool setLowLODElement ( element theElement ) - CClientEntity* pEntity; - CClientEntity* pLowLodEntity; - - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pEntity); - argStream.ReadUserData(pLowLodEntity, NULL); + // bool setLowLODElement ( element theElement [, element lowLowElement ] ) - if (!argStream.HasErrors()) - { - if (CStaticFunctionDefinitions::SetLowLodElement(*pEntity, pLowLodEntity)) - { - lua_pushboolean(luaVM, true); - return 1; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + if (pEntity->GetType() == CCLIENTBUILDING) + MinClientReqCheck(luaVM, MIN_CLIENT_REQ_LOD_FOR_BUILDING, "target is building"); - lua_pushboolean(luaVM, false); - return 1; + return CStaticFunctionDefinitions::SetLowLodElement(*pEntity, pLowLodEntity.value_or(nullptr)); } int CLuaElementDefs::IsElementLowLod(lua_State* luaVM) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h index d85c0eb449..40f93761e4 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.h @@ -97,6 +97,6 @@ class CLuaElementDefs : public CLuaDefs LUA_DECLARE(SetElementCollidableWith); LUA_DECLARE(SetElementDoubleSided); LUA_DECLARE(SetElementFrozen); - LUA_DECLARE(SetLowLodElement); + static bool SetLowLodElement(lua_State* luaVM, CClientEntity* pEntity, std::optional pLowLodEntity); LUA_DECLARE(SetElementCallPropagationEnabled); }; diff --git a/Client/sdk/game/CBuilding.h b/Client/sdk/game/CBuilding.h index 8cbaf6abb4..0138d3594d 100644 --- a/Client/sdk/game/CBuilding.h +++ b/Client/sdk/game/CBuilding.h @@ -21,4 +21,5 @@ class CBuilding : public virtual CEntity virtual ~CBuilding(){}; virtual CBuildingSAInterface* GetBuildingInterface() = 0; + virtual void SetLod(CBuilding* pLod) = 0; }; diff --git a/Client/sdk/game/CWorld.h b/Client/sdk/game/CWorld.h index 807fa09a36..fb6be0baf8 100644 --- a/Client/sdk/game/CWorld.h +++ b/Client/sdk/game/CWorld.h @@ -168,7 +168,7 @@ enum eDebugCaller CBuilding_Destructor, CBuildingPool_Constructor, CBuildingPool_Destructor, - + CBuilding_SetLod, }; enum eSurfaceProperties