From 3ae31301141399c6cdc176d12c792b633e20fa3a Mon Sep 17 00:00:00 2001 From: FileEX Date: Fri, 11 Sep 2026 09:12:14 +0200 Subject: [PATCH 1/2] Fix bug --- Client/game_sa/CModelInfoSA.cpp | 3 + Client/game_sa/CModelInfoSA.h | 5 ++ .../logic/lua/CLuaFunctionParseHelpers.cpp | 1 + .../CMultiplayerSA_Rendering.cpp | 88 +++++++++++++++++++ Client/multiplayer_sa/StdInc.h | 1 + Client/sdk/game/CModelInfo.h | 6 ++ 6 files changed, 104 insertions(+) diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 16242335597..3f4950ee422 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -676,6 +676,9 @@ void CModelInfoSA::SetIdeFlag(eModelIdeFlag eIdeFlag, bool bState) case eModelIdeFlag::DISABLE_BACKFACE_CULLING: m_pInterface->bIsBackfaceCulled = !bState; break; + case eModelIdeFlag::REALLY_DRAW_LAST: + SetRenderingAfterScene(bState); + break; default: break; } diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index 67d6925c763..48d98ca5ba5 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -356,6 +356,8 @@ class CModelInfoSA : public CModelInfo static std::map ms_DefaultTxdIDMap; SVehicleSupportedUpgrades m_ModelSupportedUpgrades; + bool m_renderAfterScene{false}; + public: CModelInfoSA(); @@ -495,6 +497,9 @@ class CModelInfoSA : public CModelInfo bool IsDynamic() { return m_pInterface ? m_pInterface->usDynamicIndex != MODEL_PROPERTIES_GROUP_STATIC : false; }; bool IsDamageableAtomic() override; + bool ShouldRenderAfterScene() const noexcept override { return m_renderAfterScene; } + void SetRenderingAfterScene(bool shouldRenderAfterScene) noexcept override { m_renderAfterScene = shouldRenderAfterScene; } + static bool IsVehicleModel(std::uint32_t model) noexcept; private: diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 2921e5aaa90..e8d1d9765ab 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -799,6 +799,7 @@ ADD_ENUM(eModelIdeFlag::IS_TAG, "is_tag") ADD_ENUM(eModelIdeFlag::DISABLE_BACKFACE_CULLING, "disable_backface_culling") ADD_ENUM(eModelIdeFlag::IS_BREAKABLE_STATUE, "is_breakable_statue") ADD_ENUM(eModelIdeFlag::IS_CRANE, "is_crane") +ADD_ENUM(eModelIdeFlag::REALLY_DRAW_LAST, "draw_after_scene") IMPLEMENT_ENUM_CLASS_END("model-ide-flag") // https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dformat diff --git a/Client/multiplayer_sa/CMultiplayerSA_Rendering.cpp b/Client/multiplayer_sa/CMultiplayerSA_Rendering.cpp index 77c49d8de2b..8163457fb30 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_Rendering.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_Rendering.cpp @@ -927,6 +927,84 @@ static void __declspec(naked) HOOK_CRenderer_EverythingBarRoads() // clang-format on } +struct AlphaObjectInfo +{ + void* atomic; + void* callback; + float distance; +}; + +static bool InsertEntityIntoSortedList(CEntitySAInterface* entity, float distance) +{ + if (auto* modelInfo = pGameInterface->GetModelInfo(entity->m_nModelIndex); modelInfo && modelInfo->ShouldRenderAfterScene()) + { + CBaseModelInfoSAInterface* baseModelInfo = modelInfo->GetInterface(); + CColModelSAInterface* col = baseModelInfo ? baseModelInfo->pColModel : nullptr; + + if (col) + { + auto boundBox = col->m_bounds; + const CVector& camPos = *(CVector*)0xB76870; + CVector closestPoint; + + closestPoint.fX = std::max(boundBox.m_vecMin.fX, std::min(camPos.fX, boundBox.m_vecMax.fX)); + closestPoint.fY = std::max(boundBox.m_vecMin.fY, std::min(camPos.fY, boundBox.m_vecMax.fY)); + closestPoint.fZ = std::max(boundBox.m_vecMin.fZ, std::min(camPos.fZ, boundBox.m_vecMax.fZ)); + + distance = (camPos - closestPoint).Length(); + } + + // CVisibilityPlugins::RenderEntity as callback + AlphaObjectInfo inf = {entity, (void*)0x732B40, distance}; + + // Call CLinkList::InsertSorted for m_alphaReallyDrawLastList + return ((bool(__thiscall*)(void*, AlphaObjectInfo*))0x733910)((void*)0xC881D0, &inf); + } + + // Call CVisibilityPlugins::InsertEntityIntoSortedList + // Insert into m_alphaList + return ((bool(__cdecl*)(CEntitySAInterface*, float))0x00734570)(entity, distance); +} + +static void RenderEntityAfterScene(CEntitySAInterface* entity, CBaseModelInfoSAInterface* modelInfoInterface) +{ + if (!modelInfoInterface) [[unlikely]] + return; + + // RwEngineInstance->dOpenDevice.fpRenderStateGet/fpRenderStateSet + DWORD engine = *(DWORD*)0xC97B24; + auto fpSet = reinterpret_cast(*(DWORD*)(engine + 0x20)); + + entity->bImBeingRendered = true; + + if (!entity->bBackfaceCulled) + fpSet(0x14 /*rwRENDERSTATECULLMODE*/, (void*)1 /*rwCULLMODECULLNONE*/); + + bool resetColours = entity->SetupLighting(); + entity->Render(); + entity->RemoveLighting(resetColours); + + if (!entity->bBackfaceCulled) + fpSet(0x14 /*rwRENDERSTATECULLMODE*/, (void*)2 /*rwCULLMODECULLBACK*/); + + entity->bImBeingRendered = false; +} + +static void TryRenderOneNonRoad(CEntitySAInterface* entity) +{ + if (entity && (entity->nType == ENTITY_TYPE_OBJECT || entity->nType == ENTITY_TYPE_BUILDING) && entity->m_pRwObject) + { + if (auto* modelInfo = pGameInterface->GetModelInfo(entity->m_nModelIndex); modelInfo && modelInfo->ShouldRenderAfterScene()) + { + RenderEntityAfterScene(entity, modelInfo->GetInterface()); + return; + } + } + + // Call CRenderer::RenderOneNonRoad + ((void(__cdecl*)(CEntitySAInterface*))0x553260)(entity); +} + ////////////////////////////////////////////////////////////////////////////////////////// // // CMultiplayerSA::InitHooks_Rendering @@ -945,6 +1023,16 @@ void CMultiplayerSA::InitHooks_Rendering() MemPut(0x5D9A91, VEHICLE_SPECULAR_LIGHT_SLOT); // RwD3D9EnableLight(TRUE) MemPut(0x5D9F1F, VEHICLE_SPECULAR_LIGHT_SLOT); // RwD3D9EnableLight(FALSE) + HookInstallCall(0x5534D1, (DWORD)InsertEntityIntoSortedList); // CRenderer::AddEntityToRenderList + HookInstallCall(0x55350C, (DWORD)InsertEntityIntoSortedList); // CRenderer::AddEntityToRenderList + HookInstallCall(0x553C45, (DWORD)InsertEntityIntoSortedList); // CRenderer::RenderEverythingBarRoads + HookInstallCall(0x55455A, (DWORD)InsertEntityIntoSortedList); // CRenderer::SetupEntityVisibility + HookInstallCall(0x732C48, (DWORD)TryRenderOneNonRoad); // CVisibilityPlugins::RenderEntity + + // TODO fix entity brightness flickering when entering fade out phase + // HookInstallCall(0x732BD7, (DWORD)RenderFadingAtomic); // CVisibilityPlugins::RenderEntity + // HookInstallCall(0x732BDE, (DWORD)RenderFadingClump); // CVisibilityPlugins::RenderEntity + EZHookInstall(CallIdle); EZHookInstall(CEntity_Render); EZHookInstall(CEntity_RenderOneNonRoad); diff --git a/Client/multiplayer_sa/StdInc.h b/Client/multiplayer_sa/StdInc.h index ae25b42d103..3e1f8672028 100644 --- a/Client/multiplayer_sa/StdInc.h +++ b/Client/multiplayer_sa/StdInc.h @@ -25,5 +25,6 @@ #include "..\game_sa\CPedSA.h" #include "..\game_sa\CProjectileSA.h" #include "..\game_sa\TaskAttackSA.h" +#include "../game_sa/CColModelSA.h" extern CMultiplayerSA* pMultiplayer; diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 03bf6bd9a2d..cbfe6443159 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -53,6 +53,9 @@ enum class eModelIdeFlag DISABLE_BACKFACE_CULLING, IS_BREAKABLE_STATUE, IS_CRANE, + + // Our custom flag + REALLY_DRAW_LAST, }; enum class eModelInfoType : unsigned char @@ -256,4 +259,7 @@ class CModelInfo virtual unsigned int GetParentID() = 0; virtual bool IsDynamic() = 0; virtual bool IsDamageableAtomic() = 0; + + virtual bool ShouldRenderAfterScene() const noexcept = 0; + virtual void SetRenderingAfterScene(bool shouldRenderAfterScene) noexcept = 0; }; From 05b62af35260bcbdae336da6cb6dd1280eff2e51 Mon Sep 17 00:00:00 2001 From: FileEX Date: Fri, 11 Sep 2026 10:12:05 +0200 Subject: [PATCH 2/2] engineGetModelFlag --- Client/game_sa/CModelInfoSA.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 3f4950ee422..febbf315dff 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -726,6 +726,8 @@ bool CModelInfoSA::GetIdeFlag(eModelIdeFlag eIdeFlag) return m_pInterface->bDontCollideWithFlyer; case eModelIdeFlag::DISABLE_BACKFACE_CULLING: return !m_pInterface->bIsBackfaceCulled; + case eModelIdeFlag::REALLY_DRAW_LAST: + return ShouldRenderAfterScene(); default: return false; }