diff --git a/Client/game_sa/C2DEffectSA.cpp b/Client/game_sa/C2DEffectSA.cpp new file mode 100644 index 0000000000..184764902c --- /dev/null +++ b/Client/game_sa/C2DEffectSA.cpp @@ -0,0 +1,456 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectSA.cpp + * PURPOSE: 2DFX static class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "gamesa_renderware.h" +#include "C2DEffectSA.h" +#include "C2DEffectSAInterface.h" +#include "CGameSA.h" + +extern CGameSA* pGame; + +C2DEffectSA::C2DEffectSA(C2DEffectSAInterface* effectInterface, std::uint32_t modelID) : m_effectInterface(effectInterface), m_model(modelID) +{ + pGame->Get2DEffects()->AddToList(this); +} + +void C2DEffectSA::Destroy() const +{ + CModelInfo* modelInfo = pGame->GetModelInfo(m_model); + if (modelInfo) + modelInfo->Remove2DFX(m_effectInterface, false); +} + +void C2DEffectSA::SetPosition(const CVector& position) +{ + if (m_effectInterface) + m_effectInterface->position = RwV3d{position.fX, position.fY, position.fZ}; +} + +CVector& C2DEffectSA::GetPosition() const +{ + if (m_effectInterface) + return CVector(m_effectInterface->position.x, m_effectInterface->position.y, m_effectInterface->position.z); + + return CVector(); +} + +void C2DEffectSA::SetCoronaFarClip(float clip) +{ + if (IsValidLight()) + m_effectInterface->effect.light.coronaFarClip = clip; +} + +void C2DEffectSA::SetCoronaPointLightRange(float range) +{ + if (IsValidLight()) + m_effectInterface->effect.light.pointLightRange = range; +} + +void C2DEffectSA::SetCoronaSize(float size) +{ + if (IsValidLight()) + m_effectInterface->effect.light.coronaSize = size; +} + +void C2DEffectSA::SetShadowSize(float size) +{ + if (IsValidLight()) + m_effectInterface->effect.light.shadowSize = size; +} + +void C2DEffectSA::SetShadowMultiplier(std::uint8_t multiplier) +{ + if (IsValidLight()) + m_effectInterface->effect.light.shadowColorMultiplier = multiplier; +} + +void C2DEffectSA::SetCoronaShowMode(e2dCoronaFlashType showMode) +{ + if (IsValidLight()) + m_effectInterface->effect.light.coronaFlashType = showMode; +} + +void C2DEffectSA::SetCoronaReflectionsEnabled(bool enable) +{ + if (IsValidLight()) + m_effectInterface->effect.light.coronaEnableReflection = enable; +} + +void C2DEffectSA::SetCoronaFlareType(std::uint8_t flareType) +{ + if (IsValidLight()) + m_effectInterface->effect.light.coronaFlareType = flareType; +} + +void C2DEffectSA::SetLightFlags(std::uint16_t flags) +{ + if (IsValidLight()) + m_effectInterface->effect.light.flags = flags; +} + +void C2DEffectSA::SetShadowDistance(std::int8_t distance) +{ + if (IsValidLight()) + m_effectInterface->effect.light.shadowZDistance = distance; +} + +void C2DEffectSA::SetCoronaOffsets(const CVector& offsets) +{ + if (IsValidLight()) + { + m_effectInterface->effect.light.offsetX = offsets.fX; + m_effectInterface->effect.light.offsetY = offsets.fY; + m_effectInterface->effect.light.offsetZ = offsets.fZ; + } +} + +void C2DEffectSA::SetCoronaColor(const RwColor& color) +{ + if (IsValidLight()) + m_effectInterface->effect.light.color = color; +} + +void C2DEffectSA::SetCoronaTexture(const std::string& name) +{ + if (IsValidLight()) + { + if (m_effectInterface->effect.light.coronaTex) + RwTextureDestroy(m_effectInterface->effect.light.coronaTex); + + // Call CTxdStore::PushCurrentTxd + ((void(__cdecl*)())FUNC_PushCurrentTxd)(); + // Call CTxdStore::FindTxdSlot + int slot = ((int(__cdecl*)(const char*))FUNC_FindTxdSlot)("particle"); + // Call CTxdStore::SetCurrentTxd + ((void(__cdecl*)(int))FUNC_SetCurrentTxd)(slot); + + m_effectInterface->effect.light.coronaTex = RwReadTexture(name.c_str(), nullptr); + + // Call CTxdStore::PopCurrentTxd + ((void(__cdecl*)())FUNC_PopCurrentTxd)(); + } +} + +void C2DEffectSA::SetShadowTexture(const std::string& name) +{ + if (IsValidLight()) + { + if (m_effectInterface->effect.light.shadowTex) + RwTextureDestroy(m_effectInterface->effect.light.shadowTex); + + // Call CTxdStore::PushCurrentTxd + ((void(__cdecl*)())FUNC_PushCurrentTxd)(); + // Call CTxdStore::FindTxdSlot + int slot = ((int(__cdecl*)(const char*))FUNC_FindTxdSlot)("particle"); + // Call CTxdStore::SetCurrentTxd + ((void(__cdecl*)(int))FUNC_SetCurrentTxd)(slot); + + m_effectInterface->effect.light.shadowTex = RwReadTexture(name.c_str(), nullptr); + + // Call CTxdStore::PopCurrentTxd + ((void(__cdecl*)())FUNC_PopCurrentTxd)(); + } +} + +CVector C2DEffectSA::GetCoronaOffsets() const +{ + if (IsValidLight()) + return CVector(m_effectInterface->effect.light.offsetX, m_effectInterface->effect.light.offsetY, m_effectInterface->effect.light.offsetZ); + + return CVector(); +} + +void C2DEffectSA::SetParticleName(const std::string& name) +{ + if (m_effectInterface && m_effectInterface->type == e2dEffectType::PARTICLE) + std::strncpy(m_effectInterface->effect.particle.szName, name.c_str(), 24); +} + +void C2DEffectSA::SetRoadsignSize(const RwV2d& size) +{ + if (IsValidRoadsign()) + m_effectInterface->effect.roadsign.size = size; +} + +void C2DEffectSA::SetRoadsignRotation(const RwV3d& rotation) +{ + if (IsValidRoadsign()) + m_effectInterface->effect.roadsign.rotation = rotation; +} + +void C2DEffectSA::SetRoadsignFlags(std::uint8_t flags) +{ + if (IsValidRoadsign()) + m_effectInterface->effect.roadsign.flags = flags; +} + +void C2DEffectSA::SetRoadsignText(const std::string& text, std::uint8_t line) +{ + if (IsValidRoadsign()) + { + if (!m_effectInterface->effect.roadsign.text) + { + m_effectInterface->effect.roadsign.text = static_cast(std::malloc(64)); + MemSetFast(m_effectInterface->effect.roadsign.text, 0, 64); + } + + if (!m_effectInterface->effect.roadsign.text) + return; + + std::strncpy(m_effectInterface->effect.roadsign.text + 16 * (line - 1), text.c_str(), 16); + } +} + +RwV2d& C2DEffectSA::GetRoadsignSize() +{ + if (IsValidRoadsign()) + return m_effectInterface->effect.roadsign.size; + + static RwV2d dummySize{0, 0}; + return dummySize; +} + +RwV3d& C2DEffectSA::GetRoadsignRotation() +{ + if (IsValidRoadsign()) + return m_effectInterface->effect.roadsign.rotation; + + static RwV3d dummyRotation{0, 0, 0}; + return dummyRotation; +} + +std::string C2DEffectSA::GetRoadsignText() const +{ + if (IsValidRoadsign() && m_effectInterface->effect.roadsign.text) + return std::string(m_effectInterface->effect.roadsign.text, 64); + + return ""; +} + +void C2DEffectSA::SetEscalatorBottom(const RwV3d& bottom) +{ + if (IsValidEscalator()) + m_effectInterface->effect.escalator.bottom = bottom; +} + +void C2DEffectSA::SetEscalatorTop(const RwV3d& top) +{ + if (IsValidEscalator()) + m_effectInterface->effect.escalator.top = top; +} + +void C2DEffectSA::SetEscalatorEnd(const RwV3d& end) +{ + if (IsValidEscalator()) + m_effectInterface->effect.escalator.end = end; +} + +void C2DEffectSA::SetEscalatorDirection(std::uint8_t direction) +{ + if (IsValidEscalator()) + m_effectInterface->effect.escalator.direction = direction; +} + +RwV3d& C2DEffectSA::GetEscalatorBottom() +{ + if (IsValidEscalator()) + return m_effectInterface->effect.escalator.bottom; + + static RwV3d dummyBottom{0, 0, 0}; + return dummyBottom; +} + +RwV3d& C2DEffectSA::GetEscalatorTop() +{ + if (IsValidEscalator()) + return m_effectInterface->effect.escalator.top; + + static RwV3d dummyTop{0, 0, 0}; + return dummyTop; +} + +RwV3d& C2DEffectSA::GetEscalatorEnd() +{ + if (IsValidEscalator()) + return m_effectInterface->effect.escalator.end; + + static RwV3d dummyEnd{0, 0, 0}; + return dummyEnd; +} + +RpAtomic* C2DEffectSA::Roadsign_CreateAtomic(const RwV3d& position, const RwV3d& rotation, float sizeX, float sizeY, std::uint32_t numLines, char* line1, char* line2, char* line3, char* line4, std::uint32_t numLetters, std::uint8_t palleteID) +{ + // Call CCustomRoadsignMgr::CreateRoadsignAtomic + RpAtomic* atomic = ((RpAtomic*(__cdecl*)(float, float, std::int32_t, char*, char*, char*, char*, std::int32_t, std::uint8_t))0x6FF2D0)(sizeX, sizeY, numLines, line1, line2, line3, line4, numLetters, palleteID); + RwFrame* frame = RpAtomicGetFrame(atomic); + RwFrameSetIdentity(frame); + + const RwV3d axis0{1.0f, 0.0f, 0.0f}, axis1{0.0f, 1.0f, 0.0f}, axis2{0.0f, 0.0f, 1.0f}; + RwFrameRotate(frame, &axis2, rotation.z, rwCOMBINEREPLACE); + RwFrameRotate(frame, &axis0, rotation.x, rwCOMBINEPOSTCONCAT); + RwFrameRotate(frame, &axis1, rotation.y, rwCOMBINEPOSTCONCAT); + + RwFrameTranslate(frame, &position, TRANSFORM_AFTER); + RwFrameUpdateObjects(frame); + + return atomic; +} + +std::uint32_t C2DEffectSA::Roadsign_GetPalleteIDFromFlags(std::uint8_t flags) +{ + std::uint32_t id = (flags >> 4) & 3; + return id <= 3 ? id : 0; +} + +std::uint32_t C2DEffectSA::Roadsign_GetNumLettersFromFlags(std::uint8_t flags) +{ + std::uint32_t letters = (flags >> 2) & 3; + switch (letters) + { + case 1u: + return 2; + case 2u: + return 4; + case 3u: + return 8; + default: + return 16; + } +} + +std::uint32_t C2DEffectSA::Roadsign_GetNumLinesFromFlags(std::uint8_t flags) +{ + std::uint32_t lines = flags & 3; + return (lines <= 3 && lines > 0) ? lines : 4; +} + +void C2DEffectSA::Roadsign_DestroyAtomic(C2DEffectSAInterface* effect) +{ + if (!effect) + return; + + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + if (roadsign.atomic) + { + RwFrame* frame = RpAtomicGetFrame(roadsign.atomic); + if (frame) + { + RpAtomicSetFrame(roadsign.atomic, nullptr); + RwFrameDestroy(frame); + } + + RpAtomicDestroy(roadsign.atomic); + roadsign.atomic = nullptr; + } +} + +C2DEffectSAInterface* C2DEffectSA::CreateCopy(C2DEffectSAInterface* effect) +{ + C2DEffectSAInterface* copy = new C2DEffectSAInterface(); + MemCpyFast(copy, effect, sizeof(C2DEffectSAInterface)); + + // Create a copy of textures for the lights + // We must to do this, because C2DEffect::Shutdown removes them + if (copy->type == e2dEffectType::LIGHT) + { + if (effect->effect.light.coronaTex && effect->effect.light.shadowTex) + C2DEffectSA::PrepareTexturesForLightEffect(copy->effect.light.coronaTex, copy->effect.light.shadowTex, effect->effect.light.coronaTex->name, effect->effect.light.shadowTex->name, false); + } + else if (copy->type == e2dEffectType::ROADSIGN) + { + // Create a copy of text and atomic for the roadsign + // We must to do this, because C2DEffect::Shutdown removes them + copy->effect.roadsign.text = static_cast(std::malloc(64)); + if (copy->effect.roadsign.text) + { + MemSetFast(copy->effect.roadsign.text, 0, 64); + MemCpyFast(copy->effect.roadsign.text, effect->effect.roadsign.text, 64); + + copy->effect.roadsign.atomic = Roadsign_CreateAtomic(copy->position, copy->effect.roadsign.rotation, copy->effect.roadsign.size.x, copy->effect.roadsign.size.y, Roadsign_GetNumLinesFromFlags(copy->effect.roadsign.flags), ©->effect.roadsign.text[0], ©->effect.roadsign.text[16], ©->effect.roadsign.text[32], ©->effect.roadsign.text[48], Roadsign_GetNumLettersFromFlags(copy->effect.roadsign.flags), Roadsign_GetPalleteIDFromFlags(copy->effect.roadsign.flags)); + } + } + + return copy; +} + +void C2DEffectSA::Shutdown(C2DEffectSAInterface* effect) +{ + if (!effect) + return; + + if (effect->type == e2dEffectType::ROADSIGN) + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + Roadsign_DestroyAtomic(effect); + + if (roadsign.text) + { + std::free(roadsign.text); + roadsign.text = nullptr; + } + } + else if (effect->type == e2dEffectType::LIGHT) + { + t2dEffectLight& light = effect->effect.light; + + if (light.coronaTex) + { + RwTextureDestroy(light.coronaTex); + light.coronaTex = nullptr; + } + + if (light.shadowTex) + { + RwTextureDestroy(light.shadowTex); + light.shadowTex = nullptr; + } + } +} + +void C2DEffectSA::SafeDelete2DFXEffect(C2DEffectSAInterface* effect) +{ + if (!effect) + return; + + if (effect->type == e2dEffectType::ROADSIGN || effect->type == e2dEffectType::LIGHT) + Shutdown(effect); + + delete effect; + effect = nullptr; +} + +void C2DEffectSA::PrepareTexturesForLightEffect(RwTexture*& coronaTex, RwTexture*& shadowTex, const char* coronaName, const char* shadowName, bool removeIfExist) +{ + // Call CTxdStore::PushCurrentTxd + ((void(__cdecl*)())FUNC_PushCurrentTxd)(); + // Call CTxdStore::FindTxdSlot + int slot = ((int(__cdecl*)(const char*))FUNC_FindTxdSlot)("particle"); + // Call CTxdStore::SetCurrentTxd + ((void(__cdecl*)(int))FUNC_SetCurrentTxd)(slot); + + if (removeIfExist) + { + if (coronaTex && coronaName) + RwTextureDestroy(coronaTex); + if (shadowTex && shadowName) + RwTextureDestroy(shadowTex); + } + + if (coronaName) + coronaTex = RwReadTexture(coronaName, nullptr); + + if (shadowName) + shadowTex = RwReadTexture(shadowName, nullptr); + + // Call CTxdStore::PopCurrentTxd + ((void(__cdecl*)())FUNC_PopCurrentTxd)(); +} diff --git a/Client/game_sa/C2DEffectSA.h b/Client/game_sa/C2DEffectSA.h new file mode 100644 index 0000000000..bb7b43980e --- /dev/null +++ b/Client/game_sa/C2DEffectSA.h @@ -0,0 +1,141 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectSA.h + * PURPOSE: Header file for 2dfx class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once +#include "game/C2DEffect.h" +#include "C2DEffectSAInterface.h" +#include + +#define ARRAY_2DFXInfoStore 0xB4C2D8 // C2dfxInfoStore d2fxModels + +#define FUNC_C2DEffect_Shutdown 0x4C57D0 +#define FUNC_PushCurrentTxd 0x7316A0 +#define FUNC_FindTxdSlot 0x731850 +#define FUNC_SetCurrentTxd 0x7319C0 +#define FUNC_PopCurrentTxd 0x7316B0 + +// Escalators stuff +#define ARRAY_CEscalators 0xC6E9A8 +#define NUM_MAX_ESCALATORS 32 +#define FUNC_CEscalator_SwitchOff 0x717860 + +// fx stuff +#define FUNC_Fx_c_DestroyEntityFx 0x4A1280 +#define FUNC_Fx_c_CreateEntityFx 0x4A11E0 +#define VAR_G_Fx 0xA9AE00 +#define OFFSET_FxSystem_Entities 0xC +#define OFFSET_FxSystem_Link_Prev 0x4 + +class C2DEffectSA : public C2DEffect +{ +public: + C2DEffectSA(C2DEffectSAInterface* effectInterface, std::uint32_t modelID); + ~C2DEffectSA() = default; + + C2DEffectSAInterface* GetInterface() noexcept { return m_effectInterface; } + e2dEffectType GetEffectType() override { return m_effectInterface ? m_effectInterface->type : e2dEffectType::NONE; } + + bool IsValidLight() const noexcept { return m_effectInterface && m_effectInterface->type == e2dEffectType::LIGHT; }; + bool IsValidRoadsign() const noexcept { return m_effectInterface && m_effectInterface->type == e2dEffectType::ROADSIGN; } + bool IsValidEscalator() const noexcept { return m_effectInterface && m_effectInterface->type == e2dEffectType::ESCALATOR; } + bool IsValidParticle() const noexcept { return m_effectInterface && m_effectInterface->type == e2dEffectType::PARTICLE; } + + void Destroy() const; + + void SetPosition(const CVector& position) override; + CVector& GetPosition() const override; + + // Light properties + // Set + void SetCoronaFarClip(float clip) override; + void SetCoronaPointLightRange(float range) override; + void SetCoronaSize(float size) override; + void SetShadowSize(float size) override; + void SetShadowMultiplier(std::uint8_t multiplier) override; + void SetCoronaShowMode(e2dCoronaFlashType showMode) override; + void SetCoronaReflectionsEnabled(bool enable) override; + void SetCoronaFlareType(std::uint8_t flareType) override; + void SetLightFlags(std::uint16_t flags) override; + void SetShadowDistance(std::int8_t distance) override; + void SetCoronaOffsets(const CVector& offsets) override; + void SetCoronaColor(const RwColor& color) override; + void SetCoronaTexture(const std::string& name) override; + void SetShadowTexture(const std::string& name) override; + + // Get + float GetCoronaFarClip() const override { return IsValidLight() ? m_effectInterface->effect.light.coronaFarClip : 0.0f; } + float GetCoronaPointLightRange() const override { return IsValidLight() ? m_effectInterface->effect.light.pointLightRange : 0.0f; } + float GetCoronaSize() const override { return IsValidLight() ? m_effectInterface->effect.light.coronaSize : 0.0f; } + float GetShadowSize() const override { return IsValidLight() ? m_effectInterface->effect.light.shadowSize : 0.0f; } + std::uint8_t GetShadowMultiplier() const override { return IsValidLight() ? m_effectInterface->effect.light.shadowColorMultiplier : 0; } + e2dCoronaFlashType GetCoronaShowMode() const override { return IsValidLight() ? m_effectInterface->effect.light.coronaFlashType : e2dCoronaFlashType::UNUSED; } + bool GetCoronaReflectionsEnabled() const override { return IsValidLight() ? m_effectInterface->effect.light.coronaEnableReflection : false; } + std::uint8_t GetCoronaFlareType() const override { return IsValidLight() ? m_effectInterface->effect.light.coronaFlareType : 0; } + std::uint16_t GetLightFlags() const override { return IsValidLight() ? m_effectInterface->effect.light.flags : 0; } + std::int8_t GetShadowDistance() const override { return IsValidLight() ? m_effectInterface->effect.light.shadowZDistance : 0; } + CVector GetCoronaOffsets() const override; + RwColor GetCoronaColor() const override { return IsValidLight() ? m_effectInterface->effect.light.color : RwColor{0,0,0,0}; } + std::string GetCoronaTexture() const override { return IsValidLight() ? (m_effectInterface->effect.light.coronaTex ? m_effectInterface->effect.light.coronaTex->name : "") : ""; } + std::string GetShadowTexture() const override { return IsValidLight() ? (m_effectInterface->effect.light.shadowTex ? m_effectInterface->effect.light.shadowTex->name : "") : ""; } + + // Particle properties + // Set + void SetParticleName(const std::string& name) override; + + // Get + std::string GetParticleName() const override { return IsValidParticle() ? (m_effectInterface->effect.particle.szName ? m_effectInterface->effect.particle.szName : "") : ""; } + + // Roadsign properties + // Set + void SetRoadsignSize(const RwV2d& size) override; + void SetRoadsignRotation(const RwV3d& rotation) override; + void SetRoadsignFlags(std::uint8_t flags) override; + void SetRoadsignText(const std::string& text, std::uint8_t line) override; + + // Get + RwV2d& GetRoadsignSize() override; + RwV3d& GetRoadsignRotation() override; + std::uint16_t GetRoadsignFlags() const override { return IsValidRoadsign() ? m_effectInterface->effect.roadsign.flags : 0; } + std::string GetRoadsignText() const override; + + // Escalator properties + // Set + void SetEscalatorBottom(const RwV3d& bottom) override; + void SetEscalatorTop(const RwV3d& top) override; + void SetEscalatorEnd(const RwV3d& end) override; + void SetEscalatorDirection(std::uint8_t direction) override; + + // Get + RwV3d& GetEscalatorBottom() override; + RwV3d& GetEscalatorTop() override; + RwV3d& GetEscalatorEnd() override; + std::uint8_t GetEscalatorDirection() const override { return IsValidEscalator() ? m_effectInterface->effect.escalator.direction : 0; } + + static RpAtomic* Roadsign_CreateAtomic(const RwV3d& position, const RwV3d& rotation, float sizeX, float sizeY, std::uint32_t numLines, char* line1, char* line2, char* line3, char* line4, std::uint32_t numLetters, std::uint8_t palleteID); + static std::uint32_t Roadsign_GetPalleteIDFromFlags(std::uint8_t flags); + static std::uint32_t Roadsign_GetNumLettersFromFlags(std::uint8_t flags); + static std::uint32_t Roadsign_GetNumLinesFromFlags(std::uint8_t flags); + static void Roadsign_DestroyAtomic(C2DEffectSAInterface* effect); + + static C2DEffectSAInterface* CreateCopy(C2DEffectSAInterface* effect); + + static void Shutdown(C2DEffectSAInterface* effect); + static void SafeDelete2DFXEffect(C2DEffectSAInterface* effect); + static void PrepareTexturesForLightEffect(RwTexture*& coronaTex, RwTexture*& shadowTex, const char* coronaName, const char* shadowName, bool removeIfExist); + +public: + static int effect2dPluginOffset; + +private: + C2DEffectSAInterface* m_effectInterface; + std::uint32_t m_model; + +}; diff --git a/Client/game_sa/C2DEffectSAInterface.h b/Client/game_sa/C2DEffectSAInterface.h new file mode 100644 index 0000000000..711275993c --- /dev/null +++ b/Client/game_sa/C2DEffectSAInterface.h @@ -0,0 +1,250 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectSAInterface.h + * PURPOSE: Header file for 2dfx game interface layer + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once + +#include "game/RenderWare.h" +#include "game/CModelInfo.h" +#include "game/C2DEffects.h" +#include "CObjectSA.h" + +struct t2dEffectLight +{ + RwColor color; + float coronaFarClip; + float pointLightRange; + float coronaSize; + float shadowSize; + + // Flags + union + { + struct + { + // Flags 1 + std::uint16_t checkObstacles : 1; + std::uint16_t fogType : 1; + std::uint16_t fogType2 : 1; + std::uint16_t withoutCorona : 1; + std::uint16_t onlyLongDistance : 1; + std::uint16_t atDay : 1; + std::uint16_t atNight : 1; + std::uint16_t blinking1 : 1; + + // Flags 2 + std::uint16_t onlyFromBelow : 1; + std::uint16_t blinking2 : 1; + std::uint16_t updateHeightAboveGround : 1; + std::uint16_t checkDirection : 1; + std::uint16_t blinking3 : 1; + }; + + std::uint16_t flags; + }; + + e2dCoronaFlashType coronaFlashType; // show mode + bool coronaEnableReflection; + std::uint8_t coronaFlareType; // lens flare effect 0 - off, 1 - on + std::uint8_t shadowColorMultiplier; + std::int8_t shadowZDistance; + std::int8_t offsetX; + std::int8_t offsetY; + std::int8_t offsetZ; + std::uint8_t field_1E[2]; + RwTexture* coronaTex; + RwTexture* shadowTex; + std::int32_t field_28; + std::int32_t field_2C; +}; + +// The particle effect name is an entry in effects.fxp +struct t2dEffectParticle +{ + char szName[24]; +}; + +// It`s used for spawning peds (Like on ticketbooth, Windows of shops, Blackjack-tables) +// It includes information about the External Script ped is going to use when spawned, it`s facing angle and it`s behaviour +struct t2dEffectAttractor +{ + RwV3d queueDirection; + RwV3d useDirection; + RwV3d forwardDirection; + e2dAttractorType attractorType; + std::uint8_t pedExistingProbability; + std::uint8_t field_26; + std::uint8_t flags; + char szScriptName[8]; +}; + +// entry-exit markers similar to ipl version +struct t2dEffectEnex +{ + float enterAngle; // Rotation angle enter-marker (relative to the object) + RwV3d size; // The radius of the approximation to the marker + RwV3d exitPosn; // The position of exit-marker (offset relative to enter position) + float exitAngle; // angle of rotation exit-marker (relative to the object) + std::int16_t interiorId; + std::uint8_t flags1; // Unknown flags + std::uint8_t skyColor; + char szInteriorName[8]; + std::uint8_t timeOn; + std::uint8_t timeOff; + + // Flags 2 + union + { + struct + { + std::uint8_t unknown1 : 1; + std::uint8_t unknown2 : 1; + std::uint8_t timedEffect : 1; + }; + std::uint8_t flags2; + }; + + std::uint8_t field_2F; +}; + +struct t2dEffectRoadsign +{ + RwV2d size; + RwV3d rotation; + + // Flags + union + { + struct + { + std::uint8_t numOfLines : 2; + std::uint8_t symbolsPerLine : 2; + std::uint8_t textColor : 2; + }; + + std::uint8_t flags; + }; + + std::uint8_t field_16[2]; + char* text; // size 64 + RpAtomic* atomic; +}; + +// Section defines a place where peds can cover during firefights +struct t2dEffectCoverPoint +{ + RwV2d direction; + std::uint8_t type; + std::uint8_t field_9[3]; +}; + +// Example in vgseesc01.dff +struct t2dEffectEscalator +{ + RwV3d bottom; + RwV3d top; + RwV3d end; // Z pos, matches top Z if escalator goes up, bottom Z if it goes down + std::uint8_t direction; // 0 - down, 1 - up + std::uint8_t field_25[3]; +}; + +// Example in kb_bandit_u.dff +// Used to determine additional coordinates that can be used in scripts +struct t2dEffectTriggerPoint +{ + std::int32_t id; +}; + +// Some interiors stuff, probably unused? +struct t2dEffectFurniture +{ + std::uint8_t type; + std::int8_t groupId; + // size + std::uint8_t width; + std::uint8_t depth; + std::uint8_t height; + // doors + std::int8_t door; + std::int8_t l_door[2]; // start, end + std::int8_t r_door[2]; // start, end + std::int8_t t_door[2]; // start, end + // windows + std::int8_t l_window[2]; // start, end + std::int8_t r_window[2]; // start, end + std::int8_t t_window[2]; // start, end + // something like offsets? + std::int8_t goLeft[3]; // x,y,z? + std::int8_t goBottom[3]; // x,y,z? + std::int8_t goWidth[3]; // x,y,z? + std::int8_t goDepth[3]; // x,y,z? + + std::uint8_t seed; + std::uint8_t status; + float rotation; +}; + +union t2dEffectUnion +{ + t2dEffectLight light; + t2dEffectParticle particle; + t2dEffectAttractor attractor; + t2dEffectEnex enex; + t2dEffectRoadsign roadsign; + t2dEffectCoverPoint coverPoint; + t2dEffectEscalator escalator; + t2dEffectTriggerPoint triggerPoint; // slot machine, k.a.a.c gates, basketball hoop + t2dEffectFurniture furniture; +}; + +class C2DEffectSAInterface +{ +public: + RwV3d position; + e2dEffectType type; + std::uint8_t field_D[3]; + + t2dEffectUnion effect; +}; + +class C2DEffectInfoStoreSAInterface +{ +public: + std::uint32_t objCount; + C2DEffectSAInterface objects[100]; +}; + +class C2DEffectPluginDataSAInterface +{ +public: + std::uint32_t count; + C2DEffectSAInterface objects[]; +}; + +class CEscalatorSAInterface +{ +public: + RwV3d startPos; + RwV3d bottomPos; + RwV3d topPos; + RwV3d endPos; + std::uint8_t rotation[72]; // CMatrixSAInterface + bool exist; + bool objectCreated; + bool moveDown; + std::uint8_t field_7B; // pad + std::int32_t numIntermediatePlanes; + std::uint32_t numBottomPlanes; + std::uint32_t numTopPlanes; + std::uint8_t field_88[8]; // unused field + RwSphere bounding; + float currentPosition; + CEntitySAInterface* entity; + CObjectSAInterface* objects[42]; +}; diff --git a/Client/game_sa/C2DEffectsSA.cpp b/Client/game_sa/C2DEffectsSA.cpp new file mode 100644 index 0000000000..f258c045ab --- /dev/null +++ b/Client/game_sa/C2DEffectsSA.cpp @@ -0,0 +1,66 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectsSA.cpp + * PURPOSE: 2dfx class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "C2DEffectsSA.h" +#include "C2DEffectSA.h" +#include "CGameSA.h" + +extern CGameSA* pGame; + +C2DEffect* C2DEffectsSA::Create(std::uint32_t model, const CVector& position, const e2dEffectType& type) +{ + CModelInfo* modelInfo = pGame->GetModelInfo(model); + if (!modelInfo) + return nullptr; + + C2DEffectSAInterface* effectInterface = modelInfo->Add2DFXEffect(position, type); + if (!effectInterface) + return nullptr; + + C2DEffectSA* effect = new C2DEffectSA(effectInterface, model); + return effect; +} + +void C2DEffectsSA::Destroy(C2DEffect* effect) +{ + if (!effect) + return; + + auto* effectInterface = dynamic_cast(effect)->GetInterface(); + for (auto it = m_effects.begin(); it != m_effects.end(); ++it) + { + if ((*it)->GetInterface() == effectInterface) + { + (*it)->Destroy(); + + delete *it; + m_effects.erase(it); + break; + } + } +} + +C2DEffectSA* C2DEffectsSA::Get(C2DEffectSAInterface* effectInterface) const +{ + for (const auto& effect : m_effects) + if (effect->GetInterface() == effectInterface) + return effect; + + return nullptr; +} + +void C2DEffectsSA::RemoveFromList(C2DEffectSA* effect) +{ + auto& it = std::find(m_effects.begin(), m_effects.end(), effect); + if (it != m_effects.end()) + m_effects.erase(it); +} diff --git a/Client/game_sa/C2DEffectsSA.h b/Client/game_sa/C2DEffectsSA.h new file mode 100644 index 0000000000..94195a4fec --- /dev/null +++ b/Client/game_sa/C2DEffectsSA.h @@ -0,0 +1,26 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectsSA.h + * PURPOSE: Header file for 2dfx class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once +#include "C2DEffectSA.h" + +class C2DEffectsSA : public C2DEffects +{ +public: + C2DEffect* Create(std::uint32_t model, const CVector& position, const e2dEffectType& type) override; + void Destroy(C2DEffect* effect) override; + C2DEffectSA* Get(C2DEffectSAInterface* effectInterface) const override; + void AddToList(C2DEffectSA* effect) { m_effects.push_back(effect); } + void RemoveFromList(C2DEffectSA* effect); + +private: + std::list m_effects; +}; diff --git a/Client/game_sa/CEntitySA.cpp b/Client/game_sa/CEntitySA.cpp index 02a9f00380..4c6f4228df 100644 --- a/Client/game_sa/CEntitySA.cpp +++ b/Client/game_sa/CEntitySA.cpp @@ -66,11 +66,6 @@ CRect* CEntitySAInterface::GetBoundRect_(CRect* pRect) return pRect; } -void CEntitySAInterface::StaticSetHooks() -{ - HookInstall(0x534120, &CEntitySAInterface::GetBoundRect_); -} - CEntitySA::CEntitySA() { // Set these variables to a constant state @@ -715,3 +710,77 @@ bool CEntitySA::GetUnderwater() { return m_pInterface->bUnderwater; } + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Hook for CEntity::CreateEffects & CEntity::DestroyEffects +// +// Handle modified 2dfx effects during streaming +// This is necessary because once the object is streamed in, the 2dfx effects are loaded from RwStream +// and the default effects are restored even though they have been modified. +// +////////////////////////////////////////////////////////////////////////////////////////// +static void Keep2DFXEffectsBeforeRemove(std::uint32_t modelID) +{ + CModelInfo* modelInfo = pGame->GetModelInfo(modelID); + if (!modelInfo) + return; + + modelInfo->CopyModified2DFXEffects(); +} + +#define HOOKPOS_CEntity_DestroyEffects 0x533C01 +#define HOOKSIZE_CEntity_DestroyEffects 5 +static constexpr DWORD CONTINUE_CEntity_DestroyEffects = 0x533C06; +static void _declspec(naked) HOOK_CEntity_DestroyEffects() +{ + _asm + { + pushad + push eax + call Keep2DFXEffectsBeforeRemove + add esp, 4 + popad + + movzx eax, byte ptr [ecx+0Dh] + push ebp + jmp CONTINUE_CEntity_DestroyEffects + } +} + +static void Restore2DFXEffects(std::uint32_t modelID) +{ + CModelInfo* modelInfo = pGame->GetModelInfo(modelID); + if (!modelInfo) + return; + + modelInfo->RestoreModified2DFXEffects(); +} + +#define HOOKPOS_CEntity_CreateEffects 0x533BAE +#define HOOKSIZE_CEntity_CreateEffects 9 +static constexpr DWORD RETURN_CEntity_CreateEffects = 0x533BB7; +static void _declspec(naked) HOOK_CEntity_CreateEffects() +{ + _asm + { + pushad + push [ebp+22h] + call Restore2DFXEffects + add esp, 4 + popad + + pop edi + pop ebp + pop ebx + add esp, 0C0h + jmp RETURN_CEntity_CreateEffects + } +} + +void CEntitySAInterface::StaticSetHooks() +{ + HookInstall(0x534120, &CEntitySAInterface::GetBoundRect_); + EZHookInstall(CEntity_DestroyEffects); + EZHookInstall(CEntity_CreateEffects); +} diff --git a/Client/game_sa/CFxSA.cpp b/Client/game_sa/CFxSA.cpp index b063cb927d..bd8c92f8cf 100644 --- a/Client/game_sa/CFxSA.cpp +++ b/Client/game_sa/CFxSA.cpp @@ -246,7 +246,7 @@ void CFxSA::AddParticle(eFxParticleSystems eFxParticle, const CVector& vecPositi FxPrtMult_c fxPrt{{fR,fG,fB,fA}, fSize, 0, fLife}; CVector newDirection; - FxSystem_c* fxParticleSystem; + FxSystem_cSAInterface* fxParticleSystem; switch (eFxParticle) { @@ -326,6 +326,6 @@ void CFxSA::AddParticle(eFxParticleSystems eFxParticle, const CVector& vecPositi newDirection.fZ = (rand() % 10000) * 0.0001f * 4 - 2 + newDirection.fZ; // Call FxSystem_c::AddParticle - ((int(__thiscall*)(FxSystem_c*, const CVector*, const CVector*, float, FxPrtMult_c*, float, float, float, int))FUNC_FXSystem_c_AddParticle)(fxParticleSystem, &vecPosition, &newDirection, 0, &fxPrt, -1.0f, fBrightness, 0, 0); + ((int(__thiscall*)(FxSystem_cSAInterface*, const CVector*, const CVector*, float, FxPrtMult_c*, float, float, float, int))FUNC_FxSystem_c_AddParticle)(fxParticleSystem, &vecPosition, &newDirection, 0, &fxPrt, -1.0f, fBrightness, 0, 0); } } diff --git a/Client/game_sa/CFxSA.h b/Client/game_sa/CFxSA.h index 7d61c1759a..8a7f4e78a2 100644 --- a/Client/game_sa/CFxSA.h +++ b/Client/game_sa/CFxSA.h @@ -12,48 +12,125 @@ #pragma once #include +#include "CFxSystemBPSA.h" +#include "CVector.h" struct RwColor; -class FxSystem_c; - -#define FUNC_CFx_AddBlood 0x49eb00 -#define FUNC_CFx_AddWood 0x49ee10 -#define FUNC_CFx_AddSparks 0x49f040 -#define FUNC_CFx_AddTyreBurst 0x49f300 -#define FUNC_CFx_AddBulletImpact 0x49f3d0 -#define FUNC_CFx_AddPunchImpact 0x49f670 -#define FUNC_CFx_AddDebris 0x49f750 -#define FUNC_CFx_AddGlass 0x49f970 -#define FUNC_CFx_TriggerWaterHydrant 0x4a0d70 -#define FUNC_CFx_TriggerGunshot 0x4a0de0 -#define FUNC_CFx_TriggerTankFire 0x4a0fa0 -#define FUNC_CFx_TriggerWaterSplash 0x4a1070 -#define FUNC_CFx_TriggerBulletSplash 0x4a10e0 -#define FUNC_CFx_TriggerFootSplash 0x4a1150 -#define FUNC_FXSystem_c_AddParticle 0x4AA440 +struct RwRaster; +struct RwMatrix; + +#define FUNC_CFx_AddBlood 0x49eb00 +#define FUNC_CFx_AddWood 0x49ee10 +#define FUNC_CFx_AddSparks 0x49f040 +#define FUNC_CFx_AddTyreBurst 0x49f300 +#define FUNC_CFx_AddBulletImpact 0x49f3d0 +#define FUNC_CFx_AddPunchImpact 0x49f670 +#define FUNC_CFx_AddDebris 0x49f750 +#define FUNC_CFx_AddGlass 0x49f970 +#define FUNC_CFx_TriggerWaterHydrant 0x4a0d70 +#define FUNC_CFx_TriggerGunshot 0x4a0de0 +#define FUNC_CFx_TriggerTankFire 0x4a0fa0 +#define FUNC_CFx_TriggerWaterSplash 0x4a1070 +#define FUNC_CFx_TriggerBulletSplash 0x4a10e0 +#define FUNC_CFx_TriggerFootSplash 0x4a1150 +#define FUNC_FxSystem_c_AddParticle 0x4AA440 + +enum class eFXQuality : std::uint32_t +{ + QUALITY_LOW = 0, + QUALITY_MEDIUM, + QUALITY_HIGH, + QUALITY_VERY_HIGH, +}; + +enum class eFxSystemPlayState : std::uint8_t +{ + PLAYING = 0, + STOPPED, + UNKNOWN, +}; + +enum class eFxSystemKillState : std::uint8_t +{ + NOT_KILLED = 0, + PLAY_AND_KILL, + KILLED, + UNKNOWN, +}; + +class FxSystem_cSAInterface +{ +public: + std::uint32_t m_link[2]; // ListItem_c + CFxSystemBPSAInterface* m_bluePrint; + void* m_transformMatrix; // RwMatrixTag* + std::uint8_t m_baseMatrix[64]; // RwMatrixTag + eFxSystemPlayState m_playState; + eFxSystemKillState m_killState; + bool m_useConstTime; + std::uint8_t field_53[2]; + float m_cameraDistance; + std::uint16_t m_constTime; + std::uint16_t m_rateMult; + std::uint16_t m_timeMult; + + union + { + struct + { + std::uint8_t m_hasOwnMatrix : 1; + std::uint8_t m_local : 1; + std::uint8_t m_useZTest : 1; + std::uint8_t m_stopParticleCreation : 1; + std::uint8_t m_prevCulled : 1; + std::uint8_t m_mustCreateParticles : 1; + }; + + std::uint8_t flags; + }; + + std::uint8_t field_63; + float m_loopInterval; + CVector m_velAdd; + void* m_boundingSphere; // CParticleBounding* or FxSphere_c* + void** m_primsList; // FxPrim_c** + std::uint8_t m_fireAE[136]; // CAEFireAudioEntity +}; class CFxSAInterface { public: - FxSystem_c* m_fxSysBlood; - FxSystem_c* m_fxSysBoatSplash; - FxSystem_c* m_fxSysBubble; - FxSystem_c* m_fxSysDebris; - FxSystem_c* m_fxSysSmoke; - FxSystem_c* m_fxSysGunshell; - FxSystem_c* m_fxSysSand; - FxSystem_c* m_fxSysSand2; - FxSystem_c* m_fxSysSmokeHuge; - FxSystem_c* m_fxSysSmoke2; - FxSystem_c* m_fxSysSpark; - FxSystem_c* m_fxSysSpark2; - FxSystem_c* m_fxSysSplash; - FxSystem_c* m_fxSysWake; - FxSystem_c* m_fxSysWaterSplash; - FxSystem_c* m_fxSysWheelDirt; - FxSystem_c* m_fxSysGlass; + FxSystem_cSAInterface* m_fxSysBlood; + FxSystem_cSAInterface* m_fxSysBoatSplash; + FxSystem_cSAInterface* m_fxSysBubble; + FxSystem_cSAInterface* m_fxSysDebris; + FxSystem_cSAInterface* m_fxSysSmoke; + FxSystem_cSAInterface* m_fxSysGunshell; + FxSystem_cSAInterface* m_fxSysSand; + FxSystem_cSAInterface* m_fxSysSand2; + FxSystem_cSAInterface* m_fxSysSmokeHuge; + FxSystem_cSAInterface* m_fxSysSmoke2; + FxSystem_cSAInterface* m_fxSysSpark; + FxSystem_cSAInterface* m_fxSysSpark2; + FxSystem_cSAInterface* m_fxSysSplash; + FxSystem_cSAInterface* m_fxSysWake; + FxSystem_cSAInterface* m_fxSysWaterSplash; + FxSystem_cSAInterface* m_fxSysWheelDirt; + FxSystem_cSAInterface* m_fxSysGlass; -private: + // List_c + void* m_lastParticleEntity; + void* m_firstParticleEntity; + std::uint32_t m_particleEntitiesCount; + + std::uint32_t m_numCreatedBloodPools; + eFXQuality m_fxQuality; + std::uint32_t m_verticesCount2; + std::uint32_t m_verticesCount; + std::uint32_t m_transformRenderFlags; + RwRaster* m_rasterToRender; + RwMatrix* m_transformLTM; + void* m_verts; // RxObjSpace3DVertex* }; class CFxSA : public CFx @@ -76,7 +153,8 @@ class CFxSA : public CFx void TriggerWaterSplash(CVector& vecPosition); void TriggerBulletSplash(CVector& vecPosition); void TriggerFootSplash(CVector& vecPosition); - void AddParticle(eFxParticleSystems 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); + void AddParticle(eFxParticleSystems 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); private: CFxSAInterface* m_pInterface; diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index 07ac611c09..f5e4767fc3 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -59,6 +59,7 @@ #include "CIplStoreSA.h" #include "CBuildingRemovalSA.h" #include "CCheckpointSA.h" +#include "C2DEffectsSA.h" extern CGameSA* pGame; @@ -146,6 +147,7 @@ CGameSA::CGameSA() m_pBuildingRemoval = new CBuildingRemovalSA(); m_pRenderer = std::make_unique(); + m_p2DEffects = std::make_unique(); // Normal weapon types (WEAPONSKILL_STD) for (int i = 0; i < NUM_WeaponInfosStdSkill; i++) @@ -1072,6 +1074,11 @@ void CGameSA::ResetAlphaTransparencies() CModelInfoSA::StaticResetAlphaTransparencies(); } +void CGameSA::ResetModel2DFXEffects() +{ + CModelInfoSA::StaticReset2DFXEffects(); +} + // Disable VSync by forcing what normally happends at the end of the loading screens // Note #1: This causes the D3D device to be reset after the next frame // Note #2: Some players do not need this to disable VSync. (Possibly because their video card driver settings override it somewhere) diff --git a/Client/game_sa/CGameSA.h b/Client/game_sa/CGameSA.h index cf4788d953..a5f06c9db7 100644 --- a/Client/game_sa/CGameSA.h +++ b/Client/game_sa/CGameSA.h @@ -18,6 +18,7 @@ #include "CCoverManagerSA.h" #include "CPlantManagerSA.h" #include "CRendererSA.h" +#include "C2DEffectSA.h" class CAnimBlendClumpDataSAInterface; class CObjectGroupPhysicalPropertiesSA; @@ -174,7 +175,8 @@ class CGameSA : public CGame CPlantManagerSA* GetPlantManager() const noexcept { return m_pPlantManager; }; CBuildingRemoval* GetBuildingRemoval() { return m_pBuildingRemoval; } CRenderer* GetRenderer() const noexcept override { return m_pRenderer.get(); } - + C2DEffects* Get2DEffects() const noexcept override { return m_p2DEffects.get(); } + CWeaponInfo* GetWeaponInfo(eWeaponType weapon, eWeaponSkill skill = WEAPONSKILL_STD); CModelInfo* GetModelInfo(DWORD dwModelID, bool bCanBeInvalid = false); CObjectGroupPhysicalProperties* GetObjectGroupPhysicalProperties(unsigned char ucObjectGroup); @@ -288,6 +290,7 @@ class CGameSA : public CGame void ResetModelLodDistances(); void ResetModelFlags(); void ResetAlphaTransparencies(); + void ResetModel2DFXEffects(); void DisableVSync(); void ResetModelTimes(); @@ -349,6 +352,7 @@ class CGameSA : public CGame CBuildingRemoval* m_pBuildingRemoval; std::unique_ptr m_pRenderer; + std::unique_ptr m_p2DEffects; CPad* m_pPad; CAERadioTrackManager* m_pCAERadioTrackManager; diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 2db97a5245..e7a23908e4 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -19,6 +19,8 @@ #include "CPedSA.h" #include "CWorldSA.h" #include "gamesa_renderware.h" +#include "CFxSA.h" +#include "C2DEffectSA.h" extern CCoreInterface* g_pCore; extern CGameSA* pGame; @@ -36,6 +38,15 @@ std::unordered_map CModelInfo std::unordered_map> CModelInfoSA::ms_VehicleModelDefaultWheelSizes; std::map CModelInfoSA::ms_DefaultTxdIDMap; +std::unordered_map CModelInfoSA::ms_DefaultNumOf2DFXEffects; +std::unordered_map> CModelInfoSA::ms_DefaultEffectsMap; +static std::unordered_map ms_NumOfCustom2DFXEffects; +static std::vector ms_Custom2DFXEffects; +std::unordered_map> CModelInfoSA::ms_TempCopiesOfDefault2DFXEffects; + +int C2DEffectSA::effect2dPluginOffset = *(int*)0xC3A1E0; // g2dEffectPluginOffset +static auto* fx = reinterpret_cast(VAR_G_Fx); + union tIdeFlags { struct @@ -105,6 +116,12 @@ CBaseModelInfoSAInterface* CModelInfoSA::GetInterface() return m_pInterface = ppModelInfo[m_dwModelID]; } +void CModelInfoSA::SetModelID(DWORD dwModelID) +{ + m_dwModelID = dwModelID; + MapSet(ms_NumOfCustom2DFXEffects, ppModelInfo[dwModelID], 0); +} + bool CModelInfoSA::IsBoat() { DWORD dwFunction = FUNC_IsBoatModel; @@ -1169,6 +1186,39 @@ void CModelInfoSA::ResetAlphaTransparency() } } +void CModelInfoSA::StaticReset2DFXEffects() +{ + for (auto& iter = ms_DefaultEffectsMap.begin(); iter != ms_DefaultEffectsMap.end(); iter++) + { + CBaseModelInfoSAInterface* modelInfoInterface = ppModelInfo[iter->first]; + if (!modelInfoInterface) + continue; + + for (auto innerIter = iter->second.begin(); innerIter != iter->second.end();) + { + // Copy default effect + MemCpy(innerIter->first, innerIter->second, sizeof(C2DEffectSAInterface)); + + // Delete copy of the default effect + C2DEffectSA::SafeDelete2DFXEffect(innerIter->second); + innerIter = iter->second.erase(innerIter); + } + + // Restore counter + modelInfoInterface->ucNumOf2DEffects = MapGet(ms_DefaultNumOf2DFXEffects, iter->first); + + // Destroy copies + auto& copies = MapGet(ms_TempCopiesOfDefault2DFXEffects, iter->first); + for (auto& copy : copies) + C2DEffectSA::SafeDelete2DFXEffect(copy); + } + + // Clear maps & vectors + ms_DefaultEffectsMap.clear(); + ms_Custom2DFXEffects.clear(); + ms_TempCopiesOfDefault2DFXEffects.clear(); +} + short CModelInfoSA::GetAvailableVehicleMod(unsigned short usUpgrade) { short sreturn = -1; @@ -1735,7 +1785,7 @@ void CModelInfoSA::MakeObjectModel(ushort usBaseID) MemCpyFast(m_pInterface, pBaseObjectInfo, sizeof(CBaseModelInfoSAInterface)); m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; ppModelInfo[m_dwModelID] = m_pInterface; @@ -1752,7 +1802,7 @@ void CModelInfoSA::MakeTimedObjectModel(ushort usBaseID) MemCpyFast(m_pInterface, pBaseObjectInfo, sizeof(CTimeModelInfoSAInterface)); m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; m_pInterface->timeInfo.m_wOtherTimeModel = 0; @@ -1769,7 +1819,7 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID) MemCpyFast(pNewInterface, pBaseObjectInfo, sizeof(CClumpModelInfoSAInterface)); pNewInterface->usNumberOfRefs = 0; pNewInterface->pRwObject = nullptr; - pNewInterface->usUnknown = 65535; + pNewInterface->s2DEffectIndex = -1; pNewInterface->usDynamicIndex = 65535; ppModelInfo[m_dwModelID] = pNewInterface; @@ -1787,7 +1837,7 @@ void CModelInfoSA::MakeVehicleAutomobile(ushort usBaseID) m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; m_pInterface->pVisualInfo = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; ppModelInfo[m_dwModelID] = m_pInterface; @@ -1824,58 +1874,6 @@ void CModelInfoSA::DeallocateModel(void) ppModelInfo[m_dwModelID] = nullptr; *pGame->GetStreaming()->GetStreamingInfo(m_dwModelID) = CStreamingInfo{}; } -////////////////////////////////////////////////////////////////////////////////////////// -// -// Hook for NodeNameStreamRead -// -// Ignore extra characters in dff frame name -// -////////////////////////////////////////////////////////////////////////////////////////// -__declspec(noinline) void OnMY_NodeNameStreamRead(RwStream* stream, char* pDest, uint uiSize) -{ - // Calc sizes - const uint uiMaxBufferSize = 24; - uint uiAmountToRead = std::min(uiMaxBufferSize - 1, uiSize); - uint uiAmountToSkip = uiSize - uiAmountToRead; - - // Read good bit - RwStreamRead(stream, pDest, uiAmountToRead); - pDest[uiAmountToRead] = 0; - - // Skip bad bit (this might not be required) - if (uiAmountToSkip > 0) - RwStreamSkip(stream, uiAmountToSkip); -} - -// Hook info -#define HOOKPOS_NodeNameStreamRead 0x072FA68 -#define HOOKSIZE_NodeNameStreamRead 15 -DWORD RETURN_NodeNameStreamRead = 0x072FA77; -void _declspec(naked) HOOK_NodeNameStreamRead() -{ - _asm - { - pushad - push edi - push esi - push ebx - call OnMY_NodeNameStreamRead - add esp, 4*3 - popad - - jmp RETURN_NodeNameStreamRead - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -// Setup hooks -// -////////////////////////////////////////////////////////////////////////////////////////// -void CModelInfoSA::StaticSetHooks() -{ - EZHookInstall(NodeNameStreamRead); -} // Recursive RwFrame children searching function void CModelInfoSA::RwSetSupportedUpgrades(RwFrame* parent, DWORD dwModel) @@ -2109,3 +2107,469 @@ bool CModelInfoSA::ForceUnload() return true; } + +auto CModelInfoSA::GetEntitiesFromFx(std::uint32_t modelID) +{ + auto vec = std::vector(); + void* lastParticle = fx->m_lastParticleEntity; + + while (lastParticle) + { + auto** entity = reinterpret_cast(reinterpret_cast(lastParticle) + OFFSET_FxSystem_Entities); + auto* prevParticle = *reinterpret_cast(reinterpret_cast(lastParticle) + OFFSET_FxSystem_Link_Prev); + + if (entity && *entity && static_cast((*entity)->m_nModelIndex) == modelID) + vec.push_back(*entity); + + lastParticle = prevParticle; + } + + return vec; +} + +void CModelInfoSA::Update2DFXEffect(C2DEffectSA* effect) +{ + if (!effect) + return; + + C2DEffectSAInterface* effectInterface = effect->GetInterface(); + if (!effectInterface) + return; + + // This function aims to keep 2dfx effects updated to avoid restreaming + switch (effectInterface->type) + { + case e2dEffectType::PARTICLE: + { + auto entities = GetEntitiesFromFx(m_dwModelID); + for (auto entity : entities) + { + // Call Fx_c::DestroyEntityFx + ((void(__thiscall*)(CFxSAInterface*, CEntitySAInterface*))FUNC_Fx_c_DestroyEntityFx)(fx, entity); + + RwMatrix* matrixTransform = nullptr; + if (auto* object = reinterpret_cast(entity->m_pRwObject)) + { + if (auto* frame = static_cast(object->parent)) + matrixTransform = &frame->modelling; + } + + // Call Fx_c::CreateEntityFx + ((void(__thiscall*)(CFxSAInterface*, CEntitySAInterface*, const char*, RwV3d*, RwMatrix*))FUNC_Fx_c_CreateEntityFx)(fx, entity, effectInterface->effect.particle.szName, &effectInterface->position, matrixTransform); + } + + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effectInterface->effect.roadsign; + C2DEffectSA::Roadsign_DestroyAtomic(effectInterface); + + std::uint32_t numLines = C2DEffectSA::Roadsign_GetNumLinesFromFlags(roadsign.flags); + std::uint32_t numLetters = C2DEffectSA::Roadsign_GetNumLettersFromFlags(roadsign.flags); + std::uint8_t palleteID = C2DEffectSA::Roadsign_GetPalleteIDFromFlags(roadsign.flags); + roadsign.atomic = C2DEffectSA::Roadsign_CreateAtomic(effectInterface->position, roadsign.rotation, roadsign.size.x, roadsign.size.y, numLines, &roadsign.text[0], &roadsign.text[16], &roadsign.text[32], &roadsign.text[48], numLetters, palleteID); + + break; + } + } +} + +void CModelInfoSA::StoreDefault2DFXEffect(C2DEffectSAInterface* effect) +{ + // Is custom effect? + if (std::find(ms_Custom2DFXEffects.begin(), ms_Custom2DFXEffects.end(), effect) != ms_Custom2DFXEffects.end()) + return; + + // Is effect stored already? + if (MapContains(ms_DefaultEffectsMap, m_dwModelID) && MapContains(MapGet(ms_DefaultEffectsMap, m_dwModelID), effect)) + return; + + // Copy an existing default effect + C2DEffectSAInterface* copy = C2DEffectSA::CreateCopy(effect); + + // Store copy in the map + MapSet(ms_DefaultEffectsMap, m_dwModelID, std::unordered_map{{effect, copy}}); +} + +bool CModelInfoSA::Reset2DFXEffects() +{ + m_pInterface = ppModelInfo[m_dwModelID]; + if (!m_pInterface) + return false; + + if (!MapContains(ms_DefaultEffectsMap, m_dwModelID)) + return false; + + // Restore default effects + auto& map = MapGet(ms_DefaultEffectsMap, m_dwModelID); + auto* effects = pGame->Get2DEffects(); + for (auto& it = map.begin(); it != map.end();) + { + // Copy data from copied effect to the default + MemCpyFast(it->first, it->second, sizeof(C2DEffectSAInterface)); + + // We no longer need a copy + // So delete it + C2DEffectSA::SafeDelete2DFXEffect(it->second); + it = map.erase(it); + + // Update effect + Update2DFXEffect(effects->Get(it->first)); + } + + // Restore counter + m_pInterface->ucNumOf2DEffects = MapGet(ms_DefaultNumOf2DFXEffects, m_dwModelID); + + // Delete temp copies + auto& copies = MapGet(ms_TempCopiesOfDefault2DFXEffects, m_dwModelID); + for (auto* copy : copies) + C2DEffectSA::SafeDelete2DFXEffect(copy); + + // Clear maps + map.clear(); + ms_DefaultEffectsMap.erase(m_dwModelID); + ms_TempCopiesOfDefault2DFXEffects.erase(m_dwModelID); + return true; +} + +C2DEffectSAInterface* CModelInfoSA::Add2DFXEffect(const CVector& position, const e2dEffectType& type) +{ + m_pInterface = ppModelInfo[m_dwModelID]; + if (!m_pInterface) + return nullptr; + + // Init new effect + C2DEffectSAInterface* effectInterface = new C2DEffectSAInterface(); + effectInterface->position = RwV3d{position.fX, position.fY, position.fZ}; + effectInterface->type = type; + + // Update counters + m_pInterface->ucNumOf2DEffects = m_pInterface->ucNumOf2DEffects ? m_pInterface->ucNumOf2DEffects + 1 : 1; + MapGet(ms_NumOfCustom2DFXEffects, m_pInterface)++; + + // Save our effect + ms_Custom2DFXEffects.push_back(effectInterface); + return effectInterface; +} + +bool CModelInfoSA::Remove2DFX(C2DEffectSAInterface* effect, bool includeDefault) +{ + m_pInterface = ppModelInfo[m_dwModelID]; + if (!m_pInterface) + return false; + + if (!effect) + return false; + + auto& it = std::find(ms_Custom2DFXEffects.begin(), ms_Custom2DFXEffects.end(), effect); + bool isCustomEffect = it != ms_Custom2DFXEffects.end(); + + if (!includeDefault && !isCustomEffect) + return false; + + if (!isCustomEffect) + StoreDefault2DFXEffect(effect); + + m_pInterface->ucNumOf2DEffects--; + + switch (effect->type) + { + case e2dEffectType::ROADSIGN: + case e2dEffectType::LIGHT: + { + C2DEffectSA::Shutdown(effect); + + // Prevent creation when stream in but keep in memory so we can restore it later + effect->type = e2dEffectType::NONE; + break; + } + case e2dEffectType::PARTICLE: + { + auto& entities = GetEntitiesFromFx(m_dwModelID); + for (auto* entity : entities) + // Call Fx_c::DestroyEntityFx + ((void(__thiscall*)(CFxSAInterface*, CEntitySAInterface*))FUNC_Fx_c_DestroyEntityFx)(fx, entity); + + entities.clear(); + + // Prevent creation when stream in but keep in memory so we can restore it later + effect->type = e2dEffectType::NONE; + break; + } + case e2dEffectType::ESCALATOR: + { + auto* escalators = reinterpret_cast(ARRAY_CEscalators); + for (std::uint8_t i = 0; i < NUM_MAX_ESCALATORS; i++) + { + if (!escalators[i].exist || !escalators[i].entity) + continue; + + if (static_cast(escalators[i].entity->m_nModelIndex) != m_dwModelID) + continue; + + // Call CEscalator::SwitchOff + ((void(__thiscall*)(CEscalatorSAInterface*))FUNC_CEscalator_SwitchOff)(&escalators[i]); + escalators[i].exist = false; + } + + // Prevent creation when stream in but keep in memory so we can restore it later + effect->type = e2dEffectType::NONE; + break; + } + case e2dEffectType::SUN_GLARE: + { + // Prevent from rendering but keep in memory so we can restore it later + effect->type = e2dEffectType::NONE; + break; + } + default: + return false; + } + + // If it's custom effect then delete it + if (isCustomEffect) + { + MapGet(ms_NumOfCustom2DFXEffects, m_pInterface)--; + ms_Custom2DFXEffects.erase(it); + + if (effect) + { + delete effect; + effect = nullptr; + } + } +} + +bool CModelInfoSA::Remove2DFXEffectAtIndex(std::uint32_t index, bool includeDefault) +{ + m_pInterface = ppModelInfo[m_dwModelID]; + if (!m_pInterface) + return false; + + C2DEffectSA* effect = Get2DFXFromIndex(index); + if (!effect) + return false; + + return Remove2DFX(effect->GetInterface(), includeDefault); +} + +bool CModelInfoSA::RemoveAll2DFXEffects(bool includeDefault) +{ + m_pInterface = ppModelInfo[m_dwModelID]; + if (!m_pInterface) + return false; + + std::uint32_t numEffects = m_pInterface->ucNumOf2DEffects; + for (std::uint32_t i = 0; i < numEffects; i++) + { + C2DEffectSA* effect = Get2DFXFromIndex(i); + if (!effect) + continue; + + Remove2DFX(effect->GetInterface(), includeDefault); + } + + return true; +} + +C2DEffectSA* CModelInfoSA::Get2DFXFromIndex(std::uint32_t index) +{ + m_pInterface = ppModelInfo[m_dwModelID]; + if (!m_pInterface) + return nullptr; + + // Call CBaseModelInfo::Get2dEffect + auto* effectInterface = ((C2DEffectSAInterface * (__thiscall*)(CBaseModelInfoSAInterface*, std::uint32_t index))FUNC_CBaseModelInfo_Get2dEffect)(m_pInterface, index); + if (!effectInterface) + return nullptr; + + return pGame->Get2DEffects()->Get(effectInterface); +} + +void CModelInfoSA::CopyModified2DFXEffects() +{ + CBaseModelInfoSAInterface* modelInfo = ppModelInfo[m_dwModelID]; + if (!modelInfo || modelInfo->ucNumOf2DEffects == 0) + return; + + bool hasModifiedEffects = MapContains(ms_DefaultEffectsMap, m_dwModelID); + auto tempVec = std::vector(); + std::uint32_t numEffects = MapGet(ms_DefaultNumOf2DFXEffects, m_dwModelID); + auto* effects = pGame->Get2DEffects(); + for (std::uint32_t i = 0; i < numEffects; i++) + { + auto effectInterface = ((C2DEffectSAInterface * (__thiscall*)(CBaseModelInfoSAInterface*, std::uint32_t index)) FUNC_CBaseModelInfo_Get2dEffect)(modelInfo, i); + if (!effectInterface) + continue; + + // Delete our 2dfx object + C2DEffectSA* effect = effects->Get(effectInterface); + if (effect) + { + effects->RemoveFromList(effect); + delete effect; + } + + if (hasModifiedEffects) + { + // Copy effect + auto* copy = C2DEffectSA::CreateCopy(effectInterface); + tempVec.push_back(copy); + } + } + + if (hasModifiedEffects) + MapSet(ms_TempCopiesOfDefault2DFXEffects, m_dwModelID, tempVec); +} + +void CModelInfoSA::RestoreModified2DFXEffects() +{ + CBaseModelInfoSAInterface* modelInfo = ppModelInfo[m_dwModelID]; + if (!modelInfo) + return; + + // Set default num of 2dfx effects + if (!MapContains(ms_DefaultNumOf2DFXEffects, m_dwModelID)) + MapSet(ms_DefaultNumOf2DFXEffects, m_dwModelID, modelInfo->ucNumOf2DEffects); + + // Create C2DEffectSA instance for each default effect interface + std::uint32_t numEffects = MapGet(ms_DefaultNumOf2DFXEffects, m_dwModelID); + auto* tempVec = MapFind(ms_TempCopiesOfDefault2DFXEffects, m_dwModelID); + for (std::uint32_t i = 0; i < numEffects; i++) + { + auto* effectInterface = ((C2DEffectSAInterface*(__thiscall*)(CBaseModelInfoSAInterface*, std::uint32_t))FUNC_CBaseModelInfo_Get2dEffect)(modelInfo, i); + if (!effectInterface) + continue; + + // Create our 2dfx object instance + C2DEffectSA* effect = new C2DEffectSA(effectInterface, m_dwModelID); + if (!effect) + continue; + + if (tempVec && (*tempVec)[i]) + { + MemCpyFast(effectInterface, (*tempVec)[i], sizeof(C2DEffectSAInterface)); + effectInterface->effect.roadsign.text = static_cast(std::malloc(64)); + if (effectInterface->effect.roadsign.text) + { + MemSetFast(effectInterface->effect.roadsign.text, 0, 64); + MemCpyFast(effectInterface->effect.roadsign.text, (*tempVec)[i]->effect.roadsign.text, 64); + } + + C2DEffectSA::SafeDelete2DFXEffect((*tempVec)[i]); + Update2DFXEffect(effect); + } + } + + if (tempVec) + { + tempVec->clear(); + ms_TempCopiesOfDefault2DFXEffects.erase(m_dwModelID); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Hook for NodeNameStreamRead +// +// Ignore extra characters in dff frame name +// +////////////////////////////////////////////////////////////////////////////////////////// +static __declspec(noinline) void OnMY_NodeNameStreamRead(RwStream* stream, char* pDest, uint uiSize) +{ + // Calc sizes + const uint uiMaxBufferSize = 24; + uint uiAmountToRead = std::min(uiMaxBufferSize - 1, uiSize); + uint uiAmountToSkip = uiSize - uiAmountToRead; + + // Read good bit + RwStreamRead(stream, pDest, uiAmountToRead); + pDest[uiAmountToRead] = 0; + + // Skip bad bit (this might not be required) + if (uiAmountToSkip > 0) + RwStreamSkip(stream, uiAmountToSkip); +} + +// Hook info +#define HOOKPOS_NodeNameStreamRead 0x072FA68 +#define HOOKSIZE_NodeNameStreamRead 15 +DWORD RETURN_NodeNameStreamRead = 0x072FA77; +static void _declspec(naked) HOOK_NodeNameStreamRead() +{ + _asm + { + pushad + push edi + push esi + push ebx + call OnMY_NodeNameStreamRead + add esp, 4*3 + popad + + jmp RETURN_NodeNameStreamRead + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Hook for CBaseModelInfo:Get2dEffect +// +// Handle custom 2dfx's +// +////////////////////////////////////////////////////////////////////////////////////////// +static C2DEffectSAInterface* Get2dEffect(CBaseModelInfoSAInterface* modelInfo, RpGeometry* geometry, std::uint32_t numPluginEffects, std::uint32_t index) +{ + if (!geometry) + numPluginEffects = 0; + + static auto* storedEffects = reinterpret_cast(ARRAY_2DFXInfoStore); + + std::uint32_t numCustomEffects = ms_NumOfCustom2DFXEffects[modelInfo]; + std::uint32_t numStoredEffects = modelInfo->ucNumOf2DEffects - numPluginEffects - numCustomEffects; + + if (index < numStoredEffects) + return &storedEffects->objects[index + modelInfo->s2DEffectIndex]; + else if (index < numStoredEffects + numPluginEffects) + { + auto* pluginEffectData = *RWPLUGINOFFSET(C2DEffectPluginDataSAInterface*, geometry, C2DEffectSA::effect2dPluginOffset); + return &pluginEffectData->objects[index - numStoredEffects]; + } + else + return ms_Custom2DFXEffects[index - numPluginEffects - numStoredEffects]; + + return nullptr; +} + +#define HOOKPOS_Get2dEffect 0x4C4CDC +#define HOOKSIZE_Get2dEffect 10 +static void _declspec(naked) HOOK_Get2dEffect() +{ + _asm + { + push esi + push eax + push edi + push ebx + call Get2dEffect + add esp, 16 + + pop edi + pop esi + pop ebp + pop ebx + retn 4 + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Setup hooks +// +////////////////////////////////////////////////////////////////////////////////////////// +void CModelInfoSA::StaticSetHooks() +{ + EZHookInstall(NodeNameStreamRead); + EZHookInstall(Get2dEffect); +} diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index b261bcc04a..964c22751e 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -12,8 +12,10 @@ #pragma once #include +#include "C2DEffectSAInterface.h" #include #include "CRenderWareSA.h" +#include "C2DEffectSA.h" class CPedModelInfoSA; class CPedModelInfoSAInterface; @@ -63,6 +65,8 @@ static void* ARRAY_ModelInfo = *(void**)(0x403DA4 + 3); #define VAR_CTempColModels_ModelPed1 0x968DF0 +#define FUNC_CBaseModelInfo_Get2dEffect 0x4C4C70 + class CBaseModelInfoSAInterface; class CModelInfoSAInterface { @@ -158,7 +162,7 @@ class CBaseModelInfoSAInterface unsigned char ucAlpha : 8; // +12 unsigned char ucNumOf2DEffects : 8; // +13 - unsigned short usUnknown : 16; // +14 Something with 2d effects + short s2DEffectIndex : 16; // +14 unsigned short usDynamicIndex : 16; // +16 @@ -329,6 +333,11 @@ class CModelInfoSA : public CModelInfo static std::unordered_map ms_OriginalObjectPropertiesGroups; static std::unordered_map> ms_VehicleModelDefaultWheelSizes; static std::map ms_DefaultTxdIDMap; + + static std::unordered_map ms_DefaultNumOf2DFXEffects; + static std::unordered_map> ms_DefaultEffectsMap; + static std::unordered_map> ms_TempCopiesOfDefault2DFXEffects; + SVehicleSupportedUpgrades m_ModelSupportedUpgrades; public: @@ -440,7 +449,7 @@ class CModelInfoSA : public CModelInfo // Decreases the collision slot reference counter for the original collision model void RemoveColRef() override; - void SetModelID(DWORD dwModelID) { m_dwModelID = dwModelID; } + void SetModelID(DWORD dwModelID); RwObject* GetRwObject() { return m_pInterface ? m_pInterface->pRwObject : NULL; } @@ -466,6 +475,26 @@ class CModelInfoSA : public CModelInfo // Vehicle towing functions bool IsTowableBy(CModelInfo* towingModel) override; + // 2DFX functions + C2DEffectSAInterface* Add2DFXEffect(const CVector& position, const e2dEffectType& type); + bool Remove2DFX(C2DEffectSAInterface* effect, bool includeDefault); + bool Remove2DFXEffectAtIndex(std::uint32_t index, bool includeDefault = false); + bool RemoveAll2DFXEffects(bool includeDefault); + C2DEffectSA* Get2DFXFromIndex(std::uint32_t index); + std::uint32_t Get2DFXCount() const { return m_pInterface ? m_pInterface->ucNumOf2DEffects : 0; } + void Update2DFXEffect(C2DEffectSA* effect); + void Update2DFXEffect(C2DEffect* effect) { Update2DFXEffect(dynamic_cast(effect)); } + + static auto GetEntitiesFromFx(std::uint32_t modelID); + + void StoreDefault2DFXEffect(C2DEffectSA* effect) { StoreDefault2DFXEffect(effect->GetInterface()); } + void StoreDefault2DFXEffect(C2DEffectSAInterface* effect); + bool Reset2DFXEffects(); + static void StaticReset2DFXEffects(); + + void CopyModified2DFXEffects(); + void RestoreModified2DFXEffects(); + bool IsDynamic() { return m_pInterface ? m_pInterface->usDynamicIndex != 0xffff : false; }; private: @@ -473,3 +502,5 @@ class CModelInfoSA : public CModelInfo void RwSetSupportedUpgrades(RwFrame* parent, DWORD dwModel); void SetModelSpecialType(eModelSpecialType eType, bool bState); }; + +void HOOK_NodeNameStreamRead(); diff --git a/Client/game_sa/gamesa_renderware.h b/Client/game_sa/gamesa_renderware.h index 594c203b59..48a1b2417c 100644 --- a/Client/game_sa/gamesa_renderware.h +++ b/Client/game_sa/gamesa_renderware.h @@ -105,6 +105,8 @@ typedef RpHAnimHierarchy*(__cdecl* GetAnimHierarchyFromSkinClump_t)(RpClump*); typedef int(__cdecl* RpHAnimIDGetIndex_t)(RpHAnimHierarchy*, int); typedef RwMatrix*(__cdecl* RpHAnimHierarchyGetMatrixArray_t)(RpHAnimHierarchy*); typedef RtQuat*(__cdecl* RtQuatRotate_t)(RtQuat* quat, const RwV3d* axis, float angle, RwOpCombineType combineOp); +typedef RwTexture*(__cdecl* RwReadTexture_t)(const char* name, const char* mask); +typedef RwFrame*(__cdecl* RwFrameRotate_t)(RwFrame* frame, const RwV3d* axis, float angle, RwOpCombineType combine); /*****************************************************************************/ /** Renderware function mappings **/ @@ -195,7 +197,8 @@ RWFUNC(GetAnimHierarchyFromSkinClump_t GetAnimHierarchyFromSkinClump, (GetAnimHi RWFUNC(RpHAnimIDGetIndex_t RpHAnimIDGetIndex, (RpHAnimIDGetIndex_t)0xDEAD) RWFUNC(RpHAnimHierarchyGetMatrixArray_t RpHAnimHierarchyGetMatrixArray, (RpHAnimHierarchyGetMatrixArray_t)0xDEAD) RWFUNC(RtQuatRotate_t RtQuatRotate, (RtQuatRotate_t)0xDEAD) - +RWFUNC(RwReadTexture_t RwReadTexture, reinterpret_cast(0xDEAD)) +RWFUNC(RwFrameRotate_t RwFrameRotate, reinterpret_cast(0xDEAD)) /*****************************************************************************/ /** GTA function definitions and mappings **/ /*****************************************************************************/ diff --git a/Client/game_sa/gamesa_renderware.hpp b/Client/game_sa/gamesa_renderware.hpp index a638cfede0..a8633cc2ab 100644 --- a/Client/game_sa/gamesa_renderware.hpp +++ b/Client/game_sa/gamesa_renderware.hpp @@ -89,7 +89,9 @@ void InitRwFunctions() RpHAnimIDGetIndex = (RpHAnimIDGetIndex_t)0x7C51A0; RpHAnimHierarchyGetMatrixArray = (RpHAnimHierarchyGetMatrixArray_t)0x7C5120; RtQuatRotate = (RtQuatRotate_t)0x7EB7C0; - + RwReadTexture = reinterpret_cast(0x7F3AC0); + RwFrameRotate = reinterpret_cast(0x7F1010); + SetTextureDict = (SetTextureDict_t)0x007319C0; LoadClumpFile = (LoadClumpFile_t)0x005371F0; LoadModel = (LoadModel_t)0x0040C6B0; diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index 465478454f..8ca32aaf5b 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -146,6 +146,7 @@ #include #include #include +#include #include // Shared includes diff --git a/Client/mods/deathmatch/logic/CClient2DFX.cpp b/Client/mods/deathmatch/logic/CClient2DFX.cpp new file mode 100644 index 0000000000..da4a1506a3 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClient2DFX.cpp @@ -0,0 +1,48 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/mods/deathmatch/logic/CClient2DFX.cpp + * PURPOSE: Handling 2DFX effects + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CClient2DFX.h" +#include "CClient2DFXManager.h" + +CClient2DFX::CClient2DFX(class CClientManager* manager, ElementID ID) + : ClassInit(this), CClientEntity(ID), m_2DFXManager(manager->Get2DFXManager()), m_effect(nullptr), m_effectType(e2dEffectType::NONE), m_model(0) +{ + m_pManager = manager; + SetTypeName("2dfx"); + + m_2DFXManager->AddToList(this); +} + +CClient2DFX::~CClient2DFX() +{ + m_2DFXManager->RemoveFromList(this); + + // Destroy effect + g_pGame->Get2DEffects()->Destroy(m_effect); +} + +bool CClient2DFX::Create(std::uint32_t model, const CVector& position, const e2dEffectType& type, const effectDataMap& effectData) +{ + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(model)); + if (!modelInfo) + return false; + + C2DEffect* effect = g_pGame->Get2DEffects()->Create(model, position, type); + if (!effect) + return false; + + m_2DFXManager->Set2DFXProperties(effect, effectData); + + m_effect = effect; + m_effectType = type; + m_model = model; + + return true; +} diff --git a/Client/mods/deathmatch/logic/CClient2DFX.h b/Client/mods/deathmatch/logic/CClient2DFX.h new file mode 100644 index 0000000000..787b60e237 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClient2DFX.h @@ -0,0 +1,43 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/mods/deathmatch/logic/CClient2DFX.h + * PURPOSE: Handling 2DFX effects + * + *****************************************************************************/ +#pragma once + +#include "CClientEntity.h" +#include "game/C2DEffects.h" +#include "game/C2DEffect.h" + +using effectDataMap = std::unordered_map>; + +class CClient2DFX final : public CClientEntity +{ + DECLARE_CLASS(CClient2DFX, CClientEntity) + friend class CClient2DFXManager; + +public: + CClient2DFX(class CClientManager* manager, ElementID ID); + ~CClient2DFX(); + + eClientEntityType GetType() const { return CCLIENT2DFX; } + + void Unlink(){}; + void GetPosition(CVector& vecPosition) const {} + void SetPosition(const CVector& vecPosition){} + + bool Create(std::uint32_t model, const CVector& position, const e2dEffectType& type, const effectDataMap& effectData); + + e2dEffectType Get2DFXType() const noexcept { return m_effectType; } + C2DEffect* Get2DFX() const noexcept { return m_effect; } + std::uint32_t GetModelID() const noexcept { return m_model; } + +private: + class CClient2DFXManager* m_2DFXManager; + C2DEffect* m_effect; + e2dEffectType m_effectType; + std::uint32_t m_model; +}; diff --git a/Client/mods/deathmatch/logic/CClient2DFXManager.cpp b/Client/mods/deathmatch/logic/CClient2DFXManager.cpp new file mode 100644 index 0000000000..eb662ef4f6 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClient2DFXManager.cpp @@ -0,0 +1,997 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/mods/deathmatch/logic/CClient2DFXManager.cpp + * PURPOSE: Handling 2DFX effects manager + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CClient2DFXManager.h" + +#define mask(n) ((1 << (n)) - 1) +static constexpr std::size_t roadsignTextSize = 64; + +CClient2DFXManager::CClient2DFXManager(CClientManager* mainManager) : m_mainManager(mainManager) +{ + // Init + m_canRemoveFromList = true; +} + +CClient2DFXManager::~CClient2DFXManager() +{ + // Delete all 2DFX's + RemoveAll(); +} + +void CClient2DFXManager::RemoveAll() +{ + // Make sure they don't remove themselves from our list + m_canRemoveFromList = false; + + // Run through our list deleting the 2DFX's + for (auto* effect : m_effectsList) + delete effect; + + // Allow list removal again + m_canRemoveFromList = true; +} + +void CClient2DFXManager::RemoveFromList(CClient2DFX* effect) +{ + if (m_canRemoveFromList) + m_effectsList.remove(effect); +} + +bool CClient2DFXManager::Exists(CClient2DFX* effect) const +{ + return std::find(m_effectsList.begin(), m_effectsList.end(), effect) != m_effectsList.end(); +} + +CClient2DFX* CClient2DFXManager::Add2DFX(std::uint32_t model, const CVector& position, const e2dEffectType& type, const effectDataMap& effectData) +{ + auto* effect = new CClient2DFX(m_mainManager, INVALID_ELEMENT_ID); + if (!effect) + return nullptr; + + if (!effect->Create(model, position, type, effectData)) + { + delete effect; + effect = nullptr; + } + + return effect; +} + +bool CClient2DFXManager::Set2DFXProperties(C2DEffect* effect, const effectDataMap& effectData) +{ + if (!effect) + return false; + + bool success = true; + + for (const auto& [k, v] : effectData) + { + e2dEffectProperty property; + if (!StringToEnum(k, property)) + { + success = false; + continue; + } + + if (!Set2DFXProperty(effect, property, v)) + success = false; + } + + return success; +} + +effectDataMap CClient2DFXManager::Get2DFXProperties(C2DEffect* effect) const +{ + auto properties = effectDataMap(); + if (!effect) + return properties; + + // Get properties + switch (effect->GetEffectType()) + { + case e2dEffectType::LIGHT: + { + MapSet(properties, "drawDistance", effect->GetCoronaFarClip()); + MapSet(properties, "lightRange", effect->GetCoronaPointLightRange()); + MapSet(properties, "coronaSize", effect->GetCoronaSize()); + MapSet(properties, "shadowSize", effect->GetShadowSize()); + MapSet(properties, "shadowMultiplier", static_cast(effect->GetShadowMultiplier())); + MapSet(properties, "showMode", EnumToString(effect->GetCoronaShowMode())); + MapSet(properties, "coronaReflection", effect->GetCoronaReflectionsEnabled()); + MapSet(properties, "flareType", static_cast(effect->GetCoronaFlareType())); + MapSet(properties, "flags", static_cast(effect->GetLightFlags())); + MapSet(properties, "shadowDistance", static_cast(effect->GetShadowDistance())); + + const CVector& offsets = effect->GetCoronaOffsets(); + MapSet(properties, "offsetX", offsets.fX); + MapSet(properties, "offsetY", offsets.fY); + MapSet(properties, "offsetZ", offsets.fZ); + + RwColor& color = effect->GetCoronaColor(); + int colorValue = (static_cast(color.a) << 24) | (static_cast(color.r) << 16) | (static_cast(color.g) << 8) | static_cast(color.b); + MapSet(properties, "color", static_cast(colorValue)); + + MapSet(properties, "coronaName", effect->GetCoronaTexture()); + MapSet(properties, "shadowName", effect->GetShadowTexture()); + + break; + } + case e2dEffectType::PARTICLE: + { + MapSet(properties, "name", effect->GetParticleName()); + break; + } + case e2dEffectType::ROADSIGN: + { + const RwV2d& size = effect->GetRoadsignSize(); + const RwV3d& rot = effect->GetRoadsignRotation(); + + MapSet(properties, "sizeX", size.x); + MapSet(properties, "sizeY", size.y); + MapSet(properties, "rotX", rot.x); + MapSet(properties, "rotY", rot.y); + MapSet(properties, "rotZ", rot.z); + MapSet(properties, "flags", static_cast(effect->GetRoadsignFlags())); + MapSet(properties, "text", effect->GetRoadsignText()); + + break; + } + case e2dEffectType::ESCALATOR: + { + const RwV3d& bottom = effect->GetEscalatorBottom(); + const RwV3d& top = effect->GetEscalatorTop(); + const RwV3d& end = effect->GetEscalatorEnd(); + + MapSet(properties, "bottomX", bottom.x); + MapSet(properties, "bottomY", bottom.y); + MapSet(properties, "bottomZ", bottom.z); + MapSet(properties, "topX", top.x); + MapSet(properties, "topY", top.y); + MapSet(properties, "topZ", top.z); + MapSet(properties, "endX", end.x); + MapSet(properties, "endY", end.y); + MapSet(properties, "endZ", end.z); + MapSet(properties, "direction", static_cast(effect->GetEscalatorDirection())); + + break; + } + } + + return properties; +} + +bool CClient2DFXManager::Set2DFXProperty(C2DEffect* effect, const e2dEffectProperty& property, const std::variant& propertyValue) +{ + if (!effect) + return false; + + switch (effect->GetEffectType()) + { + case e2dEffectType::LIGHT: + { + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetCoronaFarClip(std::get(propertyValue)); + return true; + } + break; + } + case e2dEffectProperty::LIGHT_RANGE: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetCoronaPointLightRange(std::get(propertyValue)); + return true; + } + break; + } + case e2dEffectProperty::CORONA_SIZE: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetCoronaSize(std::get(propertyValue)); + return true; + } + break; + } + case e2dEffectProperty::SHADOW_SIZE: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetShadowSize(std::get(propertyValue)); + return true; + } + break; + } + case e2dEffectProperty::SHADOW_MULT: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetShadowMultiplier(std::get(propertyValue)); + return true; + } + break; + } + case e2dEffectProperty::FLASH_TYPE: + { + if (std::holds_alternative(propertyValue)) + { + e2dCoronaFlashType showMode; + if (StringToEnum(std::get(propertyValue), showMode)) + { + effect->SetCoronaShowMode(showMode); + return true; + } + } + + break; + } + case e2dEffectProperty::CORONA_REFLECTION: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetCoronaReflectionsEnabled(std::get(propertyValue)); + return true; + } + break; + } + case e2dEffectProperty::FLARE_TYPE: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetCoronaFlareType(static_cast(std::get(propertyValue))); + return true; + } + break; + } + case e2dEffectProperty::FLAGS: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetLightFlags(static_cast(std::get(propertyValue))); + return true; + } + break; + } + case e2dEffectProperty::SHADOW_DISTANCE: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetShadowDistance(static_cast(std::get(propertyValue))); + return true; + } + break; + } + case e2dEffectProperty::OFFSET_X: + { + if (std::holds_alternative(propertyValue)) + { + CVector offsets = effect->GetCoronaOffsets(); + offsets.fX = static_cast(std::get(propertyValue)); + + effect->SetCoronaOffsets(offsets); + return true; + } + break; + } + case e2dEffectProperty::OFFSET_Y: + { + if (std::holds_alternative(propertyValue)) + { + CVector offsets = effect->GetCoronaOffsets(); + offsets.fY = static_cast(std::get(propertyValue)); + + effect->SetCoronaOffsets(offsets); + return true; + } + break; + } + case e2dEffectProperty::OFFSET_Z: + { + if (std::holds_alternative(propertyValue)) + { + CVector offsets = effect->GetCoronaOffsets(); + offsets.fZ = static_cast(std::get(propertyValue)); + + effect->SetCoronaOffsets(offsets); + return true; + } + + break; + } + case e2dEffectProperty::COLOR: + { + if (std::holds_alternative(propertyValue)) + { + std::uint32_t colorValue = static_cast(std::get(propertyValue)); + effect->SetCoronaColor(RwColor{static_cast((colorValue >> 16) & mask(8)), static_cast((colorValue >> 8) & mask(8)), static_cast((colorValue >> 0) & mask(8)), static_cast((colorValue >> 24) & mask(8))}); + + return true; + } + + break; + } + case e2dEffectProperty::CORONA_NAME: + { + if (std::holds_alternative(propertyValue)) + { + e2dEffectTextureName coronaName; + if (StringToEnum(std::get(propertyValue), coronaName)) + { + effect->SetCoronaTexture(std::get(propertyValue)); + return true; + } + } + + break; + } + case e2dEffectProperty::SHADOW_NAME: + { + if (std::holds_alternative(propertyValue)) + { + e2dEffectTextureName shadowName; + if (StringToEnum(std::get(propertyValue), shadowName)) + { + effect->SetShadowTexture(std::get(propertyValue)); + return true; + } + } + + break; + } + } + + break; + } + case e2dEffectType::PARTICLE: + { + if (property == e2dEffectProperty::PRT_NAME) + { + if (std::holds_alternative(propertyValue)) + { + effect->SetParticleName(std::get(propertyValue)); + return true; + } + } + + break; + } + case e2dEffectType::ROADSIGN: + { + switch (property) + { + case e2dEffectProperty::SIZE_X: + { + if (std::holds_alternative(propertyValue)) + { + RwV2d& size = effect->GetRoadsignSize(); + size.x = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::SIZE_Y: + { + if (std::holds_alternative(propertyValue)) + { + RwV2d& size = effect->GetRoadsignSize(); + size.y = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::ROT_X: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& rotation = effect->GetRoadsignRotation(); + rotation.x = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::ROT_Y: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& rotation = effect->GetRoadsignRotation(); + rotation.y = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::ROT_Z: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& rotation = effect->GetRoadsignRotation(); + rotation.z = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::FLAGS: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetRoadsignFlags(static_cast(std::get(propertyValue))); + return true; + } + + break; + } + case e2dEffectProperty::TEXT: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetRoadsignText(std::get(propertyValue), 1); + return true; + } + + break; + } + case e2dEffectProperty::TEXT_2: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetRoadsignText(std::get(propertyValue), 2); + return true; + } + + break; + } + case e2dEffectProperty::TEXT_3: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetRoadsignText(std::get(propertyValue), 3); + return true; + } + + break; + } + case e2dEffectProperty::TEXT_4: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetRoadsignText(std::get(propertyValue), 4); + return true; + } + + break; + } + } + break; + } + case e2dEffectType::ESCALATOR: + { + switch (property) + { + case e2dEffectProperty::BOTTOM_X: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& bottom = effect->GetEscalatorBottom(); + bottom.x = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::BOTTOM_Y: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& bottom = effect->GetEscalatorBottom(); + bottom.y = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::BOTTOM_Z: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& bottom = effect->GetEscalatorBottom(); + bottom.z = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::TOP_X: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& top = effect->GetEscalatorTop(); + top.x = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::TOP_Y: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& top = effect->GetEscalatorTop(); + top.y = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::TOP_Z: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& top = effect->GetEscalatorTop(); + top.z = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::END_X: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& end = effect->GetEscalatorEnd(); + end.x = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::END_Y: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& end = effect->GetEscalatorEnd(); + end.y = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::END_Z: + { + if (std::holds_alternative(propertyValue)) + { + RwV3d& end = effect->GetEscalatorEnd(); + end.z = std::get(propertyValue); + + return true; + } + + break; + } + case e2dEffectProperty::DIRECTION: + { + if (std::holds_alternative(propertyValue)) + { + effect->SetEscalatorDirection(static_cast(std::get(propertyValue))); + return true; + } + + break; + } + } + + break; + } + } + + return false; +} + +std::variant CClient2DFXManager::Get2DFXProperty(C2DEffect* effect, const e2dEffectProperty& property) +{ + if (!effect) + return false; + + switch (effect->GetEffectType()) + { + case e2dEffectType::LIGHT: + { + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + return effect->GetCoronaFarClip(); + case e2dEffectProperty::LIGHT_RANGE: + return effect->GetCoronaPointLightRange(); + case e2dEffectProperty::CORONA_SIZE: + return effect->GetCoronaSize(); + case e2dEffectProperty::SHADOW_SIZE: + return effect->GetShadowSize(); + case e2dEffectProperty::SHADOW_MULT: + return static_cast(effect->GetShadowMultiplier()); + case e2dEffectProperty::FLASH_TYPE: + return EnumToString(effect->GetCoronaShowMode()); + case e2dEffectProperty::CORONA_REFLECTION: + return effect->GetCoronaReflectionsEnabled(); + case e2dEffectProperty::FLARE_TYPE: + return static_cast(effect->GetCoronaFlareType()); + case e2dEffectProperty::FLAGS: + return static_cast(effect->GetLightFlags()); + case e2dEffectProperty::SHADOW_DISTANCE: + return static_cast(effect->GetShadowDistance()); + case e2dEffectProperty::OFFSET_X: + return static_cast(effect->GetCoronaOffsets().fX); + case e2dEffectProperty::OFFSET_Y: + return static_cast(effect->GetCoronaOffsets().fY); + case e2dEffectProperty::OFFSET_Z: + return static_cast(effect->GetCoronaOffsets().fZ); + case e2dEffectProperty::COLOR: + { + RwColor& color = effect->GetCoronaColor(); + int colorValue = (static_cast(color.a) << 24) | (static_cast(color.r) << 16) | (static_cast(color.g) << 8) | static_cast(color.b); + + return static_cast(colorValue); + } + case e2dEffectProperty::CORONA_NAME: + return effect->GetCoronaTexture(); + case e2dEffectProperty::SHADOW_NAME: + return effect->GetShadowTexture(); + } + + break; + } + case e2dEffectType::PARTICLE: + { + if (property == e2dEffectProperty::PRT_NAME) + return effect->GetParticleName(); + + break; + } + case e2dEffectType::ROADSIGN: + { + switch (property) + { + case e2dEffectProperty::SIZE_X: + return effect->GetRoadsignSize().x; + case e2dEffectProperty::SIZE_Y: + return effect->GetRoadsignSize().y; + case e2dEffectProperty::ROT_X: + return effect->GetRoadsignRotation().x; + case e2dEffectProperty::ROT_Y: + return effect->GetRoadsignRotation().y; + case e2dEffectProperty::ROT_Z: + return effect->GetRoadsignRotation().z; + case e2dEffectProperty::FLAGS: + return static_cast(effect->GetRoadsignFlags()); + case e2dEffectProperty::TEXT: + case e2dEffectProperty::TEXT_2: + case e2dEffectProperty::TEXT_3: + case e2dEffectProperty::TEXT_4: + return effect->GetRoadsignText(); + } + + break; + } + case e2dEffectType::ESCALATOR: + { + switch (property) + { + case e2dEffectProperty::BOTTOM_X: + return effect->GetEscalatorBottom().x; + case e2dEffectProperty::BOTTOM_Y: + return effect->GetEscalatorBottom().y; + case e2dEffectProperty::BOTTOM_Z: + return effect->GetEscalatorBottom().z; + case e2dEffectProperty::TOP_X: + return effect->GetEscalatorTop().x; + case e2dEffectProperty::TOP_Y: + return effect->GetEscalatorTop().y; + case e2dEffectProperty::TOP_Z: + return effect->GetEscalatorTop().z; + case e2dEffectProperty::END_X: + return effect->GetEscalatorEnd().x; + case e2dEffectProperty::END_Y: + return effect->GetEscalatorEnd().y; + case e2dEffectProperty::END_Z: + return effect->GetEscalatorEnd().z; + case e2dEffectProperty::DIRECTION: + return static_cast(effect->GetEscalatorDirection()); + } + + break; + } + } + + return false; +} + +void CClient2DFXManager::Set2DFXPosition(C2DEffect* effect, const CVector& position) +{ + if (!effect) + return; + + effect->SetPosition(position); +} + +CVector* CClient2DFXManager::Get2DFXPosition(C2DEffect* effect) const +{ + if (!effect) + return nullptr; + + return &effect->GetPosition(); +} + +const char* CClient2DFXManager::IsValidEffectData(const e2dEffectType& effectType, const effectDataMap& effectData) +{ + // Check if keys & values are ok! + switch (effectType) + { + case e2dEffectType::LIGHT: + { + // corona far clip + auto* drawDistance = MapFind(effectData, "drawDistance"); + if (!drawDistance || !std::holds_alternative(*drawDistance)) + return "Invalid \"drawDistance\" value"; + + auto* lightRange = MapFind(effectData, "lightRange"); + if (!lightRange || !std::holds_alternative(*lightRange)) + return "Invalid \"lightRange\" value"; + + auto* coronaSize = MapFind(effectData, "coronaSize"); + if (!coronaSize || !std::holds_alternative(*coronaSize)) + return "Invalid \"coronaSize\" value"; + + auto* shadowSize = MapFind(effectData, "shadowSize"); + if (!shadowSize || !std::holds_alternative(*shadowSize)) + return "Invalid \"shadowSize\" value"; + + auto* shadowMultiplier = MapFind(effectData, "shadowMultiplier"); + if (!shadowMultiplier || !std::holds_alternative(*shadowMultiplier) || std::get(*shadowMultiplier) < 0.0f) + return "Invalid \"shadowMultiplier\" value"; + + auto* showMode = MapFind(effectData, "showMode"); + e2dCoronaFlashType tempType; + if (!showMode || !std::holds_alternative(*showMode) || !StringToEnum(std::get(*showMode), tempType)) + return "Invalid \"showMode\" value"; + + auto* coronaReflection = MapFind(effectData, "coronaReflection"); + if (!coronaReflection || !std::holds_alternative(*coronaReflection)) + return "Invalid \"coronaReflection\" value"; + + auto* coronaFlareType = MapFind(effectData, "flareType"); + if (!coronaFlareType || !std::holds_alternative(*coronaFlareType) || (std::get(*coronaFlareType) < 0.0f || std::get(*coronaFlareType) > 1.0f)) + return "Invalid \"flareType\" value"; + + auto* flags = MapFind(effectData, "flags"); + if (!flags || !std::holds_alternative(*flags) || std::get(*flags) < 0.0f) + return "Invalid \"flags\" value"; + + auto* shadowZDistance = MapFind(effectData, "shadowDistance"); + if (!shadowZDistance || (!std::holds_alternative(*shadowZDistance))) + return "Invalid \"shadowDistance\" value"; + + auto* offsetX = MapFind(effectData, "offsetX"); + if (!offsetX || !std::holds_alternative(*offsetX)) + return "Invalid \"offsetX\" value"; + + auto* offsetY = MapFind(effectData, "offsetY"); + if (!offsetY || !std::holds_alternative(*offsetY)) + return "Invalid \"offsetY\" value"; + + auto* offsetZ = MapFind(effectData, "offsetZ"); + if (!offsetZ || !std::holds_alternative(*offsetZ)) + return "Invalid \"offsetZ\" value"; + + auto* color = MapFind(effectData, "color"); + if (!color || !std::holds_alternative(*color)) + return "Invalid \"color\" value"; + + auto* coronaTexture = MapFind(effectData, "coronaName"); + e2dEffectTextureName tempName; + if (!coronaTexture || !std::holds_alternative(*coronaTexture) || !StringToEnum(std::get(*coronaTexture), tempName)) + return "Invalid \"coronaName\" value"; + + auto* shadowTexture = MapFind(effectData, "shadowName"); + e2dEffectTextureName shadowName; + if (!shadowTexture || !std::holds_alternative(*shadowTexture) || !StringToEnum(std::get(*shadowTexture), shadowName)) + return "Invalid \"shadowName\" value"; + + break; + } + case e2dEffectType::PARTICLE: + { + auto* particleName = MapFind(effectData, "name"); + if (!particleName || !std::holds_alternative(*particleName) || std::get(*particleName).length() > 24) + return "Invalid \"particle name\" value"; + + break; + } + case e2dEffectType::ROADSIGN: + { + auto* sizeX = MapFind(effectData, "sizeX"); + if (!sizeX || !std::holds_alternative(*sizeX)) + return "Invalid \"sizeX\" value"; + + auto* sizeY = MapFind(effectData, "sizeY"); + if (!sizeY || !std::holds_alternative(*sizeY)) + return "Invalid \"sizeY\" value"; + + auto* rotX = MapFind(effectData, "rotX"); + if (!rotX || !std::holds_alternative(*rotX)) + return "Invalid \"rotX\" value"; + + auto* rotY = MapFind(effectData, "rotY"); + if (!rotY || !std::holds_alternative(*rotY)) + return "Invalid \"rotY\" value"; + + auto* rotZ = MapFind(effectData, "rotZ"); + if (!rotZ || !std::holds_alternative(*rotZ)) + return "Invalid \"rotZ\" value"; + + auto* flags = MapFind(effectData, "flags"); + if (!flags || !std::holds_alternative(*flags) || std::get(*flags) < 0.0f) + return "Invalid \"flags\" value"; + + auto* text = MapFind(effectData, "text1"); + if (!text || !std::holds_alternative(*text)) + return "Invalid \"text1\" value"; + + auto* text2 = MapFind(effectData, "text2"); + if (!text2 || !std::holds_alternative(*text2)) + return "Invalid \"text2\" value"; + + auto* text3 = MapFind(effectData, "text3"); + if (!text3 || !std::holds_alternative(*text3)) + return "Invalid \"text3\" value"; + + auto* text4 = MapFind(effectData, "text4"); + if (!text4 || !std::holds_alternative(*text4)) + return "Invalid \"text4\" value"; + + break; + } + case e2dEffectType::ESCALATOR: + { + auto* bottomX = MapFind(effectData, "bottomX"); + if (!bottomX || !std::holds_alternative(*bottomX)) + return "Invalid \"bottomX\" value"; + + auto* bottomY = MapFind(effectData, "bottomY"); + if (!bottomY || !std::holds_alternative(*bottomY)) + return "Invalid \"bottomY\" value"; + + auto* bottomZ = MapFind(effectData, "bottomZ"); + if (!bottomZ || !std::holds_alternative(*bottomZ)) + return "Invalid \"bottomZ\" value"; + + auto* topX = MapFind(effectData, "topX"); + if (!topX || !std::holds_alternative(*topX)) + return "Invalid \"topX\" value"; + + auto* topY = MapFind(effectData, "topY"); + if (!topY || !std::holds_alternative(*topY)) + return "Invalid \"topY\" value"; + + auto* topZ = MapFind(effectData, "topZ"); + if (!topX || !std::holds_alternative(*topZ)) + return "Invalid \"topZ\" value"; + + auto* endX = MapFind(effectData, "endX"); + if (!endX || !std::holds_alternative(*endX)) + return "Invalid \"endX\" value"; + + auto* endY = MapFind(effectData, "endY"); + if (!endY || !std::holds_alternative(*endY)) + return "Invalid \"endY\" value"; + + auto* endZ = MapFind(effectData, "endZ"); + if (!endZ || !std::holds_alternative(*endZ)) + return "Invalid \"endZ\" value"; + + auto* direction = MapFind(effectData, "direction"); + if (!direction || !std::holds_alternative(*direction) || (std::get(*direction) < 0.0f || std::get(*direction) > 1.0f)) + return "Invalid \"direction\" value"; + + break; + } + case e2dEffectType::SUN_GLARE: + // It has no properties, it only uses position and type fields + break; + case e2dEffectType::ATTRACTOR: + // Unnecessary in MTA + break; + case e2dEffectType::FURNITURE: + // Unnecessary in MTA (Probably unused even in SA) + break; + case e2dEffectType::ENEX: + // Unnecessary in MTA + break; + case e2dEffectType::TRIGGER_POINT: + { + // Unnecessary in MTA + break; + } + case e2dEffectType::COVER_POINT: + // Unnecessary in MTA + break; + } + + return nullptr; +} + +bool CClient2DFXManager::TryDestroyCustom2DFXEffect(std::uint32_t modelID, std::int8_t index) +{ + bool removed = false; + + if (index == -1) + { + for (auto it = m_effectsList.begin(); it != m_effectsList.end();) + { + if ((*it)->GetModelID() == modelID) + { + delete *it; + it = m_effectsList.erase(it); + + removed = true; + } + else + ++it; + } + } + else + { + CModelInfo* modelInfo = g_pGame->GetModelInfo(modelID); + if (!modelInfo) + return false; + + C2DEffect* effect = reinterpret_cast(modelInfo->Get2DFXFromIndex(static_cast(index))); + if (!effect) + return false; + + for (auto* clientEffect : m_effectsList) + { + if (clientEffect->Get2DFX() == effect) + { + RemoveFromList(clientEffect); + delete clientEffect; + + removed = true; + break; + } + } + } + + return removed; +} + +bool CClient2DFXManager::IsValidModel(std::uint32_t model) noexcept +{ + return CClientObjectManager::IsValidModel(model) || CClientBuildingManager::IsValidModel(model) || CClientVehicleManager::IsValidModel(model); +} diff --git a/Client/mods/deathmatch/logic/CClient2DFXManager.h b/Client/mods/deathmatch/logic/CClient2DFXManager.h new file mode 100644 index 0000000000..12bcbf6012 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClient2DFXManager.h @@ -0,0 +1,48 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/mods/deathmatch/logic/CClient2DFXManager.h + * PURPOSE: Handling 2DFX effects manager + * + *****************************************************************************/ +#pragma once + +#include "CClient2DFX.h" + +class CClient2DFXManager +{ + friend class CClient2DFX; + +public: + CClient2DFXManager(class CClientManager* mainManager); + ~CClient2DFXManager(); + + void RemoveAll(); + bool Exists(CClient2DFX* effect) const; + + CClient2DFX* Add2DFX(std::uint32_t model, const CVector& position, const e2dEffectType& type, const effectDataMap& effectData); + + bool Set2DFXProperties(C2DEffect* effect, const effectDataMap& effectData); + effectDataMap Get2DFXProperties(C2DEffect* effect) const; + + bool Set2DFXProperty(C2DEffect* effect, const e2dEffectProperty& property, const std::variant& propertyValue); + std::variant Get2DFXProperty(C2DEffect* effect, const e2dEffectProperty& property); + + void Set2DFXPosition(C2DEffect* effect, const CVector& position); + CVector* Get2DFXPosition(C2DEffect* effect) const; + + bool TryDestroyCustom2DFXEffect(std::uint32_t modelID, std::int8_t index); + + static bool IsValidModel(std::uint32_t model) noexcept; + static const char* IsValidEffectData(const e2dEffectType& effectType, const effectDataMap& effectData); + +private: + void AddToList(CClient2DFX* effect) { m_effectsList.push_back(effect); } + void RemoveFromList(CClient2DFX* effect); + + class CClientManager* m_mainManager; + + std::list m_effectsList; + bool m_canRemoveFromList; +}; diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index 0d84526d8b..9ef4e6fb3d 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -80,6 +80,7 @@ enum eClientEntityType CCLIENTUNKNOWN, CCLIENTIMG, CCLIENTBUILDING, + CCLIENT2DFX, }; class CEntity; @@ -145,6 +146,7 @@ enum eCClientEntityClassTypes CLASS_CClientSearchLight, CLASS_CClientIMG, CLASS_CClientBuilding, + CLASS_CClient2DFX, }; class CClientEntity : public CClientEntityBase diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index bc20947eac..03787ca567 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -3430,6 +3430,7 @@ void CClientGame::Event_OnIngame() g_pGame->ResetModelFlags(); g_pGame->ResetAlphaTransparencies(); g_pGame->ResetModelTimes(); + g_pGame->ResetModel2DFXEffects(); // Make sure we can access all areas g_pGame->GetStats()->ModifyStat(CITIES_PASSED, 2.0); diff --git a/Client/mods/deathmatch/logic/CClientManager.cpp b/Client/mods/deathmatch/logic/CClientManager.cpp index e3a808555b..b27fb069fe 100644 --- a/Client/mods/deathmatch/logic/CClientManager.cpp +++ b/Client/mods/deathmatch/logic/CClientManager.cpp @@ -55,6 +55,7 @@ CClientManager::CClientManager() m_pPacketRecorder = new CClientPacketRecorder(this); m_pImgManager = new CClientIMGManager(this); m_pBuildingManager = new CClientBuildingManager(this); + m_p2DFXManager = new CClient2DFXManager(this); m_bBeingDeleted = false; m_bGameUnloadedFlag = false; @@ -181,6 +182,9 @@ CClientManager::~CClientManager() delete m_pBuildingManager; m_pBuildingManager = nullptr; + + delete m_p2DFXManager; + m_p2DFXManager = nullptr; } // diff --git a/Client/mods/deathmatch/logic/CClientManager.h b/Client/mods/deathmatch/logic/CClientManager.h index 0e9f0c7702..19dc6da001 100644 --- a/Client/mods/deathmatch/logic/CClientManager.h +++ b/Client/mods/deathmatch/logic/CClientManager.h @@ -44,6 +44,7 @@ class CClientManager; #include "CClientModelManager.h" #include "CClientIMGManager.h" #include "CClientBuildingManager.h" +#include "CClient2DFXManager.h" class CClientProjectileManager; class CClientExplosionManager; @@ -98,6 +99,7 @@ class CClientManager CClientPointLightsManager* GetPointLightsManager() { return m_pPointLightsManager; } CClientIMGManager* GetIMGManager() { return m_pImgManager; } CClientBuildingManager* GetBuildingManager() const noexcept { return m_pBuildingManager; } + CClient2DFXManager* Get2DFXManager() const noexcept { return m_p2DFXManager; } bool IsGameLoaded() { return g_pGame->GetSystemState() == 9 && !m_bGameUnloadedFlag && g_pCore->GetNetwork()->GetServerBitStreamVersion(); } bool IsBeingDeleted() { return m_bBeingDeleted; } @@ -151,6 +153,7 @@ class CClientManager CClientIMGManager* m_pImgManager; CClientPacketRecorder* m_pPacketRecorder; CClientBuildingManager* m_pBuildingManager; + CClient2DFXManager* m_p2DFXManager; bool m_bBeingDeleted; bool m_bGameUnloadedFlag; int m_iNumLowLODElements; diff --git a/Client/mods/deathmatch/logic/CResource.cpp b/Client/mods/deathmatch/logic/CResource.cpp index dcf119ce46..352ff4fd56 100644 --- a/Client/mods/deathmatch/logic/CResource.cpp +++ b/Client/mods/deathmatch/logic/CResource.cpp @@ -73,6 +73,11 @@ CResource::CResource(unsigned short usNetID, const char* szResourceName, CClient m_pResourceIMGRoot = new CClientDummy(g_pClientGame->GetManager(), INVALID_ELEMENT_ID, "imgroot"); m_pResourceIMGRoot->MakeSystemEntity(); + // Create our 2DFX root element. We set its parent when we're loaded. + // Make it a system entity so nothing but us can delete it. + m_pResource2DFXRoot = new CClientDummy(g_pClientGame->GetManager(), INVALID_ELEMENT_ID, "2dfxroot"); + m_pResource2DFXRoot->MakeSystemEntity(); + m_strResourceDirectoryPath = SString("%s/resources/%s", g_pClientGame->GetFileCacheRoot(), *m_strResourceName); m_strResourcePrivateDirectoryPath = PathJoin(CServerIdManager::GetSingleton()->GetConnectionPrivateDirectory(), m_strResourceName); @@ -140,6 +145,10 @@ CResource::~CResource() g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResourceGUIEntity); m_pResourceGUIEntity = NULL; + // Destroy the 2dfx root so all 2dfx elements are deleted except those moved out + g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResource2DFXRoot); + m_pResource2DFXRoot = nullptr; + // Undo all changes to water g_pGame->GetWaterManager()->UndoChanges(this); @@ -256,6 +265,7 @@ void CResource::Load() m_pResourceDFFEntity->SetParent(m_pResourceEntity); m_pResourceGUIEntity->SetParent(m_pResourceEntity); m_pResourceTXDRoot->SetParent(m_pResourceEntity); + m_pResource2DFXRoot->SetParent(m_pResourceEntity); } CLogger::LogPrintf("> Starting resource '%s'", *m_strResourceName); diff --git a/Client/mods/deathmatch/logic/CResource.h b/Client/mods/deathmatch/logic/CResource.h index 8100d3fc79..17fa4b31ab 100644 --- a/Client/mods/deathmatch/logic/CResource.h +++ b/Client/mods/deathmatch/logic/CResource.h @@ -79,6 +79,7 @@ class CResource CClientEntity* GetResourceTXDRoot() { return m_pResourceTXDRoot; }; CClientEntity* GetResourceIFPRoot() { return m_pResourceIFPRoot; }; CClientEntity* GetResourceIMGRoot() { return m_pResourceIMGRoot; }; + CClientEntity* GetResource2DFXRoot() { return m_pResource2DFXRoot; }; CResourceModelStreamer* GetResourceModelStreamer() { return &m_modelStreamer; }; @@ -128,6 +129,7 @@ class CResource class CClientEntity* m_pResourceTXDRoot; class CClientEntity* m_pResourceIFPRoot; class CClientEntity* m_pResourceIMGRoot; + class CClientEntity* m_pResource2DFXRoot; unsigned short m_usRemainingNoClientCacheScripts; bool m_bLoadAfterReceivingNoClientCacheScripts; CMtaVersion m_strMinServerReq; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 64708dea0c..0a3cf88e95 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -910,6 +910,113 @@ ADD_ENUM(PreloadAreaOption::COLLISIONS, "collisions") ADD_ENUM(PreloadAreaOption::ALL, "all") IMPLEMENT_ENUM_CLASS_END("preload-area-option") +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectType) +ADD_ENUM(e2dEffectType::LIGHT, "light") +ADD_ENUM(e2dEffectType::PARTICLE, "particle") +ADD_ENUM(e2dEffectType::ATTRACTOR, "ped_attractor") +ADD_ENUM(e2dEffectType::SUN_GLARE, "sun_glare") +ADD_ENUM(e2dEffectType::FURNITURE, "furniture") +ADD_ENUM(e2dEffectType::ENEX, "enter_exit") +ADD_ENUM(e2dEffectType::ROADSIGN, "roadsign") +ADD_ENUM(e2dEffectType::TRIGGER_POINT, "trigger_point") +ADD_ENUM(e2dEffectType::COVER_POINT, "cover_point") +ADD_ENUM(e2dEffectType::ESCALATOR, "escalator") +IMPLEMENT_ENUM_CLASS_END("2dfx-type") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dCoronaFlashType) +ADD_ENUM(e2dCoronaFlashType::DEFAULT, "default") +ADD_ENUM(e2dCoronaFlashType::RANDOM, "random_flashing") +ADD_ENUM(e2dCoronaFlashType::RANDOM_WHEN_WET, "random_at_wet_weather") +ADD_ENUM(e2dCoronaFlashType::ANIM_SPEED_4X, "anim_speed_4x") +ADD_ENUM(e2dCoronaFlashType::ANIM_SPEED_2X, "anim_speed_2x") +ADD_ENUM(e2dCoronaFlashType::ANIM_SPEED_1X, "anim_speed_1x") +ADD_ENUM(e2dCoronaFlashType::WARNLIGHT, "warnlight") +ADD_ENUM(e2dCoronaFlashType::TRAFFICLIGHT, "trafficlight") +ADD_ENUM(e2dCoronaFlashType::TRAINCROSSING, "traincrosslight") +ADD_ENUM(e2dCoronaFlashType::ONLY_RAIN, "at_rain_only") +ADD_ENUM(e2dCoronaFlashType::ON5_OFF5, "on_off_at_5") +ADD_ENUM(e2dCoronaFlashType::ON6_OFF4, "on_at_6_off_at_4") +ADD_ENUM(e2dCoronaFlashType::ON4_OFF6, "on_at_4_off_at_6") +IMPLEMENT_ENUM_CLASS_END("2dfx-light-mode") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectTextureName) +ADD_ENUM(e2dEffectTextureName::CORONA_STAR, "coronastar") +ADD_ENUM(e2dEffectTextureName::SHAD_HELI, "shad_heli") +ADD_ENUM(e2dEffectTextureName::SHAD_EXP, "shad_exp") +ADD_ENUM(e2dEffectTextureName::SHAD_CAR, "shad_car") +ADD_ENUM(e2dEffectTextureName::SHAD_BIKE, "shad_bike") +ADD_ENUM(e2dEffectTextureName::SEABD32, "seabd32") +ADD_ENUM(e2dEffectTextureName::ROADSIGNFONT, "roadsignfont") +ADD_ENUM(e2dEffectTextureName::PARTICLESKID, "particleskid") +ADD_ENUM(e2dEffectTextureName::LUNAR, "lunar") +ADD_ENUM(e2dEffectTextureName::LOCKONFIRE, "lockonFire") +ADD_ENUM(e2dEffectTextureName::LOCKON, "lockon") +ADD_ENUM(e2dEffectTextureName::LAMP_SHAD_64, "lamp_shad_64") +ADD_ENUM(e2dEffectTextureName::HEADLIGHT1, "headlight1") +ADD_ENUM(e2dEffectTextureName::HEADLIGHT, "headlight") +ADD_ENUM(e2dEffectTextureName::HANDMAN, "handman") +ADD_ENUM(e2dEffectTextureName::FINISHFLAG, "finishflag") +ADD_ENUM(e2dEffectTextureName::CORONARINGB, "coronaringb") +ADD_ENUM(e2dEffectTextureName::CORONAREFLECT, "coronareflect") +ADD_ENUM(e2dEffectTextureName::CORONAMOON, "coronamoon") +ADD_ENUM(e2dEffectTextureName::CORONAHEADLIGHTLINE, "coronaheadlightline") +ADD_ENUM(e2dEffectTextureName::CLOUDMASKED, "cloudmasked") +ADD_ENUM(e2dEffectTextureName::CLOUDHIGH, "cloudhigh") +ADD_ENUM(e2dEffectTextureName::CLOUD1, "cloud1") +ADD_ENUM(e2dEffectTextureName::CARFX1, "carfx1") +ADD_ENUM(e2dEffectTextureName::BLOODPOOL_64, "bloodpool_64") +ADD_ENUM(e2dEffectTextureName::WINCRACK_32, "wincrack_32") +ADD_ENUM(e2dEffectTextureName::WHITE, "white") +ADD_ENUM(e2dEffectTextureName::WATERWAKE, "waterwake") +ADD_ENUM(e2dEffectTextureName::WATERCLEAR256, "waterclear256") +ADD_ENUM(e2dEffectTextureName::TXGRASSBIG1, "txgrassbig1") +ADD_ENUM(e2dEffectTextureName::TXGRASSBIG0, "txgrassbig0") +ADD_ENUM(e2dEffectTextureName::SHAD_RCBARON, "shad_rcbaron") +ADD_ENUM(e2dEffectTextureName::SHAD_PED, "shad_ped") +IMPLEMENT_ENUM_CLASS_END("2dfx-texture-name") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectProperty) +ADD_ENUM(e2dEffectProperty::FAR_CLIP_DISTANCE, "drawDistance") +ADD_ENUM(e2dEffectProperty::LIGHT_RANGE, "lightRange") +ADD_ENUM(e2dEffectProperty::CORONA_SIZE, "coronaSize") +ADD_ENUM(e2dEffectProperty::SHADOW_SIZE, "shadowSize") +ADD_ENUM(e2dEffectProperty::SHADOW_MULT, "shadowMultiplier") +ADD_ENUM(e2dEffectProperty::FLASH_TYPE, "showMode") +ADD_ENUM(e2dEffectProperty::CORONA_REFLECTION, "coronaReflection") +ADD_ENUM(e2dEffectProperty::FLARE_TYPE, "flareType") +ADD_ENUM(e2dEffectProperty::SHADOW_DISTANCE, "shadowDistance") +ADD_ENUM(e2dEffectProperty::OFFSET_X, "offsetX") +ADD_ENUM(e2dEffectProperty::OFFSET_Y, "offsetY") +ADD_ENUM(e2dEffectProperty::OFFSET_Z, "offsetZ") +ADD_ENUM(e2dEffectProperty::COLOR, "color") +ADD_ENUM(e2dEffectProperty::CORONA_NAME, "coronaName") +ADD_ENUM(e2dEffectProperty::SHADOW_NAME, "shadowName") +ADD_ENUM(e2dEffectProperty::FLAGS, "flags") + +ADD_ENUM(e2dEffectProperty::PRT_NAME, "name") + +ADD_ENUM(e2dEffectProperty::SIZE_X, "sizeX") +ADD_ENUM(e2dEffectProperty::SIZE_Y, "sizeY") +ADD_ENUM(e2dEffectProperty::ROT_X, "rotX") +ADD_ENUM(e2dEffectProperty::ROT_Y, "rotY") +ADD_ENUM(e2dEffectProperty::ROT_Z, "rotZ") +ADD_ENUM(e2dEffectProperty::TEXT, "text1") +ADD_ENUM(e2dEffectProperty::TEXT_2, "text2") +ADD_ENUM(e2dEffectProperty::TEXT_3, "text3") +ADD_ENUM(e2dEffectProperty::TEXT_4, "text4") + +ADD_ENUM(e2dEffectProperty::BOTTOM_X, "bottomX") +ADD_ENUM(e2dEffectProperty::BOTTOM_Y, "bottomY") +ADD_ENUM(e2dEffectProperty::BOTTOM_Z, "bottomZ") +ADD_ENUM(e2dEffectProperty::TOP_X, "topX") +ADD_ENUM(e2dEffectProperty::TOP_Y, "topY") +ADD_ENUM(e2dEffectProperty::TOP_Z, "topZ") +ADD_ENUM(e2dEffectProperty::END_X, "endX") +ADD_ENUM(e2dEffectProperty::END_Y, "endY") +ADD_ENUM(e2dEffectProperty::END_Z, "endZ") +ADD_ENUM(e2dEffectProperty::DIRECTION, "direction") +IMPLEMENT_ENUM_CLASS_END("2dfx-property-name") + // // CResource from userdata // diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index e134a73bea..820dcfda76 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -88,6 +88,49 @@ DECLARE_ENUM(ePools); DECLARE_ENUM(eWorldProperty); DECLARE_ENUM_CLASS(eModelLoadState); DECLARE_ENUM_CLASS(PreloadAreaOption); +DECLARE_ENUM_CLASS(e2dEffectType); +DECLARE_ENUM_CLASS(e2dCoronaFlashType); +DECLARE_ENUM_CLASS(e2dEffectProperty); + +// For corona name & shadow name +enum class e2dEffectTextureName +{ + CORONA_STAR, + SHAD_HELI, + SHAD_EXP, + SHAD_CAR, + SHAD_BIKE, + SEABD32, + ROADSIGNFONT, + PARTICLESKID, + LUNAR, + LOCKONFIRE, + LOCKON, + LAMP_SHAD_64, + HEADLIGHT1, + HEADLIGHT, + HANDMAN, + FINISHFLAG, + CORONARINGB, + CORONAREFLECT, + CORONAMOON, + CORONAHEADLIGHTLINE, + CLOUDMASKED, + CLOUDHIGH, + CLOUD1, + CARFX1, + BLOODPOOL_64, + WINCRACK_32, + WHITE, + WATERWAKE, + WATERCLEAR256, + TXGRASSBIG1, + TXGRASSBIG0, + TARGET256, + SHAD_RCBARON, + SHAD_PED, +}; +DECLARE_ENUM_CLASS(e2dEffectTextureName) class CRemoteCall; @@ -511,6 +554,11 @@ inline SString GetClassTypeName(CClientVectorGraphic*) return "svg"; } +inline SString GetClassTypeName(CClient2DFX*) noexcept +{ + return "2dfx"; +} + // // CResource from userdata // diff --git a/Client/mods/deathmatch/logic/lua/CLuaMain.cpp b/Client/mods/deathmatch/logic/lua/CLuaMain.cpp index 6b794be016..33c25e78de 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaMain.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaMain.cpp @@ -128,6 +128,7 @@ void CLuaMain::InitClasses(lua_State* luaVM) CLuaWaterDefs::AddClass(luaVM); CLuaWeaponDefs::AddClass(luaVM); CLuaBuildingDefs::AddClass(luaVM); + CLua2DFXDefs::AddClass(luaVM); CLuaShared::AddClasses(luaVM); } diff --git a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp index c2a28809af..ae1d1585d8 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp @@ -281,4 +281,5 @@ void CLuaManager::LoadCFunctions() CLuaClientDefs::LoadFunctions(); CLuaDiscordDefs::LoadFunctions(); CLuaBuildingDefs::LoadFunctions(); + CLua2DFXDefs::LoadFunctions(); } diff --git a/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.cpp new file mode 100644 index 0000000000..308a7ee795 --- /dev/null +++ b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.cpp @@ -0,0 +1,341 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/luadefs/CLua2DFXDefs.cpp + * PURPOSE: Lua definitions class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CLua2DFXDefs.h" +#include + +void CLua2DFXDefs::LoadFunctions() +{ + constexpr static const std::pair functions[]{ + // Create / destroy functions + {"addModel2DFX", ArgumentParser}, + {"removeModel2DFX", ArgumentParser}, + {"resetModel2DFX", ArgumentParser}, + + // Set functions + {"setModel2DFXProperties", ArgumentParser}, + {"set2DFXProperties", ArgumentParser}, + {"setModel2DFXProperty", ArgumentParser}, + {"set2DFXProperty", ArgumentParser}, + {"setModel2DFXPosition", ArgumentParser}, + {"set2DFXPosition", ArgumentParser}, + + // Get functions + {"getModel2DFXProperties", ArgumentParser}, + {"get2DFXProperties", ArgumentParser}, + {"getModel2DFXProperty", ArgumentParser}, + {"get2DFXProperty", ArgumentParser}, + {"getModel2DFXPosition", ArgumentParser}, + {"get2DFXPosition", ArgumentParser}, + {"getModel2DFXCount", ArgumentParser}, + }; + + // Add functions + for (const auto& [name, func] : functions) + CLuaCFunctions::AddFunction(name, func); +} + +void CLua2DFXDefs::AddClass(lua_State* luaVM) +{ + lua_newclass(luaVM); + + lua_classfunction(luaVM, "add", "addModel2DFX"); + + lua_classfunction(luaVM, "setProperties", "set2DFXProperties"); + lua_classfunction(luaVM, "setProperty", "set2DFXProperty"); + lua_classfunction(luaVM, "setPosition", "set2DFXPosition"); + + lua_classfunction(luaVM, "getProperties", "get2DFXProperties"); + lua_classfunction(luaVM, "getProperty", "get2DFXProperty"); + lua_classfunction(luaVM, "getPosition", "get2DFXPosition"); + + lua_classvariable(luaVM, "properties", "get2DFXProperties", "set2DFXProperties"); + lua_classvariable(luaVM, "position", "get2DFXPosition", "set2DFXPosition"); + + lua_registerclass(luaVM, "2DFX", "Element"); +} + +std::variant CLua2DFXDefs::AddModel2DFX(lua_State* luaVM, std::uint32_t modelID, CVector position, e2dEffectType effectType, effectDataMap effectData) +{ + // Only these effects make sense in MTA + if (effectType != e2dEffectType::LIGHT && effectType != e2dEffectType::PARTICLE && effectType != e2dEffectType::ROADSIGN && + effectType != e2dEffectType::ESCALATOR && effectType != e2dEffectType::SUN_GLARE) + return false; + + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + const char* error = CClient2DFXManager::IsValidEffectData(effectType, effectData); + if (error) + throw LuaFunctionError(error); + + CClient2DFX* effect = m_p2DFXManager->Add2DFX(modelID, position, effectType, effectData); + if (!effect) + return false; + + CResource* resource = &lua_getownerresource(luaVM); + if (resource) + effect->SetParent(resource->GetResource2DFXRoot()); + + return effect; +} + +bool CLua2DFXDefs::RemoveModel2DFX(std::uint32_t modelID, std::optional index, std::optional includeDefault) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(modelID); + if (!modelInfo) + return false; + + if (index.has_value()) + { + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + } + + // If this is custom effect destroy it (CClient2DFX) + if (m_p2DFXManager->TryDestroyCustom2DFXEffect(modelID, index.value_or(-1))) + return true; + + return index.has_value() ? modelInfo->Remove2DFXEffectAtIndex(index.value(), includeDefault.value_or(false)) : modelInfo->RemoveAll2DFXEffects(includeDefault.value_or(false)); +} + +bool CLua2DFXDefs::ResetModel2DFX(std::uint32_t modelID, std::optional removeCustomEffects) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + if (removeCustomEffects.has_value() && removeCustomEffects.value()) + m_p2DFXManager->TryDestroyCustom2DFXEffect(modelID, -1); + + return modelInfo->Reset2DFXEffects(); +} + +bool CLua2DFXDefs::SetModel2DFXProperties(std::uint32_t modelID, std::uint32_t index, effectDataMap effectData) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + auto* effect = modelInfo->Get2DFXFromIndex(index); + if (!effect) + return false; + + const char* error = CClient2DFXManager::IsValidEffectData(reinterpret_cast(effect)->GetEffectType(), effectData); + if (error) + throw LuaFunctionError(error); + + modelInfo->StoreDefault2DFXEffect(effect); + + if (!m_p2DFXManager->Set2DFXProperties(reinterpret_cast(effect), effectData)) + return false; + + modelInfo->Update2DFXEffect(effect); + return true; +} + +bool CLua2DFXDefs::Set2DFXProperties(CClient2DFX* effect, effectDataMap effectData) +{ + CModelInfo* modelInfo = g_pGame->GetModelInfo(effect->GetModelID()); + if (!modelInfo) + return false; + + const char* error = CClient2DFXManager::IsValidEffectData(effect->Get2DFXType(), effectData); + if (error) + throw LuaFunctionError(error); + + if (!m_p2DFXManager->Set2DFXProperties(effect->Get2DFX(), effectData)) + return false; + + modelInfo->Update2DFXEffect(effect->Get2DFX()); + return true; +} + +bool CLua2DFXDefs::SetModel2DFXProperty(std::uint32_t modelID, std::uint32_t index, e2dEffectProperty property, std::variant propertyValue) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + auto* effect = modelInfo->Get2DFXFromIndex(index); + if (!effect) + return false; + + modelInfo->StoreDefault2DFXEffect(effect); + + if (!m_p2DFXManager->Set2DFXProperty(reinterpret_cast(effect), property, propertyValue)) + return false; + + modelInfo->Update2DFXEffect(effect); + return true; +} + +bool CLua2DFXDefs::Set2DFXProperty(CClient2DFX* effect, e2dEffectProperty property, std::variant propertyValue) +{ + CModelInfo* modelInfo = g_pGame->GetModelInfo(effect->GetModelID()); + if (!modelInfo) + return false; + + if (!m_p2DFXManager->Set2DFXProperty(effect->Get2DFX(), property, propertyValue)) + return false; + + modelInfo->Update2DFXEffect(effect->Get2DFX()); + return true; +} + +bool CLua2DFXDefs::SetModel2DFXPosition(std::uint32_t modelID, std::uint32_t index, CVector position) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + auto* effect = modelInfo->Get2DFXFromIndex(index); + if (!effect) + return false; + + modelInfo->StoreDefault2DFXEffect(effect); + m_p2DFXManager->Set2DFXPosition(reinterpret_cast(effect), position); + + modelInfo->Update2DFXEffect(effect); + return true; +} + +bool CLua2DFXDefs::Set2DFXPosition(CClient2DFX* effect, CVector position) +{ + CModelInfo* modelInfo = g_pGame->GetModelInfo(effect->GetModelID()); + if (!modelInfo) + return false; + + m_p2DFXManager->Set2DFXPosition(effect->Get2DFX(), position); + modelInfo->Update2DFXEffect(effect->Get2DFX()); + return true; +} + +std::variant> CLua2DFXDefs::GetModel2DFXPosition(std::uint32_t modelID, std::uint32_t index) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + auto* effect = modelInfo->Get2DFXFromIndex(index); + if (!effect) + return false; + + CVector* position = m_p2DFXManager->Get2DFXPosition(reinterpret_cast(effect)); + return std::make_tuple(position->fX, position->fY, position->fZ); +} + +std::variant> CLua2DFXDefs::Get2DFXPosition(CClient2DFX* effect) +{ + CVector* position = m_p2DFXManager->Get2DFXPosition(effect->Get2DFX()); + if (!position) + return false; + + return std::make_tuple(position->fX, position->fY, position->fZ); +} + +std::variant CLua2DFXDefs::GetModel2DFXProperties(std::uint32_t modelID, std::uint32_t index) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + auto* effect = modelInfo->Get2DFXFromIndex(index); + if (!effect) + return false; + + return m_p2DFXManager->Get2DFXProperties(reinterpret_cast(effect)); +} + +std::variant CLua2DFXDefs::Get2DFXProperties(CClient2DFX* effect) +{ + return m_p2DFXManager->Get2DFXProperties(effect->Get2DFX()); +} + +std::variant CLua2DFXDefs::GetModel2DFXProperty(std::uint32_t modelID, std::uint32_t index, e2dEffectProperty property) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + auto count = GetModel2DFXCount(modelID); + if (std::holds_alternative(count) && index >= std::get(count)) + throw std::invalid_argument("Invalid effect index"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(static_cast(modelID)); + if (!modelInfo) + return false; + + auto* effect = modelInfo->Get2DFXFromIndex(index); + if (!effect) + return false; + + return m_p2DFXManager->Get2DFXProperty(reinterpret_cast(effect), property); +} + +std::variant CLua2DFXDefs::Get2DFXProperty(CClient2DFX* effect, e2dEffectProperty property) +{ + return m_p2DFXManager->Get2DFXProperty(effect->Get2DFX(), property); +} + +std::variant CLua2DFXDefs::GetModel2DFXCount(std::uint32_t modelID) +{ + if (!CClient2DFXManager::IsValidModel(modelID)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(modelID); + if (!modelInfo) + return false; + + return modelInfo->Get2DFXCount(); +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.h b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.h new file mode 100644 index 0000000000..5b1b087ca6 --- /dev/null +++ b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.h @@ -0,0 +1,43 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/luadefs/CLua2DFXDefs.cpp + * PURPOSE: Lua definitions class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once +#include "CLuaDefs.h" +#include + +class CLua2DFXDefs : public CLuaDefs +{ +public: + static void LoadFunctions(); + static void AddClass(lua_State* luaVM); + + // Create/destroy functions + static std::variant AddModel2DFX(lua_State* luaVM, std::uint32_t modelID, CVector position, e2dEffectType effectType, effectDataMap effectData); + static bool RemoveModel2DFX(std::uint32_t modelID, std::optional index, std::optional includeDefault); + static bool ResetModel2DFX(std::uint32_t modelID, std::optional removeCustomEffects); + + // Set functions + static bool SetModel2DFXProperties(std::uint32_t modelID, std::uint32_t index, effectDataMap effectData); + static bool Set2DFXProperties(CClient2DFX* effect, effectDataMap effectData); + static bool SetModel2DFXProperty(std::uint32_t modelID, std::uint32_t index, e2dEffectProperty property, std::variant propertyValue); + static bool Set2DFXProperty(CClient2DFX* effect, e2dEffectProperty property, std::variant propertyValue); + static bool SetModel2DFXPosition(std::uint32_t modelID, std::uint32_t index, CVector position); + static bool Set2DFXPosition(CClient2DFX* effect, CVector position); + + // Get functions + static std::variant> GetModel2DFXPosition(std::uint32_t modelID, std::uint32_t index); + static std::variant> Get2DFXPosition(CClient2DFX* effect); + static std::variant GetModel2DFXProperty(std::uint32_t modelID, std::uint32_t index, e2dEffectProperty property); + static std::variant Get2DFXProperty(CClient2DFX* effect, e2dEffectProperty property); + static std::variant GetModel2DFXProperties(std::uint32_t modelID, std::uint32_t index); + static std::variant Get2DFXProperties(CClient2DFX* effect); + static std::variant GetModel2DFXCount(std::uint32_t modelID); +}; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp index 457f72998f..107af6dcbd 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp @@ -32,6 +32,7 @@ CClientColModelManager* CLuaDefs::m_pColModelManager = NULL; CRegisteredCommands* CLuaDefs::m_pRegisteredCommands = NULL; CClientIMGManager* CLuaDefs::m_pImgManager = NULL; CClientBuildingManager* CLuaDefs::m_pBuildingManager = nullptr; +CClient2DFXManager* CLuaDefs::m_p2DFXManager = nullptr; bool ms_bRegisterdPostCallHook = false; void CLuaDefs::Initialize(CClientGame* pClientGame, CLuaManager* pLuaManager, CScriptDebugging* pScriptDebugging) @@ -58,6 +59,7 @@ void CLuaDefs::Initialize(CClientGame* pClientGame, CLuaManager* pLuaManager, CS m_pRegisteredCommands = pClientGame->GetRegisteredCommands(); m_pImgManager = m_pManager->GetIMGManager(); m_pBuildingManager = m_pManager->GetBuildingManager(); + m_p2DFXManager = m_pManager->Get2DFXManager(); } 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 3f3c18386f..e65cfc8131 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h @@ -66,6 +66,7 @@ class CLuaDefs static CRegisteredCommands* m_pRegisteredCommands; static CClientIMGManager* m_pImgManager; static CClientBuildingManager* m_pBuildingManager; + static CClient2DFXManager* m_p2DFXManager; protected: // Old style: Only warn on failure. This should diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 7ca02230d5..be20b18451 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -146,7 +146,7 @@ void CLuaEngineDefs::LoadFunctions() {"engineGetPoolUsedCapacity", ArgumentParser}, {"engineSetPoolCapacity", ArgumentParser}, {"enginePreloadWorldArea", ArgumentParser}, - + // CLuaCFunctions::AddFunction ( "engineReplaceMatchingAtomics", EngineReplaceMatchingAtomics ); // CLuaCFunctions::AddFunction ( "engineReplaceWheelAtomics", EngineReplaceWheelAtomics ); // CLuaCFunctions::AddFunction ( "enginePositionAtomic", EnginePositionAtomic ); diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index 28d5b1f551..63e4429a44 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -3018,8 +3018,10 @@ void _declspec(naked) HOOK_CCustomRoadsignMgr__RenderRoadsignAtomic() cmp esi, 0 jz no_render - // original code + // original code with our null check mov eax, dword ptr[esi+4] + test eax, eax + jz no_render fsub [eax+64] mov edx, HOOKPOS_CCustomRoadsignMgr__RenderRoadsignAtomic add edx, 6 diff --git a/Client/sdk/game/C2DEffect.h b/Client/sdk/game/C2DEffect.h new file mode 100644 index 0000000000..788580893f --- /dev/null +++ b/Client/sdk/game/C2DEffect.h @@ -0,0 +1,95 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/C2DEffect.h + * PURPOSE: Game 2dfx interface + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + * + *****************************************************************************/ + +#pragma once +#include "C2DEffects.h" + +struct RwColor; +struct RwV2d; +struct RwV3d; +class CVector; + +class C2DEffect +{ +public: + virtual void SetPosition(const CVector& position) = 0; + virtual CVector& GetPosition() const = 0; + + virtual e2dEffectType GetEffectType() = 0; + + // Light properties + // Set + virtual void SetCoronaFarClip(float clip) = 0; + virtual void SetCoronaPointLightRange(float range) = 0; + virtual void SetCoronaSize(float size) = 0; + virtual void SetShadowSize(float size) = 0; + virtual void SetShadowMultiplier(std::uint8_t multiplier) = 0; + virtual void SetCoronaShowMode(e2dCoronaFlashType showMode) = 0; + virtual void SetCoronaReflectionsEnabled(bool enable) = 0; + virtual void SetCoronaFlareType(std::uint8_t flareType) = 0; + virtual void SetLightFlags(std::uint16_t flags) = 0; + virtual void SetShadowDistance(std::int8_t distance) = 0; + virtual void SetCoronaOffsets(const CVector& offsets) = 0; + virtual void SetCoronaColor(const RwColor& color) = 0; + virtual void SetCoronaTexture(const std::string& name) = 0; + virtual void SetShadowTexture(const std::string& name) = 0; + + // Get + virtual float GetCoronaFarClip() const = 0; + virtual float GetCoronaPointLightRange() const = 0; + virtual float GetCoronaSize() const = 0; + virtual float GetShadowSize() const = 0; + virtual std::uint8_t GetShadowMultiplier() const = 0; + virtual e2dCoronaFlashType GetCoronaShowMode() const = 0; + virtual bool GetCoronaReflectionsEnabled() const = 0; + virtual std::uint8_t GetCoronaFlareType() const = 0; + virtual std::uint16_t GetLightFlags() const = 0; + virtual std::int8_t GetShadowDistance() const = 0; + virtual CVector GetCoronaOffsets() const = 0; + virtual RwColor GetCoronaColor() const = 0; + virtual std::string GetCoronaTexture() const = 0; + virtual std::string GetShadowTexture() const = 0; + + // Particle properties + // Set + virtual void SetParticleName(const std::string& name) = 0; + + // Get + virtual std::string GetParticleName() const = 0; + + // Roadsign properties + // Set + virtual void SetRoadsignSize(const RwV2d& size) = 0; + virtual void SetRoadsignRotation(const RwV3d& rotation) = 0; + virtual void SetRoadsignFlags(std::uint8_t flags) = 0; + virtual void SetRoadsignText(const std::string& text, std::uint8_t line) = 0; + + // Get + virtual RwV2d& GetRoadsignSize() = 0; + virtual RwV3d& GetRoadsignRotation() = 0; + virtual std::uint16_t GetRoadsignFlags() const = 0; + virtual std::string GetRoadsignText() const = 0; + + // Escalator properties + // Set + virtual void SetEscalatorBottom(const RwV3d& bottom) = 0; + virtual void SetEscalatorTop(const RwV3d& top) = 0; + virtual void SetEscalatorEnd(const RwV3d& end) = 0; + virtual void SetEscalatorDirection(std::uint8_t direction) = 0; + + // Get + virtual RwV3d& GetEscalatorBottom() = 0; + virtual RwV3d& GetEscalatorTop() = 0; + virtual RwV3d& GetEscalatorEnd() = 0; + virtual std::uint8_t GetEscalatorDirection() const = 0; + +}; diff --git a/Client/sdk/game/C2DEffects.h b/Client/sdk/game/C2DEffects.h new file mode 100644 index 0000000000..f897afc6d1 --- /dev/null +++ b/Client/sdk/game/C2DEffects.h @@ -0,0 +1,125 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/C2DEffect.h + * PURPOSE: Game 2dfx interface + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + * + *****************************************************************************/ + +#pragma once + +class C2DEffectSA; +class C2DEffect; +class CVector; +class C2DEffectSAInterface; + +enum class e2dEffectType : std::uint8_t +{ + LIGHT = 0, + PARTICLE, + UNKNOWN, + ATTRACTOR, + SUN_GLARE, + FURNITURE, + ENEX, + ROADSIGN, + TRIGGER_POINT, + COVER_POINT, + ESCALATOR, + + NONE, +}; + +enum class e2dCoronaFlashType : std::uint8_t +{ + DEFAULT = 0, + RANDOM, + RANDOM_WHEN_WET, + ANIM_SPEED_4X, + ANIM_SPEED_2X, + ANIM_SPEED_1X, + WARNLIGHT, // Used on model nt_roadblockci + TRAFFICLIGHT, + TRAINCROSSING, + UNUSED, + ONLY_RAIN, + ON5_OFF5, + ON6_OFF4, + ON4_OFF6, +}; + +enum class e2dEffectProperty +{ + // light properties + FAR_CLIP_DISTANCE = 0, + LIGHT_RANGE, + CORONA_SIZE, + SHADOW_SIZE, + SHADOW_MULT, + FLASH_TYPE, + CORONA_REFLECTION, + FLARE_TYPE, + SHADOW_DISTANCE, + OFFSET_X, + OFFSET_Y, + OFFSET_Z, + COLOR, + CORONA_NAME, + SHADOW_NAME, + FLAGS, // for light & roadsign + + // particle properties + PRT_NAME, + + // roadsign properties + SIZE_X, + SIZE_Y, + ROT_X, + ROT_Y, + ROT_Z, + TEXT, + TEXT_2, + TEXT_3, + TEXT_4, + + // escalator properties + BOTTOM_X, + BOTTOM_Y, + BOTTOM_Z, + TOP_X, + TOP_Y, + TOP_Z, + END_X, + END_Y, + END_Z, + DIRECTION, +}; + +enum class e2dAttractorType : std::int8_t +{ + UNDEFINED = -1, + ATM = 0, + SEAT, + STOP, + PIZZA, + SHELTER, + TRIGGER_SCRIPT, + LOOK_AT, + SCRIPTED, + PARK, + STEP, +}; + +class C2DEffects +{ +public: + virtual C2DEffect* Create(std::uint32_t model, const CVector& position, const e2dEffectType& type) = 0; + virtual void Destroy(C2DEffect* effect) = 0; + virtual C2DEffectSA* Get(C2DEffectSAInterface* effectInterface) const = 0; + virtual void AddToList(C2DEffectSA* effect) = 0; + virtual void RemoveFromList(C2DEffectSA* effect) = 0; +}; diff --git a/Client/sdk/game/CGame.h b/Client/sdk/game/CGame.h index 2314fdad0e..dce44502ff 100644 --- a/Client/sdk/game/CGame.h +++ b/Client/sdk/game/CGame.h @@ -69,6 +69,7 @@ class CWorld; class CIplStore; class CBuildingRemoval; class CRenderer; +class C2DEffects; enum eEntityType; enum ePedPieceTypes; @@ -151,6 +152,7 @@ class __declspec(novtable) CGame virtual CColStore* GetCollisionStore() = 0; virtual CBuildingRemoval* GetBuildingRemoval() = 0; virtual CRenderer* GetRenderer() const noexcept = 0; + virtual C2DEffects* Get2DEffects() const noexcept = 0; virtual CWeaponInfo* GetWeaponInfo(eWeaponType weapon, eWeaponSkill skill = WEAPONSKILL_STD) = 0; virtual CModelInfo* GetModelInfo(DWORD dwModelID, bool bCanBeInvalid = false) = 0; @@ -246,6 +248,7 @@ class __declspec(novtable) CGame virtual void ResetModelLodDistances() = 0; virtual void ResetModelFlags() = 0; virtual void ResetAlphaTransparencies() = 0; + virtual void ResetModel2DFXEffects() = 0; virtual void DisableVSync() = 0; virtual void ResetModelTimes() = 0; diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 08cb032e4c..e998251291 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -14,6 +14,7 @@ #include #include "CAnimBlock.h" #include "Common.h" +#include "C2DEffect.h" class CBaseModelInfoSAInterface; class CColModel; @@ -247,6 +248,25 @@ class CModelInfo // Vehicle towing functions virtual bool IsTowableBy(CModelInfo* towingModel) = 0; + // 2dfx functions + virtual C2DEffectSAInterface* Add2DFXEffect(const CVector& position, const e2dEffectType& type) = 0; + virtual bool Remove2DFX(C2DEffectSAInterface* effect, bool includeDefault) = 0; + virtual bool Remove2DFXEffectAtIndex(std::uint32_t index, bool includeDefault = false) = 0; + virtual bool RemoveAll2DFXEffects(bool includeDefault = false) = 0; + + virtual C2DEffectSA* Get2DFXFromIndex(std::uint32_t index) = 0; + virtual std::uint32_t Get2DFXCount() const = 0; + + virtual void Update2DFXEffect(C2DEffectSA* effect) = 0; + virtual void Update2DFXEffect(C2DEffect* effect) = 0; + + virtual void StoreDefault2DFXEffect(C2DEffectSA* effect) = 0; + virtual void StoreDefault2DFXEffect(C2DEffectSAInterface* effect) = 0; + virtual bool Reset2DFXEffects() = 0; + + virtual void CopyModified2DFXEffects() = 0; + virtual void RestoreModified2DFXEffects() = 0; + virtual unsigned int GetParentID() = 0; virtual bool IsDynamic() = 0; };