diff --git a/Client/core/DXHook/CDirect3DEvents9.cpp b/Client/core/DXHook/CDirect3DEvents9.cpp index 0765092aa2..ea35c911f1 100644 --- a/Client/core/DXHook/CDirect3DEvents9.cpp +++ b/Client/core/DXHook/CDirect3DEvents9.cpp @@ -12,6 +12,7 @@ #include "StdInc.h" #include #include +#include #define DECLARE_PROFILER_SECTION_CDirect3DEvents9 #include "profiler/SharedUtil.Profiler.h" #include "CProxyDirect3DVertexBuffer.h" @@ -510,6 +511,9 @@ void CDirect3DEvents9::OnInvalidate(IDirect3DDevice9* pDevice) { WriteDebugEvent("CDirect3DEvents9::OnInvalidate"); + if (auto game = g_pCore->GetGame()) + game->GetFx()->ClearCustomShadows(true); + const HRESULT hrCooperativeLevel = pDevice->TestCooperativeLevel(); const bool bDeviceOperational = (hrCooperativeLevel == D3D_OK); const bool bDeviceTemporarilyLost = (hrCooperativeLevel == D3DERR_DEVICELOST || hrCooperativeLevel == D3DERR_DEVICENOTRESET); diff --git a/Client/game_sa/CFxSA.cpp b/Client/game_sa/CFxSA.cpp index 0f72213e89..d355b2b8f9 100644 --- a/Client/game_sa/CFxSA.cpp +++ b/Client/game_sa/CFxSA.cpp @@ -10,6 +10,10 @@ *****************************************************************************/ #include "StdInc.h" +#include +#include +#include "gamesa_renderware.h" +#include "CVector2D.h" #include "CFxSA.h" #include "CEntitySA.h" @@ -359,3 +363,107 @@ void CFxSA::AddParticle(FxParticleSystems::Enum eFxParticle, const CVector& vecP fxParticleSystem, &vecPosition, &newDirection, 0, &fxPrt, -1.0f, fBrightness, 0, 0); } } + +namespace +{ + constexpr unsigned int MAX_SCRIPT_SHADOWS = 48; + RwTexture* customShadowTextures[MAX_SCRIPT_SHADOWS] = {}; + + void DestroyCustomShadowTexture(RwTexture* texture) + { + if (!texture) + return; + auto native = reinterpret_cast(&texture->raster->renderResource); + if (native->texture) + native->texture->Release(); + native->texture = nullptr; + RwTextureDestroy(texture); + } + + unsigned short& StoredShadowCount() + { + return *reinterpret_cast(VAR_FXSystem_StoreShadows); + } +} + +CFxSA::~CFxSA() +{ + ClearCustomShadows(true); +} + +void CFxSA::ClearCustomShadows(bool force) +{ + // GTA consumes the queue before the pre-FX callback. Keep textures alive if + // rendering was skipped; device invalidation instead discards the queue. + if (force) + StoredShadowCount() = 0; + else if (StoredShadowCount() != 0) + return; + + for (auto& texture : customShadowTextures) + { + DestroyCustomShadowTexture(texture); + texture = nullptr; + } +} + +bool CFxSA::IsShadowsLimitReached() +{ + return StoredShadowCount() >= MAX_SCRIPT_SHADOWS; +} + +bool CFxSA::AddShadow(eShadowTextureType shadowTextureType, const CVector& vecPosition, const CVector2D& vecOffset1, const CVector2D& vecOffset2, SColor color, + eShadowType shadowType, float fZDistance, bool bDrawOnWater, bool bDrawOnBuildings, IDirect3DBaseTexture9* customTexture) +{ + if (IsShadowsLimitReached() || shadowTextureType < eShadowTextureType::CAR || shadowTextureType >= eShadowTextureType::COUNT) + return false; + + const auto index = StoredShadowCount(); + RwTexture* texture = reinterpret_cast(TEXTURE_FXSystem_Shadow)[static_cast(shadowTextureType)]; + if (customTexture) + { + if (customTexture->GetType() != D3DRTYPE_TEXTURE) + return false; + D3DSURFACE_DESC desc; + auto d3dTexture = static_cast(customTexture); + if (FAILED(d3dTexture->GetLevelDesc(0, &desc))) + return false; + + // A real RW raster without pixel allocation. The queue holds its own + // COM reference, so destroying the Lua element cannot invalidate it. + RwRaster* raster = RwRasterCreate(desc.Width, desc.Height, 32, 0x0500 | 0x04 | 0x80); // 8888 | TEXTURE | DONTALLOCATE + if (!raster) + return false; + texture = RwTextureCreate(raster); + if (!texture) + { + reinterpret_cast(0x7FB020)(raster); // RwRasterDestroy + return false; + } + raster->width = desc.Width; + raster->height = desc.Height; + raster->depth = 32; + raster->format = 0x05; // 8888 + auto native = reinterpret_cast(&raster->renderResource); + d3dTexture->AddRef(); + native->texture = d3dTexture; + native->alpha = true; + native->format = desc.Format; + if (desc.Format >= D3DFMT_DXT1 && desc.Format <= D3DFMT_DXT5) + native->textureFlags |= 0x10; + texture->flags = 0x3302; // linear filtering, clamp U/V + + // A reused queue index can only refer to an already consumed frame. + DestroyCustomShadowTexture(customShadowTextures[index]); + customShadowTextures[index] = texture; + } + if (!texture || !texture->raster) + return false; + + using StoreShadow = void(__cdecl*)(unsigned char, RwTexture*, const CVector*, float, float, float, float, short, unsigned char, unsigned char, + unsigned char, float, bool, float, void*, bool); + reinterpret_cast(FUNC_FXSystem_StoreShadows)(static_cast(shadowType), texture, &vecPosition, vecOffset1.fX, vecOffset1.fY, + vecOffset2.fX, vecOffset2.fY, color.A, color.R, color.G, color.B, fZDistance, bDrawOnWater, 1.0f, + nullptr, bDrawOnBuildings); + return StoredShadowCount() > index; +} diff --git a/Client/game_sa/CFxSA.h b/Client/game_sa/CFxSA.h index 57e0524109..04891da617 100644 --- a/Client/game_sa/CFxSA.h +++ b/Client/game_sa/CFxSA.h @@ -31,6 +31,9 @@ class FxSystem_c; #define FUNC_CFx_TriggerBulletSplash 0x4a10e0 #define FUNC_CFx_TriggerFootSplash 0x4a1150 #define FUNC_FXSystem_c_AddParticle 0x4AA440 +#define FUNC_FXSystem_StoreShadows 0x707390 +#define VAR_FXSystem_StoreShadows 0xC403DC +#define TEXTURE_FXSystem_Shadow 0xC403E0 class CFxSAInterface { @@ -61,6 +64,11 @@ class CFxSA : public CFx public: CFxSA(CFxSAInterface* pInterface) { m_pInterface = pInterface; } + ~CFxSA(); + void ClearCustomShadows(bool force = false) override; + bool AddShadow(eShadowTextureType shadowTextureType, const CVector& vecPosition, const CVector2D& vecOffset1, const CVector2D& vecOffset2, SColor color, + eShadowType shadowType, float fZDistance, bool bDrawOnWater, bool bDrawOnBuildings, IDirect3DBaseTexture9* customTexture = nullptr); + static bool IsShadowsLimitReached(); void AddBlood(CVector& vecPosition, CVector& vecDirection, int iCount, float fBrightness); void AddWood(CVector& vecPosition, CVector& vecDirection, int iCount, float fBrightness); void AddSparks(CVector& vecPosition, CVector& vecDirection, float fForce, int iCount, CVector vecAcrossLine, unsigned char ucBlurIf0, float fSpread, diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 6ed7e01717..74cdb2684c 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -3664,6 +3664,9 @@ void CClientGame::StaticPostWorldProcessPedsAfterPreRenderHandler() void CClientGame::StaticPreFxRenderHandler() { + // Clear custom shadows, so that we don't have any left over from the previous frame. + g_pGame->GetFx()->ClearCustomShadows(); + // RenderFadingInEntities is done at this point, so alpha entity list callbacks // no longer reference CModelRenderer's queue elements. g_pClientGame->GetModelRenderer()->NotifyFrameEnd(); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index e42a5a683c..5916c8c72d 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -8135,6 +8135,14 @@ bool CStaticFunctionDefinitions::FxCreateParticle(FxParticleSystems::Enum eFxPar return true; } +bool CStaticFunctionDefinitions::FxAddShadow(eShadowTextureType shadowTextureType, const CVector& vecPosition, const CVector2D& vecOffset1, + const CVector2D& vecOffset2, SColor color, eShadowType shadowType, float fZDistance, bool bDrawOnWater, + bool bDrawOnBuildings, IDirect3DBaseTexture9* customTexture) +{ + return g_pGame->GetFx()->AddShadow(shadowTextureType, vecPosition, vecOffset1, vecOffset2, color, shadowType, fZDistance, bDrawOnWater, bDrawOnBuildings, + customTexture); +} + CClientEffect* CStaticFunctionDefinitions::CreateEffect(CResource& Resource, const SString& strFxName, const CVector& vecPosition, bool bSoundEnable) { CClientEffect* pFx = m_pManager->GetEffectManager()->Create(strFxName, vecPosition, INVALID_ELEMENT_ID, bSoundEnable); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h index 0d1bc5f2c4..4da7530c14 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -749,6 +749,9 @@ class CStaticFunctionDefinitions static bool FxAddFootSplash(CVector& vecPosition); static bool FxCreateParticle(FxParticleSystems::Enum eFxParticle, CVector& vecPosition, CVector& vecDirection, float fR, float fG, float fB, float fA, bool bRandomizeColors, std::uint32_t iCount, float fBrightness, float fSize, bool bRandomizeSizes, float fLife); + static bool FxAddShadow(eShadowTextureType shadowTextureType, const CVector& vecPosition, const CVector2D& vecOffset1, const CVector2D& vecOffset2, + SColor color, eShadowType shadowType, float fZDistance, bool bDrawOnWater, bool bDrawOnBuildings, + IDirect3DBaseTexture9* customTexture = nullptr); static CClientEffect* CreateEffect(CResource& Resource, const SString& strFxName, const CVector& vecPosition, bool bSoundEnable); // Sound funcs diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 2921e5aaa9..cfd77b4ee5 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -834,6 +834,27 @@ ADD_ENUM(_D3DFORMAT::D3DFMT_G32R32F, "g32r32f") ADD_ENUM(_D3DFORMAT::D3DFMT_A32B32G32R32F, "a32b32g32r32f") IMPLEMENT_ENUM_CLASS_END("surface-format") +IMPLEMENT_ENUM_CLASS_BEGIN(eShadowTextureType) +ADD_ENUM(eShadowTextureType::CAR, "car") +ADD_ENUM(eShadowTextureType::PED, "ped") +ADD_ENUM(eShadowTextureType::HELI, "heli") +ADD_ENUM(eShadowTextureType::BIKE, "bike") +ADD_ENUM(eShadowTextureType::RCBARON, "rcbaron") +ADD_ENUM(eShadowTextureType::EXPLOSION, "explosion") +ADD_ENUM(eShadowTextureType::HEADLIGHT1, "headlight1") +ADD_ENUM(eShadowTextureType::HEADLIGHT2, "headlight2") +ADD_ENUM(eShadowTextureType::BLOOD, "blood") +ADD_ENUM(eShadowTextureType::HANDMAN, "handman") +ADD_ENUM(eShadowTextureType::WINCRACK, "wincrack") +ADD_ENUM(eShadowTextureType::LAMP, "lamp") +IMPLEMENT_ENUM_CLASS_END("shadow-texture-type") + +IMPLEMENT_ENUM_CLASS_BEGIN(eShadowType) +ADD_ENUM(eShadowType::DEFAULT, "default") +ADD_ENUM(eShadowType::ADDITIVE, "additive") +ADD_ENUM(eShadowType::INVCOLOR, "invcolor") +IMPLEMENT_ENUM_CLASS_END("shadow-type") + IMPLEMENT_ENUM_CLASS_BEGIN(eRenderStage) ADD_ENUM(eRenderStage::PRE_FX, "prefx") ADD_ENUM(eRenderStage::POST_FX, "postfx") diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index f8020e29b6..2e673b6fe5 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "enums/WeaponProperty.h" @@ -93,6 +94,8 @@ DECLARE_ENUM_CLASS(eModelIdeFlag); DECLARE_ENUM_CLASS(_D3DFORMAT); DECLARE_ENUM_CLASS(eRenderStage); DECLARE_ENUM(FxParticleSystems::Enum); +DECLARE_ENUM_CLASS(eShadowTextureType); +DECLARE_ENUM_CLASS(eShadowType); DECLARE_ENUM(ePools); DECLARE_ENUM(WorldProperty::Enum); DECLARE_ENUM_CLASS(eModelLoadState); diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.cpp index 8d799aea67..1b1e14cb94 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.cpp @@ -35,6 +35,7 @@ void CLuaEffectDefs::LoadFunctions() {"setEffectDensity", SetEffectDensity}, {"getEffectDensity", GetEffectDensity}, {"fxCreateParticle", ArgumentParser}, + {"fxAddShadow", ArgumentParser}, }; // Add functions @@ -62,6 +63,7 @@ void CLuaEffectDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "addWaterSplash", "fxAddWaterSplash"); lua_classfunction(luaVM, "addWood", "fxAddWood"); lua_classfunction(luaVM, "createParticle", "fxCreateParticle"); + lua_classfunction(luaVM, "createShadow", "fxAddShadow"); lua_classfunction(luaVM, "setDensity", "setEffectDensity"); lua_classfunction(luaVM, "setSpeed", "setEffectSpeed"); @@ -651,3 +653,37 @@ bool CLuaEffectDefs::FxCreateParticle(FxParticleSystems::Enum eParticleSystem, C bRandomizeColors.value_or(false), iCount.value_or(1), fBrightness.value_or(1.0f), fSize.value_or(0.3f), bRandomizeSizes.value_or(false), fLife.value_or(1.0f)); } + +bool CLuaEffectDefs::FxAddShadow(std::variant texture, CVector vecPosition, CVector2D vecOffset1, CVector2D vecOffset2, + SColor color, eShadowType shadowType, float zDistance, bool bDrawOnWater, bool bDrawOnBuildings) +{ + if (!std::isfinite(vecPosition.fX) || !std::isfinite(vecPosition.fY) || !std::isfinite(vecPosition.fZ) || !std::isfinite(vecOffset1.fX) || + !std::isfinite(vecOffset1.fY) || !std::isfinite(vecOffset2.fX) || !std::isfinite(vecOffset2.fY)) + throw std::invalid_argument("Position and offsets must be finite"); + if (vecOffset1.Length() > 32) + { + throw std::invalid_argument("First offset can not be longer than 32 units"); + } + else if (vecOffset2.Length() > 32) // bigger and close to limit shadows size can be partially invisible + { + throw std::invalid_argument("Second offset can not be longer than 32 units"); + } + else if (!std::isfinite(zDistance) || zDistance < 0 || zDistance > 3000) // negative distance not working + { + throw std::invalid_argument("Z Distance must be between 0.0 and 3000.0"); + } + IDirect3DBaseTexture9* customTexture = nullptr; + eShadowTextureType shadowTextureType = eShadowTextureType::CAR; + if (auto element = std::get_if(&texture)) + { + customTexture = (*element)->GetTextureItem()->m_pD3DTexture; + if (!customTexture) + return false; + if (customTexture->GetType() != D3DRTYPE_TEXTURE) + throw std::invalid_argument("Shadow texture must be a 2D texture"); + } + else + shadowTextureType = std::get(texture); + return CStaticFunctionDefinitions::FxAddShadow(shadowTextureType, vecPosition, vecOffset1, vecOffset2, color, shadowType, zDistance, bDrawOnWater, + bDrawOnBuildings, customTexture); +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.h index 1f50c21e0c..a6e6bd6984 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEffectDefs.h @@ -41,4 +41,6 @@ class CLuaEffectDefs : public CLuaDefs static bool FxCreateParticle(FxParticleSystems::Enum eParticleSystem, CVector vecPosition, CVector vecDirection, float fR, float fG, float fB, float fA, std::optional bRandomizeColors, std::optional iCount, std::optional fBrightness, std::optional fSize, std::optional bRandomizeSizes, std::optional fLife); + static bool FxAddShadow(std::variant texture, CVector vecPosition, CVector2D vecOffset1, CVector2D vecOffset2, + SColor color, eShadowType shadowType, float zDistance, bool bDrawOnWater, bool bDrawOnBuildings); }; diff --git a/Client/sdk/game/CFx.h b/Client/sdk/game/CFx.h index e320e6f152..5fc2544746 100644 --- a/Client/sdk/game/CFx.h +++ b/Client/sdk/game/CFx.h @@ -12,6 +12,35 @@ #pragma once #include "enums/FxParticleSystems.h" +#include +class CVector2D; +struct IDirect3DBaseTexture9; +enum class eShadowTextureType +{ + CAR, + PED, + HELI, + BIKE, + RCBARON, + EXPLOSION, + HEADLIGHT1, + HEADLIGHT2, + BLOOD, + HANDMAN, + WINCRACK, + LAMP, + COUNT + +}; + +enum class eShadowType +{ + NONE, + DEFAULT, + ADDITIVE, + INVCOLOR, +}; + class CEntity; class CVector; class CVehicle; @@ -37,4 +66,8 @@ class CFx virtual void TriggerFootSplash(CVector& vecPosition) = 0; virtual void AddParticle(FxParticleSystems::Enum eFxParticle, const CVector& vecPosition, const CVector& vecDirection, float fR, float fG, float fB, float fA, bool bRandomizeColors, std::uint32_t iCount, float fBrightness, float fSize, bool bRandomizeSizes, float fLife) = 0; + virtual bool AddShadow(eShadowTextureType shadowTextureType, const CVector& vecPosition, const CVector2D& vecOffset1, const CVector2D& vecOffset2, + SharedUtil::SColor color, eShadowType shadowType, float fZDistance, bool bDrawOnWater, bool bDrawOnBuildings, + IDirect3DBaseTexture9* customTexture = nullptr) = 0; + virtual void ClearCustomShadows(bool force = false) = 0; };