diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index 761ae58535..f143c1784e 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -10,6 +10,8 @@ *****************************************************************************/ #include "StdInc.h" +#include +#include #include "CAutomobileSA.h" #include "CBikeSA.h" #include "CCameraSA.h" @@ -28,7 +30,8 @@ #include "gamesa_renderware.h" #include "CFireManagerSA.h" -extern CGameSA* pGame; +extern CCoreInterface* g_pCore; +extern CGameSA* pGame; static BOOL m_bVehicleSunGlare = false; _declspec(naked) void DoVehicleSunGlare(void* this_) @@ -54,13 +57,39 @@ void _declspec(naked) HOOK_Vehicle_PreRender(void) } } +static float& fTimeStep = *(float*)(0xB7CB5C); static bool __fastcall CanProcessFlyingCarStuff(CAutomobileSAInterface* vehicleInterface) { SClientEntity* vehicle = pGame->GetPools()->GetVehicle((DWORD*)vehicleInterface); if (!vehicle || !vehicle->pEntity) return true; - return vehicle->pEntity->GetVehicleRotorState(); + if (vehicle->pEntity->GetVehicleRotorState()) + { + if (g_pCore->GetMultiplayer()->IsVehicleEngineAutoStartEnabled()) // keep default behavior + return true; + + if (vehicle->pEntity->GetEntityStatus() != eEntityStatus::STATUS_PHYSICS && !vehicle->pEntity->IsBeingDriven()) + { + vehicle->pEntity->SetEntityStatus(eEntityStatus::STATUS_PHYSICS); // this will make rotors spin without driver when engine is on + return false; + } + if (!vehicle->pEntity->IsEngineOn()) + { + // Smoothly change rotors speed to 0 + float speed = vehicle->pEntity->GetHeliRotorSpeed(); + if (speed > 0) + vehicle->pEntity->SetHeliRotorSpeed(std::max(0.0f, speed - fTimeStep * 0.00055f)); // 0x6C4EB7 + + speed = vehicle->pEntity->GetPlaneRotorSpeed(); + if (speed > 0) + vehicle->pEntity->SetPlaneRotorSpeed(std::max(0.0f, speed - fTimeStep * 0.003f)); // 0x6CC145 + + return false; + } + return true; + } + return false; } static constexpr DWORD CONTINUE_CHeli_ProcessFlyingCarStuff = 0x6C4E82; diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index a83905d473..25f440fab5 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -6057,6 +6057,9 @@ bool CClientGame::SetWorldSpecialProperty(const WorldSpecialProperty property, c case WorldSpecialProperty::VEHICLEBURNEXPLOSIONS: g_pGame->SetVehicleBurnExplosionsEnabled(enabled); break; + case WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART: + SetVehicleEngineAutoStartEnabled(enabled); + break; default: return false; } @@ -6103,6 +6106,8 @@ bool CClientGame::IsWorldSpecialProperty(const WorldSpecialProperty property) return m_pVehicleManager->IsSpawnFlyingComponentEnabled(); case WorldSpecialProperty::VEHICLEBURNEXPLOSIONS: return g_pGame->IsVehicleBurnExplosionsEnabled(); + case WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART: + return IsVehicleEngineAutoStartEnabled(); } return false; @@ -6138,6 +6143,20 @@ bool CClientGame::IsWeaponRenderEnabled() const return g_pGame->IsWeaponRenderEnabled(); } +void CClientGame::SetVehicleEngineAutoStartEnabled(bool enabled) +{ + if (enabled == g_pMultiplayer->IsVehicleEngineAutoStartEnabled()) + return; + + g_pMultiplayer->SetVehicleEngineAutoStartEnabled(enabled); + m_pVehicleManager->ResetNotControlledRotors(enabled); +} + +bool CClientGame::IsVehicleEngineAutoStartEnabled() const +{ + return g_pMultiplayer->IsVehicleEngineAutoStartEnabled(); +} + #pragma code_seg(".text") bool CClientGame::VerifySADataFiles(int iEnableClientChecks) { @@ -6823,6 +6842,7 @@ void CClientGame::ResetWorldProperties(const ResetWorldPropsInfo& resetPropsInfo g_pGame->SetIgnoreFireStateEnabled(false); m_pVehicleManager->SetSpawnFlyingComponentEnabled(true); g_pGame->SetVehicleBurnExplosionsEnabled(true); + SetVehicleEngineAutoStartEnabled(true); } // Reset all setWorldProperty to default diff --git a/Client/mods/deathmatch/logic/CClientGame.h b/Client/mods/deathmatch/logic/CClientGame.h index 9780a185c2..d21de68f06 100644 --- a/Client/mods/deathmatch/logic/CClientGame.h +++ b/Client/mods/deathmatch/logic/CClientGame.h @@ -426,6 +426,9 @@ class CClientGame void SetWeaponRenderEnabled(bool enabled); bool IsWeaponRenderEnabled() const; + void SetVehicleEngineAutoStartEnabled(bool enabled); + bool IsVehicleEngineAutoStartEnabled() const; + void ResetWorldProperties(const ResetWorldPropsInfo& resetPropsInfo); CTransferBox* GetTransferBox() { return m_pTransferBox; }; diff --git a/Client/mods/deathmatch/logic/CClientVehicleManager.cpp b/Client/mods/deathmatch/logic/CClientVehicleManager.cpp index 3a9494def9..f1d32978c6 100644 --- a/Client/mods/deathmatch/logic/CClientVehicleManager.cpp +++ b/Client/mods/deathmatch/logic/CClientVehicleManager.cpp @@ -791,3 +791,20 @@ void CClientVehicleManager::RestreamVehicleUpgrades(unsigned short usModel) pVehicle->GetUpgrades()->RestreamVehicleUpgrades(usModel); } } + +void CClientVehicleManager::ResetNotControlledRotors(bool engineAutoStart) +{ + // Reset rotors to original or custom state for loaded vehicles without controller + // Custom state allows rotors to spin without driver inside (if engine is on) + eEntityStatus status = engineAutoStart ? eEntityStatus::STATUS_ABANDONED : eEntityStatus::STATUS_PHYSICS; + for (auto& pVehicle : m_List) + { + if (pVehicle->GetGameEntity() && pVehicle->GetVehicleRotorState() && !pVehicle->IsDriven()) + { + float speed = (!engineAutoStart && pVehicle->IsEngineOn()) ? 0.001f : 0.0f; + pVehicle->GetGameEntity()->SetEntityStatus(status); + pVehicle->SetHeliRotorSpeed(speed); + pVehicle->SetPlaneRotorSpeed(speed); + } + } +} diff --git a/Client/mods/deathmatch/logic/CClientVehicleManager.h b/Client/mods/deathmatch/logic/CClientVehicleManager.h index 5f0ad55f61..c9d14d848d 100644 --- a/Client/mods/deathmatch/logic/CClientVehicleManager.h +++ b/Client/mods/deathmatch/logic/CClientVehicleManager.h @@ -73,6 +73,8 @@ class CClientVehicleManager bool IsSpawnFlyingComponentEnabled() const noexcept { return m_spawnFlyingComponentsDuringRecreate; } void SetSpawnFlyingComponentEnabled(bool isEnabled) noexcept { m_spawnFlyingComponentsDuringRecreate = isEnabled; } + void ResetNotControlledRotors(bool engineAutoStart); + protected: CClientManager* m_pManager; bool m_bCanRemoveFromList; diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index f14c281978..1f50f7ac67 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -2402,6 +2402,7 @@ void CPacketHandler::Packet_MapInfo(NetBitStreamInterface& bitStream) g_pClientGame->SetWorldSpecialProperty(WorldSpecialProperty::IGNOREFIRESTATE, wsProps.data6.ignoreFireState); g_pClientGame->SetWorldSpecialProperty(WorldSpecialProperty::FLYINGCOMPONENTS, wsProps.data7.flyingcomponents); g_pClientGame->SetWorldSpecialProperty(WorldSpecialProperty::VEHICLEBURNEXPLOSIONS, wsProps.data8.vehicleburnexplosions); + g_pClientGame->SetWorldSpecialProperty(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART, wsProps.data9.vehicleEngineAutoStart); float fJetpackMaxHeight = 100; if (!bitStream.Read(fJetpackMaxHeight)) diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index 8ac305efca..2389110fbf 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -6634,6 +6634,25 @@ void CMultiplayerSA::SetAutomaticVehicleStartupOnPedEnter(bool bSet) MemSet((char*)0x64BC0D, 0x90, 6); } +bool CMultiplayerSA::IsVehicleEngineAutoStartEnabled() const noexcept +{ + return *(unsigned char*)0x64BC03 == 0x75; +} + +void CMultiplayerSA::SetVehicleEngineAutoStartEnabled(bool enabled) +{ + if (enabled) + { + MemCpy((void*)0x64BC03, "\x75\x05\x80\xC9\x10", 5); + MemCpy((void*)0x6C4EA9, "\x8A\x86\x28\x04", 4); + } + else + { + MemSet((void*)0x64BC03, 0x90, 5); // prevent vehicle engine from turning on (driver enter) + MemCpy((void*)0x6C4EA9, "\xE9\x15\x03\x00", 4); // prevent aircraft engine from turning off (driver exit) + } +} + // Storage CVehicleSAInterface* pHeliKiller = NULL; CEntitySAInterface* pHitByHeli = NULL; diff --git a/Client/multiplayer_sa/CMultiplayerSA.h b/Client/multiplayer_sa/CMultiplayerSA.h index 7a48e56106..f4c79ce9b6 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.h +++ b/Client/multiplayer_sa/CMultiplayerSA.h @@ -333,6 +333,9 @@ class CMultiplayerSA : public CMultiplayer void SetAutomaticVehicleStartupOnPedEnter(bool bSet); + bool IsVehicleEngineAutoStartEnabled() const noexcept override; + void SetVehicleEngineAutoStartEnabled(bool enabled) override; + void SetPedTargetingMarkerEnabled(bool bEnable); bool IsPedTargetingMarkerEnabled(); bool IsConnected(); diff --git a/Client/sdk/multiplayer/CMultiplayer.h b/Client/sdk/multiplayer/CMultiplayer.h index 8895234df2..555de9e526 100644 --- a/Client/sdk/multiplayer/CMultiplayer.h +++ b/Client/sdk/multiplayer/CMultiplayer.h @@ -444,6 +444,9 @@ class CMultiplayer virtual void SetAutomaticVehicleStartupOnPedEnter(bool bSet) = 0; + virtual bool IsVehicleEngineAutoStartEnabled() const noexcept = 0; + virtual void SetVehicleEngineAutoStartEnabled(bool enabled) = 0; + virtual void SetPedTargetingMarkerEnabled(bool bEnabled) = 0; virtual bool IsPedTargetingMarkerEnabled() = 0; diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index 442f12b22b..16e7b7312b 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -263,6 +263,7 @@ CGame::CGame() : m_FloodProtect(4, 30000, 30000) // Max of 4 connecti m_WorldSpecialProps[WorldSpecialProperty::IGNOREFIRESTATE] = false; m_WorldSpecialProps[WorldSpecialProperty::FLYINGCOMPONENTS] = true; m_WorldSpecialProps[WorldSpecialProperty::VEHICLEBURNEXPLOSIONS] = true; + m_WorldSpecialProps[WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART] = true; m_JetpackWeapons[WEAPONTYPE_MICRO_UZI] = true; m_JetpackWeapons[WEAPONTYPE_TEC9] = true; @@ -3405,7 +3406,8 @@ void CGame::Packet_Vehicle_InOut(CVehicleInOutPacket& Packet) pPed->SetVehicleAction(CPed::VEHICLEACTION_NONE); // Update our engine State - pVehicle->SetEngineOn(true); + if (g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART)) + pVehicle->SetEngineOn(true); // Tell everyone he's in (they should warp him in) CVehicleInOutPacket Reply(PedID, VehicleID, ucOccupiedSeat, VEHICLE_NOTIFY_IN_RETURN); @@ -4524,6 +4526,7 @@ void CGame::ResetWorldProperties(const ResetWorldPropsInfo& resetPropsInfo) g_pGame->SetWorldSpecialPropertyEnabled(WorldSpecialProperty::IGNOREFIRESTATE, false); g_pGame->SetWorldSpecialPropertyEnabled(WorldSpecialProperty::FLYINGCOMPONENTS, true); g_pGame->SetWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLEBURNEXPLOSIONS, true); + g_pGame->SetWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART, true); } // Reset all weather stuff like heat haze, wind velocity etc diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index ad64816a6d..f8bfad986a 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -4262,7 +4262,7 @@ bool CStaticFunctionDefinitions::WarpPedIntoVehicle(CPed* pPed, CVehicle* pVehic pPed->SetVehicleAction(CPed::VEHICLEACTION_NONE); // If he's the driver, switch on the engine - if (uiSeat == 0) + if (uiSeat == 0 && g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART)) pVehicle->SetEngineOn(true); // Tell all the players diff --git a/Server/mods/deathmatch/logic/packets/CMapInfoPacket.cpp b/Server/mods/deathmatch/logic/packets/CMapInfoPacket.cpp index 08db7a8dc3..0fd78c8bd3 100644 --- a/Server/mods/deathmatch/logic/packets/CMapInfoPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CMapInfoPacket.cpp @@ -195,6 +195,7 @@ bool CMapInfoPacket::Write(NetBitStreamInterface& BitStream) const wsProps.data6.ignoreFireState = g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::IGNOREFIRESTATE); wsProps.data7.flyingcomponents = g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::FLYINGCOMPONENTS); wsProps.data8.vehicleburnexplosions = g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLEBURNEXPLOSIONS); + wsProps.data9.vehicleEngineAutoStart = g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART); BitStream.Write(&wsProps); } diff --git a/Shared/mods/deathmatch/logic/Enums.cpp b/Shared/mods/deathmatch/logic/Enums.cpp index a531fdb842..8bb99db974 100644 --- a/Shared/mods/deathmatch/logic/Enums.cpp +++ b/Shared/mods/deathmatch/logic/Enums.cpp @@ -119,6 +119,7 @@ ADD_ENUM(WorldSpecialProperty::TUNNELWEATHERBLEND, "tunnelweatherblend") ADD_ENUM(WorldSpecialProperty::IGNOREFIRESTATE, "ignorefirestate") ADD_ENUM(WorldSpecialProperty::FLYINGCOMPONENTS, "flyingcomponents") ADD_ENUM(WorldSpecialProperty::VEHICLEBURNEXPLOSIONS, "vehicleburnexplosions") +ADD_ENUM(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART, "vehicle_engine_autostart") IMPLEMENT_ENUM_CLASS_END("world-special-property") IMPLEMENT_ENUM_BEGIN(ePacketID) diff --git a/Shared/mods/deathmatch/logic/Enums.h b/Shared/mods/deathmatch/logic/Enums.h index 2b845f25a6..884bb559f5 100644 --- a/Shared/mods/deathmatch/logic/Enums.h +++ b/Shared/mods/deathmatch/logic/Enums.h @@ -96,6 +96,7 @@ enum class WorldSpecialProperty IGNOREFIRESTATE, FLYINGCOMPONENTS, VEHICLEBURNEXPLOSIONS, + VEHICLE_ENGINE_AUTOSTART, }; DECLARE_ENUM_CLASS(WorldSpecialProperty); diff --git a/Shared/sdk/net/SyncStructures.h b/Shared/sdk/net/SyncStructures.h index 55887dd42c..c9d4a98075 100644 --- a/Shared/sdk/net/SyncStructures.h +++ b/Shared/sdk/net/SyncStructures.h @@ -2083,6 +2083,10 @@ struct SWorldSpecialPropertiesStateSync : public ISyncStructure { BITCOUNT8 = 1 }; + enum + { + BITCOUNT9 = 1 + }; bool Read(NetBitStreamInterface& bitStream) { @@ -2121,6 +2125,11 @@ struct SWorldSpecialPropertiesStateSync : public ISyncStructure isOK &= bitStream.ReadBits(reinterpret_cast(&data8), BITCOUNT8); else data8.vehicleburnexplosions = true; + + if (bitStream.Can(eBitStreamVersion::WorldSpecialProperty_VehicleEngineAutoStart)) + isOK &= bitStream.ReadBits(reinterpret_cast(&data9), BITCOUNT9); + else + data9.vehicleEngineAutoStart = true; //// Example for adding item: // if (bitStream.Can(eBitStreamVersion::YourProperty)) @@ -2154,6 +2163,9 @@ struct SWorldSpecialPropertiesStateSync : public ISyncStructure if (bitStream.Can(eBitStreamVersion::WorldSpecialProperty_VehicleBurnExplosions)) bitStream.WriteBits(reinterpret_cast(&data8), BITCOUNT8); + if (bitStream.Can(eBitStreamVersion::WorldSpecialProperty_VehicleEngineAutoStart)) + bitStream.WriteBits(reinterpret_cast(&data9), BITCOUNT9); + //// Example for adding item: // if (bitStream.Can(eBitStreamVersion::YourProperty)) // bitStream.WriteBits(reinterpret_cast(&data9), BITCOUNT9); @@ -2210,6 +2222,11 @@ struct SWorldSpecialPropertiesStateSync : public ISyncStructure { bool vehicleburnexplosions : 1; } data8; + + struct + { + bool vehicleEngineAutoStart : 1; + } data9; SWorldSpecialPropertiesStateSync() { @@ -2233,6 +2250,7 @@ struct SWorldSpecialPropertiesStateSync : public ISyncStructure data6.ignoreFireState = false; data7.flyingcomponents = true; data8.vehicleburnexplosions = true; + data9.vehicleEngineAutoStart = true; } }; diff --git a/Shared/sdk/net/bitstream.h b/Shared/sdk/net/bitstream.h index f8a4058f57..160077ec35 100644 --- a/Shared/sdk/net/bitstream.h +++ b/Shared/sdk/net/bitstream.h @@ -620,6 +620,10 @@ enum class eBitStreamVersion : unsigned short // 2025-05-26 ServersideBuildingElement, + // Add "vehenginemanualmode" to setWorldSpecialPropertyEnabled + // 2025-06-02 + WorldSpecialProperty_VehicleEngineAutoStart, + // This allows us to automatically increment the BitStreamVersion when things are added to this enum. // Make sure you only add things above this comment. Next,