From 1ea64b44faa446b16c34a39385e68a6d1b9ac23f Mon Sep 17 00:00:00 2001 From: Federico Romero Date: Thu, 11 Jun 2026 00:24:22 -0300 Subject: [PATCH 1/4] Add set/get/resetVehicleNitroColor functions Allows scripts to recolour a vehicle's nitro flames per-vehicle (client and server side, synced via a new element RPC). The shared "nitro" FX blueprint's colour keyframes are re-tinted just-in-time as each vehicle's particles are rendered, using the original keyframes as a brightness envelope so any colour (including white) fully replaces the default cyan hue. No colour set means the original effect colours are used. --- Client/game_sa/CFxManagerSA.cpp | 13 ++ Client/game_sa/CFxManagerSA.h | 5 + Client/game_sa/CFxSystemBPSA.h | 3 +- Client/game_sa/CFxSystemSA.cpp | 196 ++++++++++++++++++ Client/game_sa/CFxSystemSA.h | 47 ++++- Client/game_sa/CVehicleSA.cpp | 40 ++++ Client/game_sa/CVehicleSA.h | 5 + .../mods/deathmatch/logic/CClientVehicle.cpp | 17 ++ Client/mods/deathmatch/logic/CClientVehicle.h | 8 +- .../logic/CStaticFunctionDefinitions.cpp | 20 ++ .../logic/CStaticFunctionDefinitions.h | 4 + .../logic/luadefs/CLuaVehicleDefs.cpp | 77 +++++++ .../logic/luadefs/CLuaVehicleDefs.h | 3 + .../deathmatch/logic/rpc/CVehicleRPCs.cpp | 24 +++ .../mods/deathmatch/logic/rpc/CVehicleRPCs.h | 1 + Client/sdk/game/CVehicle.h | 5 + .../logic/CStaticFunctionDefinitions.cpp | 30 +++ .../logic/CStaticFunctionDefinitions.h | 2 + Server/mods/deathmatch/logic/CVehicle.cpp | 1 + Server/mods/deathmatch/logic/CVehicle.h | 6 + .../logic/luadefs/CLuaVehicleDefs.cpp | 80 +++++++ .../logic/luadefs/CLuaVehicleDefs.h | 3 + Shared/sdk/net/rpc_enums.h | 2 + 23 files changed, 584 insertions(+), 8 deletions(-) diff --git a/Client/game_sa/CFxManagerSA.cpp b/Client/game_sa/CFxManagerSA.cpp index d7a23380296..3c3cb545a64 100644 --- a/Client/game_sa/CFxManagerSA.cpp +++ b/Client/game_sa/CFxManagerSA.cpp @@ -71,6 +71,8 @@ void CFxManagerSA::OnFxSystemSAInterfaceDestroyed(CFxSystemSAInterface* pFxSyste CFxSystemSA* pFxSystemSA = GetFxSystem(pFxSystemSAInterface); if (pFxSystemSA) delete pFxSystemSA; + + MapRemove(m_NitroSystemMap, pFxSystemSAInterface); } CFxSystemBPSAInterface* CFxManagerSA::GetFxSystemBlueprintByName(SString sName) @@ -105,3 +107,14 @@ CFxSystemSA* CFxManagerSA::GetFxSystem(CFxSystemSAInterface* pFxSystemSAInterfac { return MapFindRef(m_FxInterfaceMap, pFxSystemSAInterface); } + +void CFxManagerSA::RegisterNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface, CVehicle* pVehicle) +{ + if (pFxSystemSAInterface) + MapSet(m_NitroSystemMap, pFxSystemSAInterface, pVehicle); +} + +CVehicle* CFxManagerSA::GetVehicleFromNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface) +{ + return MapFindRef(m_NitroSystemMap, pFxSystemSAInterface); +} diff --git a/Client/game_sa/CFxManagerSA.h b/Client/game_sa/CFxManagerSA.h index c1f72389143..5602a52171d 100644 --- a/Client/game_sa/CFxManagerSA.h +++ b/Client/game_sa/CFxManagerSA.h @@ -20,6 +20,7 @@ class CFxSystemBPSAInterface; class CFxSystemSAInterface; class CFxEmitterSAInterface; class CFxSystemSA; +class CVehicle; class CFxMemoryPoolSAInterface { @@ -74,7 +75,11 @@ class CFxManagerSA : public CFxManager void AddToList(CFxSystemSAInterface* pFxSystemSAInterface, CFxSystemSA* pFxSystemSA); void RemoveFromList(CFxSystemSA* pFxSystemSA); + void RegisterNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface, CVehicle* pVehicle); + CVehicle* GetVehicleFromNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface); + private: CFxManagerSAInterface* m_pInterface; CFastHashMap m_FxInterfaceMap; + CFastHashMap m_NitroSystemMap; }; diff --git a/Client/game_sa/CFxSystemBPSA.h b/Client/game_sa/CFxSystemBPSA.h index 5283dfacfbd..2f6103e03da 100644 --- a/Client/game_sa/CFxSystemBPSA.h +++ b/Client/game_sa/CFxSystemBPSA.h @@ -21,7 +21,8 @@ class CFxSystemBPSAInterface CFxSystemBPSAInterface* pNext; // 0x04 // Actual members - char* szNameHash; // 0x08 + // CRC-32 (no final XOR) of the lowercased blueprint name, e.g. "nitro" - NOT a string pointer + uint32_t uiNameHash; // 0x08 float fLength; // 0x0C float fLoopIntervalMinimum; // 0x10 float fLoopIntervalLength; // 0x14 diff --git a/Client/game_sa/CFxSystemSA.cpp b/Client/game_sa/CFxSystemSA.cpp index 78c3eaed5b0..0f0229e5b2d 100644 --- a/Client/game_sa/CFxSystemSA.cpp +++ b/Client/game_sa/CFxSystemSA.cpp @@ -10,13 +10,205 @@ *****************************************************************************/ #include "StdInc.h" +#include #include +#include #include "CFxSystemBPSA.h" #include "CFxSystemSA.h" #include "CGameSA.h" extern CGameSA* pGame; +// CRC-32/JAMCRC of the uppercased blueprint name "NITRO" (see CKeyGen::GetUppercaseKey) +static constexpr uint32_t FX_BLUEPRINT_HASH_NITRO = 0x3D591CC6; + +// One animated colour block ("FX_INFO_COLOUR_DATA"/"FX_INFO_COLOURBRIGHT_DATA"/ +// "FX_INFO_COLOURRANGE_DATA") of the "nitro" blueprint's R/G/B(/Range) channels, plus their +// original (unmodified) keyframe values so a per-vehicle tint can be re-derived from scratch +// every time a different vehicle's nitro particles are about to be rendered. +struct SNitroColorBlock +{ + uint8_t nNumKeyframes; + uint8_t nStride; // 1 = single value per component, 2 = value + randomisation range per component + + // Indexed by [component (0=R, 1=G, 2=B)][stride slot (0=value, 1=range)]; null if the + // blueprint has no such channel. + uint16_t* pChannelValues[3][2]{}; + std::vector originalValues[3][2]; +}; + +static std::vector ms_NitroColorBlocks; +static bool ms_bNitroColorChannelsCached = false; +static CFxSystemSAInterface* ms_pLastNitroFxSystem = nullptr; +static std::optional ms_LastAppliedNitroColor; + +////////////////////////////////////////////////////////////////////////////////////////// +// +// CacheNitroColorChannels +// +// Collects every R/G/B(/Range) keyframe channel of the "nitro" blueprint's colour data, so +// that ApplyNitroColor can later re-tint or restore them on a per-vehicle basis without +// losing precision. +// +////////////////////////////////////////////////////////////////////////////////////////// +static void CacheNitroColorChannels(CFxSystemBPSAInterface* pBlueprint) +{ + if (ms_bNitroColorChannelsCached) + return; + + for (uint8_t i = 0; i < (uint8_t)pBlueprint->cNumOfPrims; ++i) + { + CFxEmitterBPSAInterface* pEmitterBP = (CFxEmitterBPSAInterface*)pBlueprint->pPrims[i]; + for (uint32_t j = 0; j < pEmitterBP->m_infoManager.m_nNumInfos; ++j) + { + FxInfoSAInterface* pInfo = pEmitterBP->m_infoManager.m_pInfos[j]; + + uint8_t stride = 0; + if (pInfo->nType == FX_INFO_COLOUR_DATA || pInfo->nType == FX_INFO_COLOURBRIGHT_DATA) + stride = 1; + else if (pInfo->nType == FX_INFO_COLOURRANGE_DATA) + stride = 2; + + if (stride == 0) + continue; + + // Each colour component spans `stride` consecutive channels (e.g. ColourRange + // stores R, RRange, G, GRange, B, BRange). + FxInfoColorSAInterface* pColorInfo = (FxInfoColorSAInterface*)pInfo; + + SNitroColorBlock block; + block.nNumKeyframes = (uint8_t)pColorInfo->nNumKeyframes; + block.nStride = stride; + + for (int component = 0; component < 3; ++component) + { + for (int s = 0; s < stride; ++s) + { + int channel = component * stride + s; + if (channel >= pColorInfo->nNumChannels) + break; + + uint16_t* pValues = pColorInfo->ppChannelValues[channel]; + block.pChannelValues[component][s] = pValues; + block.originalValues[component][s].assign(pValues, pValues + block.nNumKeyframes); + } + } + ms_NitroColorBlocks.push_back(std::move(block)); + } + } + ms_bNitroColorChannelsCached = true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// ApplyNitroColor +// +// If `color` has no value, restores every cached "nitro" colour channel to its original +// keyframe values (i.e. the game's default cyan effect). +// +// Otherwise, re-tints every cached channel using `color`. Rather than multiplying the +// original (cyan) values by the requested colour - which would mix the two hues together - +// the brightest of a block's original R/G/B values at each keyframe is used as a brightness +// envelope, and every channel is recoloured from scratch using that envelope. This allows +// any colour, including white, to fully replace the original hue. +// +// Always derived from the cached originals, so it can be called repeatedly for different +// vehicles without ever needing to restore the blueprint first. +// +////////////////////////////////////////////////////////////////////////////////////////// +static void ApplyNitroColor(const std::optional& color) +{ + for (auto& block : ms_NitroColorBlocks) + { + if (!color.has_value()) + { + for (int component = 0; component < 3; ++component) + { + for (int s = 0; s < block.nStride; ++s) + { + if (uint16_t* pValues = block.pChannelValues[component][s]) + std::copy(block.originalValues[component][s].begin(), block.originalValues[component][s].end(), pValues); + } + } + continue; + } + + const uint32_t components[3] = {color->R, color->G, color->B}; + + for (uint8_t keyframe = 0; keyframe < block.nNumKeyframes; ++keyframe) + { + uint16_t brightness = 0; + for (int component = 0; component < 3; ++component) + brightness = std::max(brightness, block.originalValues[component][0][keyframe]); + + for (int component = 0; component < 3; ++component) + { + for (int s = 0; s < block.nStride; ++s) + { + uint16_t* pValues = block.pChannelValues[component][s]; + if (!pValues) + continue; + + const uint16_t source = (s == 0) ? brightness : block.originalValues[component][s][keyframe]; + pValues[keyframe] = (uint16_t)(source * components[component] / 255); + } + } + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// FxEmitterBP_c::Render +// +// Called once per particle while the "nitro" blueprint's particles are rendered. +// Re-tints the shared blueprint's colour data with the owning vehicle's nitro colour +// just before its colour keyframes are sampled. +// +////////////////////////////////////////////////////////////////////////////////////////// +__declspec(noinline) static void OnFxParticleProcessRenderInfo(CFxParticleSAInterface* pParticle) +{ + CFxSystemSAInterface* pFxSystem = pParticle->pSystem; + if (!pFxSystem || !pFxSystem->pBlueprint || pFxSystem->pBlueprint->uiNameHash != FX_BLUEPRINT_HASH_NITRO) + return; + + CVehicle* pVehicle = pGame->GetFxManagerSA()->GetVehicleFromNitroSystem(pFxSystem); + const std::optional color = pVehicle ? pVehicle->GetNitroColor() : std::nullopt; + + if (pFxSystem == ms_pLastNitroFxSystem && color == ms_LastAppliedNitroColor) + return; + + CacheNitroColorChannels(pFxSystem->pBlueprint); + ApplyNitroColor(color); + + ms_pLastNitroFxSystem = pFxSystem; + ms_LastAppliedNitroColor = color; +} + +// Hook info +// Redirects the CALL that initialises a particle's default render colour (at the start of +// FxEmitterBP_c::Render's per-particle loop) through our hook first. +// At this call site: EBX = current particle (CFxParticleSAInterface*, callee-saved). +#define HOOKPOS_FxEmitterBP_c_Render_ProcessRenderInfo 0x4A2E31 +DWORD ORIGINAL_FxEmitterBP_c_Render_ProcessRenderInfo = 0x4A4A80; +static void __declspec(naked) HOOK_FxEmitterBP_c_Render_ProcessRenderInfo() +{ + MTA_VERIFY_HOOK_LOCAL_SIZE; + + // clang-format off + __asm + { + pushad + push ebx + call OnFxParticleProcessRenderInfo + add esp, 4 + popad + + jmp ORIGINAL_FxEmitterBP_c_Render_ProcessRenderInfo + } + // clang-format on +} + // Variables used in the hooks static CFxSystemSAInterface* ms_pUsingFxSystemSAInterface = NULL; static float ms_fUsingDrawDistance = 0; @@ -278,6 +470,10 @@ void CFxSystemSA::StaticSetHooks() EZHookInstall(FxSystem_c_Update_MidA); EZHookInstall(FxSystem_c_Update_MidB); + // Redirect the per-particle render info CALL in FxEmitterBP_c::Render so we can apply + // per-vehicle nitro colours just before they are sampled + HookInstallCall(HOOKPOS_FxEmitterBP_c_Render_ProcessRenderInfo, (DWORD)HOOK_FxEmitterBP_c_Render_ProcessRenderInfo); + // Redirect these constants so we can change them MemPut(VAR_FxSystemUpdateCullDistMultiplier, &ms_fFxSystemUpdateCullDistMultiplier); MemPut(VAR_FxCreateParticleCullDistMultiplierA, &ms_fFxCreateParticleCullDistMultiplier); diff --git a/Client/game_sa/CFxSystemSA.h b/Client/game_sa/CFxSystemSA.h index 5a4bd2685ea..596f10e1d55 100644 --- a/Client/game_sa/CFxSystemSA.h +++ b/Client/game_sa/CFxSystemSA.h @@ -33,7 +33,35 @@ class CAEFireAudioEntitySAInterface }; static_assert(sizeof(CAEFireAudioEntitySAInterface) == 0x88, "Invalid size for CAEFireAudioEntitySAInterface"); -class FxInfoSAInterface; +// Internal SA Name: FxInfo_c +// nType is one of the eFxInfoType values (e.g. FX_INFO_COLOUR_DATA) +class FxInfoSAInterface +{ +public: + void* vmt; // 0x00 + uint16_t nType; // 0x04 +}; + +constexpr uint16_t FX_INFO_COLOUR_DATA = 0x4001; +constexpr uint16_t FX_INFO_COLOURBRIGHT_DATA = 0x4400; +constexpr uint16_t FX_INFO_COLOURRANGE_DATA = 0x4100; + +// Internal SA Name: FxInfoColour_c (FxInfo_c + an embedded FxInterpInfoU255_c) +// Holds ppChannelValues[channel][keyframe] for nNumChannels channels (4 = R, G, B, A) of +// nNumKeyframes keyframes each. Values are fixed-point, scaled by 1/256. +class FxInfoColorSAInterface : public FxInfoSAInterface +{ +public: + void* pInterpInfoVmt; // 0x08 + bool bLooped; // 0x0C + int8_t nNumKeyframes; // 0x0D + int8_t nNumChannels; // 0x0E + char pad; // 0x0F + uint16_t* pTimes; // 0x10 + uint16_t** ppChannelValues; // 0x14 +}; +static_assert(sizeof(FxInfoColorSAInterface) == 0x18, "Invalid size for FxInfoColorSAInterface"); + class CFxSystemBPSAInterface; class CFxSystemSAInterface // Internal SA Name: FxSystem_c @@ -103,10 +131,10 @@ class CFxSystemSA : public CFxSystem class FxInfoManagerSAInterface { public: - uint32_t m_nNumInfos; // 0x00 - FxInfoSAInterface* m_pInfos; // 0x04 - uint8_t m_nFirstMovementInfo; // 0x08 - uint8_t m_nFirstRenderInfo; // 0x09 + uint32_t m_nNumInfos; // 0x00 + FxInfoSAInterface** m_pInfos; // 0x04 + uint8_t m_nFirstMovementInfo; // 0x08 + uint8_t m_nFirstRenderInfo; // 0x09 }; static_assert(sizeof(FxInfoManagerSAInterface) == 0xC, "Invalid size for FxInfoManagerSAInterface"); @@ -144,3 +172,12 @@ class CFxEmitterSAInterface CFxSystemSAInterface* pOwner; // 0x08 // TODO the rest }; + +// Internal SA Name: FxParticle_c +// A single particle instance, owned by an FxSystem_c and rendered via its FxEmitterBP_c. +class CFxParticleSAInterface +{ +public: + uint8_t pad[0x28]; // 0x00 + CFxSystemSAInterface* pSystem; // 0x28 +}; diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index 2b5f2c3e5ec..51fc9ba8db7 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -2039,6 +2039,43 @@ bool CVehicleSA::SetOnFire(bool onFire) return true; } +// CAutomobile::DoNitroEffect, hooked at a point that every code path through the function +// passes through, right after the first exhaust's nitro FX system has been created/updated +// (ESI still holds the CAutomobileSAInterface this pointer). +#define HOOKPOS_CAutomobile_DoNitroEffect_Mid 0x6A3D81 +#define HOOKSIZE_CAutomobile_DoNitroEffect_Mid 6 +static constexpr DWORD RETURN_CAutomobile_DoNitroEffect_Mid = HOOKPOS_CAutomobile_DoNitroEffect_Mid + HOOKSIZE_CAutomobile_DoNitroEffect_Mid; +static void __declspec(naked) HOOK_CAutomobile_DoNitroEffect_Mid() +{ + MTA_VERIFY_HOOK_LOCAL_SIZE; + + // clang-format off + __asm + { + pushad + push esi + call CVehicleSA::OnDoNitroEffectMid + add esp, 4 + popad + + // Overwritten instruction + mov eax, dword ptr [esi + 0x384] + + jmp RETURN_CAutomobile_DoNitroEffect_Mid + } + // clang-format on +} + +void CVehicleSA::OnDoNitroEffectMid(CVehicleSAInterface* pInterface) +{ + if (pInterface && pInterface->m_pVehicle) + { + auto pAutomobile = (CAutomobileSAInterface*)pInterface; + pGame->GetFxManagerSA()->RegisterNitroSystem(pAutomobile->pNitroParticle[0], pInterface->m_pVehicle); + pGame->GetFxManagerSA()->RegisterNitroSystem(pAutomobile->pNitroParticle[1], pInterface->m_pVehicle); + } +} + void CVehicleSA::StaticSetHooks() { // Setup vehicle sun glare hook @@ -2047,6 +2084,9 @@ void CVehicleSA::StaticSetHooks() // Setup hooks to handle setVehicleRotorState function HookInstall(FUNC_CHeli_ProcessFlyingCarStuff, (DWORD)HOOK_CHeli_ProcessFlyingCarStuff, 5); HookInstall(FUNC_CPlane_ProcessFlyingCarStuff, (DWORD)HOOK_CPlane_ProcessFlyingCarStuff, 5); + + // Setup hook to capture nitro effects + HookInstall(HOOKPOS_CAutomobile_DoNitroEffect_Mid, (DWORD)HOOK_CAutomobile_DoNitroEffect_Mid, HOOKSIZE_CAutomobile_DoNitroEffect_Mid); } void CVehicleSA::SetVehiclesSunGlareEnabled(bool bEnabled) diff --git a/Client/game_sa/CVehicleSA.h b/Client/game_sa/CVehicleSA.h index d9e8700d00a..9b106210995 100644 --- a/Client/game_sa/CVehicleSA.h +++ b/Client/game_sa/CVehicleSA.h @@ -429,6 +429,7 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA unsigned char m_ucAlpha{255}; CVector m_vecGravity{0.0f, 0.0f, -1.0f}; SharedUtil::SColor m_HeadLightColor{SharedUtil::SColorRGBA{255, 255, 255, 255}}; + std::optional m_NitroColor; // No value = original (unmodified) nitro colours SharedUtil::SColor m_RGBColors[4]; SharedUtil::SColor m_RGBColorsFixed[4]; CDoorSA m_doors[6]; @@ -637,6 +638,9 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA SharedUtil::SColor GetHeadLightColor() { return m_HeadLightColor; } void SetHeadLightColor(const SharedUtil::SColor color) { m_HeadLightColor = color; } + std::optional GetNitroColor() { return m_NitroColor; } + void SetNitroColor(const std::optional color) { m_NitroColor = color; } + bool SpawnFlyingComponent(const eCarNodes& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime = -1); void SetWheelVisibility(eWheelPosition wheel, bool bVisible); CVector GetWheelPosition(eWheelPosition wheel); @@ -714,6 +718,7 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA bool SetOnFire(bool onFire) override; static void StaticSetHooks(); + static void OnDoNitroEffectMid(CVehicleSAInterface* pInterface); static void SetVehiclesSunGlareEnabled(bool bEnabled); static bool GetVehiclesSunGlareEnabled(); diff --git a/Client/mods/deathmatch/logic/CClientVehicle.cpp b/Client/mods/deathmatch/logic/CClientVehicle.cpp index b26486838b6..38ccfe08968 100644 --- a/Client/mods/deathmatch/logic/CClientVehicle.cpp +++ b/Client/mods/deathmatch/logic/CClientVehicle.cpp @@ -2682,6 +2682,7 @@ void CClientVehicle::Create() m_pVehicle->SetSmokeTrailEnabled(m_bSmokeTrail); m_pVehicle->SetGravity(&m_vecGravity); m_pVehicle->SetHeadLightColor(m_HeadLightColor); + m_pVehicle->SetNitroColor(m_NitroColor); m_pVehicle->SetRadioStatus(0); if (IsNitroInstalled()) @@ -4111,6 +4112,22 @@ void CClientVehicle::SetHeadLightColor(const SColor color) m_HeadLightColor = color; } +std::optional CClientVehicle::GetNitroColor() +{ + if (m_pVehicle) + return m_pVehicle->GetNitroColor(); + + return m_NitroColor; +} + +void CClientVehicle::SetNitroColor(const std::optional color) +{ + if (m_pVehicle) + m_pVehicle->SetNitroColor(color); + + m_NitroColor = color; +} + // // Below here is basically awful. // But there you go. diff --git a/Client/mods/deathmatch/logic/CClientVehicle.h b/Client/mods/deathmatch/logic/CClientVehicle.h index 2c3c75e89c2..de1d223b5fe 100644 --- a/Client/mods/deathmatch/logic/CClientVehicle.h +++ b/Client/mods/deathmatch/logic/CClientVehicle.h @@ -456,6 +456,9 @@ class CClientVehicle : public CClientStreamElement SColor GetHeadLightColor(); void SetHeadLightColor(const SColor color); + std::optional GetNitroColor(); + void SetNitroColor(const std::optional color); + int GetCurrentGear(); bool IsEnterable(bool localEntity = false); @@ -727,8 +730,9 @@ class CClientVehicle : public CClientStreamElement bool m_bNitroActivated; - CVector m_vecGravity; - SColor m_HeadLightColor; + CVector m_vecGravity; + SColor m_HeadLightColor; + std::optional m_NitroColor; bool m_bHasCustomHandling; diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index dcd11a4e984..3948efbafe4 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2967,6 +2967,12 @@ bool CStaticFunctionDefinitions::GetVehicleHeadLightColor(CClientVehicle& Vehicl return true; } +bool CStaticFunctionDefinitions::GetVehicleNitroColor(CClientVehicle& Vehicle, std::optional& outColor) +{ + outColor = Vehicle.GetNitroColor(); + return true; +} + bool CStaticFunctionDefinitions::GetVehicleCurrentGear(CClientVehicle& Vehicle, unsigned short& currentGear) { currentGear = static_cast(Vehicle.GetCurrentGear()); @@ -3537,6 +3543,20 @@ bool CStaticFunctionDefinitions::SetVehicleHeadLightColor(CClientEntity& Entity, return false; } +bool CStaticFunctionDefinitions::SetVehicleNitroColor(CClientEntity& Entity, const std::optional color) +{ + RUN_CHILDREN(SetVehicleNitroColor(**iter, color)) + + if (IS_VEHICLE(&Entity)) + { + CClientVehicle& Vehicle = static_cast(Entity); + Vehicle.SetNitroColor(color); + return true; + } + + return false; +} + bool CStaticFunctionDefinitions::GetVehicleEngineState(CClientVehicle& Vehicle, bool& bState) { bState = Vehicle.IsEngineOn(); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h index d1e019758e9..f0fb7eca812 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -13,6 +13,8 @@ class CStaticFunctionDefinitions; #pragma once +#include + #include #include @@ -218,6 +220,7 @@ class CStaticFunctionDefinitions static bool GetTrainPosition(CClientVehicle& Vehicle, float& fPosition); static bool IsTrainChainEngine(CClientVehicle& Vehicle, bool& bChainEngine); static bool GetVehicleHeadLightColor(CClientVehicle& Vehicle, SColor& outColor); + static bool GetVehicleNitroColor(CClientVehicle& Vehicle, std::optional& outColor); static bool GetVehicleCurrentGear(CClientVehicle& Vehicle, unsigned short& currentGear); static bool GetVehicleVariant(CClientVehicle* pVehicle, unsigned char& ucVariant, unsigned char& ucVariant2); static bool IsVehicleNitroRecharging(CClientVehicle& Vehicle, bool& bRecharging); @@ -267,6 +270,7 @@ class CStaticFunctionDefinitions static bool SetTrainSpeed(CClientVehicle& Vehicle, float fSpeed); static bool SetTrainPosition(CClientVehicle& Vehicle, float fPosition); static bool SetVehicleHeadLightColor(CClientEntity& Vehicle, const SColor color); + static bool SetVehicleNitroColor(CClientEntity& Vehicle, const std::optional color); static bool SetVehicleDoorOpenRatio(CClientEntity& Vehicle, unsigned char ucDoor, float fRatio, unsigned long ulTime = 0); static bool SetVehicleSirens(CClientVehicle& pVehicle, unsigned char ucSirenID, SSirenInfo tSirenInfo); static bool SetVehicleNitroActivated(CClientEntity& Entity, bool bActivated); diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index 80137594c94..a4bae9524b9 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -72,6 +72,7 @@ void CLuaVehicleDefs::LoadFunctions() {"isVehicleBlown", ArgumentParserWarn}, {"isVehicleTaxiLightOn", IsVehicleTaxiLightOn}, {"getVehicleHeadLightColor", GetVehicleHeadLightColor}, + {"getVehicleNitroColor", GetVehicleNitroColor}, {"getVehicleCurrentGear", GetVehicleCurrentGear}, {"getVehicleHandling", GetVehicleHandling}, {"getOriginalHandling", GetOriginalHandling}, @@ -141,6 +142,8 @@ void CLuaVehicleDefs::LoadFunctions() {"setVehicleTaxiLightOn", SetVehicleTaxiLightOn}, {"setVehicleGravity", SetVehicleGravity}, {"setVehicleHeadLightColor", SetVehicleHeadLightColor}, + {"setVehicleNitroColor", SetVehicleNitroColor}, + {"resetVehicleNitroColor", ResetVehicleNitroColor}, {"setVehicleTurretPosition", SetVehicleTurretPosition}, {"setVehicleDoorOpenRatio", SetVehicleDoorOpenRatio}, {"setVehicleHandling", SetVehicleHandling}, @@ -247,6 +250,7 @@ void CLuaVehicleDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "isTaxiLightOn", "isVehicleTaxiLightOn"); lua_classfunction(luaVM, "getComponents", "getVehicleComponents"); lua_classfunction(luaVM, "getHeadLightColor", "getVehicleHeadLightColor"); + lua_classfunction(luaVM, "getNitroColor", "getVehicleNitroColor"); lua_classfunction(luaVM, "getColor", "getVehicleColor"); lua_classfunction(luaVM, "getGravity", OOP_GetVehicleGravity); lua_classfunction(luaVM, "getSirenParams", "getVehicleSirenParams"); @@ -309,6 +313,8 @@ void CLuaVehicleDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setDerailable", "setTrainDerailable"); lua_classfunction(luaVM, "setDerailed", "setTrainDerailed"); lua_classfunction(luaVM, "setHeadLightColor", "setVehicleHeadLightColor"); + lua_classfunction(luaVM, "setNitroColor", "setVehicleNitroColor"); + lua_classfunction(luaVM, "resetNitroColor", "resetVehicleNitroColor"); lua_classfunction(luaVM, "setColor", "setVehicleColor"); lua_classfunction(luaVM, "setPlateText", "setVehiclePlateText"); lua_classfunction(luaVM, "setGravity", "setVehicleGravity"); @@ -1632,6 +1638,30 @@ int CLuaVehicleDefs::GetVehicleHeadLightColor(lua_State* luaVM) return 1; } +int CLuaVehicleDefs::GetVehicleNitroColor(lua_State* luaVM) +{ + CClientVehicle* pVehicle = NULL; + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pVehicle); + + if (!argStream.HasErrors()) + { + std::optional color; + if (CStaticFunctionDefinitions::GetVehicleNitroColor(*pVehicle, color) && color.has_value()) + { + lua_pushnumber(luaVM, color->R); + lua_pushnumber(luaVM, color->G); + lua_pushnumber(luaVM, color->B); + return 3; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + int CLuaVehicleDefs::GetVehicleCurrentGear(lua_State* luaVM) { CClientVehicle* pVehicle = NULL; @@ -2496,6 +2526,53 @@ int CLuaVehicleDefs::SetVehicleHeadLightColor(lua_State* luaVM) return 1; } +int CLuaVehicleDefs::SetVehicleNitroColor(lua_State* luaVM) +{ + CClientEntity* pEntity = NULL; + SColor color; + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pEntity); + argStream.ReadNumber(color.R); + argStream.ReadNumber(color.G); + argStream.ReadNumber(color.B); + color.A = 255; + + if (!argStream.HasErrors()) + { + if (CStaticFunctionDefinitions::SetVehicleNitroColor(*pEntity, color)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + +int CLuaVehicleDefs::ResetVehicleNitroColor(lua_State* luaVM) +{ + CClientEntity* pEntity = NULL; + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pEntity); + + if (!argStream.HasErrors()) + { + if (CStaticFunctionDefinitions::SetVehicleNitroColor(*pEntity, std::nullopt)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + int CLuaVehicleDefs::SetVehicleTurretPosition(lua_State* luaVM) { CClientVehicle* pVehicle = NULL; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h index 945efed4c47..3d828b0f925 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h @@ -73,6 +73,7 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE_OOP(GetVehicleGravity); static bool IsVehicleBlown(CClientVehicle* vehicle); LUA_DECLARE(GetVehicleHeadLightColor); + LUA_DECLARE(GetVehicleNitroColor); LUA_DECLARE(GetVehicleCurrentGear); LUA_DECLARE(GetVehicleHandling); LUA_DECLARE(GetOriginalHandling); @@ -130,6 +131,8 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE(SetTrainPosition); LUA_DECLARE(SetVehicleGravity); LUA_DECLARE(SetVehicleHeadLightColor); + LUA_DECLARE(SetVehicleNitroColor); + LUA_DECLARE(ResetVehicleNitroColor); LUA_DECLARE(SetVehicleTurretPosition); LUA_DECLARE(SetVehicleDoorOpenRatio); LUA_DECLARE(SetVehicleHandling); diff --git a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp index d78b978b928..d324713f360 100644 --- a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp +++ b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp @@ -45,6 +45,7 @@ void CVehicleRPCs::LoadFunctions() AddHandler(SET_TRAIN_POSITION, SetTrainPosition, "SetTrainPosition"); AddHandler(SET_TAXI_LIGHT_ON, SetVehicleTaxiLightOn, "SetVehicleTaxiLightOn"); AddHandler(SET_VEHICLE_HEADLIGHT_COLOR, SetVehicleHeadLightColor, "SetVehicleHeadLightColor"); + AddHandler(SET_VEHICLE_NITRO_COLOR, SetVehicleNitroColor, "SetVehicleNitroColor"); AddHandler(SET_VEHICLE_TURRET_POSITION, SetVehicleTurretPosition, "SetVehicleTurretPosition"); AddHandler(SET_VEHICLE_DOOR_OPEN_RATIO, SetVehicleDoorOpenRatio, "SetVehicleDoorOpenRatio"); AddHandler(SET_VEHICLE_VARIANT, SetVehicleVariant, "SetVehicleVariant"); @@ -561,6 +562,29 @@ void CVehicleRPCs::SetVehicleHeadLightColor(CClientEntity* pSource, NetBitStream } } +void CVehicleRPCs::SetVehicleNitroColor(CClientEntity* pSource, NetBitStreamInterface& bitStream) +{ + bool bHasColor; + if (bitStream.ReadBit(bHasColor)) + { + std::optional color; + if (bHasColor) + { + SColorRGBA rgba(255, 255, 255, 255); + if (!bitStream.Read(rgba.R) || !bitStream.Read(rgba.G) || !bitStream.Read(rgba.B)) + return; + + color = rgba; + } + + CClientVehicle* pVehicle = m_pVehicleManager->Get(pSource->GetID()); + if (pVehicle) + { + pVehicle->SetNitroColor(color); + } + } +} + void CVehicleRPCs::SetVehicleTurretPosition(CClientEntity* pSource, NetBitStreamInterface& bitStream) { float fHorizontal; diff --git a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.h b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.h index c1649abd4bf..805be9f6f87 100644 --- a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.h +++ b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.h @@ -50,6 +50,7 @@ class CVehicleRPCs : public CRPCFunctions DECLARE_ELEMENT_RPC(SetTrainPosition); DECLARE_ELEMENT_RPC(SetVehicleTaxiLightOn); DECLARE_ELEMENT_RPC(SetVehicleHeadLightColor); + DECLARE_ELEMENT_RPC(SetVehicleNitroColor); DECLARE_ELEMENT_RPC(SetVehicleTurretPosition); DECLARE_ELEMENT_RPC(SetVehicleDoorOpenRatio); DECLARE_ELEMENT_RPC(SetVehicleVariant); diff --git a/Client/sdk/game/CVehicle.h b/Client/sdk/game/CVehicle.h index 778dbf8a37d..b2561e96aa8 100644 --- a/Client/sdk/game/CVehicle.h +++ b/Client/sdk/game/CVehicle.h @@ -13,6 +13,7 @@ #include #include +#include #include #include "Common.h" #include "CPhysical.h" @@ -278,6 +279,10 @@ class CVehicle : public virtual CPhysical virtual SColor GetHeadLightColor() = 0; virtual void SetHeadLightColor(const SColor color) = 0; + // No value means the nitro effect uses its original (unmodified) colours. + virtual std::optional GetNitroColor() = 0; + virtual void SetNitroColor(const std::optional color) = 0; + virtual bool SpawnFlyingComponent(const eCarNodes& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime = -1) = 0; virtual void SetWheelVisibility(eWheelPosition wheel, bool bVisible) = 0; virtual CVector GetWheelPosition(eWheelPosition wheel) = 0; diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 7bcd5afe321..99398cd0c82 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -5644,6 +5644,14 @@ bool CStaticFunctionDefinitions::GetVehicleHeadLightColor(CVehicle* pVehicle, SC return true; } +bool CStaticFunctionDefinitions::GetVehicleNitroColor(CVehicle* pVehicle, std::optional& outColor) +{ + assert(pVehicle); + + outColor = pVehicle->GetNitroColor(); + return true; +} + bool CStaticFunctionDefinitions::GetVehicleDoorOpenRatio(CVehicle* pVehicle, unsigned char ucDoor, float& fRatio) { if (ucDoor <= 5 && pVehicle != NULL) @@ -7490,6 +7498,28 @@ bool CStaticFunctionDefinitions::SetVehicleHeadLightColor(CVehicle* pVehicle, co return true; } +bool CStaticFunctionDefinitions::SetVehicleNitroColor(CVehicle* pVehicle, const std::optional color) +{ + assert(pVehicle); + + if (color != pVehicle->GetNitroColor()) + { + pVehicle->SetNitroColor(color); + + CBitStream BitStream; + BitStream.pBitStream->WriteBit(color.has_value()); + if (color.has_value()) + { + BitStream.pBitStream->Write(color->R); + BitStream.pBitStream->Write(color->G); + BitStream.pBitStream->Write(color->B); + } + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pVehicle, SET_VEHICLE_NITRO_COLOR, *BitStream.pBitStream)); + } + + return true; +} + /*bool CStaticFunctionDefinitions::SetVehicleHandling ( CVehicle* pVehicle, bool bValue ) { assert ( pVehicle ); diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h index 911710b06b4..5530dfd4651 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -285,6 +285,7 @@ class CStaticFunctionDefinitions static bool GetTrainSpeed(CVehicle* pVehicle, float& fSpeed); static bool GetTrainPosition(CVehicle* pVehicle, float& fPosition); static bool GetVehicleHeadLightColor(CVehicle* pVehicle, SColor& outColor); + static bool GetVehicleNitroColor(CVehicle* pVehicle, std::optional& outColor); static bool GetVehicleDoorOpenRatio(CVehicle* pVehicle, unsigned char ucDoor, float& fRatio); static bool GetVehicleHandling(CVehicle* pVehicle, eHandlingProperty eProperty, float& fValue); @@ -347,6 +348,7 @@ class CStaticFunctionDefinitions static bool SetTrainSpeed(CVehicle* pVehicle, float fSpeed); static bool SetTrainPosition(CVehicle* pVehicle, float fPosition); static bool SetVehicleHeadLightColor(CVehicle* pVehicle, const SColor color); + static bool SetVehicleNitroColor(CVehicle* pVehicle, const std::optional color); static bool SetVehicleTurretPosition(CVehicle* pVehicle, float fHorizontal, float fVertical); static bool SetVehicleDoorOpenRatio(CElement* pElement, unsigned char ucDoor, float fRatio, unsigned long ulTime = 0); static bool SetVehiclePlateText(CElement* pElement, const SString& strPlateText); diff --git a/Server/mods/deathmatch/logic/CVehicle.cpp b/Server/mods/deathmatch/logic/CVehicle.cpp index 7d263140e05..9ddc11ec502 100644 --- a/Server/mods/deathmatch/logic/CVehicle.cpp +++ b/Server/mods/deathmatch/logic/CVehicle.cpp @@ -195,6 +195,7 @@ CElement* CVehicle::Clone(bool* bAddEntity, CResource* pResource) pTemp->SetBlowState(m_blowState); pTemp->SetHealth(GetHealth()); pTemp->SetColor(GetColor()); + pTemp->SetNitroColor(GetNitroColor()); pTemp->SetUpgrades(GetUpgrades()); pTemp->m_ucDoorStates = m_ucDoorStates; pTemp->m_ucWheelStates = m_ucWheelStates; diff --git a/Server/mods/deathmatch/logic/CVehicle.h b/Server/mods/deathmatch/logic/CVehicle.h index 4430a5a41bf..496c59be5a5 100644 --- a/Server/mods/deathmatch/logic/CVehicle.h +++ b/Server/mods/deathmatch/logic/CVehicle.h @@ -13,6 +13,8 @@ class CVehicle; #pragma once +#include + #include "CCommon.h" #include "packets/CPacket.h" #include "CElement.h" @@ -348,6 +350,9 @@ class CVehicle final : public CElement SColor GetHeadLightColor() { return m_HeadLightColor; } void SetHeadLightColor(const SColor color) { m_HeadLightColor = color; } + std::optional GetNitroColor() { return m_NitroColor; } + void SetNitroColor(const std::optional color) { m_NitroColor = color; } + bool IsHeliSearchLightVisible() { return m_bHeliSearchLightVisible; } void SetHeliSearchLightVisible(bool bVisible) { m_bHeliSearchLightVisible = bVisible; } @@ -460,6 +465,7 @@ class CVehicle final : public CElement bool m_bInWater; CPed* m_pJackingPed; SColor m_HeadLightColor; + std::optional m_NitroColor; bool m_bHeliSearchLightVisible; // Train specific data diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index 08c8eb6838b..98555995209 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -69,6 +69,7 @@ void CLuaVehicleDefs::LoadFunctions() {"getTrainPosition", GetTrainPosition}, {"isVehicleBlown", ArgumentParserWarn}, {"getVehicleHeadLightColor", GetVehicleHeadLightColor}, + {"getVehicleNitroColor", GetVehicleNitroColor}, {"getVehicleDoorOpenRatio", GetVehicleDoorOpenRatio}, // Vehicle set funcs @@ -118,6 +119,8 @@ void CLuaVehicleDefs::LoadFunctions() {"setTrainSpeed", SetTrainSpeed}, {"setTrainPosition", SetTrainPosition}, {"setVehicleHeadLightColor", SetVehicleHeadLightColor}, + {"setVehicleNitroColor", SetVehicleNitroColor}, + {"resetVehicleNitroColor", ResetVehicleNitroColor}, {"setVehicleTurretPosition", SetVehicleTurretPosition}, {"setVehicleDoorOpenRatio", SetVehicleDoorOpenRatio}, {"setVehicleVariant", SetVehicleVariant}, @@ -186,6 +189,7 @@ void CLuaVehicleDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "getTrainTrack", "getTrainTrack"); lua_classfunction(luaVM, "getTrainPosition", "getTrainPosition"); lua_classfunction(luaVM, "getHeadLightColor", "getVehicleHeadLightColor"); + lua_classfunction(luaVM, "getNitroColor", "getVehicleNitroColor"); lua_classfunction(luaVM, "getColor", "getVehicleColor"); lua_classfunction(luaVM, "getCompatibleUpgrades", "getVehicleCompatibleUpgrades"); lua_classfunction(luaVM, "getController", "getVehicleController"); @@ -241,6 +245,8 @@ void CLuaVehicleDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setTurnVelocity", "setVehicleTurnVelocity"); lua_classfunction(luaVM, "setWheelStates", "setVehicleWheelStates"); lua_classfunction(luaVM, "setHeadLightColor", "setVehicleHeadLightColor"); + lua_classfunction(luaVM, "setNitroColor", "setVehicleNitroColor"); + lua_classfunction(luaVM, "resetNitroColor", "resetVehicleNitroColor"); lua_classfunction(luaVM, "setTaxiLightOn", "setVehicleTaxiLightOn"); lua_classfunction(luaVM, "setVariant", "setVehicleVariant"); lua_classfunction(luaVM, "setSirens", "setVehicleSirens"); @@ -2893,6 +2899,80 @@ int CLuaVehicleDefs::SetVehicleHeadLightColor(lua_State* luaVM) return 1; } +int CLuaVehicleDefs::SetVehicleNitroColor(lua_State* luaVM) +{ + CVehicle* pVehicle; + SColor color; + + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pVehicle); + argStream.ReadNumber(color.R); + argStream.ReadNumber(color.G); + argStream.ReadNumber(color.B); + color.A = 0xFF; + + if (!argStream.HasErrors()) + { + if (CStaticFunctionDefinitions::SetVehicleNitroColor(pVehicle, color)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + +int CLuaVehicleDefs::ResetVehicleNitroColor(lua_State* luaVM) +{ + CVehicle* pVehicle; + + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pVehicle); + + if (!argStream.HasErrors()) + { + if (CStaticFunctionDefinitions::SetVehicleNitroColor(pVehicle, std::nullopt)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + +int CLuaVehicleDefs::GetVehicleNitroColor(lua_State* luaVM) +{ + CVehicle* pVehicle; + + CScriptArgReader argStream(luaVM); + argStream.ReadUserData(pVehicle); + + if (!argStream.HasErrors()) + { + std::optional color; + if (CStaticFunctionDefinitions::GetVehicleNitroColor(pVehicle, color) && color.has_value()) + { + lua_pushnumber(luaVM, color->R); + lua_pushnumber(luaVM, color->G); + lua_pushnumber(luaVM, color->B); + return 3; + } + } + else + m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + + lua_pushboolean(luaVM, false); + return 1; +} + int CLuaVehicleDefs::SetVehicleTurretPosition(lua_State* luaVM) { CVehicle* pVehicle; diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h index 8fb2530b6a6..c11be1795e2 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h +++ b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h @@ -66,6 +66,7 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE(GetTrainPosition); static bool IsVehicleBlown(CVehicle* vehicle); LUA_DECLARE(GetVehicleHeadLightColor); + LUA_DECLARE(GetVehicleNitroColor); LUA_DECLARE(GetVehicleDoorOpenRatio); // Vehicle set functions @@ -116,6 +117,8 @@ class CLuaVehicleDefs : public CLuaDefs static bool SetTrainTrack(CVehicle* pVehicle, CTrainTrack* pTrack); LUA_DECLARE(SetTrainPosition); LUA_DECLARE(SetVehicleHeadLightColor); + LUA_DECLARE(SetVehicleNitroColor); + LUA_DECLARE(ResetVehicleNitroColor); LUA_DECLARE(SetVehicleTurretPosition); LUA_DECLARE(SetVehicleDoorOpenRatio); LUA_DECLARE(SetVehicleVariant); diff --git a/Shared/sdk/net/rpc_enums.h b/Shared/sdk/net/rpc_enums.h index 81ec05b0444..50dc7f25ca7 100644 --- a/Shared/sdk/net/rpc_enums.h +++ b/Shared/sdk/net/rpc_enums.h @@ -293,5 +293,7 @@ enum eElementRPCFunctions SET_ELEMENT_ON_FIRE, + SET_VEHICLE_NITRO_COLOR, + NUM_RPC_FUNCS // Add above this line }; From 0024ec32116b511aaf9275f9105e34c1255dab4b Mon Sep 17 00:00:00 2001 From: Federico Romero Date: Thu, 11 Jun 2026 12:51:35 -0300 Subject: [PATCH 2/4] Improve setVehicleNitroColor: exact colours, optional alpha, join sync - While a custom colour is applied, the nitro emitters switch from additive to standard alpha blending (restored on reset), so the requested colour renders exactly - dark colours and black included - instead of fading out as the additive contribution approaches zero. - setVehicleNitroColor now accepts an optional alpha (r, g, b [, a = 255]) that scales the effect's alpha keyframes, preserving the flame's fade animation; getVehicleNitroColor returns r, g, b, a. - The nitro colour is now included in the vehicle's entity-add data, so players who join (or stream the vehicle in) after the colour was set see it correctly instead of the default colours. --- Client/game_sa/CFxSystemSA.cpp | 76 +++++++++++++------ .../mods/deathmatch/logic/CPacketHandler.cpp | 11 +++ .../logic/luadefs/CLuaVehicleDefs.cpp | 5 +- .../deathmatch/logic/rpc/CVehicleRPCs.cpp | 2 +- .../logic/CStaticFunctionDefinitions.cpp | 1 + .../logic/luadefs/CLuaVehicleDefs.cpp | 5 +- .../logic/packets/CEntityAddPacket.cpp | 11 +++ 7 files changed, 81 insertions(+), 30 deletions(-) diff --git a/Client/game_sa/CFxSystemSA.cpp b/Client/game_sa/CFxSystemSA.cpp index 0f0229e5b2d..f575bd35d9b 100644 --- a/Client/game_sa/CFxSystemSA.cpp +++ b/Client/game_sa/CFxSystemSA.cpp @@ -23,7 +23,7 @@ extern CGameSA* pGame; static constexpr uint32_t FX_BLUEPRINT_HASH_NITRO = 0x3D591CC6; // One animated colour block ("FX_INFO_COLOUR_DATA"/"FX_INFO_COLOURBRIGHT_DATA"/ -// "FX_INFO_COLOURRANGE_DATA") of the "nitro" blueprint's R/G/B(/Range) channels, plus their +// "FX_INFO_COLOURRANGE_DATA") of the "nitro" blueprint's R/G/B/A(/Range) channels, plus their // original (unmodified) keyframe values so a per-vehicle tint can be re-derived from scratch // every time a different vehicle's nitro particles are about to be rendered. struct SNitroColorBlock @@ -31,24 +31,37 @@ struct SNitroColorBlock uint8_t nNumKeyframes; uint8_t nStride; // 1 = single value per component, 2 = value + randomisation range per component - // Indexed by [component (0=R, 1=G, 2=B)][stride slot (0=value, 1=range)]; null if the + // Indexed by [component (0=R, 1=G, 2=B, 3=A)][stride slot (0=value, 1=range)]; null if the // blueprint has no such channel. - uint16_t* pChannelValues[3][2]{}; - std::vector originalValues[3][2]; + uint16_t* pChannelValues[4][2]{}; + std::vector originalValues[4][2]; }; -static std::vector ms_NitroColorBlocks; -static bool ms_bNitroColorChannelsCached = false; -static CFxSystemSAInterface* ms_pLastNitroFxSystem = nullptr; -static std::optional ms_LastAppliedNitroColor; +// Original destination blend mode id of a "nitro" emitter, so ApplyNitroColor can switch +// between the effect's original (additive) blending and standard alpha blending. +struct SNitroEmitterBlend +{ + CFxEmitterBPSAInterface* pEmitterBP; + uint8_t nOriginalDstBlendId; +}; + +// Blend mode ids index a table (0x8A6230) that maps id -> RwBlendFunction(id + 1), +// so id 5 = rwBLENDINVSRCALPHA +static constexpr uint8_t FX_BLEND_ID_INVSRCALPHA = 5; + +static std::vector ms_NitroColorBlocks; +static std::vector ms_NitroEmitterBlends; +static bool ms_bNitroColorChannelsCached = false; +static CFxSystemSAInterface* ms_pLastNitroFxSystem = nullptr; +static std::optional ms_LastAppliedNitroColor; ////////////////////////////////////////////////////////////////////////////////////////// // // CacheNitroColorChannels // -// Collects every R/G/B(/Range) keyframe channel of the "nitro" blueprint's colour data, so -// that ApplyNitroColor can later re-tint or restore them on a per-vehicle basis without -// losing precision. +// Collects every R/G/B/A(/Range) keyframe channel of the "nitro" blueprint's colour data +// and each emitter's original blend mode, so that ApplyNitroColor can later re-tint or +// restore them on a per-vehicle basis without losing precision. // ////////////////////////////////////////////////////////////////////////////////////////// static void CacheNitroColorChannels(CFxSystemBPSAInterface* pBlueprint) @@ -59,6 +72,9 @@ static void CacheNitroColorChannels(CFxSystemBPSAInterface* pBlueprint) for (uint8_t i = 0; i < (uint8_t)pBlueprint->cNumOfPrims; ++i) { CFxEmitterBPSAInterface* pEmitterBP = (CFxEmitterBPSAInterface*)pBlueprint->pPrims[i]; + + ms_NitroEmitterBlends.push_back({pEmitterBP, pEmitterBP->m_nDstBlendId}); + for (uint32_t j = 0; j < pEmitterBP->m_infoManager.m_nNumInfos; ++j) { FxInfoSAInterface* pInfo = pEmitterBP->m_infoManager.m_pInfos[j]; @@ -72,15 +88,15 @@ static void CacheNitroColorChannels(CFxSystemBPSAInterface* pBlueprint) if (stride == 0) continue; - // Each colour component spans `stride` consecutive channels (e.g. ColourRange - // stores R, RRange, G, GRange, B, BRange). + // Each colour component spans `stride` consecutive channels, with alpha last and + // never randomised (e.g. ColourRange stores R, RRange, G, GRange, B, BRange, A). FxInfoColorSAInterface* pColorInfo = (FxInfoColorSAInterface*)pInfo; SNitroColorBlock block; block.nNumKeyframes = (uint8_t)pColorInfo->nNumKeyframes; block.nStride = stride; - for (int component = 0; component < 3; ++component) + for (int component = 0; component < 4; ++component) { for (int s = 0; s < stride; ++s) { @@ -103,14 +119,21 @@ static void CacheNitroColorChannels(CFxSystemBPSAInterface* pBlueprint) // // ApplyNitroColor // -// If `color` has no value, restores every cached "nitro" colour channel to its original -// keyframe values (i.e. the game's default cyan effect). +// If `color` has no value, restores every cached "nitro" colour channel and the emitters' +// blend modes to their original values (i.e. the game's default additive cyan effect). +// +// Otherwise, re-tints every cached channel using `color` and switches the emitters to +// standard alpha blending, so dark colours (including black) remain visible instead of +// fading out as the additive blending's contribution approaches zero. For R/G/B, rather +// than multiplying the original (cyan) values by the requested colour - which would mix the +// two hues together - the brightest of a block's original R/G/B values at each keyframe is +// used as a brightness envelope, and every channel is recoloured from scratch using that +// envelope. This allows any colour, including white, to fully replace the original hue. +// Alpha and the randomisation ranges instead scale the original keyframes, preserving the +// flame's fade animation. // -// Otherwise, re-tints every cached channel using `color`. Rather than multiplying the -// original (cyan) values by the requested colour - which would mix the two hues together - -// the brightest of a block's original R/G/B values at each keyframe is used as a brightness -// envelope, and every channel is recoloured from scratch using that envelope. This allows -// any colour, including white, to fully replace the original hue. +// The blend mode is shared by every nitro particle rendered in a frame, so vehicles using +// the original colours share it whenever both kinds are on screen at once. // // Always derived from the cached originals, so it can be called repeatedly for different // vehicles without ever needing to restore the blueprint first. @@ -118,11 +141,14 @@ static void CacheNitroColorChannels(CFxSystemBPSAInterface* pBlueprint) ////////////////////////////////////////////////////////////////////////////////////////// static void ApplyNitroColor(const std::optional& color) { + for (auto& blend : ms_NitroEmitterBlends) + blend.pEmitterBP->m_nDstBlendId = color.has_value() ? FX_BLEND_ID_INVSRCALPHA : blend.nOriginalDstBlendId; + for (auto& block : ms_NitroColorBlocks) { if (!color.has_value()) { - for (int component = 0; component < 3; ++component) + for (int component = 0; component < 4; ++component) { for (int s = 0; s < block.nStride; ++s) { @@ -133,7 +159,7 @@ static void ApplyNitroColor(const std::optional& color) continue; } - const uint32_t components[3] = {color->R, color->G, color->B}; + const uint32_t components[4] = {color->R, color->G, color->B, color->A}; for (uint8_t keyframe = 0; keyframe < block.nNumKeyframes; ++keyframe) { @@ -141,7 +167,7 @@ static void ApplyNitroColor(const std::optional& color) for (int component = 0; component < 3; ++component) brightness = std::max(brightness, block.originalValues[component][0][keyframe]); - for (int component = 0; component < 3; ++component) + for (int component = 0; component < 4; ++component) { for (int s = 0; s < block.nStride; ++s) { @@ -149,7 +175,7 @@ static void ApplyNitroColor(const std::optional& color) if (!pValues) continue; - const uint16_t source = (s == 0) ? brightness : block.originalValues[component][s][keyframe]; + const uint16_t source = (component < 3 && s == 0) ? brightness : block.originalValues[component][s][keyframe]; pValues[keyframe] = (uint16_t)(source * components[component] / 255); } } diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index b46c7497c31..8f5825f9725 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -3587,6 +3587,17 @@ void CPacketHandler::Packet_EntityAdd(NetBitStreamInterface& bitStream) pVehicle->SetSirenOrAlarmActive(bSirenesActive); } + // Read out the nitro colour + if (bitStream.ReadBit() == true) + { + SColorRGBA nitroColor(255, 255, 255, 255); + bitStream.Read(nitroColor.R); + bitStream.Read(nitroColor.G); + bitStream.Read(nitroColor.B); + bitStream.Read(nitroColor.A); + pVehicle->SetNitroColor(nitroColor); + } + pVehicle->ApplyHandling(); // Set the matrix diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index a4bae9524b9..c2de84157c5 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -1652,7 +1652,8 @@ int CLuaVehicleDefs::GetVehicleNitroColor(lua_State* luaVM) lua_pushnumber(luaVM, color->R); lua_pushnumber(luaVM, color->G); lua_pushnumber(luaVM, color->B); - return 3; + lua_pushnumber(luaVM, color->A); + return 4; } } else @@ -2535,7 +2536,7 @@ int CLuaVehicleDefs::SetVehicleNitroColor(lua_State* luaVM) argStream.ReadNumber(color.R); argStream.ReadNumber(color.G); argStream.ReadNumber(color.B); - color.A = 255; + argStream.ReadNumber(color.A, 255); if (!argStream.HasErrors()) { diff --git a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp index d324713f360..5528b9fa36d 100644 --- a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp +++ b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp @@ -571,7 +571,7 @@ void CVehicleRPCs::SetVehicleNitroColor(CClientEntity* pSource, NetBitStreamInte if (bHasColor) { SColorRGBA rgba(255, 255, 255, 255); - if (!bitStream.Read(rgba.R) || !bitStream.Read(rgba.G) || !bitStream.Read(rgba.B)) + if (!bitStream.Read(rgba.R) || !bitStream.Read(rgba.G) || !bitStream.Read(rgba.B) || !bitStream.Read(rgba.A)) return; color = rgba; diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 99398cd0c82..d03f3a8fb94 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -7513,6 +7513,7 @@ bool CStaticFunctionDefinitions::SetVehicleNitroColor(CVehicle* pVehicle, const BitStream.pBitStream->Write(color->R); BitStream.pBitStream->Write(color->G); BitStream.pBitStream->Write(color->B); + BitStream.pBitStream->Write(color->A); } m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pVehicle, SET_VEHICLE_NITRO_COLOR, *BitStream.pBitStream)); } diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index 98555995209..b6061731aaa 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -2909,7 +2909,7 @@ int CLuaVehicleDefs::SetVehicleNitroColor(lua_State* luaVM) argStream.ReadNumber(color.R); argStream.ReadNumber(color.G); argStream.ReadNumber(color.B); - color.A = 0xFF; + argStream.ReadNumber(color.A, 255); if (!argStream.HasErrors()) { @@ -2963,7 +2963,8 @@ int CLuaVehicleDefs::GetVehicleNitroColor(lua_State* luaVM) lua_pushnumber(luaVM, color->R); lua_pushnumber(luaVM, color->G); lua_pushnumber(luaVM, color->B); - return 3; + lua_pushnumber(luaVM, color->A); + return 4; } } else diff --git a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp index 0a92e763762..c50ae9d6c23 100644 --- a/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CEntityAddPacket.cpp @@ -677,6 +677,17 @@ bool CEntityAddPacket::Write(NetBitStreamInterface& BitStream) const BitStream.Write(&syncData); } } + + // Write nitro colour + const std::optional nitroColor = pVehicle->GetNitroColor(); + BitStream.WriteBit(nitroColor.has_value()); + if (nitroColor.has_value()) + { + BitStream.Write(nitroColor->R); + BitStream.Write(nitroColor->G); + BitStream.Write(nitroColor->B); + BitStream.Write(nitroColor->A); + } break; } From 89db9d1fbf93a21dbc35a0b2a9613d69c007a223 Mon Sep 17 00:00:00 2001 From: Federico Romero Date: Thu, 11 Jun 2026 13:02:37 -0300 Subject: [PATCH 3/4] Fix use of destroyed vehicle in the nitro colour render hook A vehicle's nitro FX systems are destroyed deferred by the game (they fade out first), so destroying a vehicle left its entries in the nitro system map pointing at a deleted CVehicleSA, and the render hook then called a virtual function on it (pure virtual call -> 0xC0000409). Unregister the vehicle's nitro systems in the CVehicleSA destructor, so entries are removed when either side is destroyed first. --- Client/game_sa/CFxManagerSA.cpp | 5 +++++ Client/game_sa/CFxManagerSA.h | 1 + Client/game_sa/CVehicleSA.cpp | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/Client/game_sa/CFxManagerSA.cpp b/Client/game_sa/CFxManagerSA.cpp index 3c3cb545a64..f5ba77dfe4c 100644 --- a/Client/game_sa/CFxManagerSA.cpp +++ b/Client/game_sa/CFxManagerSA.cpp @@ -118,3 +118,8 @@ CVehicle* CFxManagerSA::GetVehicleFromNitroSystem(CFxSystemSAInterface* pFxSyste { return MapFindRef(m_NitroSystemMap, pFxSystemSAInterface); } + +void CFxManagerSA::UnregisterVehicleNitroSystems(CVehicle* pVehicle) +{ + MapRemoveByValue(m_NitroSystemMap, pVehicle); +} diff --git a/Client/game_sa/CFxManagerSA.h b/Client/game_sa/CFxManagerSA.h index 5602a52171d..502ac961a4f 100644 --- a/Client/game_sa/CFxManagerSA.h +++ b/Client/game_sa/CFxManagerSA.h @@ -77,6 +77,7 @@ class CFxManagerSA : public CFxManager void RegisterNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface, CVehicle* pVehicle); CVehicle* GetVehicleFromNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface); + void UnregisterVehicleNitroSystems(CVehicle* pVehicle); private: CFxManagerSAInterface* m_pInterface; diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index 51fc9ba8db7..6b4e65ae41b 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -361,6 +361,10 @@ CVehicleSA::~CVehicleSA() { if (!BeingDeleted) { + // The game destroys this vehicle's nitro FX systems deferred (they fade out first), + // so the nitro colour render hook must not be able to reach this vehicle anymore + pGame->GetFxManagerSA()->UnregisterVehicleNitroSystems(this); + if (!m_pInterface->IsPlaceableVTBL()) { GetVehicleInterface()->m_pVehicle = nullptr; From 6b704a7b6cddce22403bcd69bc3d5e3b03ce8271 Mon Sep 17 00:00:00 2001 From: Federico Romero Date: Thu, 11 Jun 2026 15:29:16 -0300 Subject: [PATCH 4/4] Use ArgumentParser for the nitro color Lua functions Converts setVehicleNitroColor, getVehicleNitroColor and resetVehicleNitroColor on both client and server from the legacy CScriptArgReader pattern to the new ArgumentParser, as requested in review. --- .../logic/luadefs/CLuaVehicleDefs.cpp | 77 +++--------------- .../logic/luadefs/CLuaVehicleDefs.h | 6 +- .../logic/luadefs/CLuaVehicleDefs.cpp | 80 +++---------------- .../logic/luadefs/CLuaVehicleDefs.h | 7 +- 4 files changed, 31 insertions(+), 139 deletions(-) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index c2de84157c5..da98ba03574 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -72,7 +72,7 @@ void CLuaVehicleDefs::LoadFunctions() {"isVehicleBlown", ArgumentParserWarn}, {"isVehicleTaxiLightOn", IsVehicleTaxiLightOn}, {"getVehicleHeadLightColor", GetVehicleHeadLightColor}, - {"getVehicleNitroColor", GetVehicleNitroColor}, + {"getVehicleNitroColor", ArgumentParser}, {"getVehicleCurrentGear", GetVehicleCurrentGear}, {"getVehicleHandling", GetVehicleHandling}, {"getOriginalHandling", GetOriginalHandling}, @@ -142,8 +142,8 @@ void CLuaVehicleDefs::LoadFunctions() {"setVehicleTaxiLightOn", SetVehicleTaxiLightOn}, {"setVehicleGravity", SetVehicleGravity}, {"setVehicleHeadLightColor", SetVehicleHeadLightColor}, - {"setVehicleNitroColor", SetVehicleNitroColor}, - {"resetVehicleNitroColor", ResetVehicleNitroColor}, + {"setVehicleNitroColor", ArgumentParser}, + {"resetVehicleNitroColor", ArgumentParser}, {"setVehicleTurretPosition", SetVehicleTurretPosition}, {"setVehicleDoorOpenRatio", SetVehicleDoorOpenRatio}, {"setVehicleHandling", SetVehicleHandling}, @@ -1638,29 +1638,13 @@ int CLuaVehicleDefs::GetVehicleHeadLightColor(lua_State* luaVM) return 1; } -int CLuaVehicleDefs::GetVehicleNitroColor(lua_State* luaVM) +std::variant> CLuaVehicleDefs::GetVehicleNitroColor(CClientVehicle* vehicle) { - CClientVehicle* pVehicle = NULL; - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pVehicle); - - if (!argStream.HasErrors()) - { - std::optional color; - if (CStaticFunctionDefinitions::GetVehicleNitroColor(*pVehicle, color) && color.has_value()) - { - lua_pushnumber(luaVM, color->R); - lua_pushnumber(luaVM, color->G); - lua_pushnumber(luaVM, color->B); - lua_pushnumber(luaVM, color->A); - return 4; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + std::optional color; + if (!CStaticFunctionDefinitions::GetVehicleNitroColor(*vehicle, color) || !color.has_value()) + return false; - lua_pushboolean(luaVM, false); - return 1; + return CLuaMultiReturn{color->R, color->G, color->B, color->A}; } int CLuaVehicleDefs::GetVehicleCurrentGear(lua_State* luaVM) @@ -2527,51 +2511,14 @@ int CLuaVehicleDefs::SetVehicleHeadLightColor(lua_State* luaVM) return 1; } -int CLuaVehicleDefs::SetVehicleNitroColor(lua_State* luaVM) +bool CLuaVehicleDefs::SetVehicleNitroColor(CClientEntity* entity, std::uint8_t r, std::uint8_t g, std::uint8_t b, std::optional a) { - CClientEntity* pEntity = NULL; - SColor color; - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pEntity); - argStream.ReadNumber(color.R); - argStream.ReadNumber(color.G); - argStream.ReadNumber(color.B); - argStream.ReadNumber(color.A, 255); - - if (!argStream.HasErrors()) - { - if (CStaticFunctionDefinitions::SetVehicleNitroColor(*pEntity, color)) - { - lua_pushboolean(luaVM, true); - return 1; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return CStaticFunctionDefinitions::SetVehicleNitroColor(*entity, SColorRGBA(r, g, b, a.value_or(255))); } -int CLuaVehicleDefs::ResetVehicleNitroColor(lua_State* luaVM) +bool CLuaVehicleDefs::ResetVehicleNitroColor(CClientEntity* entity) { - CClientEntity* pEntity = NULL; - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pEntity); - - if (!argStream.HasErrors()) - { - if (CStaticFunctionDefinitions::SetVehicleNitroColor(*pEntity, std::nullopt)) - { - lua_pushboolean(luaVM, true); - return 1; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return CStaticFunctionDefinitions::SetVehicleNitroColor(*entity, std::nullopt); } int CLuaVehicleDefs::SetVehicleTurretPosition(lua_State* luaVM) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h index 3d828b0f925..2f647392de3 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h @@ -73,7 +73,7 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE_OOP(GetVehicleGravity); static bool IsVehicleBlown(CClientVehicle* vehicle); LUA_DECLARE(GetVehicleHeadLightColor); - LUA_DECLARE(GetVehicleNitroColor); + static std::variant> GetVehicleNitroColor(CClientVehicle* vehicle); LUA_DECLARE(GetVehicleCurrentGear); LUA_DECLARE(GetVehicleHandling); LUA_DECLARE(GetOriginalHandling); @@ -131,8 +131,8 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE(SetTrainPosition); LUA_DECLARE(SetVehicleGravity); LUA_DECLARE(SetVehicleHeadLightColor); - LUA_DECLARE(SetVehicleNitroColor); - LUA_DECLARE(ResetVehicleNitroColor); + static bool SetVehicleNitroColor(CClientEntity* entity, std::uint8_t r, std::uint8_t g, std::uint8_t b, std::optional a); + static bool ResetVehicleNitroColor(CClientEntity* entity); LUA_DECLARE(SetVehicleTurretPosition); LUA_DECLARE(SetVehicleDoorOpenRatio); LUA_DECLARE(SetVehicleHandling); diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index b6061731aaa..764ca0f0bba 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -69,7 +69,7 @@ void CLuaVehicleDefs::LoadFunctions() {"getTrainPosition", GetTrainPosition}, {"isVehicleBlown", ArgumentParserWarn}, {"getVehicleHeadLightColor", GetVehicleHeadLightColor}, - {"getVehicleNitroColor", GetVehicleNitroColor}, + {"getVehicleNitroColor", ArgumentParser}, {"getVehicleDoorOpenRatio", GetVehicleDoorOpenRatio}, // Vehicle set funcs @@ -119,8 +119,8 @@ void CLuaVehicleDefs::LoadFunctions() {"setTrainSpeed", SetTrainSpeed}, {"setTrainPosition", SetTrainPosition}, {"setVehicleHeadLightColor", SetVehicleHeadLightColor}, - {"setVehicleNitroColor", SetVehicleNitroColor}, - {"resetVehicleNitroColor", ResetVehicleNitroColor}, + {"setVehicleNitroColor", ArgumentParser}, + {"resetVehicleNitroColor", ArgumentParser}, {"setVehicleTurretPosition", SetVehicleTurretPosition}, {"setVehicleDoorOpenRatio", SetVehicleDoorOpenRatio}, {"setVehicleVariant", SetVehicleVariant}, @@ -2899,79 +2899,23 @@ int CLuaVehicleDefs::SetVehicleHeadLightColor(lua_State* luaVM) return 1; } -int CLuaVehicleDefs::SetVehicleNitroColor(lua_State* luaVM) +bool CLuaVehicleDefs::SetVehicleNitroColor(CVehicle* vehicle, std::uint8_t r, std::uint8_t g, std::uint8_t b, std::optional a) { - CVehicle* pVehicle; - SColor color; - - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pVehicle); - argStream.ReadNumber(color.R); - argStream.ReadNumber(color.G); - argStream.ReadNumber(color.B); - argStream.ReadNumber(color.A, 255); - - if (!argStream.HasErrors()) - { - if (CStaticFunctionDefinitions::SetVehicleNitroColor(pVehicle, color)) - { - lua_pushboolean(luaVM, true); - return 1; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return CStaticFunctionDefinitions::SetVehicleNitroColor(vehicle, SColorRGBA(r, g, b, a.value_or(255))); } -int CLuaVehicleDefs::ResetVehicleNitroColor(lua_State* luaVM) +bool CLuaVehicleDefs::ResetVehicleNitroColor(CVehicle* vehicle) { - CVehicle* pVehicle; - - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pVehicle); - - if (!argStream.HasErrors()) - { - if (CStaticFunctionDefinitions::SetVehicleNitroColor(pVehicle, std::nullopt)) - { - lua_pushboolean(luaVM, true); - return 1; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return CStaticFunctionDefinitions::SetVehicleNitroColor(vehicle, std::nullopt); } -int CLuaVehicleDefs::GetVehicleNitroColor(lua_State* luaVM) +std::variant> CLuaVehicleDefs::GetVehicleNitroColor(CVehicle* vehicle) { - CVehicle* pVehicle; - - CScriptArgReader argStream(luaVM); - argStream.ReadUserData(pVehicle); - - if (!argStream.HasErrors()) - { - std::optional color; - if (CStaticFunctionDefinitions::GetVehicleNitroColor(pVehicle, color) && color.has_value()) - { - lua_pushnumber(luaVM, color->R); - lua_pushnumber(luaVM, color->G); - lua_pushnumber(luaVM, color->B); - lua_pushnumber(luaVM, color->A); - return 4; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); + std::optional color; + if (!CStaticFunctionDefinitions::GetVehicleNitroColor(vehicle, color) || !color.has_value()) + return false; - lua_pushboolean(luaVM, false); - return 1; + return CLuaMultiReturn{color->R, color->G, color->B, color->A}; } int CLuaVehicleDefs::SetVehicleTurretPosition(lua_State* luaVM) diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h index c11be1795e2..5352fd1ec46 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h +++ b/Server/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h @@ -11,6 +11,7 @@ #pragma once #include "CLuaDefs.h" +#include "lua/CLuaMultiReturn.h" class CLuaVehicleDefs : public CLuaDefs { @@ -66,7 +67,7 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE(GetTrainPosition); static bool IsVehicleBlown(CVehicle* vehicle); LUA_DECLARE(GetVehicleHeadLightColor); - LUA_DECLARE(GetVehicleNitroColor); + static std::variant> GetVehicleNitroColor(CVehicle* vehicle); LUA_DECLARE(GetVehicleDoorOpenRatio); // Vehicle set functions @@ -117,8 +118,8 @@ class CLuaVehicleDefs : public CLuaDefs static bool SetTrainTrack(CVehicle* pVehicle, CTrainTrack* pTrack); LUA_DECLARE(SetTrainPosition); LUA_DECLARE(SetVehicleHeadLightColor); - LUA_DECLARE(SetVehicleNitroColor); - LUA_DECLARE(ResetVehicleNitroColor); + static bool SetVehicleNitroColor(CVehicle* vehicle, std::uint8_t r, std::uint8_t g, std::uint8_t b, std::optional a); + static bool ResetVehicleNitroColor(CVehicle* vehicle); LUA_DECLARE(SetVehicleTurretPosition); LUA_DECLARE(SetVehicleDoorOpenRatio); LUA_DECLARE(SetVehicleVariant);