diff --git a/Client/game_sa/CAutomobileSA.cpp b/Client/game_sa/CAutomobileSA.cpp index 8a58869458..909880f242 100644 --- a/Client/game_sa/CAutomobileSA.cpp +++ b/Client/game_sa/CAutomobileSA.cpp @@ -15,6 +15,9 @@ extern CGameSA* pGame; +using namespace CarNodes; +using namespace VehicleFeatures; + CAutomobileSA::CAutomobileSA(CAutomobileSAInterface* pInterface) { SetInterface(pInterface); @@ -27,7 +30,7 @@ void CAutomobileSAInterface::SetPanelDamage(std::uint8_t panelId, bool breakGlas if (nodeId < 0) return; - eCarNodes node = static_cast(nodeId); + CarNodes::Enum node = static_cast(nodeId); RwFrame* frame = m_aCarNodes[nodeId]; if (!frame) @@ -47,7 +50,7 @@ void CAutomobileSAInterface::SetPanelDamage(std::uint8_t panelId, bool breakGlas if ((pHandlingData->uiModelFlags & 0x10000000) != 0) // check bouncePanels flag return; - if (node != eCarNodes::WINDSCREEN && node != eCarNodes::WING_LF && node != eCarNodes::WING_RF) + if (node != WINDSCREEN && node != WING_LF && node != WING_RF) { // Get free bouncing panel for (auto& panel : m_panels) @@ -87,3 +90,78 @@ void CAutomobileSAInterface::SetPanelDamage(std::uint8_t panelId, bool breakGlas } } } + +void CAutomobileSA::PreRender_End(CAutomobileSAInterface* vehicleInterface) +{ + auto* vehicle = pGame->GetPools()->GetVehicle(reinterpret_cast(vehicleInterface)); + if (!vehicle || !vehicle->pEntity) + return; + + // Support for default vehicles is still in the GTA code, so we don't need to do it again + if (vehicleInterface->m_nModelIndex == 407 || vehicleInterface->m_nModelIndex == 601) + return; + + // Simple turret like in fire truck + if (vehicle->pEntity->IsSpecialFeatureEnabled(WATER_CANNON) && !vehicle->pEntity->IsSpecialFeatureEnabled(TURRET)) + { + CVehicleSA::SetComponentRotation(vehicleInterface->m_aCarNodes[MISC_A], eComponentRotationAxis::AXIS_X, vehicleInterface->m_fDoomHorizontalRotation, true); + CVehicleSA::SetComponentRotation(vehicleInterface->m_aCarNodes[MISC_A], eComponentRotationAxis::AXIS_Z, vehicleInterface->m_fDoomVerticalRotation, false); + } + + // Turret like rhino or swat van + if (vehicle->pEntity->IsSpecialFeatureEnabled(TURRET)) + { + CVehicleSA::SetComponentRotation(vehicleInterface->m_aCarNodes[MISC_A], eComponentRotationAxis::AXIS_Z, vehicleInterface->m_fDoomVerticalRotation, true); + CVehicleSA::SetComponentRotation(vehicleInterface->m_aCarNodes[MISC_B], eComponentRotationAxis::AXIS_X, vehicleInterface->m_fDoomHorizontalRotation, true); + } +} + +bool CAutomobileSA::HasFeatureEnabled(CAutomobileSAInterface* vehicleInterface, VehicleFeatures::Enum feature) +{ + auto* vehicle = pGame->GetPools()->GetVehicle(reinterpret_cast(vehicleInterface)); + if (!vehicle || !vehicle->pEntity) // This really shouldn't happen + return true; + + return vehicle->pEntity->IsSpecialFeatureEnabled(feature); +} + +static constexpr std::uintptr_t SKIIP_FIRE_TRUCK = 0x6B1F77; +static constexpr std::uintptr_t CONTINUE_FIRE_TRUCK = 0x6B1F5B; +static void _declspec(naked) HOOK_CAutomobile_ProcessControl_FireTruckCheck() +{ + _asm + { + push WATER_CANNON + push esi + call CAutomobileSA::HasFeatureEnabled + add esp, 8 + + test al, al + jz skip + + jmp CONTINUE_FIRE_TRUCK + + skip: + jmp SKIIP_FIRE_TRUCK + } +} + +static constexpr std::uintptr_t FINISH_PRE_RENDER = 0x6ACC92; +static void _declspec(naked) HOOK_CAutomobile_PreRender_End() +{ + _asm + { + push esi + call CAutomobileSA::PreRender_End + add esp, 4 + + lea ecx, [esp+94h] + jmp FINISH_PRE_RENDER + } +} + +void CAutomobileSA::StaticSetHooks() +{ + HookInstall(0x6B1F4B, (DWORD)HOOK_CAutomobile_ProcessControl_FireTruckCheck, 8); // Model check in CAutomobile::ProcessControl + HookInstall(0x6ACC8B, (DWORD)HOOK_CAutomobile_PreRender_End, 7); // The end of the CAutomobile::PreRender function +} diff --git a/Client/game_sa/CAutomobileSA.h b/Client/game_sa/CAutomobileSA.h index 3aa3a6286d..792be9887c 100644 --- a/Client/game_sa/CAutomobileSA.h +++ b/Client/game_sa/CAutomobileSA.h @@ -42,97 +42,97 @@ class CAutomobileSAInterface : public CVehicleSAInterface public: void SetPanelDamage(std::uint8_t panelId, bool breakGlass, bool spawnFlyingComponent = true); - CObjectSAInterface* SpawnFlyingComponent(const eCarNodes& nodeId, const eCarComponentCollisionTypes& collType) + CObjectSAInterface* SpawnFlyingComponent(const CarNodes::Enum& nodeId, const eCarComponentCollisionTypes& collType) { - return ((CObjectSAInterface*(__thiscall*)(CAutomobileSAInterface*, eCarNodes, eCarComponentCollisionTypes))0x6a8580)(this, nodeId, collType); + return ((CObjectSAInterface*(__thiscall*)(CAutomobileSAInterface*, CarNodes::Enum, eCarComponentCollisionTypes))0x6a8580)(this, nodeId, collType); } - CDamageManagerSAInterface m_damageManager; - CDoorSAInterface m_doors[MAX_DOORS]; - RwFrame* m_aCarNodes[static_cast(eCarNodes::NUM_NODES)]; - CBouncingPanelSAInterface m_panels[3]; - CDoorSAInterface m_swingingChassis; - CColPointSAInterface m_wheelColPoint[MAX_WHEELS]; - float m_wheelsDistancesToGround1[4]; - float m_wheelsDistancesToGround2[4]; - float m_wheelCollisionState[4]; - float field_800; - float field_804; - float field_80C; - int m_wheelSkidmarkType[4]; - bool m_wheelSkidmarkBloodState[4]; - bool m_wheelSkidmarkSomeBool[4]; - float m_wheelRotation[4]; - float m_wheelPosition[4]; - float m_wheelSpeed[4]; - int field_858[4]; - char taxiAvaliable; - char field_869; - short field_86A; - unsigned short m_wMiscComponentAngle; - unsigned short m_wVoodooSuspension; - int m_dwBusDoorTimerEnd; - int m_dwBusDoorTimerStart; - float field_878; - float wheelOffsetZ[4]; - int field_88C[3]; - float m_fFrontHeightAboveRoad; - float m_fRearHeightAboveRoad; - float m_fCarTraction; - float m_fNitroValue; - int field_8A4; - int m_fRotationBalance; // used in CHeli::TestSniperCollision - float m_fMoveDirection; - int field_8B4[6]; - int field_8C8[6]; - float m_fBurningTime; - CEntitySAInterface* m_pWheelCollisionEntity[4]; - CVector m_vWheelCollisionPos[4]; - char field_924; - char field_925; - char field_926; - char field_927; - char field_928; - char field_929; - char field_92A; - char field_92B; - char field_92C; - char field_92D; - char field_92E; - char field_92F; - char field_930; - char field_931; - char field_932; - char field_933; - char field_934; - char field_935; - char field_936; - char field_937; - char field_938; - char field_939; - char field_93A; - char field_93B; - char field_93C; - char field_93D; - char field_93E; - char field_93F; - int field_940; - int field_944; - float m_fDoomVerticalRotation; - float m_fDoomHorizontalRotation; - float m_fForcedOrientation; - float m_fUpDownLightAngle[2]; - unsigned char m_nNumContactWheels; - unsigned char m_nWheelsOnGround; - char field_962; - char field_963; - float field_964; - int m_wheelFrictionState[4]; - CFxSystemSAInterface* pNitroParticle[2]; - char field_980; - char field_981; - short field_982; - float field_984; + CDamageManagerSAInterface m_damageManager; + CDoorSAInterface m_doors[MAX_DOORS]; + std::array m_aCarNodes; + CBouncingPanelSAInterface m_panels[3]; + CDoorSAInterface m_swingingChassis; + CColPointSAInterface m_wheelColPoint[MAX_WHEELS]; + float m_wheelsDistancesToGround1[4]; + float m_wheelsDistancesToGround2[4]; + float m_wheelCollisionState[4]; + float field_800; + float field_804; + float field_80C; + int m_wheelSkidmarkType[4]; + bool m_wheelSkidmarkBloodState[4]; + bool m_wheelSkidmarkSomeBool[4]; + float m_wheelRotation[4]; + float m_wheelPosition[4]; + float m_wheelSpeed[4]; + int field_858[4]; + char taxiAvaliable; + char field_869; + short field_86A; + unsigned short m_wMiscComponentAngle; + unsigned short m_wVoodooSuspension; + int m_dwBusDoorTimerEnd; + int m_dwBusDoorTimerStart; + float field_878; + float wheelOffsetZ[4]; + int field_88C[3]; + float m_fFrontHeightAboveRoad; + float m_fRearHeightAboveRoad; + float m_fCarTraction; + float m_fNitroValue; + int field_8A4; + int m_fRotationBalance; // used in CHeli::TestSniperCollision + float m_fMoveDirection; + int field_8B4[6]; + int field_8C8[6]; + float m_fBurningTime; + CEntitySAInterface* m_pWheelCollisionEntity[4]; + CVector m_vWheelCollisionPos[4]; + char field_924; + char field_925; + char field_926; + char field_927; + char field_928; + char field_929; + char field_92A; + char field_92B; + char field_92C; + char field_92D; + char field_92E; + char field_92F; + char field_930; + char field_931; + char field_932; + char field_933; + char field_934; + char field_935; + char field_936; + char field_937; + char field_938; + char field_939; + char field_93A; + char field_93B; + char field_93C; + char field_93D; + char field_93E; + char field_93F; + int field_940; + int field_944; + float m_fDoomVerticalRotation; + float m_fDoomHorizontalRotation; + float m_fForcedOrientation; + float m_fUpDownLightAngle[2]; + unsigned char m_nNumContactWheels; + unsigned char m_nWheelsOnGround; + char field_962; + char field_963; + float field_964; + int m_wheelFrictionState[4]; + CFxSystemSAInterface* pNitroParticle[2]; + char field_980; + char field_981; + short field_982; + float field_984; }; static_assert(sizeof(CAutomobileSAInterface) == 0x988, "Invalid size for CAutomobileSAInterface"); @@ -143,4 +143,11 @@ class CAutomobileSA : public virtual CAutomobile, public virtual CVehicleSA CAutomobileSA(CAutomobileSAInterface* pInterface); CAutomobileSAInterface* GetAutomobileInterface() { return reinterpret_cast(GetInterface()); } + + static void StaticSetHooks(); + +private: + static void PreRender_End(CAutomobileSAInterface* vehicleInterface); + + static bool HasFeatureEnabled(CAutomobileSAInterface* vehicleInterface, VehicleFeatures::Enum feature); }; diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index 7cac177f42..8703b39716 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -250,6 +250,7 @@ CGameSA::CGameSA() CFireSA::StaticSetHooks(); CPtrNodeSingleLinkPoolSA::StaticSetHooks(); CVehicleAudioSettingsManagerSA::StaticSetHooks(); + CAutomobileSA::StaticSetHooks(); } catch (const std::bad_alloc& e) { diff --git a/Client/game_sa/CGameSA.h b/Client/game_sa/CGameSA.h index 9ed552b92d..f74d11cd7d 100644 --- a/Client/game_sa/CGameSA.h +++ b/Client/game_sa/CGameSA.h @@ -19,6 +19,7 @@ #include "CPlantManagerSA.h" #include "CRendererSA.h" #include "CVehicleAudioSettingsManagerSA.h" +#include "CVehicleSA.h" class CAnimBlendClumpDataSAInterface; class CObjectGroupPhysicalPropertiesSA; @@ -322,6 +323,13 @@ class CGameSA : public CGame bool SetBuildingPoolSize(size_t size); + bool SetVehicleModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature, bool enabled) override { return CVehicleSA::SetModelSpecialFeatureEnabled(model, feature, enabled); }; + bool IsVehicleModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature) const override { return CVehicleSA::IsModelSpecialFeatureEnabled(model, feature); }; + + ModelFeaturesArray GetModelSpecialFeatures(std::uint16_t model) const noexcept override { return CVehicleSA::GetModelSpecialFeatures(model); } + + void ResetVehicleModelsSpecialFeatures() const noexcept override { CVehicleSA::ResetVehicleModelsSpecialFeatures(); }; + private: std::unique_ptr m_Pools; CPlayerInfo* m_pPlayerInfo; diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index f143c1784e..68a6440305 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -33,6 +33,8 @@ extern CCoreInterface* g_pCore; extern CGameSA* pGame; +std::unordered_map CVehicleSA::m_modelSpecialFeatures = {}; + static BOOL m_bVehicleSunGlare = false; _declspec(naked) void DoVehicleSunGlare(void* this_) { @@ -1543,9 +1545,9 @@ void CVehicleSA::SetGravity(const CVector* pvecGravity) m_vecGravity = *pvecGravity; } -bool CVehicleSA::SpawnFlyingComponent(const eCarNodes& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime) +bool CVehicleSA::SpawnFlyingComponent(const CarNodes::Enum& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime) { - if (nodeIndex == eCarNodes::NONE) + if (nodeIndex == CarNodes::Enum::NONE) return false; DWORD nodesOffset = OFFSET_CAutomobile_Nodes; @@ -1644,16 +1646,16 @@ void CVehicleSA::SetWheelVisibility(eWheelPosition wheel, bool bVisible) switch (wheel) { case FRONT_LEFT_WHEEL: - pFrame = vehicle->m_aCarNodes[static_cast(eCarNodes::WHEEL_LF)]; + pFrame = vehicle->m_aCarNodes[CarNodes::Enum::WHEEL_LF]; break; case REAR_LEFT_WHEEL: - pFrame = vehicle->m_aCarNodes[static_cast(eCarNodes::WHEEL_LB)]; + pFrame = vehicle->m_aCarNodes[CarNodes::Enum::WHEEL_LB]; break; case FRONT_RIGHT_WHEEL: - pFrame = vehicle->m_aCarNodes[static_cast(eCarNodes::WHEEL_RF)]; + pFrame = vehicle->m_aCarNodes[CarNodes::Enum::WHEEL_RF]; break; case REAR_RIGHT_WHEEL: - pFrame = vehicle->m_aCarNodes[static_cast(eCarNodes::WHEEL_RB)]; + pFrame = vehicle->m_aCarNodes[CarNodes::Enum::WHEEL_RB]; break; default: break; @@ -1897,16 +1899,6 @@ bool CVehicleSA::SetOnFire(bool onFire) return true; } -void CVehicleSA::StaticSetHooks() -{ - // Setup vehicle sun glare hook - HookInstall(FUNC_CAutomobile_OnVehiclePreRender, (DWORD)HOOK_Vehicle_PreRender, 5); - - // Setup hooks to handle setVehicleRotorState function - HookInstall(FUNC_CHeli_ProcessFlyingCarStuff, (DWORD)HOOK_CHeli_ProcessFlyingCarStuff, 5); - HookInstall(FUNC_CPlane_ProcessFlyingCarStuff, (DWORD)HOOK_CPlane_ProcessFlyingCarStuff, 5); -} - void CVehicleSA::SetVehiclesSunGlareEnabled(bool bEnabled) { m_bVehicleSunGlare = bEnabled; @@ -2004,6 +1996,12 @@ bool CVehicleSA::GetComponentPosition(const SString& vehicleComponent, CVector& return false; } +void CVehicleSA::SetComponentRotation(RwFrame* frame, eComponentRotationAxis axis, float angle, bool resetPosition) +{ + // CVehicle::SetComponentRotation + ((void(__stdcall*)(RwFrame*, eComponentRotationAxis, float, bool))0x6DBA30)(frame, axis, angle, resetPosition); +} + bool CVehicleSA::SetComponentScale(const SString& vehicleComponent, const CVector& vecScale) { SVehicleFrame* pComponent = GetVehicleComponent(vehicleComponent); @@ -2423,3 +2421,76 @@ void CVehicleSA::ReinitAudio() if (IsPassenger(pLocalPlayer) || GetDriver() == pLocalPlayer) audioInterface->SoundJoin(); } + +bool CVehicleSA::SetSpecialFeatureEnabled(VehicleFeatures::Enum feature, bool enabled) +{ + CVehicleSAInterface* vehicleInterface = GetVehicleInterface(); + + switch (feature) + { + case WATER_CANNON: + { + if (vehicleInterface->m_vehicleClass != VehicleClass::AUTOMOBILE) + return false; + + // The vehicle must have the misc_a component, which is the water cannon, otherwise it will crash in CAutomobile::PreRender during turret rotation. + if (!static_cast(vehicleInterface)->m_aCarNodes[CarNodes::Enum::MISC_A]) + return false; + } + } + + m_specialFeatures[feature] = enabled; + return true; +} + +bool CVehicleSA::SetModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature, bool enabled) +{ + CModelInfo* modelInfo = pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + if (feature == VehicleFeatures::Enum::WATER_CANNON) + { + if (static_cast(modelInfo->GetVehicleType()) != VehicleClass::AUTOMOBILE) + return false; + } + + m_modelSpecialFeatures[model][feature] = enabled; + return true; +} + +bool CVehicleSA::IsModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature) +{ + auto it = m_modelSpecialFeatures.find(model); + return it == m_modelSpecialFeatures.end() ? false : it->second[feature]; +} + +ModelFeaturesArray CVehicleSA::GetModelSpecialFeatures(std::uint16_t model) +{ + auto it = m_modelSpecialFeatures.find(model); + return it != m_modelSpecialFeatures.end() ? it->second : ModelFeaturesArray{}; +} + +void CVehicleSA::ResetVehicleModelsSpecialFeatures() noexcept +{ + m_modelSpecialFeatures.clear(); + + ModelFeaturesArray firetruck{}; + firetruck[VehicleFeatures::Enum::WATER_CANNON] = true; + m_modelSpecialFeatures[407] = firetruck; + + ModelFeaturesArray swat{}; + swat[VehicleFeatures::Enum::WATER_CANNON] = true; + swat[VehicleFeatures::Enum::TURRET] = true; + m_modelSpecialFeatures[601] = swat; +} + +void CVehicleSA::StaticSetHooks() +{ + // Setup vehicle sun glare hook + HookInstall(FUNC_CAutomobile_OnVehiclePreRender, (DWORD)HOOK_Vehicle_PreRender, 5); + + // Setup hooks to handle setVehicleRotorState function + HookInstall(FUNC_CHeli_ProcessFlyingCarStuff, (DWORD)HOOK_CHeli_ProcessFlyingCarStuff, 5); + HookInstall(FUNC_CPlane_ProcessFlyingCarStuff, (DWORD)HOOK_CPlane_ProcessFlyingCarStuff, 5); +} diff --git a/Client/game_sa/CVehicleSA.h b/Client/game_sa/CVehicleSA.h index ceaba79e49..7ae4b9da8f 100644 --- a/Client/game_sa/CVehicleSA.h +++ b/Client/game_sa/CVehicleSA.h @@ -222,6 +222,13 @@ class CAutoPilot BYTE pad[56]; }; +enum class eComponentRotationAxis +{ + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + #define MAX_UPGRADES_ATTACHED 15 // perhaps? class CVehicleSAInterface : public CPhysicalSAInterface @@ -421,6 +428,9 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA { friend class CPoolsSA; +protected: + static std::unordered_map m_modelSpecialFeatures; + private: CDamageManagerSA* m_pDamageManager{nullptr}; CAEVehicleAudioEntitySA* m_pVehicleAudioEntity{nullptr}; @@ -445,6 +455,8 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA std::array(VehicleDummies::VEHICLE_DUMMY_COUNT)> m_dummyPositions; + ModelFeaturesArray m_specialFeatures{}; + public: CVehicleSA() = default; ~CVehicleSA(); @@ -639,7 +651,7 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA SharedUtil::SColor GetHeadLightColor() { return m_HeadLightColor; } void SetHeadLightColor(const SharedUtil::SColor color) { m_HeadLightColor = color; } - bool SpawnFlyingComponent(const eCarNodes& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime = -1); + bool SpawnFlyingComponent(const CarNodes::Enum& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime = -1); void SetWheelVisibility(eWheelPosition wheel, bool bVisible); CVector GetWheelPosition(eWheelPosition wheel); @@ -686,6 +698,7 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA bool GetComponentRotation(const SString& vehicleComponent, CVector& vecPositionModelling); bool SetComponentPosition(const SString& vehicleComponent, const CVector& vecPosition); bool GetComponentPosition(const SString& vehicleComponent, CVector& vecPositionModelling); + static void SetComponentRotation(RwFrame* frame, eComponentRotationAxis axis, float angle, bool resetPosition); bool SetComponentScale(const SString& vehicleComponent, const CVector& vecScale); bool GetComponentScale(const SString& vehicleComponent, CVector& vecScaleModelling); bool IsComponentPresent(const SString& vehicleComponent); @@ -715,6 +728,18 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA bool IsOnFire() override { return GetVehicleInterface()->m_pFire != nullptr; } bool SetOnFire(bool onFire) override; + void SetSpecialFeaturesEnabled(const ModelFeaturesArray& features) noexcept { m_specialFeatures = features; } + + bool SetSpecialFeatureEnabled(VehicleFeatures::Enum feature, bool enabled) override; + bool IsSpecialFeatureEnabled(VehicleFeatures::Enum feature) const noexcept override { return m_specialFeatures[feature]; } + + static bool SetModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature, bool enabled); + static bool IsModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature); + + static ModelFeaturesArray GetModelSpecialFeatures(std::uint16_t model); + + static void ResetVehicleModelsSpecialFeatures() noexcept; + static void StaticSetHooks(); static void SetVehiclesSunGlareEnabled(bool bEnabled); static bool GetVehiclesSunGlareEnabled(); diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 49343f0c9e..e02c22d531 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -3492,6 +3492,8 @@ void CClientGame::Event_OnIngame() g_pGame->GetVehicleAudioSettingsManager()->ResetAudioSettingsData(); + g_pGame->ResetVehicleModelsSpecialFeatures(); + // Tell doggy we got the game running WatchDogCompletedSection("L1"); } diff --git a/Client/mods/deathmatch/logic/CClientVehicle.cpp b/Client/mods/deathmatch/logic/CClientVehicle.cpp index af04f360ea..c59ffba0ec 100644 --- a/Client/mods/deathmatch/logic/CClientVehicle.cpp +++ b/Client/mods/deathmatch/logic/CClientVehicle.cpp @@ -213,6 +213,9 @@ CClientVehicle::CClientVehicle(CClientManager* pManager, ElementID ID, unsigned m_clientModel = pManager->GetModelManager()->FindModelByID(usModel); m_pSoundSettingsEntry = nullptr; + + // Init firetruck & swatvan watercannon + m_specialFeatures = g_pGame->GetModelSpecialFeatures(m_usModel); } CClientVehicle::~CClientVehicle() @@ -2941,6 +2944,8 @@ void CClientVehicle::Create() } } + m_pVehicle->SetSpecialFeaturesEnabled(m_specialFeatures); + // We've just been streamed in m_bJustStreamedIn = true; @@ -5117,7 +5122,7 @@ void CClientVehicle::ResetWheelScale() m_bWheelScaleChanged = false; } -bool CClientVehicle::SpawnFlyingComponent(const eCarNodes& nodeID, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime) +bool CClientVehicle::SpawnFlyingComponent(const CarNodes::Enum& nodeID, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime) { if (!m_pVehicle) return false; @@ -5154,3 +5159,35 @@ void CClientVehicle::ResetAudioSettings() ApplyAudioSettings(); } +bool CClientVehicle::SetSpecialFeatureEnabled(const VehicleFeatures::Enum& feature, bool enabled) +{ + if (m_pVehicle) + { + if (!m_pVehicle->SetSpecialFeatureEnabled(feature, enabled)) + return false; + } + else + { + // Anti-crash checks + switch (feature) + { + case VehicleFeatures::Enum::WATER_CANNON: + { + if (m_ComponentData.find("misc_a") == m_ComponentData.end()) + return false; + + break; + } + case VehicleFeatures::Enum::TURRET: + { + if (m_ComponentData.find("misc_a") == m_ComponentData.end() || m_ComponentData.find("misc_b") == m_ComponentData.end()) + return false; + + break; + } + } + } + + m_specialFeatures[feature] = enabled; + return true; +} diff --git a/Client/mods/deathmatch/logic/CClientVehicle.h b/Client/mods/deathmatch/logic/CClientVehicle.h index 05c2601cf6..cf857c106b 100644 --- a/Client/mods/deathmatch/logic/CClientVehicle.h +++ b/Client/mods/deathmatch/logic/CClientVehicle.h @@ -551,13 +551,16 @@ class CClientVehicle : public CClientStreamElement bool SetDummyPosition(VehicleDummies dummy, const CVector& position); bool ResetDummyPositions(); - bool SpawnFlyingComponent(const eCarNodes& nodeID, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime); + bool SpawnFlyingComponent(const CarNodes::Enum& nodeID, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime); CVector GetEntryPoint(std::uint32_t entryPointIndex); bool IsOnFire() override { return m_pVehicle ? m_pVehicle->IsOnFire() : false; } bool SetOnFire(bool onFire) override { return m_pVehicle ? m_pVehicle->SetOnFire(onFire) : false; } + bool SetSpecialFeatureEnabled(const VehicleFeatures::Enum& feature, bool enabled); + bool IsSpecialFeatureEnabled(const VehicleFeatures::Enum& feature) const noexcept { return m_pVehicle ? m_pVehicle->IsSpecialFeatureEnabled(feature) : m_specialFeatures[feature]; } + protected: void ConvertComponentRotationBase(const SString& vehicleComponent, CVector& vecInOutRotation, EComponentBaseType inputBase, EComponentBaseType outputBase); void ConvertComponentPositionBase(const SString& vehicleComponent, CVector& vecInOutPosition, EComponentBaseType inputBase, EComponentBaseType outputBase); @@ -676,6 +679,8 @@ class CClientVehicle : public CClientStreamElement float m_fWheelScale; std::unique_ptr m_pSoundSettingsEntry; + std::array m_specialFeatures{}; + bool m_bChainEngine; bool m_bIsDerailed; bool m_bIsDerailable; diff --git a/Client/mods/deathmatch/logic/CClientVehicleManager.cpp b/Client/mods/deathmatch/logic/CClientVehicleManager.cpp index f1d32978c6..75fe17b092 100644 --- a/Client/mods/deathmatch/logic/CClientVehicleManager.cpp +++ b/Client/mods/deathmatch/logic/CClientVehicleManager.cpp @@ -808,3 +808,22 @@ void CClientVehicleManager::ResetNotControlledRotors(bool engineAutoStart) } } } + +bool CClientVehicleManager::SetModelSpecialFeatureEnabled(std::uint16_t model, const VehicleFeatures::Enum& feature, bool enabled) +{ + if (!IsValidModel(model)) + return false; + + if (!g_pGame->SetVehicleModelSpecialFeatureEnabled(model, feature, enabled)) + return false; + + for (CClientVehicle* vehicle : m_List) + { + if (vehicle->GetModel() != model) + continue; + + vehicle->SetSpecialFeatureEnabled(feature, enabled); + } + + return true; +} diff --git a/Client/mods/deathmatch/logic/CClientVehicleManager.h b/Client/mods/deathmatch/logic/CClientVehicleManager.h index c9d14d848d..a558f6bc9a 100644 --- a/Client/mods/deathmatch/logic/CClientVehicleManager.h +++ b/Client/mods/deathmatch/logic/CClientVehicleManager.h @@ -75,6 +75,8 @@ class CClientVehicleManager void ResetNotControlledRotors(bool engineAutoStart); + bool SetModelSpecialFeatureEnabled(std::uint16_t model, const VehicleFeatures::Enum& feature, bool enabled); + protected: CClientManager* m_pManager; bool m_bCanRemoveFromList; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index eb3cb08904..53dfecca6d 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -977,6 +977,9 @@ ADD_ENUM(VehicleAudioSettingProperty::ENGINE_UPGRADE, "engine-upgrade") ADD_ENUM(VehicleAudioSettingProperty::VEHICLE_TYPE_FOR_AUDIO, "vehicle-type-for-audio") IMPLEMENT_ENUM_CLASS_END("vehicle-audio-setting") +IMPLEMENT_ENUM_BEGIN(VehicleFeatures::Enum) +ADD_ENUM(VehicleFeatures::Enum::WATER_CANNON, "water_cannon") +IMPLEMENT_ENUM_END("vehicle-features") // // CResource from userdata diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index da35c0d1cc..158a8fb6d3 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -101,6 +101,7 @@ DECLARE_ENUM_CLASS(PreloadAreaOption); DECLARE_ENUM_CLASS(taskType); DECLARE_ENUM(eEntityType); DECLARE_ENUM_CLASS(VehicleAudioSettingProperty); +DECLARE_ENUM(VehicleFeatures::Enum); class CRemoteCall; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp index 219c8cb6a4..fe4b2920b3 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.cpp @@ -100,6 +100,7 @@ void CLuaVehicleDefs::LoadFunctions() {"getVehicleRotorState", ArgumentParser}, {"getVehicleModelAudioSettings", ArgumentParser}, {"getVehicleAudioSettings", ArgumentParser}, + {"isVehicleSpecialFeatureEnabled", ArgumentParser}, // Vehicle set funcs {"createVehicle", CreateVehicle}, @@ -172,6 +173,7 @@ void CLuaVehicleDefs::LoadFunctions() {"resetVehicleModelAudioSettings", ArgumentParser}, {"setVehicleAudioSetting", ArgumentParser}, {"resetVehicleAudioSettings", ArgumentParser}, + {"setVehicleSpecialFeatureEnabled", ArgumentParser}, }; // Add functions @@ -262,6 +264,7 @@ void CLuaVehicleDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "getEntryPoints", ArgumentParser); lua_classfunction(luaVM, "isSmokeTrailEnabled", "isVehicleSmokeTrailEnabled"); lua_classfunction(luaVM, "getRotorState", "getVehicleRotorState"); + lua_classfunction(luaVM, "isSpecialFeatureEnabled", "isVehicleSpecialFeatureEnabled"); lua_classfunction(luaVM, "setComponentVisible", "setVehicleComponentVisible"); lua_classfunction(luaVM, "setSirensOn", "setVehicleSirensOn"); @@ -314,6 +317,7 @@ void CLuaVehicleDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setRotorState", "setVehicleRotorState"); lua_classfunction(luaVM, "resetAudioSettings", "resetVehicleAudioSettings"); lua_classfunction(luaVM, "setAudioSetting", "setVehicleAudioSetting"); + lua_classfunction(luaVM, "setSpecialFeatureEnabled", "setVehicleSpecialFeatureEnabled"); lua_classfunction(luaVM, "resetComponentPosition", "resetVehicleComponentPosition"); lua_classfunction(luaVM, "resetComponentRotation", "resetVehicleComponentRotation"); @@ -4313,11 +4317,11 @@ std::variant> CLuaVehicleDefs::OOP_GetVehicleEntryP bool CLuaVehicleDefs::SpawnVehicleFlyingComponent(CClientVehicle* const vehicle, std::uint8_t nodeIndex, std::optional componentCollisionType, std::optional removalTime) { - auto partNodeIndex = static_cast(nodeIndex); + auto partNodeIndex = static_cast(nodeIndex); auto collisionType = componentCollisionType.has_value() ? static_cast(componentCollisionType.value()) : eCarComponentCollisionTypes::COL_NODE_PANEL; - if (nodeIndex < 1 || partNodeIndex >= eCarNodes::NUM_NODES) + if (nodeIndex < 1 || partNodeIndex >= CarNodes::Enum::NUM_NODES) throw std::invalid_argument("Invalid component index"); if (collisionType >= eCarComponentCollisionTypes::COL_NODES_NUM) @@ -4327,38 +4331,38 @@ bool CLuaVehicleDefs::SpawnVehicleFlyingComponent(CClientVehicle* const vehicle, { switch (partNodeIndex) { - case eCarNodes::WHEEL_RF: - case eCarNodes::WHEEL_RB: - case eCarNodes::WHEEL_LF: - case eCarNodes::WHEEL_LB: + case CarNodes::Enum::WHEEL_RF: + case CarNodes::Enum::WHEEL_RB: + case CarNodes::Enum::WHEEL_LF: + case CarNodes::Enum::WHEEL_LB: { collisionType = eCarComponentCollisionTypes::COL_NODE_WHEEL; break; } - case eCarNodes::DOOR_RF: - case eCarNodes::DOOR_RR: - case eCarNodes::DOOR_LF: - case eCarNodes::DOOR_LR: + case CarNodes::Enum::DOOR_RF: + case CarNodes::Enum::DOOR_RR: + case CarNodes::Enum::DOOR_LF: + case CarNodes::Enum::DOOR_LR: { collisionType = eCarComponentCollisionTypes::COL_NODE_DOOR; break; } - case eCarNodes::BUMP_FRONT: - case eCarNodes::BUMP_REAR: - case eCarNodes::WHEEL_LM: - case eCarNodes::WHEEL_RM: + case CarNodes::Enum::BUMP_FRONT: + case CarNodes::Enum::BUMP_REAR: + case CarNodes::Enum::WHEEL_LM: + case CarNodes::Enum::WHEEL_RM: { collisionType = eCarComponentCollisionTypes::COL_NODE_BUMPER; break; } - case eCarNodes::BOOT: - case eCarNodes::CHASSIS: + case CarNodes::Enum::BOOT: + case CarNodes::Enum::CHASSIS: { collisionType = eCarComponentCollisionTypes::COL_NODE_BOOT; break; } - case eCarNodes::BONNET: - case eCarNodes::WINDSCREEN: + case CarNodes::Enum::BONNET: + case CarNodes::Enum::WINDSCREEN: { collisionType = eCarComponentCollisionTypes::COL_NODE_BONNET; break; @@ -4514,7 +4518,8 @@ bool CLuaVehicleDefs::ResetVehicleModelAudioSettings(const uint32_t uiModel) if (!CClientVehicleManager::IsStandardModel(uiModel)) throw std::invalid_argument("Cannot change audio setting for allocated vechiles"); - g_pGame->GetVehicleAudioSettingsManager()->ResetModelSettings(uiModel); + g_pGame->GetVehicleAudioSettingsManager()->ResetModelSettings(uiModel); + return true; } bool CLuaVehicleDefs::SetVehicleAudioSetting(CClientVehicle* pVehicle, const VehicleAudioSettingProperty eProperty, float varValue) @@ -4653,3 +4658,34 @@ std::unordered_map CLuaVehicleDefs::GetVehicleAudioSettings( return output; } +bool CLuaVehicleDefs::SetVehicleSpecialFeatureEnabled(std::variant vehicle, VehicleFeatures::Enum feature, bool enabled) noexcept +{ + if (std::holds_alternative(vehicle)) + return std::get(vehicle)->SetSpecialFeatureEnabled(feature, enabled); + else if (std::holds_alternative(vehicle)) + { + std::uint16_t model = std::get(vehicle); + if (!CClientVehicleManager::IsValidModel(model)) + return false; + + return m_pVehicleManager->SetModelSpecialFeatureEnabled(model, feature, enabled); + } + + return false; +} + +bool CLuaVehicleDefs::IsVehicleSpecialFeatureEnabled(std::variant vehicle, VehicleFeatures::Enum feature) noexcept +{ + if (std::holds_alternative(vehicle)) + return std::get(vehicle)->IsSpecialFeatureEnabled(feature); + else if (std::holds_alternative(vehicle)) + { + std::uint16_t model = std::get(vehicle); + if (!CClientVehicleManager::IsValidModel(model)) + return false; + + return g_pGame->IsVehicleModelSpecialFeatureEnabled(model, feature); + } + + return false; +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h index d213961918..a98ba2d419 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaVehicleDefs.h @@ -199,4 +199,7 @@ class CLuaVehicleDefs : public CLuaDefs static bool SetSmokeTrailEnabled(CClientVehicle* vehicle, bool state); static bool IsSmokeTrailEnabled(CClientVehicle* vehicle) noexcept; + + static bool SetVehicleSpecialFeatureEnabled(std::variant vehicle, VehicleFeatures::Enum feature, bool enabled) noexcept; + static bool IsVehicleSpecialFeatureEnabled(std::variant vehicle, VehicleFeatures::Enum feature) noexcept; }; diff --git a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp index 2fbd2c703b..1f5311debb 100644 --- a/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp +++ b/Client/mods/deathmatch/logic/rpc/CVehicleRPCs.cpp @@ -668,7 +668,7 @@ void CVehicleRPCs::SpawnVehicleFlyingComponent(CClientEntity* const sourceEntity std::int32_t removalTime; if (bitStream.Read(nodeIndex) && bitStream.Read(collisionType) && bitStream.Read(removalTime)) - vehicle->SpawnFlyingComponent(static_cast(nodeIndex), static_cast(collisionType), removalTime); + vehicle->SpawnFlyingComponent(static_cast(nodeIndex), static_cast(collisionType), removalTime); } void CVehicleRPCs::SetVehicleNitroActivated(CClientEntity* pSourceEntity, NetBitStreamInterface& bitStream) diff --git a/Client/sdk/game/CDamageManager.h b/Client/sdk/game/CDamageManager.h index 326ebaeba6..773e684e65 100644 --- a/Client/sdk/game/CDamageManager.h +++ b/Client/sdk/game/CDamageManager.h @@ -121,37 +121,6 @@ enum eLights MAX_LIGHTS // MUST BE 16 OR LESS }; -enum class eCarNodes -{ - NONE = 0, - CHASSIS, - WHEEL_RF, - WHEEL_RM, - WHEEL_RB, - WHEEL_LF, - WHEEL_LM, - WHEEL_LB, - DOOR_RF, - DOOR_RR, - DOOR_LF, - DOOR_LR, - BUMP_FRONT, - BUMP_REAR, - WING_RF, - WING_LF, - BONNET, - BOOT, - WINDSCREEN, - EXHAUST, - MISC_A, - MISC_B, - MISC_C, - MISC_D, - MISC_E, - - NUM_NODES -}; - enum class eCarComponentCollisionTypes { COL_NODE_BUMPER = 0, diff --git a/Client/sdk/game/CGame.h b/Client/sdk/game/CGame.h index baa6103e10..7e14a5195b 100644 --- a/Client/sdk/game/CGame.h +++ b/Client/sdk/game/CGame.h @@ -17,6 +17,7 @@ #include "Common.h" #include "CWeaponInfo.h" #include "enums/SystemState.h" +#include "enums/VehicleFeatures.h" class C3DMarkers; class CAEAudioHardware; @@ -288,4 +289,10 @@ class __declspec(novtable) CGame virtual bool SetBuildingPoolSize(size_t size) = 0; + virtual bool SetVehicleModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature, bool enabled) = 0; + virtual bool IsVehicleModelSpecialFeatureEnabled(std::uint16_t model, VehicleFeatures::Enum feature) const = 0; + + virtual std::array GetModelSpecialFeatures(std::uint16_t model) const noexcept = 0; + + virtual void ResetVehicleModelsSpecialFeatures() const noexcept = 0; }; diff --git a/Client/sdk/game/CVehicle.h b/Client/sdk/game/CVehicle.h index ce4b337d54..8bbd5a2d3a 100644 --- a/Client/sdk/game/CVehicle.h +++ b/Client/sdk/game/CVehicle.h @@ -21,6 +21,8 @@ #include "enums/VehicleDummies.h" #include "enums/ResizableVehicleWheelGroup.h" +#include "enums/VehicleFeatures.h" +#include "enums/CarNodes.h" class CAEVehicleAudioEntity; class CColModel; @@ -91,6 +93,8 @@ struct SVehicleFrame std::vector frameList; // Frames from root to parent }; +using ModelFeaturesArray = std::array; + class CVehicle : public virtual CPhysical { public: @@ -269,7 +273,7 @@ class CVehicle : public virtual CPhysical virtual SColor GetHeadLightColor() = 0; virtual void SetHeadLightColor(const SColor color) = 0; - virtual bool SpawnFlyingComponent(const eCarNodes& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime = -1) = 0; + virtual bool SpawnFlyingComponent(const CarNodes::Enum& nodeIndex, const eCarComponentCollisionTypes& collisionType, std::int32_t removalTime = -1) = 0; virtual void SetWheelVisibility(eWheelPosition wheel, bool bVisible) = 0; virtual CVector GetWheelPosition(eWheelPosition wheel) = 0; @@ -331,4 +335,9 @@ class CVehicle : public virtual CPhysical virtual const CVector* GetDummyPositions() const = 0; virtual void ReinitAudio() = 0; + + virtual void SetSpecialFeaturesEnabled(const ModelFeaturesArray& features) noexcept = 0; + + virtual bool SetSpecialFeatureEnabled(VehicleFeatures::Enum feature, bool enabled) = 0; + virtual bool IsSpecialFeatureEnabled(VehicleFeatures::Enum feature) const noexcept = 0; }; diff --git a/Shared/sdk/enums/CarNodes.h b/Shared/sdk/enums/CarNodes.h new file mode 100644 index 0000000000..2121f2c202 --- /dev/null +++ b/Shared/sdk/enums/CarNodes.h @@ -0,0 +1,45 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/CarNodes.h + * PURPOSE: Header for common definitions + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ \ +#pragma once + +namespace CarNodes +{ + enum Enum + { + NONE = 0, + CHASSIS, + WHEEL_RF, + WHEEL_RM, + WHEEL_RB, + WHEEL_LF, + WHEEL_LM, + WHEEL_LB, + DOOR_RF, + DOOR_RR, + DOOR_LF, + DOOR_LR, + BUMP_FRONT, + BUMP_REAR, + WING_RF, + WING_LF, + BONNET, + BOOT, + WINDSCREEN, + EXHAUST, + MISC_A, + MISC_B, + MISC_C, + MISC_D, + MISC_E, + + NUM_NODES + }; +} diff --git a/Shared/sdk/enums/VehicleFeatures.h b/Shared/sdk/enums/VehicleFeatures.h new file mode 100644 index 0000000000..6da2fd2ae8 --- /dev/null +++ b/Shared/sdk/enums/VehicleFeatures.h @@ -0,0 +1,26 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/VehicleFeatures.h + * PURPOSE: Header for common definitions + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +namespace VehicleFeatures +{ + enum Enum + { + WATER_CANNON, + TURRET, + TOW_BAR, + FORKLIFT, + TANK_BARREL, + + MAX_FEATURES, + }; +}