diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index 163e807136c..e0d565a6ca7 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -251,6 +251,7 @@ CGameSA::CGameSA() CPtrNodeSingleLinkPoolSA::StaticSetHooks(); CVehicleAudioSettingsManagerSA::StaticSetHooks(); CPointLightsSA::StaticSetHooks(); + CVisibilityPluginsSA::StaticSetHooks(); } catch (const std::bad_alloc& e) { @@ -470,6 +471,9 @@ void CGameSA::Reset() // Restore changed TXD IDs CModelInfoSA::StaticResetTextureDictionaries(); + // Restore visibility plugin lists + CVisibilityPluginsSA::ResetRenderingEntityLists(); + // Restore default world state RestoreGameWorld(); diff --git a/Client/game_sa/CLinkListSA.h b/Client/game_sa/CLinkListSA.h new file mode 100644 index 00000000000..f3ae72fa616 --- /dev/null +++ b/Client/game_sa/CLinkListSA.h @@ -0,0 +1,103 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CLinkListSA.h + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once + +#include "CLinkSA.h" +#include +#include + +template +class CLinkListSA +{ +public: + CLinkSA usedListHead{}; + CLinkSA usedListTail{}; + CLinkSA freeListHead{}; + CLinkSA freeListTail{}; + CLinkSA* entries{}; + + void* operator new(std::size_t size) { return ((void*(__cdecl*)(std::size_t))0x821195)(size); } + void* operator new[](std::size_t size) { return ((void*(__cdecl*)(std::size_t))0x821195)(size); } + void operator delete(void* ptr) { ((void(__cdecl*)(void*))0x8213AE)(ptr); } + void operator delete[](void* ptr) { ((void(__cdecl*)(void*))0x8213AE)(ptr); } + + void Init(std::size_t count) + { + if (count == 0) + return; + + usedListHead.next = &usedListTail; + usedListTail.prev = &usedListHead; + freeListHead.next = &freeListTail; + freeListTail.prev = &freeListHead; + + entries = new CLinkSA[count]; + for (std::int32_t i = count - 1; i >= 0; i--) + entries[i].Insert(&freeListHead); + } + + void Shutdown() { delete[] std::exchange(entries, nullptr); } + + void Insert(CLinkSA& link) + { + link.Remove(); + link.Insert(&usedListHead); + } + + CLinkSA* Insert(T const& data) + { + CLinkSA* link = freeListHead.next; + if (link == &freeListTail) + return nullptr; + + link->data = data; + Insert(*link); + return link; + } + + CLinkSA* InsertSorted(T const& data) + { + CLinkSA* i = nullptr; + for (i = usedListHead.next; i != &usedListTail; i = i->next) + { + if (i->data.distance >= data.distance) + break; + } + + CLinkSA* link = freeListHead.next; + if (link == &freeListTail) + return nullptr; + + link->data = data; + link->Remove(); + link->Insert(i->prev); + return link; + } + + void Clear() + { + for (CLinkSA* link = usedListHead.next; link != &usedListTail; link = usedListHead.next) + Remove(link); + } + + auto Remove(CLinkSA* l) + { + l->Remove(); + l->Insert(&freeListHead); + return l; + } + + auto GetTail() { return usedListTail.prev; } + auto& GetTailLink() { return usedListTail; } + + auto GetHead() { return usedListHead.next; } + auto& GetHeadLink() { return usedListHead; } +}; +static_assert(sizeof(CLinkListSA) == 0x34, "Invalid size for CLinkListSA class!"); diff --git a/Client/game_sa/CLinkSA.h b/Client/game_sa/CLinkSA.h new file mode 100644 index 00000000000..69d6fbf2168 --- /dev/null +++ b/Client/game_sa/CLinkSA.h @@ -0,0 +1,42 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CLinkSA.h + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +template +class CLinkSA +{ +public: + T data; + CLinkSA* prev; + CLinkSA* next; + + void* operator new(std::size_t size) { return ((void*(__cdecl*)(std::size_t))0x821195)(size); } + void* operator new[](std::size_t size) { return ((void*(__cdecl*)(std::size_t))0x821195)(size); } + void operator delete(void* ptr) { ((void(__cdecl*)(void*))0x8213AE)(ptr); } + void operator delete[](void* ptr) { ((void(__cdecl*)(void*))0x8213AE)(ptr); } + + void Remove() + { + next->prev = prev; + prev->next = next; + } + + // If `this` is already in another list, `Remove()` must first be called! (Not doing so will result in the list (`this` is in) getting corrupted) + void Insert(CLinkSA* after) + { + next = after->next; + next->prev = this; + + prev = after; + prev->next = this; + } +}; +static_assert(sizeof(CLinkSA) == 0xC, "Invalid size for CLinkSA class!"); diff --git a/Client/game_sa/CVisibilityPluginsSA.cpp b/Client/game_sa/CVisibilityPluginsSA.cpp index fbfc62962eb..29265cdfbe8 100644 --- a/Client/game_sa/CVisibilityPluginsSA.cpp +++ b/Client/game_sa/CVisibilityPluginsSA.cpp @@ -14,6 +14,13 @@ #define FUNC_CVisibilityPlugins_InsertEntityIntoEntityList 0x733DD0 +// m_alphaEntitiesList +static RenderingListState alphaEntitiesList{(CLinkListSA*)0xC88120, (*(std::size_t*)0x733B05) / sizeof(CLinkSA), false}; + +// m_alphaUnderwaterEntityList +static RenderingListState underwaterEntitiesList{(CLinkListSA*)0xC88178, (*(std::size_t*)0x733BD5) / sizeof(CLinkSA), + false}; + void CVisibilityPluginsSA::SetClumpAlpha(RpClump* pClump, int iAlpha) { DWORD dwFunc = FUNC_CVisiblityPlugins_SetClumpAlpha; @@ -58,15 +65,73 @@ int CVisibilityPluginsSA::GetAtomicId(RwObject* pAtomic) return iResult; } +bool CVisibilityPluginsSA::IsAtomicVisible(RpAtomic* atomic) const +{ + if (!atomic) + return false; + + return ((bool(__cdecl*)(RpAtomic*))0x732990)(atomic); +} + bool CVisibilityPluginsSA::InsertEntityIntoEntityList(void* entity, float distance, void* callback) { return ((bool(_cdecl*)(void*, float, void*))FUNC_CVisibilityPlugins_InsertEntityIntoEntityList)(entity, distance, callback); } -bool CVisibilityPluginsSA::IsAtomicVisible(RpAtomic* atomic) const +void CVisibilityPluginsSA::SetRenderingListSize(RenderingEntityListType listType, std::size_t elementsCount) { - if (!atomic) - return false; + RenderingListState& state = listType == RenderingEntityListType::ENTITY_LIST_TYPE_ALPHA ? alphaEntitiesList : underwaterEntitiesList; + if (state.size == elementsCount) + return; - return ((bool(__cdecl*)(RpAtomic*))0x732990)(atomic); + state.size = elementsCount; + state.pendingReinit = true; +} + +void CVisibilityPluginsSA::CheckRenderingList(RenderingListState& state) +{ + if (!state.pendingReinit) + return; + + ReInitRenderingList(state.list, state.size); + state.pendingReinit = false; +} + +void CVisibilityPluginsSA::ReInitRenderingList(CLinkListSA* list, std::size_t count) +{ + if (!list || count == 0) + return; + + list->Shutdown(); + list->Init(count); +} + +void* __fastcall CVisibilityPluginsSA::InsertAlphaEntityIntoSortedList(CLinkListSA* entitiesList, void*, AlphaObjectInfoSA* info) +{ + CheckRenderingList(alphaEntitiesList); + return entitiesList->InsertSorted(*info); +} + +void* __fastcall CVisibilityPluginsSA::InsertUnderwaterEntityIntoSortedList(CLinkListSA* entitiesList, void*, AlphaObjectInfoSA* info) +{ + CheckRenderingList(underwaterEntitiesList); + return entitiesList->InsertSorted(*info); +} + +void CVisibilityPluginsSA::ResetRenderingEntityLists() +{ + alphaEntitiesList.size = DEFAULT_MAX_ALPHA_ENTITIES; + underwaterEntitiesList.size = DEFAULT_MAX_UNDERWATER_ENTITIES; + + ReInitRenderingList(alphaEntitiesList.list, alphaEntitiesList.size); + ReInitRenderingList(underwaterEntitiesList.list, underwaterEntitiesList.size); +} + +void CVisibilityPluginsSA::StaticSetHooks() +{ + HookInstallCall(0x7345F2, (DWORD)InsertAlphaEntityIntoSortedList); // CVisibilityPlugins::InsertEntityIntoSortedList + HookInstallCall(0x733DF5, (DWORD)InsertAlphaEntityIntoSortedList); // CVisibilityPlugins::InsertObjectIntoSortedList + + HookInstallCall(0x7345D9, (DWORD)InsertUnderwaterEntityIntoSortedList); // CVisibilityPlugins::InsertEntityIntoSortedList + HookInstallCall(0x733DB5, (DWORD)InsertUnderwaterEntityIntoSortedList); // CVisibilityPlugins::InsertEntityIntoUnderwaterList } diff --git a/Client/game_sa/CVisibilityPluginsSA.h b/Client/game_sa/CVisibilityPluginsSA.h index c6c92298591..b6f06eda0eb 100644 --- a/Client/game_sa/CVisibilityPluginsSA.h +++ b/Client/game_sa/CVisibilityPluginsSA.h @@ -12,17 +12,43 @@ #pragma once #include +#include "CLinkListSA.h" #define FUNC_CVisiblityPlugins_SetClumpAlpha 0x732B00 #define FUNC_CVisibilityPlugins_GetAtomicId 0x732370 +struct AlphaObjectInfoSA +{ + void* object; + void* callback; + float distance; +}; + +struct RenderingListState +{ + CLinkListSA* list; + std::size_t size; + bool pendingReinit; +}; + class CVisibilityPluginsSA : public CVisibilityPlugins { public: void SetClumpAlpha(RpClump* pClump, int iAlpha); int GetAtomicId(RwObject* pAtomic); + bool IsAtomicVisible(RpAtomic* atomic) const override; + bool InsertEntityIntoEntityList(void* entity, float distance, void* callback); + void SetRenderingListSize(RenderingEntityListType listType, std::size_t elementsCount) override; - bool IsAtomicVisible(RpAtomic* atomic) const override; + static void ResetRenderingEntityLists(); + static void StaticSetHooks(); + +private: + static void CheckRenderingList(RenderingListState& state); + static void ReInitRenderingList(CLinkListSA* list, std::size_t count); + + static void* __fastcall InsertAlphaEntityIntoSortedList(CLinkListSA* entitiesList, void*, AlphaObjectInfoSA* info); + static void* __fastcall InsertUnderwaterEntityIntoSortedList(CLinkListSA* entitiesList, void*, AlphaObjectInfoSA* info); }; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 7ae83f8bfbb..5d6baef1a89 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -992,6 +992,11 @@ ADD_ENUM(PostFXType::CONTRAST, "contrast") ADD_ENUM(PostFXType::SATURATION, "saturation") IMPLEMENT_ENUM_CLASS_END("postfx-type") +IMPLEMENT_ENUM_CLASS_BEGIN(RenderingEntityListType) +ADD_ENUM(RenderingEntityListType::ENTITY_LIST_TYPE_ALPHA, "alpha") +ADD_ENUM(RenderingEntityListType::ENTITY_LIST_TYPE_UNDERWATER, "underwater") +IMPLEMENT_ENUM_CLASS_END("rendering-entity-list-type") + // // CResource from userdata // diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index bae91981974..7506af168c6 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -26,6 +26,7 @@ #include "enums/SoundEffectType.h" #include "enums/ObjectGroupPhysicalProperties.h" #include "enums/PostFXType.h" +#include "enums/RenderingEntityListType.h" enum eLuaType { @@ -104,6 +105,7 @@ DECLARE_ENUM_CLASS(taskType); DECLARE_ENUM(eEntityType); DECLARE_ENUM_CLASS(VehicleAudioSettingProperty); DECLARE_ENUM_CLASS(PostFXType); +DECLARE_ENUM_CLASS(RenderingEntityListType); class CRemoteCall; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 2ac1acf9acc..9a64c1fd5e4 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -14,9 +14,11 @@ #include #include #include +#include #include #include "CLuaEngineDefs.h" #include +#include //! Set the CModelCacheManager limits //! By passing `nil`/no value the original values are restored @@ -163,6 +165,7 @@ void CLuaEngineDefs::LoadFunctions() {"enginePreloadWorldArea", ArgumentParser}, {"engineRestreamModel", ArgumentParser}, {"engineRestream", ArgumentParser}, + {"engineSetRenderingListSize", ArgumentParser}, // CLuaCFunctions::AddFunction ( "engineReplaceMatchingAtomics", EngineReplaceMatchingAtomics ); // CLuaCFunctions::AddFunction ( "engineReplaceWheelAtomics", EngineReplaceWheelAtomics ); @@ -2631,3 +2634,13 @@ void CLuaEngineDefs::EngineRestream(std::optional option) { g_pClientGame->Restream(option); } + +void CLuaEngineDefs::EngineSetRenderingListSize(RenderingEntityListType listType, std::size_t elementsCount) +{ + if (listType == RenderingEntityListType::ENTITY_LIST_TYPE_ALPHA && elementsCount < DEFAULT_MAX_ALPHA_ENTITIES) + throw std::invalid_argument("Alpha entities list size cannot be less than 200"); + else if (listType == RenderingEntityListType::ENTITY_LIST_TYPE_UNDERWATER && elementsCount < DEFAULT_MAX_UNDERWATER_ENTITIES) + throw std::invalid_argument("Underwater entities list size cannot be less than 100"); + + g_pGame->GetVisibilityPlugins()->SetRenderingListSize(listType, elementsCount); +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index 6a107c0ded0..e6a04bfb556 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -99,6 +99,8 @@ class CLuaEngineDefs : public CLuaDefs static bool EngineRestreamModel(std::uint16_t modelId); static void EngineRestream(std::optional option); + static void EngineSetRenderingListSize(RenderingEntityListType listType, std::size_t elementsCount); + private: static void AddEngineColClass(lua_State* luaVM); static void AddEngineTxdClass(lua_State* luaVM); diff --git a/Client/sdk/game/CVisibilityPlugins.h b/Client/sdk/game/CVisibilityPlugins.h index e8cf67c9153..8be705cdfdc 100644 --- a/Client/sdk/game/CVisibilityPlugins.h +++ b/Client/sdk/game/CVisibilityPlugins.h @@ -10,10 +10,14 @@ *****************************************************************************/ #pragma once +#include "enums/RenderingEntityListType.h" #define ATOMIC_ID_FLAG_TWO_VERSIONS_UNDAMAGED 1 #define ATOMIC_ID_FLAG_TWO_VERSIONS_DAMAGED 2 +#define DEFAULT_MAX_ALPHA_ENTITIES 200 +#define DEFAULT_MAX_UNDERWATER_ENTITIES 100 + struct RpClump; struct RpAtomic; struct RwObject; @@ -24,7 +28,8 @@ class CVisibilityPlugins virtual void SetClumpAlpha(RpClump* pClump, int iAlpha) = 0; virtual int GetAtomicId(RwObject* pAtomic) = 0; - virtual bool InsertEntityIntoEntityList(void* entity, float distance, void* callback) = 0; - virtual bool IsAtomicVisible(RpAtomic* atomic) const = 0; + + virtual bool InsertEntityIntoEntityList(void* entity, float distance, void* callback) = 0; + virtual void SetRenderingListSize(RenderingEntityListType listType, std::size_t elementsCount) = 0; }; diff --git a/Shared/sdk/enums/RenderingEntityListType.h b/Shared/sdk/enums/RenderingEntityListType.h new file mode 100644 index 00000000000..3f718f5fbdc --- /dev/null +++ b/Shared/sdk/enums/RenderingEntityListType.h @@ -0,0 +1,18 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/RenderingEntityListType.h + * PURPOSE: Header for common definitions + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +enum class RenderingEntityListType +{ + ENTITY_LIST_TYPE_ALPHA, + ENTITY_LIST_TYPE_UNDERWATER, +};