diff --git a/Client/game_sa/CFxManagerSA.cpp b/Client/game_sa/CFxManagerSA.cpp index d7a23380296..f5ba77dfe4c 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,19 @@ 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); +} + +void CFxManagerSA::UnregisterVehicleNitroSystems(CVehicle* pVehicle) +{ + MapRemoveByValue(m_NitroSystemMap, pVehicle); +} diff --git a/Client/game_sa/CFxManagerSA.h b/Client/game_sa/CFxManagerSA.h index c1f72389143..502ac961a4f 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,12 @@ class CFxManagerSA : public CFxManager void AddToList(CFxSystemSAInterface* pFxSystemSAInterface, CFxSystemSA* pFxSystemSA); void RemoveFromList(CFxSystemSA* pFxSystemSA); + void RegisterNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface, CVehicle* pVehicle); + CVehicle* GetVehicleFromNitroSystem(CFxSystemSAInterface* pFxSystemSAInterface); + void UnregisterVehicleNitroSystems(CVehicle* pVehicle); + 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..f575bd35d9b 100644 --- a/Client/game_sa/CFxSystemSA.cpp +++ b/Client/game_sa/CFxSystemSA.cpp @@ -10,13 +10,231 @@ *****************************************************************************/ #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/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 +{ + 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, 3=A)][stride slot (0=value, 1=range)]; null if the + // blueprint has no such channel. + uint16_t* pChannelValues[4][2]{}; + std::vector originalValues[4][2]; +}; + +// 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/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) +{ + if (ms_bNitroColorChannelsCached) + return; + + 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]; + + 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, 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 < 4; ++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 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. +// +// 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. +// +////////////////////////////////////////////////////////////////////////////////////////// +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 < 4; ++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[4] = {color->R, color->G, color->B, color->A}; + + 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 < 4; ++component) + { + for (int s = 0; s < block.nStride; ++s) + { + uint16_t* pValues = block.pChannelValues[component][s]; + if (!pValues) + continue; + + const uint16_t source = (component < 3 && 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 +496,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..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; @@ -2039,6 +2043,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 +2088,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/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/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..da98ba03574 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", ArgumentParser}, {"getVehicleCurrentGear", GetVehicleCurrentGear}, {"getVehicleHandling", GetVehicleHandling}, {"getOriginalHandling", GetOriginalHandling}, @@ -141,6 +142,8 @@ void CLuaVehicleDefs::LoadFunctions() {"setVehicleTaxiLightOn", SetVehicleTaxiLightOn}, {"setVehicleGravity", SetVehicleGravity}, {"setVehicleHeadLightColor", SetVehicleHeadLightColor}, + {"setVehicleNitroColor", ArgumentParser}, + {"resetVehicleNitroColor", ArgumentParser}, {"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,15 @@ int CLuaVehicleDefs::GetVehicleHeadLightColor(lua_State* luaVM) return 1; } +std::variant> CLuaVehicleDefs::GetVehicleNitroColor(CClientVehicle* vehicle) +{ + std::optional color; + if (!CStaticFunctionDefinitions::GetVehicleNitroColor(*vehicle, color) || !color.has_value()) + return false; + + return CLuaMultiReturn{color->R, color->G, color->B, color->A}; +} + int CLuaVehicleDefs::GetVehicleCurrentGear(lua_State* luaVM) { CClientVehicle* pVehicle = NULL; @@ -2496,6 +2511,16 @@ int CLuaVehicleDefs::SetVehicleHeadLightColor(lua_State* luaVM) return 1; } +bool CLuaVehicleDefs::SetVehicleNitroColor(CClientEntity* entity, std::uint8_t r, std::uint8_t g, std::uint8_t b, std::optional a) +{ + return CStaticFunctionDefinitions::SetVehicleNitroColor(*entity, SColorRGBA(r, g, b, a.value_or(255))); +} + +bool CLuaVehicleDefs::ResetVehicleNitroColor(CClientEntity* entity) +{ + return CStaticFunctionDefinitions::SetVehicleNitroColor(*entity, std::nullopt); +} + 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..2f647392de3 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); + static std::variant> GetVehicleNitroColor(CClientVehicle* vehicle); 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); + 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/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp index d78b978b928..5528b9fa36d 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) || !bitStream.Read(rgba.A)) + 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..d03f3a8fb94 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,29 @@ 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); + BitStream.pBitStream->Write(color->A); + } + 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..764ca0f0bba 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", ArgumentParser}, {"getVehicleDoorOpenRatio", GetVehicleDoorOpenRatio}, // Vehicle set funcs @@ -118,6 +119,8 @@ void CLuaVehicleDefs::LoadFunctions() {"setTrainSpeed", SetTrainSpeed}, {"setTrainPosition", SetTrainPosition}, {"setVehicleHeadLightColor", SetVehicleHeadLightColor}, + {"setVehicleNitroColor", ArgumentParser}, + {"resetVehicleNitroColor", ArgumentParser}, {"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,25 @@ int CLuaVehicleDefs::SetVehicleHeadLightColor(lua_State* luaVM) return 1; } +bool CLuaVehicleDefs::SetVehicleNitroColor(CVehicle* vehicle, std::uint8_t r, std::uint8_t g, std::uint8_t b, std::optional a) +{ + return CStaticFunctionDefinitions::SetVehicleNitroColor(vehicle, SColorRGBA(r, g, b, a.value_or(255))); +} + +bool CLuaVehicleDefs::ResetVehicleNitroColor(CVehicle* vehicle) +{ + return CStaticFunctionDefinitions::SetVehicleNitroColor(vehicle, std::nullopt); +} + +std::variant> CLuaVehicleDefs::GetVehicleNitroColor(CVehicle* vehicle) +{ + std::optional color; + if (!CStaticFunctionDefinitions::GetVehicleNitroColor(vehicle, color) || !color.has_value()) + return false; + + return CLuaMultiReturn{color->R, color->G, color->B, color->A}; +} + 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..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,6 +67,7 @@ class CLuaVehicleDefs : public CLuaDefs LUA_DECLARE(GetTrainPosition); static bool IsVehicleBlown(CVehicle* vehicle); LUA_DECLARE(GetVehicleHeadLightColor); + static std::variant> GetVehicleNitroColor(CVehicle* vehicle); LUA_DECLARE(GetVehicleDoorOpenRatio); // Vehicle set functions @@ -116,6 +118,8 @@ class CLuaVehicleDefs : public CLuaDefs static bool SetTrainTrack(CVehicle* pVehicle, CTrainTrack* pTrack); LUA_DECLARE(SetTrainPosition); LUA_DECLARE(SetVehicleHeadLightColor); + 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); 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; } 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 };