diff --git a/Client/game_sa/CCameraSA.cpp b/Client/game_sa/CCameraSA.cpp index b40d4a93e4b..cf066c2bd19 100644 --- a/Client/game_sa/CCameraSA.cpp +++ b/Client/game_sa/CCameraSA.cpp @@ -317,6 +317,7 @@ CMatrix* CCameraSA::GetMatrix(CMatrix* matrix) else { *matrix = CMatrix(); + matrix->vPos = cameraInterface->m_transform.m_translate; } return matrix; } diff --git a/Client/game_sa/CCreepingFireSA.cpp b/Client/game_sa/CCreepingFireSA.cpp new file mode 100644 index 00000000000..9bbaa4e351e --- /dev/null +++ b/Client/game_sa/CCreepingFireSA.cpp @@ -0,0 +1,91 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/CCreepingFireSA.cpp + * PURPOSE: Fire manager + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CCreepingFireSA.h" +#include "CGameSA.h" +#include "CFireManagerSA.h" +#include "CColPointSA.h" +#include "CFireSA.h" +#include "CPoolsSA.h" + +extern CGameSA* pGame; + +static std::array, 32> m_fireStatus{}; + +bool CCreepingFireSA::TryToStartFireAtCoors(CVector position, std::uint8_t numGenerationsAllowed, void* registerWithCreator, bool unused_2, float zDistance) +{ + std::uint8_t status = m_fireStatus[static_cast(position.fX) & 31][static_cast(position.fY) & 31]; + if (status != 0) + return false; + + if (!pGame->GetFireManager()->PlentyFiresAvailable()) + return false; + + // Call CWorld::ProcessVerticalLine + CEntitySAInterface* hitEntity = nullptr; + CColPointSAInterface cp; + if (!((bool(__cdecl*)(CVector*, float, CColPointSAInterface*, CEntitySAInterface**, bool, bool, bool, bool, bool, bool, void*))0x5674E0)( + &position, position.fZ - zDistance, &cp, &hitEntity, true, false, false, false, false, false, nullptr)) + return false; + + position.fZ = cp.Position.fZ; + status = 6; + + CFire* fire = pGame->GetFireManager()->StartFire(position, 0.8f, nullptr, 20000, numGenerationsAllowed); + if (!fire) + return false; + + CEntitySAInterface* creatorInterface = nullptr; + if (registerWithCreator) + { + void* returnAddress = _ReturnAddress(); + if (returnAddress == (void*)0x7377D8) // CExplosion::Update + creatorInterface = *(CEntitySAInterface**)((char*)registerWithCreator - 0x18); // [esi-0x18] + else if (returnAddress == (void*)0x73EC03) // CWeapon::FireAreaEffect + // esp + 0x6C+0x4 + 0xC (CVector) + 0x4 (push) = esp + 0x80 + creatorInterface = *(CEntitySAInterface**)((char*)registerWithCreator + 0x80); // [esp+0x80] + else // our CFireSA::ProcessFire + creatorInterface = static_cast(registerWithCreator); + } + + CEntity* creator = creatorInterface ? pGame->GetPools()->GetEntity((DWORD*)creatorInterface) : nullptr; + if (creator) + fire->SetCreator(creator); + + return true; +} + +void CCreepingFireSA::Update() +{ + int frame = pGame->GetSystemFrameCounter(); + std::uint8_t status = m_fireStatus[frame % 32][(frame / 32) % 32]; + + if (status == 4) + status = 0; + else if (status > 4 && status <= 6) + status--; +} + +void CCreepingFireSA::StaticSetHooks() +{ + // Patch calls to CCreepingFire::TryToStartFireAtCoors + HookInstallCall(0x7377D3, (DWORD)CCreepingFireSA::TryToStartFireAtCoors); + HookInstallCall(0x73EBFE, (DWORD)CCreepingFireSA::TryToStartFireAtCoors); + + // Change push 1 to push esi in CExplosion::Update + MemCpy((void*)0x7377BE, "\x56\x90", 2); + // Change push 1 to push esp in CWeapon::FireAreaEffect + MemCpy((void*)0x73EB7F, "\x54\x90", 2); + + // Patch call to CCreepingFire::Update + HookInstallCall(0x53BFFB, (DWORD)CCreepingFireSA::Update); +} diff --git a/Client/game_sa/CCreepingFireSA.h b/Client/game_sa/CCreepingFireSA.h new file mode 100644 index 00000000000..49fef4068d2 --- /dev/null +++ b/Client/game_sa/CCreepingFireSA.h @@ -0,0 +1,24 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/CCreepingFireSA.h + * PURPOSE: Creeping fire reimplementation + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once +#include "CVector.h" +#include +#include + +class CCreepingFireSA +{ +public: + static bool TryToStartFireAtCoors(CVector position, std::uint8_t numGenerationsAllowed, void* registerWithCreator, bool unused_2, float zDistance); + static void Update(); + + static void StaticSetHooks(); +}; diff --git a/Client/game_sa/CEntitySA.cpp b/Client/game_sa/CEntitySA.cpp index 85269b237fc..20f955afb10 100644 --- a/Client/game_sa/CEntitySA.cpp +++ b/Client/game_sa/CEntitySA.cpp @@ -20,6 +20,7 @@ #include "CVehicleSA.h" #include "CWorldSA.h" #include "gamesa_renderware.h" +#include "CPhysicalSA.h" extern CGameSA* pGame; @@ -709,6 +710,11 @@ bool CEntitySA::SetBonePosition(eBone boneId, const CVector& position) return true; } +bool CEntitySA::IsFireProof() const +{ + return static_cast(GetInterface())->bFireProof; +} + BYTE CEntitySA::GetAreaCode() { return m_pInterface->m_areaCode; diff --git a/Client/game_sa/CEntitySA.h b/Client/game_sa/CEntitySA.h index ef69c6fbf27..0b4d63f9a8e 100644 --- a/Client/game_sa/CEntitySA.h +++ b/Client/game_sa/CEntitySA.h @@ -212,6 +212,18 @@ class CEntitySAInterface : public CPlaceableSAInterface ((CEntity_ResolveReferences)0x571A40)(this); }; + void CleanUpOldReference(CEntitySAInterface** entity) + { + if (entity) + ((void(__thiscall*)(CEntitySAInterface*, CEntitySAInterface**))0x571A00)(this, entity); + } + + void RegisterReference(CEntitySAInterface** entity) + { + if (entity) + ((void(__thiscall*)(CEntitySAInterface*, CEntitySAInterface**))0x571B70)(this, entity); + } + void RemoveShadows() { using CStencilShadow_dtorByOwner = void*(__cdecl*)(CEntitySAInterface * pEntity); @@ -314,9 +326,11 @@ class CEntitySA : public virtual CEntity bool GetBonePosition(eBone boneId, CVector& position); bool SetBonePosition(eBone boneId, const CVector& position); - bool IsOnFire() override { return false; } + bool IsOnFire() const override { return false; } bool SetOnFire(bool onFire) override { return false; } + bool IsFireProof() const override; + // CEntitySA interface virtual void OnChangingPosition(const CVector& vecNewPosition) {} diff --git a/Client/game_sa/CFireManagerSA.cpp b/Client/game_sa/CFireManagerSA.cpp index 2243b84e4d2..b8c3617d875 100644 --- a/Client/game_sa/CFireManagerSA.cpp +++ b/Client/game_sa/CFireManagerSA.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * - * PROJECT: Multi Theft Auto v1.0 + * PROJECT: Multi Theft Auto * LICENSE: See LICENSE in the top level directory * FILE: game_sa/CFireManagerSA.cpp * PURPOSE: Fire manager @@ -13,113 +13,383 @@ #include "CFireManagerSA.h" #include "CFireSA.h" #include "CGameSA.h" +#include "CPoolsSA.h" +#include "game/CCamera.h" extern CGameSA* pGame; +// Reserve space to avoid reallocations +// This is not a hard limit - m_Fires can grow beyond MAX_FIRES if needed +static constexpr const std::uint32_t MAX_FIRES = 100; + CFireManagerSA::CFireManagerSA() { - for (int i = 0; i < MAX_FIRES; i++) - Fires[i] = new CFireSA((CFireSAInterface*)(CLASS_CFireManager + 40 * i)); // + 4 because thats the position of CFire array in CFireManager (see source) + m_Fires.reserve(MAX_FIRES); + m_Visited.reserve(MAX_FIRES); } CFireManagerSA::~CFireManagerSA() { - for (int i = 0; i < MAX_FIRES; i++) + m_Fires.clear(); +} + +CFire* CFireManagerSA::StartFire(const CVector& position, float size, CEntity* creator, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed, + bool makeNoise) +{ + // Call CWaterLevel::GetWaterLevelNoWaves + float waterLevel = 0.0f; + ((float(__cdecl*)(CVector, float*, float*, float*))0x6E8580)(position, &waterLevel, nullptr, nullptr); + if (waterLevel > position.fZ) + return nullptr; + + // Disable spreading fires (world property todo) + numGenerationsAllowed = 0; + + m_Fires.push_back(std::make_unique(this, creator, position, pGame->GetSystemTime() + lifetime, numGenerationsAllowed, makeNoise)); + return m_Fires.back().get(); +} + +CFire* CFireManagerSA::StartFire(CEntity* target, CEntity* creator, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed, bool makeNoise) +{ + if (target->IsOnFire() || target->IsFireProof()) + return nullptr; + + if (m_creationHandler && !m_creationHandler(target, creator)) + return nullptr; + + // Disable spreading fires (world property todo) + numGenerationsAllowed = 0; + + switch (target->GetEntityType()) { - delete Fires[i]; + case eEntityType::ENTITY_TYPE_PED: + { + auto* ped = dynamic_cast(target); + if (!ped->IsPedInControl()) + return nullptr; + + break; + } + case eEntityType::ENTITY_TYPE_VEHICLE: + { + auto* vehicle = dynamic_cast(target); + if (vehicle->GetVehicleInterface()->m_vehicleClass == VehicleClass::AUTOMOBILE && vehicle->GetDamageManager()->GetEngineStatus() >= 225) + return nullptr; + + break; + } + default: + break; } + + m_Fires.push_back(std::make_unique(this, creator, target, pGame->GetSystemTime() + lifetime, numGenerationsAllowed, makeNoise)); + return m_Fires.back().get(); } -void CFireManagerSA::ExtinguishPoint(CVector& vecPosition, float fRadius) +CFire* CFireManagerSA::FindNearestFire(CVector* position, bool checkExtinguished, bool checkScript) { - float fX = vecPosition.fX; - float fY = vecPosition.fY; - float fZ = vecPosition.fZ; - DWORD dwFunction = FUNC_ExtinguishPoint; + float nearestDistance2DSquared = std::numeric_limits::infinity(); + CFireSA* nearestFire = nullptr; - // clang-format off - __asm + for (const auto& fire : m_Fires) { - mov ecx, CLASS_CFireManager - push fRadius - push fZ - push fY - push fX - call dwFunction + if (checkExtinguished && fire->IsBeingExtinguished()) + continue; + + if (CEntitySAInterface* entityOnFire = fire->GetEntityOnFire()) + { + if (entityOnFire->nType == eEntityType::ENTITY_TYPE_PED) + continue; + } + + float distance2DSquared = (fire->GetPosition() - *position).Length2DSquared(); + if (distance2DSquared < nearestDistance2DSquared) + { + nearestDistance2DSquared = distance2DSquared; + nearestFire = fire.get(); + } } - // clang-format on + + return nearestFire; } -CFire* CFireManagerSA::StartFire(CEntity* entityTarget, CEntity* entityCreator, float fSize = DEFAULT_FIRE_PARTICLE_SIZE) +CFireSA* CFireManagerSA::GetRandomFire() const noexcept { - CFire* fire = FindFreeFire(); + if (m_Fires.empty()) + return nullptr; - if (fire != NULL) + return m_Fires[std::rand() % m_Fires.size()].get(); +} + +CFire* CFireManagerSA::GetStrongestFire() const +{ + CFireSA* strongestFire = nullptr; + for (std::size_t i = 0; i < m_Fires.size(); i++) { - fire->SetTarget(entityTarget); - fire->SetStrength(fSize); - fire->SetTimeToBurnOut(pGame->GetSystemTime() + 5000); - fire->SetSilent(false); - fire->Ignite(); + if (m_Visited[i]) + continue; + + if (!strongestFire || m_Fires[i]->GetStrength() > strongestFire->GetStrength()) + strongestFire = m_Fires[i].get(); } - return fire; + return strongestFire; +} + +CFireSA* CFireManagerSA::GetFire(CFireSAInterface* iface) noexcept +{ + return reinterpret_cast(reinterpret_cast(iface) - offsetof(CFireSA, m_interface)); } -CFire* CFireManagerSA::StartFire(CVector& vecPosition, float fSize = DEFAULT_FIRE_PARTICLE_SIZE) +void CFireManagerSA::ExtinguishPoint(const CVector& position, float radius) { - CFire* fire = FindFreeFire(); + float radiusSquared = radius * radius; - if (fire != NULL) + for (const auto& fire : m_Fires) { - fire->SetPosition(vecPosition); - fire->SetStrength(fSize); - fire->SetTimeToBurnOut(pGame->GetSystemTime() + 5000); - fire->SetSilent(false); - fire->Ignite(); - } + if ((fire->GetPosition() - position).LengthSquared() > radiusSquared) + continue; - return fire; + fire->Extinguish(); + } } -void CFireManagerSA::ExtinguishAllFires() +bool CFireManagerSA::ExtinguishPointWithWater(const CVector& position, float radius, float waterStrength) { - CFireSA* fire; - for (int i = 0; i < MAX_FIRES; i++) + float radiusSquared = radius * radius; + bool anyFireExtinguished = false; + + for (const auto& fire : m_Fires) { - fire = (CFireSA*)GetFire(i); - if (fire && fire->IsIgnited()) - fire->Extinguish(); + if ((fire->GetPosition() - position).LengthSquared() > radiusSquared) + continue; + + fire->ExtinguishWithWater(waterStrength); + anyFireExtinguished = true; } + + return anyFireExtinguished; +} + +void CFireManagerSA::ExtinguishAllFires() +{ + for (auto& fire : m_Fires) + fire->Extinguish(); +} + +bool CFireManagerSA::PlentyFiresAvailable() +{ + return m_Fires.size() >= 6; } -CFire* CFireManagerSA::GetFire(DWORD ID) +std::uint32_t CFireManagerSA::GetNumFiresInRange(const CVector& position, float radius) const { - if (ID < MAX_FIRES) - return Fires[ID]; - else - return NULL; + std::uint32_t firesCount = 0; + float radiusSquared = radius * radius; + + for (const auto& fire : m_Fires) + { + float dist = (fire->GetPosition() - position).Length2DSquared(); + if (dist <= radiusSquared) + firesCount++; + } + + return firesCount; } -DWORD CFireManagerSA::GetFireCount() +void CFireManagerSA::ClearExtinguishedFires() { - return *(DWORD*)CLASS_CFireManager; + m_Fires.erase(std::remove_if(m_Fires.begin(), m_Fires.end(), [](const std::unique_ptr& fire) { return !fire->IsActive(); }), m_Fires.end()); } -CFire* CFireManagerSA::FindFreeFire() +void CFireManagerSA::Update() { - CFireSA* fire; - for (int i = 0; i < MAX_FIRES; i++) + for (auto& fire : m_Fires) { - fire = (CFireSA*)GetFire(i); - if (fire && !fire->IsIgnited()) - return fire; + if (!fire->IsActive()) + continue; + + fire->ProcessFire(); + } + + std::size_t numFires = m_Fires.size(); + m_Visited.assign(numFires, false); + + // Find strongest un-visited fire, and sum of the strength of all fires within 6.0 units of it + // Based on this strength possibly create a shadow (if combined strength > 4), and coronas (combined strength > 6) + while (numFires > 0) + { + CFire* strongestFire = GetStrongestFire(); + if (!strongestFire) + break; + + const CVector& strongestFirePos = strongestFire->GetPosition(); + float totalStrength = 0.0f; + int totalCeilStrength = 0; + + for (std::size_t i = 0; i < m_Fires.size(); i++) + { + if (m_Visited[i]) + continue; + + const CVector& pos = m_Fires[i]->GetPosition(); + + float dx = pos.fX - strongestFirePos.fX; + float dy = pos.fY - strongestFirePos.fY; + if (std::abs(dx) > 6.0f || std::abs(dy) > 6.0f) + continue; + + if (dx * dx + dy * dy < 36.0f) + { + float strength = m_Fires[i]->GetStrength(); + + totalStrength += strength; + totalCeilStrength += static_cast(std::ceil(strength)); + + m_Visited[i] = true; + --numFires; + } + } + + if (totalStrength > 4.0f && totalCeilStrength > 0) + { + float direction = std::min(7.0f, totalStrength - 6.0f + 3.0f); + auto colorMult = GetRandomNumberInRange(0.6f, 1.0f); + + // The shadow is buggy, which is why R* set its intensity to 0 here + + // This corona also seems to not achieve what R* intended + // It is barely visible, positioned above the particle + if (totalStrength > 6.0f) + { + int strongestId = reinterpret_cast(strongestFire); + + CMatrix cameraMatrix; + pGame->GetCamera()->GetMatrix(&cameraMatrix); + + CVector point = strongestFire->GetPosition() + CVector(0.0f, 0.0f, 2.6f); + CVector diffVec = (cameraMatrix.GetPosition() - point); + diffVec.Normalize(); + point += diffVec * 3.5f; + + const auto RegisterCorona = [&](auto idx, CVector pos, char flare = 0) + { + ((void(__cdecl*)(int, void*, char, char, char, char, CVector*, float, float, int, char, bool, bool, int, float, bool, float, bool, float, + bool, bool))0x6FC580)(strongestId, nullptr, 40 * colorMult * 0.8f, 32 * colorMult * 0.8f, 20 * colorMult * 0.8f, 255, &pos, + direction * 0.5f, 70.0f, 1, flare, 0, 0, 0, 0.0f, false, 1.5f, 0, 15.0f, false, false); + }; + + RegisterCorona(strongestId, point, 2); + point.fZ += 2.0f; + + RegisterCorona(strongestId + 1, point); + + point.fZ -= 2.0f; + + CVector camRightNorm = cameraMatrix.vRight; + camRightNorm.fZ = 0.0f; + camRightNorm.Normalize(); + point += camRightNorm * 2.0f; + + RegisterCorona(strongestId + 2, point); + + point -= camRightNorm * 4.0f; + RegisterCorona(strongestId + 3, point); + } + } } - return NULL; + + if (pGame->GetSystemFrameCounter() % 30 == 0) + ClearExtinguishedFires(); +} + +static void __fastcall StaticStartFireAtCoors(void* gFireManagerSA, void* edx, CVector position, float size, int unused_1, CEntitySAInterface* creator, + std::uint32_t lifetime, std::uint8_t numGenerationsAllowed, int unused_2) +{ + CEntity* creatorEntity = creator ? pGame->GetPools()->GetEntity((DWORD*)creator) : nullptr; + pGame->GetFireManager()->StartFire(position, size, creatorEntity, lifetime, numGenerationsAllowed); +} + +static void __fastcall StaticStartFireAtEntity(void* gFireManagerSA, void* edx, CEntitySAInterface* target, CEntitySAInterface* creator, float size_unused, + int unused_1, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed) +{ + CPools* pools = pGame->GetPools(); + + CEntity* targetEntity = target ? pools->GetEntity((DWORD*)target) : nullptr; + CEntity* creatorEntity = creator ? pools->GetEntity((DWORD*)creator) : nullptr; + pGame->GetFireManager()->StartFire(targetEntity, creatorEntity, lifetime, numGenerationsAllowed); +} + +static CFireSAInterface* __fastcall StaticFindNearestFire(void* gFireManagerSA, void* edx, CVector* position, bool checkExtinguished, bool checkScript) +{ + CFire* fire = pGame->GetFireManager()->FindNearestFire(position, checkExtinguished, checkScript); + return fire ? fire->GetInterface() : nullptr; +} + +static void __fastcall StaticExtinguishPoint(void* gFireManagerSA, void* edx, CVector position, float radius) +{ + pGame->GetFireManager()->ExtinguishPoint(position, radius); +} + +static bool __fastcall StaticExtinguishPointWithWater(void* gFireManagerSA, void* edx, CVector position, float radius, float waterStrength) +{ + return pGame->GetFireManager()->ExtinguishPointWithWater(position, radius, waterStrength); +} + +static std::uint32_t __fastcall StaticGetNumFiresInRange(void* gFireManagerSA, void* edx, const CVector& position, float range) +{ + return pGame->GetFireManager()->GetNumFiresInRange(position, range); +} + +static void __fastcall StaticUpdate(void* gFireManagerSA) +{ + pGame->GetFireManager()->Update(); } -CFire* CFireManagerSA::GetFire(CFireSAInterface* fire) +void CFireManagerSA::StaticSetHooks() { - DWORD dwID = ((DWORD)fire - CLASS_CFireManager + 4) / sizeof(CFireSAInterface); - return GetFire(dwID); + // Patch calls to CFireManager::StartFire (point) + HookInstallCall(0x53A544, (DWORD)StaticStartFireAtCoors); // CCreepingFire::TryToStartFireAtCoors + HookInstallCall(0x56B9AA, (DWORD)StaticStartFireAtCoors); // CWorld::SetWorldOnFire + HookInstallCall(0x70765D, (DWORD)StaticStartFireAtCoors); // CShadows::GunShotSetsOilOnFire + HookInstallCall(0x70CB87, (DWORD)StaticStartFireAtCoors); // CShadows::UpdatePermanentShadows + HookInstallCall(0x70CC7C, (DWORD)StaticStartFireAtCoors); // CShadows::UpdatePermanentShadows + HookInstallCall(0x7373DA, (DWORD)StaticStartFireAtCoors); // CExplosion::AddExplosion + + // Patch calls to CFireManager::StartFire (entity) + HookInstallCall(0x4621D3, (DWORD)StaticStartFireAtEntity); // CRoadBlocks::CreateRoadBlockBetween2Points + HookInstallCall(0x4624A9, (DWORD)StaticStartFireAtEntity); // CRoadBlocks::CreateRoadBlockBetween2Points + HookInstallCall(0x5657DE, (DWORD)StaticStartFireAtEntity); // CWorld::SetPedsOnFire + HookInstallCall(0x565B5A, (DWORD)StaticStartFireAtEntity); // CWorld::SetCarsOnFire + HookInstallCall(0x6B39DF, (DWORD)StaticStartFireAtEntity); // CAutomobile::BlowUpCar + HookInstallCall(0x6B3E08, (DWORD)StaticStartFireAtEntity); // CAutomobile::BlowUpCarCutSceneNoExtras + HookInstallCall(0x6C6FCC, (DWORD)StaticStartFireAtEntity); // CHeli::BlowUpCar + HookInstallCall(0x6CD011, (DWORD)StaticStartFireAtEntity); // CPlane::BlowUpCar + HookInstallCall(0x7364A5, (DWORD)StaticStartFireAtEntity); // CBulletInfo::Update + HookInstallCall(0x73A063, (DWORD)StaticStartFireAtEntity); // CShotInfo::Update + + // Patch calls to CFireManager::FindNearestFire + HookInstallCall(0x603F49, (DWORD)StaticFindNearestFire); // CNearbyFireScanner::ScanForNearbyFires + HookInstallCall(0x6599AE, (DWORD)StaticFindNearestFire); // sub_659990 called by CTaskComplexExtinguishFireOnFoot::CreateFirstSubTask + HookInstallCall(0x65B180, (DWORD)StaticFindNearestFire); // CTaskComplexDriveFireTruck::CreateFirstSubTask + HookInstallCall(0x65B24A, (DWORD)StaticFindNearestFire); // CTaskComplexDriveFireTruck::ControlSubTask + HookInstallCall(0x65B2C3, (DWORD)StaticFindNearestFire); // CTaskComplexDriveFireTruck::ControlSubTask + HookInstallCall(0x69761E, (DWORD)StaticFindNearestFire); // sub_697600 called by CTaskComplexExtinguishFires::CreateNextSubTask + + // Patch calls to CFireManager::ExtinguishPoint + HookInstallCall(0x56A421, (DWORD)StaticExtinguishPoint); // CWorld::ClearExcitingStuffFromArea + HookInstallCall(0x56E945, (DWORD)StaticExtinguishPoint); // CPlayerInfo::MakePlayerSafe + + // Patch calls to CFireManager::ExtinguishPointWithWater + HookInstallCall(0x72A36B, (DWORD)StaticExtinguishPointWithWater); // CWaterCannon::Update_OncePerFrame + HookInstallCall(0x73A1E1, (DWORD)StaticExtinguishPointWithWater); // CShotInfo::Update + + // Patch call to CFireManager::GetNumFiresInRange + HookInstallCall(0x56B972, (DWORD)StaticGetNumFiresInRange); // CWorld::SetWorldOnFire + + // Patch call to CFireManager::Update + HookInstallCall(0x53C00A, (DWORD)StaticUpdate); // CGame::Process + + // Ignoring 0x539340 (CFireManager::PlentyFiresAvailable) because it’s inlined and has no xrefs + // Ignoring all functions/calls related to scripts & replay stuff } diff --git a/Client/game_sa/CFireManagerSA.h b/Client/game_sa/CFireManagerSA.h index f1d4da3e23d..a1a19b7a493 100644 --- a/Client/game_sa/CFireManagerSA.h +++ b/Client/game_sa/CFireManagerSA.h @@ -12,34 +12,51 @@ #pragma once #include +#include class CEntity; class CFireSA; class CFireSAInterface; -#define FUNC_ExtinguishPoint 0x539450 - -#define ARRAY_CFire (VAR_CFireCount + 4) -#define CLASS_CFireManager 0xB71F80 - -#define DEFAULT_FIRE_PARTICLE_SIZE 1.8 -#define MAX_FIRES 60 - class CFireManagerSA : public CFireManager { + friend class CFireSA; + private: - CFireSA* Fires[MAX_FIRES]; + std::vector> m_Fires; + std::vector m_Visited; + + FireCreationHandler m_creationHandler{nullptr}; + FireDestructionHandler m_destructionHandler{nullptr}; public: CFireManagerSA(); ~CFireManagerSA(); - void ExtinguishPoint(CVector& vecPosition, float fRadius); - CFire* StartFire(CEntity* entityTarget, CEntity* entityCreator, float fSize); - CFire* StartFire(CVector& vecPosition, float fSize); - void ExtinguishAllFires(); - CFire* GetFire(DWORD ID); - DWORD GetFireCount(); - CFire* FindFreeFire(); - CFire* GetFire(CFireSAInterface* fire); + CFire* StartFire(const CVector& position, float size, CEntity* creator, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed = 100, + bool makeNoise = true) override; + CFire* StartFire(CEntity* target, CEntity* creator, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed = 100, bool makeNoise = true) override; + CFire* FindNearestFire(CVector* position, bool checkExtinguished, bool checkScript) override; + CFireSA* GetRandomFire() const noexcept; + + static CFireSA* GetFire(CFireSAInterface* iface) noexcept; + + void ExtinguishPoint(const CVector& position, float radius) override; + bool ExtinguishPointWithWater(const CVector& position, float radius, float waterStrength) override; + void ExtinguishAllFires() override; + + bool PlentyFiresAvailable() override; + std::uint32_t GetNumFiresInRange(const CVector& position, float radius) const override; + std::size_t GetNumFires() const noexcept { return m_Fires.size(); } + + void Update(); + + void SetFireCreationHandler(FireCreationHandler creationHandler) noexcept override { m_creationHandler = creationHandler; } + void SetFireDestructionHandler(FireDestructionHandler destructionHandler) noexcept override { m_destructionHandler = destructionHandler; } + + static void StaticSetHooks(); + +private: + void ClearExtinguishedFires(); + CFire* GetStrongestFire() const; }; diff --git a/Client/game_sa/CFireSA.cpp b/Client/game_sa/CFireSA.cpp index 1c67be69842..89921976862 100644 --- a/Client/game_sa/CFireSA.cpp +++ b/Client/game_sa/CFireSA.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * - * PROJECT: Multi Theft Auto v1.0 + * PROJECT: Multi Theft Auto * LICENSE: See LICENSE in the top level directory * FILE: game_sa/CFireSA.cpp * PURPOSE: Fire @@ -12,263 +12,478 @@ #include "StdInc.h" #include "CEntitySA.h" #include "CFireSA.h" +#include "CFxSystemSA.h" #include "CGameSA.h" #include "CPoolsSA.h" #include #include +#include "CFxSA.h" +#include "CFireManagerSA.h" +#include "CCreepingFireSA.h" +#include +#include extern CGameSA* pGame; -/** - * Put the fire out - */ -void CFireSA::Extinguish() +CFireSA::CFireSA(CFireManagerSA* fireMgr, CEntity* creator, CVector position, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed, bool makeNoise) + : m_fireManager{fireMgr}, m_creator{creator} { - DWORD dwFunction = FUNC_Extinguish; - DWORD dwPointer = (DWORD)internalInterface; - // clang-format off - __asm - { - mov ecx, dwPointer - call dwFunction - } - // clang-format on - internalInterface->bActive = false; + SetSilent(!makeNoise); + SetActive(true); + SetLifetime(pGame->GetSystemTime() + static_cast(GetRandomNumberInRange(1.0f, 1.3f) * static_cast(lifetime))); + SetNumGenerationsAllowed(numGenerationsAllowed); + SetCreator(creator); + SetPosition(position); + SetStrength(1.0f); } -/** - * Gets the position the fire is burning at - * @return CVector * containing the fire's position - */ -CVector* CFireSA::GetPosition() +CFireSA::CFireSA(CFireManagerSA* fireMgr, CEntity* creator, CEntity* target, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed, bool makeNoise) + : m_fireManager{fireMgr}, m_creator{creator}, m_entityOnFire{target} { - return &internalInterface->vecPosition; -} + SetSilent(!makeNoise); + SetActive(true); + SetNumGenerationsAllowed(numGenerationsAllowed); + SetCreator(creator); + SetEntityOnFire(target); + SetPosition(*target->GetPosition()); -/** - * Set the position the fire is burning at. This won't have any effect if this fire has a target set - * @param vecPosition CVector * containing the desired position for the fire. - * @see CFireSA::SetTarget - */ -void CFireSA::SetPosition(CVector& vecPosition) -{ - internalInterface->entityTarget = 0; - MemCpyFast(&internalInterface->vecPosition, &vecPosition, sizeof(CVector)); -} + std::uint32_t new_lifetime = pGame->GetSystemTime() + GetRandomNumberInRange(0.0f, 1000.0f) + lifetime; -/** - * Set the time that the fire will be extinguished. - * @param dwTime DWORD containing the time that the fire will be extinguished. This is in game-time units which can be got from CGame::GetSystemTime; - */ -void CFireSA::SetTimeToBurnOut(DWORD dwTime) -{ - internalInterface->nTimeToBurn = dwTime; -} + switch (target->GetEntityType()) + { + case eEntityType::ENTITY_TYPE_PED: + { + auto* pedInterface = dynamic_cast(target)->GetPedInterface(); + pedInterface->pFireOnPed = &m_interface; -DWORD CFireSA::GetTimeToBurnOut() -{ - return internalInterface->nTimeToBurn; + if (pedInterface->bPedType < 2) + new_lifetime = pGame->GetSystemTime() + 2333; + break; + } + case eEntityType::ENTITY_TYPE_VEHICLE: + { + dynamic_cast(target)->GetVehicleInterface()->m_pFire = &m_interface; + new_lifetime = pGame->GetSystemTime() + GetRandomNumberInRange(0.0f, 1000.0f) + 3000; + break; + } + case eEntityType::ENTITY_TYPE_OBJECT: + { + dynamic_cast(target)->GetObjectInterface()->pFire = &m_interface; + break; + } + default: + break; + } + + SetLifetime(new_lifetime); + SetStrength(1.0f); } -CEntity* CFireSA::GetCreator() +void CFireSA::Extinguish(bool ProcessPedCall) { - CEntity* creatorEntity = NULL; - CEntitySAInterface* createEntitySA = internalInterface->entityCreator; - CPoolsSA* pPools = ((CPoolsSA*)pGame->GetPools()); - if (pPools && createEntitySA) + if (!IsActive()) + return; + + // If the fire was created by a script, we need to remove CClientFire object + if (IsCreatedByScript()) + { + if (m_fireManager->m_destructionHandler) + m_fireManager->m_destructionHandler(this); + } + + SetLifetime(0); + + SetActive(false); + SetBeingExtinguished(false); + + DestroyFxSys(); + + if (CEntitySAInterface* entityOnFire = GetEntityOnFire()) { - switch (createEntitySA->nType) + switch (entityOnFire->nType) { - case ENTITY_TYPE_PED: + case eEntityType::ENTITY_TYPE_PED: { - SClientEntity* pPedClientEntity = pPools->GetPed((DWORD*)createEntitySA); - if (pPedClientEntity) + reinterpret_cast(entityOnFire)->pFireOnPed = nullptr; + + // Fix Github #3249 (PLAYER_ON_FIRE task is not aborted after the fire is extinguished) + if (!ProcessPedCall) { - creatorEntity = pPedClientEntity->pEntity; + auto* ped = pGame->GetPools()->GetPed(reinterpret_cast(entityOnFire)); + if (!ped || !ped->pEntity) + return; + + CTaskManager* taskManager = ped->pEntity->GetPedIntelligence()->GetTaskManager(); + if (!taskManager) + return; + + taskManager->RemoveTaskSecondary(TASK_SECONDARY_PARTIAL_ANIM, TASK_SIMPLE_PLAYER_ON_FIRE); } break; } - case ENTITY_TYPE_VEHICLE: + case eEntityType::ENTITY_TYPE_VEHICLE: { - SClientEntity* pVehicleClientEntity = pPools->GetVehicle((DWORD*)createEntitySA); - if (pVehicleClientEntity) - { - creatorEntity = pVehicleClientEntity->pEntity; - } + reinterpret_cast(entityOnFire)->m_pFire = nullptr; break; } - default: + case eEntityType::ENTITY_TYPE_OBJECT: // R* forgot to set this to nullptr here { - creatorEntity = NULL; + reinterpret_cast(entityOnFire)->pFire = nullptr; + break; } + default: + break; } + + entityOnFire->CleanUpOldReference(&m_interface.m_entityOnFire); + SetEntityOnFire(nullptr); + } + + if (CEntitySAInterface* creator = GetCreator()) + { + creator->CleanUpOldReference(&m_interface.m_creator); + SetCreator(nullptr); } - return creatorEntity; } -CEntity* CFireSA::GetEntityOnFire() +void CFireSA::ExtinguishWithWater(float waterStrength) { - CEntity* TargetEntity = NULL; - CEntitySAInterface* TargetEntitySA = internalInterface->entityTarget; - CPoolsSA* pPools = ((CPoolsSA*)pGame->GetPools()); - if (pPools && TargetEntitySA) + float currentStrength = GetStrength(); + float newStrength = currentStrength - waterStrength * pGame->GetTimeStepInSeconds(); + SetStrength(newStrength, false); + + const CVector& firePos = GetPosition(); + CVector particlePos = + firePos + CVector(GetRandomNumberInRange(-1.28f, 1.28f), GetRandomNumberInRange(-1.28f, 1.28f), GetRandomNumberInRange(-0.64f, 0.64f)); + FxPrtMult_c fxPrt{{1.0f, 1.0f, 1.0f, 0.6f}, 0.75f, 0.0f, 0.4f}; + + static FxSystem_c* smokeII3expand = pGame->GetFx()->GetInterface()->m_fxSysSmoke2; + + const auto AddParticle = [&](CVector velocity) + { + // Call FxSystem_c::AddParticle + ((int(__thiscall*)(FxSystem_c*, const CVector*, const CVector*, float, FxPrtMult_c*, float, float, float, int))0x4AA440)( + smokeII3expand, &particlePos, &velocity, 0, &fxPrt, -1.0f, 1.2f, 0.6f, 0); + }; + + AddParticle(CVector(0.0f, 0.0f, 0.8f)); + AddParticle(CVector(0.0f, 0.0f, 1.4f)); + + SetBeingExtinguished(true); + + if (newStrength >= 0.0f) { - switch (TargetEntitySA->nType) + if (static_cast(currentStrength) != static_cast(newStrength)) + CreateFxSysForStrength(firePos, nullptr); + } + else + Extinguish(); +} + +void CFireSA::SetPosition(const CVector& position, bool updateParticle) +{ + m_interface.m_position = position; + + if (m_interface.m_fxSystem && updateParticle) + m_interface.m_fxSystem->SetOffsetPos(position); +} + +void CFireSA::SetStrength(float strength, bool updateFX) +{ + m_interface.m_strength = strength; + + if (updateFX) + CreateFxSysForStrength(GetPosition(), nullptr); +} + +void CFireSA::SetCreator(CEntity* entity) +{ + CEntitySAInterface* entityInterface = entity ? entity->GetInterface() : nullptr; + if (entityInterface) + entityInterface->CleanUpOldReference(&m_interface.m_creator); + + m_interface.m_creator = entityInterface; + m_creator = entity; + + if (entityInterface) + entityInterface->RegisterReference(&m_interface.m_creator); +} + +void CFireSA::SetEntityOnFire(CEntity* entity) +{ + CEntitySAInterface* entityInterface = entity ? entity->GetInterface() : nullptr; + if (entityInterface) + entityInterface->CleanUpOldReference(&m_interface.m_entityOnFire); + + m_interface.m_entityOnFire = entityInterface; + m_entityOnFire = entity; + + if (entityInterface) + entityInterface->RegisterReference(&m_interface.m_entityOnFire); +} + +void CFireSA::ProcessFire() +{ + float currentStrength = GetStrength(); + float newStrength = std::min(3.0f, currentStrength + pGame->GetTimeStep() / 500.0f); + SetStrength(newStrength, false); + + if (static_cast(currentStrength) != static_cast(newStrength)) + SetStrength(currentStrength, false); + + if (m_entityOnFire) + { + SetPosition(*m_entityOnFire->GetPosition()); + + switch (m_entityOnFire->GetEntityType()) { - case ENTITY_TYPE_PED: + case eEntityType::ENTITY_TYPE_PED: { - SClientEntity* pPedClientEntity = pPools->GetPed((DWORD*)TargetEntitySA); - if (pPedClientEntity) + auto* pedInterface = reinterpret_cast(m_interface.m_entityOnFire); + if (pedInterface->pFireOnPed != &m_interface) { - TargetEntity = pPedClientEntity->pEntity; + Extinguish(); + return; } + + if (pedInterface->pedState == 0x36 || pedInterface->pedState == 0x37) + m_interface.m_position.fZ -= 1.0f; + + if (pedInterface->pedFlags.bInVehicle && pedInterface->pVehicle) + { + if (pedInterface->pVehicle->m_nModelIndex != 407 && pedInterface->pVehicle->m_vehicleClass == VehicleClass::AUTOMOBILE) + pedInterface->pVehicle->m_nHealth = 75.0f; + } + else if (pedInterface->bPedType > 0 && (pedInterface->pedState != 0x36 && pedInterface->pedState != 0x37)) + reinterpret_cast(pedInterface)->bDestroyed = true; + break; } - case ENTITY_TYPE_VEHICLE: + case eEntityType::ENTITY_TYPE_VEHICLE: { - SClientEntity* pVehicleClientEntity = pPools->GetVehicle((DWORD*)TargetEntitySA); - if (pVehicleClientEntity) + auto* vehicleInterface = reinterpret_cast(m_interface.m_entityOnFire); + if (vehicleInterface->m_pFire != &m_interface) { - TargetEntity = pVehicleClientEntity->pEntity; + Extinguish(); + return; } + + // CVehicle::InflictDamage + ((void(__thiscall*)(CVehicleSAInterface*, CEntitySAInterface*, int, float, CVector))0x6D7C90)( + vehicleInterface, GetCreator(), WEAPONTYPE_FLAMETHROWER, pGame->GetTimeStep() * 1.2f, CVector{}); + + if (vehicleInterface->m_vehicleClass == VehicleClass::AUTOMOBILE) + { + CVector fireOnVehPos = reinterpret_cast(pGame->GetModelInfo(vehicleInterface->m_nModelIndex)->GetInterface()) + ->pVisualInfo->vecDummies[0] + + CVector(0, 0, 0.15f); + + CMatrix vehMatrix; + dynamic_cast(m_entityOnFire)->GetMatrix(&vehMatrix); + + SetPosition(vehMatrix.TransformVector(fireOnVehPos)); + } + break; } - default: + } + + if (m_interface.m_fxSystem) + { + float doubleStep = pGame->GetTimeStep() * 2.0f; + CVector vec = GetPosition() + reinterpret_cast(m_interface.m_entityOnFire)->m_vecLinearVelocity * doubleStep; + m_interface.m_fxSystem->SetOffsetPos(vec); + } + } + + CVector& firePosition = GetPosition(); + + if (!m_entityOnFire || m_entityOnFire->GetEntityType() != eEntityType::ENTITY_TYPE_VEHICLE) + { + CPed* player = pGame->GetPedContext(); + if (!player->GetVehicle() && !player->IsOnFire() && !player->IsFireProof() && !player->GetPedInterface()->m_pAttachedEntity) + { + if ((*player->GetPosition() - firePosition).LengthSquared() < 1.2f) { - TargetEntity = NULL; + // CPlayerPed::DoStuffToGoOnFire + ((void(__thiscall*)(CPedSAInterface*))0x60A020)(player->GetPedInterface()); + m_fireManager->StartFire(player, m_creator, 7000, 100, !IsSilent()); } } } - return TargetEntity; -} -void CFireSA::SetTarget(CEntity* entity) -{ - if (entity) + if (rand() % 32 == 0) { - CEntitySA* pEntitySA = dynamic_cast(entity); - if (pEntitySA) - internalInterface->entityTarget = pEntitySA->GetInterface(); + SPoolData vehiclesPool; + pGame->GetPools()->GetVehiclesPool(&vehiclesPool); + + for (uint i = 0; i < vehiclesPool.ulCount; i++) + { + CEntitySA* pEntitySA = vehiclesPool.arrayOfClientEntities[i].pEntity; + if (!pEntitySA) + continue; + + if ((*pEntitySA->GetPosition() - firePosition).LengthSquared() >= 4.0f) + continue; + + CVehicleSA* vehicle = dynamic_cast(pEntitySA); + CVehicleSAInterface* vehicleInterface = vehicle->GetVehicleInterface(); + + if (static_cast(vehicleInterface->m_vehicleSubClass) == VehicleClass::BMX) + { + CPed* player = pGame->GetPedContext(); + // CPlayerPed::DoStuffToGoOnFire + ((void(__thiscall*)(CPedSAInterface*))0x60A020)(player->GetPedInterface()); + m_fireManager->StartFire(player, m_creator, 7000, 100, !IsSilent()); + + // CVehicle::FindTyreNearestPoint + int tyre = ((int(__thiscall*)(CVehicleSAInterface*, CVector2D))0x6D7BC0)(vehicleInterface, CVector2D(firePosition.fX, firePosition.fY)); + vehicleInterface->BurstTyre(tyre + 13, false); + } + else + m_fireManager->StartFire(pEntitySA, m_creator, 7000, 100, !IsSilent()); + } } - else + + if (rand() % 4 == 0) { - internalInterface->entityTarget = NULL; + SPoolData objectsPool; + pGame->GetPools()->GetObjectsPool(&objectsPool); + + for (uint i = 0; i < objectsPool.ulCount; i++) + { + CEntitySA* pEntitySA = objectsPool.arrayOfClientEntities[i].pEntity; + if (!pEntitySA) + continue; + + if ((*pEntitySA->GetPosition() - firePosition).LengthSquared() >= 9.0f) + continue; + + // CObject::ObjectFireDamage + ((void(__thiscall*)(CObjectSAInterface*, float, CEntitySAInterface*))0x5A1580)(dynamic_cast(pEntitySA)->GetObjectInterface(), + pGame->GetTimeStep() * 8.0f, m_interface.m_creator); + } } -} -bool CFireSA::IsIgnited() -{ - return internalInterface->bActive; -} + std::uint8_t numGens = GetNumGenerationsAllowed(); -bool CFireSA::IsFree() -{ - if (!internalInterface->bActive && !internalInterface->bCreatedByScript) - return true; - else - return false; -} + if (numGens > 0 && rand() % 128 == 0 && m_fireManager->GetNumFires() < 25) + { + CVector direction(GetRandomNumberInRange(-1.0f, 1.0f), GetRandomNumberInRange(-1.0f, 1.0f), 0.0f); + direction.Normalize(); -void CFireSA::SetSilent(bool bSilent) -{ - internalInterface->bMakesNoise = !bSilent; -} + CVector newPos = firePosition + direction * GetRandomNumberInRange(2.0f, 2.1f); // default: 2-2.003 + newPos.fZ = firePosition.fZ + 2.0f; -bool CFireSA::IsBeingExtinguished() -{ - return internalInterface->bBeingExtinguished; -} + CCreepingFireSA::TryToStartFireAtCoors(newPos, numGens - 1, m_creator ? m_creator->GetInterface() : nullptr, false, 10.0f); + } -void CFireSA::Ignite() -{ - internalInterface->bActive = true; + if (GetStrength() <= 2.0f && numGens > 0 && rand() % 16 == 0) + { + // We don’t have a fire limit, but we want to preserve the original chance of fire spreading + // as if there were only 60 slots + float probabilityScale = std::min(static_cast(m_fireManager->GetNumFires()) / 60.0f, 1.0f); + + if ((rand() / static_cast(RAND_MAX)) < probabilityScale) + { + CFireSA* randomFire = m_fireManager->GetRandomFire(); + if (randomFire != this && randomFire->IsActive() && randomFire->GetStrength() <= 1.0f) + { + const CVector& randomFirePos = randomFire->GetPosition(); + if ((firePosition - randomFirePos).LengthSquared() < 12.25f) + { + randomFire->SetPosition(randomFirePos * 0.3f + firePosition * 0.7f); + SetStrength(GetStrength() + 1.0f); + + SetLifetime(std::max(GetLifetime(), static_cast(pGame->GetSystemTime() + 7000))); + SetNumGenerationsAllowed(std::min(numGens, randomFire->GetNumGenerationsAllowed())); + + randomFire->Extinguish(); + } + } + } + } - CVector* vecPosition = GetPosition(); - DWORD dwFunc = FUNC_CreateFxSysForStrength; - DWORD dwThis = (DWORD)internalInterface; - // clang-format off - __asm + if (m_interface.m_fxSystem) { - mov ecx, dwThis - push 0 - push vecPosition - call dwFunc + float unused_field; + float fractionalPart = std::modf(GetStrength(), &unused_field); + m_interface.m_fxSystem->SetConstTime(true, + std::min(std::max(0.0f, (GetLifetime() - static_cast(pGame->GetSystemTime())) / 3500.0f), fractionalPart)); } - // clang-format on - internalInterface->bBeingExtinguished = 0; - internalInterface->bFirstGeneration = 1; - internalInterface->nNumGenerationsAllowed = 100; -} + CMatrix camMatrix; + pGame->GetCamera()->GetMatrix(&camMatrix); -float CFireSA::GetStrength() -{ - return internalInterface->Strength; + if (GetLifetime() > pGame->GetSystemTime() && (camMatrix.GetPosition() - firePosition).Length2D() < m_interface.m_removalDistance) + { + // CPointLights::AddLight + float color = (float)(rand() % 128) / 512.0f; + ((void(__cdecl*)(char, const CVector&, const CVector&, float, float, float, float, char, bool, CEntitySAInterface*))0x7000E0)( + 0, firePosition, CVector(), 8.0f, color, color, 0.0f, 0, false, nullptr); + } + else + { + if (GetStrength() <= 1.0f) + Extinguish(); + else + { + SetStrength(GetStrength() - 1.0f); + SetLifetime(pGame->GetSystemTime() + 7000); + } + } } -void CFireSA::SetStrength(float fStrength) +void CFireSA::DestroyFxSys() { - internalInterface->Strength = fStrength; -} + if (!m_interface.m_fxSystem) + return; -void CFireSA::SetNumGenerationsAllowed(char generations) -{ - internalInterface->nNumGenerationsAllowed = generations; + m_interface.m_fxSystem->Kill(); + m_interface.m_fxSystem = nullptr; } -//////////////////////////////////////////////////////////////////////// -// CFire::Extinguish -// -// Fix GH #3249 (PLAYER_ON_FIRE task is not aborted after the fire is extinguished) -//////////////////////////////////////////////////////////////////////// -static void AbortFireTask(CEntitySAInterface* entityOnFire, DWORD returnAddress) +void CFireSA::CreateFxSysForStrength(const CVector& position, RwMatrix* matrix) { - // We can't and shouldn't remove the task if we're in CTaskSimplePlayerOnFire::ProcessPed. Otherwise we will crash. - if (returnAddress == 0x633783) - return; + DestroyFxSys(); - auto* ped = pGame->GetPools()->GetPed(reinterpret_cast(entityOnFire)); - if (!ped || !ped->pEntity) - return; + const char* particleName = "fire"; + float strength = GetStrength(); - CTaskManager* taskManager = ped->pEntity->GetPedIntelligence()->GetTaskManager(); - if (!taskManager) + if (strength > 1.0f) + particleName = strength > 2.0f ? "fire_large" : "fire_med"; + + auto* fx = pGame->GetFxManagerSA()->CreateFxSystem(particleName, position, matrix, true, !IsSilent()); + if (!fx) return; - taskManager->RemoveTaskSecondary(TASK_SECONDARY_PARTIAL_ANIM, TASK_SIMPLE_PLAYER_ON_FIRE); + if (auto* fxSystemInterface = fx->GetInterface()) + m_interface.m_fxSystem = static_cast(fxSystemInterface); + + if (m_interface.m_fxSystem) + m_interface.m_fxSystem->Play(); } -#define HOOKPOS_CFire_Extinguish 0x539429 -#define HOOKSIZE_CFire_Extinguish 6 -static constexpr intptr_t CONTINUE_CFire_Extinguish = 0x53942F; -static void __declspec(naked) HOOK_CFire_Extinguish() +static void __fastcall StaticExtinguish(CFireSAInterface* fireInterface) { - MTA_VERIFY_HOOK_LOCAL_SIZE; + CFireSA* fire = CFireManagerSA::GetFire(fireInterface); + if (!fire) + return; - // clang-format off - __asm - { - mov [eax+730h], edi - - push ebx - mov ebx, [esp+12] - push edi - push esi - - push ebx // returnAddress - push eax // entityOnFire - call AbortFireTask - add esp, 8 - - pop esi - pop edi - pop ebx - jmp CONTINUE_CFire_Extinguish - } - // clang-format on + // We can't and shouldn't remove the task if we're in CTaskSimplePlayerOnFire::ProcessPed. Otherwise we will crash. + fire->Extinguish(_ReturnAddress() == reinterpret_cast(0x633783)); } void CFireSA::StaticSetHooks() { - EZHookInstall(CFire_Extinguish); + // Patch calls to CFire::Extinguish + HookInstallCall(0x442157, (DWORD)StaticExtinguish); // CGameLogic::RestorePlayerStuffDuringResurrection + HookInstallCall(0x59F81B, (DWORD)StaticExtinguish); // CObject::~CObject + HookInstallCall(0x5E8714, (DWORD)StaticExtinguish); // CPed::~CPed + HookInstallCall(0x60CD7F, (DWORD)StaticExtinguish); // CPlayerPed::SetInitialState + HookInstallCall(0x63377E, (DWORD)StaticExtinguish); // CTaskSimplePlayerOnFire::ProcessPed + HookInstallCall(0x639224, (DWORD)StaticExtinguish); // CTaskComplexOnFire::ControlSubTask + HookInstallCall(0x698261, (DWORD)StaticExtinguish); // CTaskComplexExtinguishFires::CreateNextSubTask + HookInstallCall(0x6D24A6, (DWORD)StaticExtinguish); // CVehicle::ExtinguishCarFire + HookInstallCall(0x6E2C0B, (DWORD)StaticExtinguish); // CVehicle::~CVehicle } diff --git a/Client/game_sa/CFireSA.h b/Client/game_sa/CFireSA.h index 67a5e74eaac..8a07d1636a5 100644 --- a/Client/game_sa/CFireSA.h +++ b/Client/game_sa/CFireSA.h @@ -1,6 +1,6 @@ /***************************************************************************** * - * PROJECT: Multi Theft Auto v1.0 + * PROJECT: Multi Theft Auto * LICENSE: See LICENSE in the top level directory * FILE: game_sa/CFireSA.h * PURPOSE: Header file for fire class @@ -14,56 +14,100 @@ #include #include +class CEntitySA; +class CFxSystemSAInterface; class CEntitySAInterface; -class FxSystem_c; - -#define FUNC_Extinguish 0x5393F0 -#define FUNC_CreateFxSysForStrength 0x539360 +class RwMatrix; +// Even though we have our own fire implementation, we still need to return +// a CFireSAInterface to ensure the correct memory layout is returned to GTA +// when performing operations on the fire object (e.g., FindNearestFire). +// We cannot return CFireSA directly, because its structure is offset by 4 bytes +// due to the vfptr at offset 0. class CFireSAInterface { public: - BYTE bActive : 1; - BYTE bCreatedByScript : 1; - BYTE bMakesNoise : 1; - BYTE bBeingExtinguished : 1; - BYTE bFirstGeneration : 1; - WORD ScriptReferenceIndex; - CVector vecPosition; - CEntitySAInterface* entityTarget; - CEntitySAInterface* entityCreator; - DWORD nTimeToBurn; - float Strength; - signed char nNumGenerationsAllowed; - BYTE RemovalDist; - FxSystem_c* m_fxSysPtr; + struct + { + std::uint8_t isActive : 1; + std::uint8_t createdByScript : 1; // unused in MTA + std::uint8_t makeNoise : 1; + std::uint8_t isBeingExtinguished : 1; + std::uint8_t isFirstGeneration : 1; // unused in MTA + } m_flags{}; + + std::int16_t scriptRefIndex{0}; // unused in MTA + CVector m_position; + CEntitySAInterface* m_entityOnFire{nullptr}; + CEntitySAInterface* m_creator{nullptr}; + std::uint32_t m_lifetime{0}; + float m_strength{1.0f}; + std::uint8_t m_numGenerationsAllowed{100}; + std::uint8_t m_removalDistance{60}; + CFxSystemSAInterface* m_fxSystem{nullptr}; }; class CFireSA : public CFire { -private: - CFireSAInterface* internalInterface; + friend class CFireManagerSA; + friend class CCreepingFireSA; public: - CFireSA(CFireSAInterface* fireInterface) { internalInterface = fireInterface; } - - void Extinguish(); - CVector* GetPosition(); - void SetPosition(CVector& vecPosition); - void SetTimeToBurnOut(DWORD dwTime); - DWORD GetTimeToBurnOut(); - CEntity* GetCreator(); - CEntity* GetEntityOnFire(); - void SetTarget(CEntity* entity); - bool IsIgnited(); - bool IsFree(); - void SetSilent(bool bSilent); - bool IsBeingExtinguished(); - void Ignite(); - float GetStrength(); - void SetStrength(float fStrength); - void SetNumGenerationsAllowed(char generations); - CFireSAInterface* GetInterface() { return internalInterface; } + CFireSA(CFireManagerSA* fireMgr, CEntity* creator, CVector position, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed = 100, + bool makeNoise = true); + CFireSA(CFireManagerSA* fireMgr, CEntity* creator, CEntity* target, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed = 100, + bool makeNoise = true); + + CFireSAInterface* GetInterface() noexcept { return &m_interface; } + + // Always pass ProcessPedCall = false, unless you know what you're doing. This is only used when extinguishing fire on a ped, and it will prevent the + // PLAYER_ON_FIRE task from being aborted, which can cause crashes if the task is removed while it's being processed. + void Extinguish(bool ProcessPedCall = false) override; + void ExtinguishWithWater(float waterStrength); + + bool IsActive() const noexcept { return m_interface.m_flags.isActive; } + void SetActive(bool active) noexcept { m_interface.m_flags.isActive = active; } + + bool IsBeingExtinguished() const noexcept { return m_interface.m_flags.isBeingExtinguished; } + void SetBeingExtinguished(bool extinguished) noexcept { m_interface.m_flags.isBeingExtinguished = extinguished; } + + bool IsSilent() const noexcept { return !m_interface.m_flags.makeNoise; } + void SetSilent(bool silent) noexcept { m_interface.m_flags.makeNoise = !silent; } + + std::uint8_t GetNumGenerationsAllowed() const noexcept { return m_interface.m_numGenerationsAllowed; } + void SetNumGenerationsAllowed(std::uint8_t numGenerationsAllowed) noexcept { m_interface.m_numGenerationsAllowed = numGenerationsAllowed; } + + std::uint32_t GetLifetime() const noexcept override { return m_interface.m_lifetime; } + void SetLifetime(std::uint32_t lifetime) noexcept override { m_interface.m_lifetime = lifetime; } + + void SetPosition(const CVector& position, bool updateParticle = false) override; + CVector& GetPosition() noexcept override { return m_interface.m_position; } + + float GetStrength() const noexcept override { return m_interface.m_strength; } + void SetStrength(float strength, bool updateFX = true) override; + + void SetCreatedByScript(bool createdByScript) noexcept override { m_interface.m_flags.createdByScript = createdByScript; } + bool IsCreatedByScript() const noexcept override { return m_interface.m_flags.createdByScript; } + + void ProcessFire(); static void StaticSetHooks(); + +private: + CFireSAInterface m_interface{}; + CEntity* m_entityOnFire{nullptr}; + CEntity* m_creator{nullptr}; + bool m_createdByScript{false}; // created by createFire function + + CFireManagerSA* m_fireManager{nullptr}; + +private: + CEntitySAInterface* GetCreator() const noexcept { return m_interface.m_creator; } + void SetCreator(CEntity* entity) override; + + CEntitySAInterface* GetEntityOnFire() const noexcept { return m_interface.m_entityOnFire; } + void SetEntityOnFire(CEntity* entity); + + void DestroyFxSys(); + void CreateFxSysForStrength(const CVector& position, RwMatrix* matrix); }; diff --git a/Client/game_sa/CFxSA.h b/Client/game_sa/CFxSA.h index f846c0d444a..66b9cb9140f 100644 --- a/Client/game_sa/CFxSA.h +++ b/Client/game_sa/CFxSA.h @@ -32,6 +32,21 @@ class FxSystem_c; #define FUNC_CFx_TriggerFootSplash 0x4a1150 #define FUNC_FXSystem_c_AddParticle 0x4AA440 +struct FxPrtMult_c +{ + struct + { + float red; + float green; + float blue; + float alpha; + } m_color; + + float m_fSize; + float unk; + float m_fLife; +}; + class CFxSAInterface { public: @@ -44,7 +59,7 @@ class CFxSAInterface FxSystem_c* m_fxSysSand; FxSystem_c* m_fxSysSand2; FxSystem_c* m_fxSysSmokeHuge; - FxSystem_c* m_fxSysSmoke2; + FxSystem_c* m_fxSysSmoke2; // prt_smokeII_3_expand FxSystem_c* m_fxSysSpark; FxSystem_c* m_fxSysSpark2; FxSystem_c* m_fxSysSplash; @@ -61,6 +76,8 @@ class CFxSA : public CFx public: CFxSA(CFxSAInterface* pInterface) { m_pInterface = pInterface; } + CFxSAInterface* GetInterface() override { return m_pInterface; } + void AddBlood(CVector& vecPosition, CVector& vecDirection, int iCount, float fBrightness); void AddWood(CVector& vecPosition, CVector& vecDirection, int iCount, float fBrightness); void AddSparks(CVector& vecPosition, CVector& vecDirection, float fForce, int iCount, CVector vecAcrossLine, unsigned char ucBlurIf0, float fSpread, @@ -81,19 +98,4 @@ class CFxSA : public CFx private: CFxSAInterface* m_pInterface; - - struct FxPrtMult_c - { - struct - { - float red; - float green; - float blue; - float alpha; - } m_color; - - float m_fSize; - float unk; - float m_fLife; - }; }; diff --git a/Client/game_sa/CFxSystemSA.h b/Client/game_sa/CFxSystemSA.h index 5a4bd2685ea..6f641f54fe5 100644 --- a/Client/game_sa/CFxSystemSA.h +++ b/Client/game_sa/CFxSystemSA.h @@ -62,6 +62,14 @@ class CFxSystemSAInterface // Internal SA Name: FxSystem_c void* pSphere; // 0x74 void** ppParticleEmitters; // 0x78 (Array of particle emitters, amount is defined by the blueprint) CAEFireAudioEntitySAInterface audioEntity; // 0x7C + + void Kill() { ((void(__thiscall*)(CFxSystemSAInterface*))0x4AA3F0)(this); } + + void Play() { ((void(__thiscall*)(CFxSystemSAInterface*))0x4AA2F0)(this); } + + void SetOffsetPos(const CVector& offset) { ((void(__thiscall*)(CFxSystemSAInterface*, const CVector&))0x4AA660)(this, offset); } + + void SetConstTime(bool enableConstTime, float time) { ((void(__thiscall*)(CFxSystemSAInterface*, bool, float))0x4AA6C0)(this, enableConstTime, time); } }; static_assert(sizeof(CFxSystemSAInterface) == 0x104, "Invalid size for CFxSystemSAInterface"); diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index 59d55ae61ca..cd84acae365 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -61,6 +61,7 @@ #include "CBuildingRemovalSA.h" #include "CCheckpointSA.h" #include "CPtrNodeSingleLinkPoolSA.h" +#include "CCreepingFireSA.h" extern CGameSA* pGame; @@ -251,6 +252,8 @@ CGameSA::CGameSA() CFireSA::StaticSetHooks(); CPtrNodeSingleLinkPoolSA::StaticSetHooks(); CVehicleAudioSettingsManagerSA::StaticSetHooks(); + CFireManagerSA::StaticSetHooks(); + CCreepingFireSA::StaticSetHooks(); CPointLightsSA::StaticSetHooks(); CBuildingRemovalSA::StaticSetHooks(); } @@ -564,6 +567,16 @@ float CGameSA::GetTimeStep() return *(float*)0xB7CB5C; // CTimer::ms_fTimeStep } +float CGameSA::GetTimeStepInSeconds() +{ + return GetTimeStep() / 50.0f; +} + +float CGameSA::GetTimeStepInMS() +{ + return GetTimeStepInSeconds() * 1000.0f; +} + float CGameSA::GetOldTimeStep() { return *(float*)0xB7CB54; // CTimer::ms_fOldTimeStep diff --git a/Client/game_sa/CGameSA.h b/Client/game_sa/CGameSA.h index 782c13ffe4e..8f7cdd35091 100644 --- a/Client/game_sa/CGameSA.h +++ b/Client/game_sa/CGameSA.h @@ -211,6 +211,8 @@ class CGameSA : public CGame float GetFPS(); float GetTimeStep(); + float GetTimeStepInSeconds(); + float GetTimeStepInMS(); float GetOldTimeStep(); float GetTimeScale(); void SetTimeScale(float fTimeScale); diff --git a/Client/game_sa/CObjectSA.cpp b/Client/game_sa/CObjectSA.cpp index afff035c334..91feabdfda4 100644 --- a/Client/game_sa/CObjectSA.cpp +++ b/Client/game_sa/CObjectSA.cpp @@ -312,15 +312,10 @@ bool CObjectSA::SetOnFire(bool onFire) if (onFire) { - CFire* fire = fireManager->StartFire(this, nullptr, static_cast(DEFAULT_FIRE_PARTICLE_SIZE)); + CFire* fire = fireManager->StartFire(this, nullptr, 5000, 0); if (!fire) return false; - fire->SetTarget(this); - fire->SetStrength(1.0f); - fire->Ignite(); - fire->SetNumGenerationsAllowed(0); - objectInterface->pFire = fire->GetInterface(); } else diff --git a/Client/game_sa/CObjectSA.h b/Client/game_sa/CObjectSA.h index 91e5636b38c..394cbd08a4a 100644 --- a/Client/game_sa/CObjectSA.h +++ b/Client/game_sa/CObjectSA.h @@ -133,7 +133,8 @@ class CObjectSA : public virtual CObject, public virtual CPhysicalSA CObjectSA(DWORD dwModel, bool bBreakingDisabled); ~CObjectSA(); - CObjectSAInterface* GetObjectInterface() { return (CObjectSAInterface*)GetInterface(); } + CObjectSAInterface* GetObjectInterface() { return static_cast(GetInterface()); } + CObjectSAInterface* GetObjectInterface() const { return reinterpret_cast(GetInterface()); } void Explode(); void Break(); @@ -153,7 +154,7 @@ class CObjectSA : public virtual CObject, public virtual CPhysicalSA CVector* GetScale(); void ResetScale(); - bool IsOnFire() override { return GetObjectInterface()->pFire != nullptr; } + bool IsOnFire() const override { return GetObjectInterface()->pFire != nullptr; } bool SetOnFire(bool onFire) override; private: diff --git a/Client/game_sa/CPedSA.cpp b/Client/game_sa/CPedSA.cpp index e9433852e68..79c378e0bc1 100644 --- a/Client/game_sa/CPedSA.cpp +++ b/Client/game_sa/CPedSA.cpp @@ -426,22 +426,15 @@ bool CPedSA::SetOnFire(bool onFire) if (onFire) { - CFire* fire = fireManager->StartFire(this, nullptr, static_cast(DEFAULT_FIRE_PARTICLE_SIZE)); + CFire* fire = fireManager->StartFire(this, nullptr, 5000, 0); if (!fire) return false; - // Start the fire - fire->SetTarget(this); - fire->Ignite(); - fire->SetStrength(1.0f); - // Attach the fire only to the player, do not let it - // create child fires when moving. - fire->SetNumGenerationsAllowed(0); pInterface->pFireOnPed = fire->GetInterface(); } else { - CFire* fire = fireManager->GetFire(static_cast(pInterface->pFireOnPed)); + CFire* fire = fireManager->GetFire(pInterface->pFireOnPed); if (!fire) return false; @@ -598,6 +591,15 @@ void CPedSA::SetInWaterFlags(bool inWater) physicalInterface->bSubmergedInWater = inWater; } +bool CPedSA::IsPedInControl() const +{ + CPedSAInterface* pedInterface = GetPedInterface(); + if (!pedInterface) + return false; + + return ((bool(__thiscall*)(CPedSAInterface*))0x5E3960)(pedInterface); +} + //////////////////////////////////////////////////////////////// // // CPed_PreRenderAfterTest diff --git a/Client/game_sa/CPedSA.h b/Client/game_sa/CPedSA.h index a45397f1e97..6fd2413a9f0 100644 --- a/Client/game_sa/CPedSA.h +++ b/Client/game_sa/CPedSA.h @@ -445,6 +445,8 @@ class CPedSA : public virtual CPed, public virtual CPhysicalSA bool IsOnFire() const override { return GetPedInterface()->pFireOnPed != nullptr; } bool SetOnFire(bool onFire) override; + bool IsFireProof() const override { return static_cast(GetInterface())->bFireProof; } + bool GetStayInSamePlace() const override { return GetPedInterface()->pedFlags.bStayInSamePlace; } void SetStayInSamePlace(bool stay) override { GetPedInterface()->pedFlags.bStayInSamePlace = stay; } @@ -477,6 +479,8 @@ class CPedSA : public virtual CPed, public virtual CPhysicalSA void SetInWaterFlags(bool inWater) override; + bool IsPedInControl() const override; + static void StaticSetHooks(); private: diff --git a/Client/game_sa/CPoolsSA.h b/Client/game_sa/CPoolsSA.h index 98bb9fd6cbf..5dcd51a64d8 100644 --- a/Client/game_sa/CPoolsSA.h +++ b/Client/game_sa/CPoolsSA.h @@ -100,6 +100,9 @@ class CPoolsSA : public CPools CTxdPool& GetTxdPool() noexcept { return m_TxdPool; }; CPtrNodeSingleLinkPool& GetPtrNodeSingleLinkPool() noexcept override { return m_PtrNodeSingleLinkPool; }; + void GetVehiclesPool(void* pool) noexcept override { *reinterpret_cast*>(pool) = m_vehiclePool; } + void GetObjectsPool(void* pool) noexcept override { *reinterpret_cast*>(pool) = m_objectPool; } + private: // Pools SPoolData m_vehiclePool; diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index cf113ea8270..e4a611756e9 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -2024,15 +2024,10 @@ bool CVehicleSA::SetOnFire(bool onFire) if (onFire) { - CFire* fire = fireManager->StartFire(this, nullptr, static_cast(DEFAULT_FIRE_PARTICLE_SIZE)); + CFire* fire = fireManager->StartFire(this, nullptr, 5000, 0); if (!fire) return false; - fire->SetTarget(this); - fire->SetStrength(1.0f); - fire->Ignite(); - fire->SetNumGenerationsAllowed(0); - vehicleInterface->m_pFire = fire->GetInterface(); } else diff --git a/Client/game_sa/CVehicleSA.h b/Client/game_sa/CVehicleSA.h index c1246fcb096..0b3b695f5bd 100644 --- a/Client/game_sa/CVehicleSA.h +++ b/Client/game_sa/CVehicleSA.h @@ -717,7 +717,7 @@ class CVehicleSA : public virtual CVehicle, public virtual CPhysicalSA CVector* GetDummyPositions() { return m_dummyPositions.data(); } const CVector* GetDummyPositions() const override { return m_dummyPositions.data(); } - bool IsOnFire() override { return GetVehicleInterface()->m_pFire != nullptr; } + bool IsOnFire() const override { return GetVehicleInterface()->m_pFire != nullptr; } bool SetOnFire(bool onFire) override; static void StaticSetHooks(); diff --git a/Client/game_sa/CWeaponSA.h b/Client/game_sa/CWeaponSA.h index 5282981d411..e753d4836a8 100644 --- a/Client/game_sa/CWeaponSA.h +++ b/Client/game_sa/CWeaponSA.h @@ -10,6 +10,7 @@ *****************************************************************************/ #pragma once +class FxSystem_c; #define FUNC_CWeapon_CheckForShootingVehicleOccupant 0x73f480 #define FUNC_CBirds_CheckForHit 0x712E40 diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index cc7fcf20860..f88aba680cd 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index ad2c6aa68e6..4dc5f3943cc 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -81,6 +81,7 @@ enum eClientEntityType CCLIENTUNKNOWN, CCLIENTIMG, CCLIENTBUILDING, + CCLIENTFIRE, }; class CEntity; @@ -146,6 +147,7 @@ enum eCClientEntityClassTypes CLASS_CClientSearchLight, CLASS_CClientIMG, CLASS_CClientBuilding, + CLASS_CClientFire, }; class CClientEntity : public CClientEntityBase diff --git a/Client/mods/deathmatch/logic/CClientFire.cpp b/Client/mods/deathmatch/logic/CClientFire.cpp new file mode 100644 index 00000000000..99a2e3b930b --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientFire.cpp @@ -0,0 +1,101 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientFire.cpp + * PURPOSE: Fire class + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CClientFire.h" +#include "game/CFireManager.h" + +CClientFire::CClientFire(CClientFireManager* fireManager, const CVector& position, float strength, std::uint32_t lifetime, bool makeNoise) + : ClassInit(this), CClientEntity(INVALID_ELEMENT_ID), m_fireManager{fireManager} +{ + SetTypeName("fire"); + + CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); + m_fire = g_pGame->GetFireManager()->StartFire(position, strength, localPlayer ? localPlayer->GetGameEntity() : nullptr, lifetime, 0, makeNoise); + + if (m_fire) + m_fire->SetCreatedByScript(true); + + m_fireManager->AddToList(this); +} + +CClientFire::~CClientFire() +{ + Unlink(); + Extinguish(); +} + +void CClientFire::Unlink() +{ + m_fireManager->RemoveFromList(this); +} + +void CClientFire::Extinguish() +{ + if (!m_fire) + return; + + m_fire->Extinguish(); +} + +void CClientFire::GetPosition(CVector& position) const +{ + position = m_fire ? m_fire->GetPosition() : CVector(); +} + +void CClientFire::SetPosition(const CVector& position) +{ + if (!m_fire) + return; + + m_fire->SetPosition(position, true); +} + +void CClientFire::SetStrength(float strength) +{ + if (!m_fire) + return; + + m_fire->SetStrength(strength); +} + +float CClientFire::GetStrength() const noexcept +{ + if (!m_fire) + return 0.0f; + + return m_fire->GetStrength(); +} + +void CClientFire::SetLifetime(std::uint32_t lifetime) +{ + if (!m_fire) + return; + + m_fire->SetLifetime(lifetime); +} + +std::uint32_t CClientFire::GetLifetime() const noexcept +{ + if (!m_fire) + return 0; + + return m_fire->GetLifetime(); +} + +void CClientFire::OnGameFireDestroyed(CFire* fire) +{ + if (!fire->IsCreatedByScript()) + return; + + CClientFire* clientFire = g_pClientGame->GetManager()->GetFireManager()->Get(fire); + if (clientFire && !clientFire->IsBeingDeleted()) + CStaticFunctionDefinitions::DestroyElement(*clientFire); +} diff --git a/Client/mods/deathmatch/logic/CClientFire.h b/Client/mods/deathmatch/logic/CClientFire.h new file mode 100644 index 00000000000..cd06741ba53 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientFire.h @@ -0,0 +1,49 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientFire.h + * PURPOSE: Fire class header + * + *****************************************************************************/ + +#pragma once + +#include +#include + +class CClientFire : public CClientEntity +{ + DECLARE_CLASS(CClientFire, CClientEntity) + friend class CClientFireManager; + +public: + CClientFire(CClientFireManager* fireManager, const CVector& position, float strength, std::uint32_t lifetime, bool makeNoise); + ~CClientFire(); + + // CClientEntity stuff + eClientEntityType GetType() const override { return CCLIENTFIRE; } + CEntity* GetGameEntity() override { return nullptr; } + void Unlink() override; + + CFire* GetGameFire() const noexcept { return m_fire; } + + void Extinguish(); + + void GetPosition(CVector& position) const override; + void SetPosition(const CVector& position); + + void SetStrength(float strength); + float GetStrength() const noexcept; + + void SetLifetime(std::uint32_t lifetime); + std::uint32_t GetLifetime() const noexcept; + + static void OnGameFireDestroyed(CFire* fire); + +private: + CFire* m_fire; + + CClientFireManager* m_fireManager{nullptr}; +}; diff --git a/Client/mods/deathmatch/logic/CClientFireManager.cpp b/Client/mods/deathmatch/logic/CClientFireManager.cpp new file mode 100644 index 00000000000..fc0bbdbdf8e --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientFireManager.cpp @@ -0,0 +1,32 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientFireManager.cpp + * PURPOSE: Fire manager class + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CClientFireManager.h" + +void CClientFireManager::RemoveAll() +{ + for (const auto& pair : m_list) + delete pair.second; + + m_list.clear(); +} + +CClientFire* CClientFireManager::Get(CFire* gameFire) +{ + if (!gameFire) + return nullptr; + + auto it = m_list.find(gameFire); + if (it != m_list.end()) + return it->second; + + return nullptr; +} diff --git a/Client/mods/deathmatch/logic/CClientFireManager.h b/Client/mods/deathmatch/logic/CClientFireManager.h new file mode 100644 index 00000000000..333ad1120a0 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientFireManager.h @@ -0,0 +1,33 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientFireManager.h + * PURPOSE: Fire manager class header + * + *****************************************************************************/ + +#pragma once + +#include "CClientFire.h", +#include + +class CClientFireManager +{ + friend class CClientFire; + +public: + CClientFireManager() = default; + ~CClientFireManager() { RemoveAll(); } + + void RemoveAll(); + + CClientFire* Get(CFire* gameFire); + +private: + void AddToList(CClientFire* fire) { m_list[fire->GetGameFire()] = fire; }; + void RemoveFromList(CClientFire* fire) { m_list.erase(fire->GetGameFire()); } + + std::unordered_map m_list; +}; diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index c3407838ac9..8f65f69beb4 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -37,6 +37,7 @@ #include "game/CClock.h" #include #include +#include #include #include "CServerInfo.h" #include "CClientPed.h" @@ -277,7 +278,6 @@ CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo()) g_pMultiplayer->SetDrawRadarAreasHandler(CClientGame::StaticDrawRadarAreasHandler); g_pMultiplayer->SetDamageHandler(CClientGame::StaticDamageHandler); g_pMultiplayer->SetDeathHandler(CClientGame::StaticDeathHandler); - g_pMultiplayer->SetFireHandler(CClientGame::StaticFireHandler); g_pMultiplayer->SetProjectileStopHandler(CClientProjectileManager::Hook_StaticProjectileAllow); g_pMultiplayer->SetProjectileHandler(CClientProjectileManager::Hook_StaticProjectileCreation); g_pMultiplayer->SetRender3DStuffHandler(CClientGame::StaticRender3DStuffHandler); @@ -343,6 +343,9 @@ CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo()) // Disable GTA's pickup processing as we want to confirm the hits with the server m_pPickupManager->SetPickupProcessingDisabled(true); + g_pGame->GetFireManager()->SetFireCreationHandler(CClientGame::StaticFireHandler); + g_pGame->GetFireManager()->SetFireDestructionHandler(CClientFire::OnGameFireDestroyed); + // Key-bind for fire-key (for handling satchels and stealth-kills) g_pCore->GetKeyBinds()->AddControlFunction("fire", CClientGame::StaticUpdateFireKey, true); @@ -488,7 +491,6 @@ CClientGame::~CClientGame() g_pMultiplayer->SetBreakTowLinkHandler(NULL); g_pMultiplayer->SetDrawRadarAreasHandler(NULL); g_pMultiplayer->SetDamageHandler(NULL); - g_pMultiplayer->SetFireHandler(NULL); g_pMultiplayer->SetProjectileStopHandler(NULL); g_pMultiplayer->SetProjectileHandler(NULL); g_pMultiplayer->SetProcessCamHandler(nullptr); @@ -527,6 +529,8 @@ CClientGame::~CClientGame() g_pMultiplayer->SetPedStepHandler(nullptr); g_pMultiplayer->SetVehicleWeaponHitHandler(nullptr); g_pMultiplayer->SetAudioZoneRadioSwitchHandler(nullptr); + g_pGame->GetFireManager()->SetFireCreationHandler(nullptr); + g_pGame->GetFireManager()->SetFireDestructionHandler(nullptr); g_pGame->SetPreWeaponFireHandler(NULL); g_pGame->SetPostWeaponFireHandler(NULL); g_pGame->SetTaskSimpleBeHitHandler(NULL); @@ -3597,11 +3601,6 @@ void CClientGame::StaticDeathHandler(CPed* pKilledPed, unsigned char ucDeathReas g_pClientGame->DeathHandler(pKilledPed, ucDeathReason, ucBodyPart); } -bool CClientGame::StaticFireHandler(CEntitySAInterface* target, CEntitySAInterface* creator) -{ - return g_pClientGame->FireHandler(target, creator); -} - void CClientGame::StaticRender3DStuffHandler() { g_pClientGame->Render3DStuffHandler(); @@ -3867,10 +3866,10 @@ bool CClientGame::BreakTowLinkHandler(CVehicle* pTowedVehicle) return true; } -bool CClientGame::FireHandler(CEntitySAInterface* target, CEntitySAInterface* creator) +bool CClientGame::StaticFireHandler(CEntity* target, CEntity* creator) { - CClientEntity* creatorClientEntity = g_pGame->GetPools()->GetClientEntity((DWORD*)creator); - CClientEntity* targetClientEntity = g_pGame->GetPools()->GetClientEntity((DWORD*)target); + CClientEntity* creatorClientEntity = g_pGame->GetPools()->GetClientEntity(creator ? (DWORD*)creator->GetInterface() : nullptr); + CClientEntity* targetClientEntity = g_pGame->GetPools()->GetClientEntity(target ? (DWORD*)target->GetInterface() : nullptr); if (creatorClientEntity && targetClientEntity && IS_PLAYER(targetClientEntity) && IS_PLAYER(creatorClientEntity)) { @@ -5405,7 +5404,7 @@ void CClientGame::SendExplosionSync(const CVector& vecPosition, eExplosionType T void CClientGame::SendFireSync(CFire* pFire) { #ifdef MTA_DEBUG - CVector* vecPos = pFire->GetPosition(); + CVector* vecPos = &pFire->GetPosition(); if (vecPos) g_pCore->GetConsole()->Printf("we're sending fire: %f %f %f %f", pFire->GetStrength(), vecPos->fX, vecPos->fY, vecPos->fZ); else diff --git a/Client/mods/deathmatch/logic/CClientGame.h b/Client/mods/deathmatch/logic/CClientGame.h index bad7247c905..55c21bc1337 100644 --- a/Client/mods/deathmatch/logic/CClientGame.h +++ b/Client/mods/deathmatch/logic/CClientGame.h @@ -568,7 +568,6 @@ class CClientGame static bool StaticDamageHandler(CPed* pDamagePed, CEventDamage* pEvent); static void StaticDeathHandler(CPed* pKilledPed, unsigned char ucDeathReason, unsigned char ucBodyPart); - static bool StaticFireHandler(CEntitySAInterface* target, CEntitySAInterface* creator); static bool StaticBreakTowLinkHandler(CVehicle* pTowedVehicle); static void StaticDrawRadarAreasHandler(); static void StaticRender3DStuffHandler(); @@ -617,9 +616,10 @@ class CClientGame static AnimationId StaticDrivebyAnimationHandler(AnimationId animGroup, AssocGroupId animId); static void StaticAudioZoneRadioSwitchHandler(DWORD dwStationID); + static bool StaticFireHandler(CEntity* target, CEntity* creator); + bool DamageHandler(CPed* pDamagePed, CEventDamage* pEvent); void DeathHandler(CPed* pKilledPed, unsigned char ucDeathReason, unsigned char ucBodyPart); - bool FireHandler(CEntitySAInterface* target, CEntitySAInterface* creator); bool BreakTowLinkHandler(CVehicle* pTowedVehicle); void DrawRadarAreasHandler(); void Render3DStuffHandler(); diff --git a/Client/mods/deathmatch/logic/CClientManager.cpp b/Client/mods/deathmatch/logic/CClientManager.cpp index bf79bf9ec93..aa46ecd1ad9 100644 --- a/Client/mods/deathmatch/logic/CClientManager.cpp +++ b/Client/mods/deathmatch/logic/CClientManager.cpp @@ -55,6 +55,7 @@ CClientManager::CClientManager() m_pPacketRecorder = new CClientPacketRecorder(this); m_pImgManager = new CClientIMGManager(this); m_pBuildingManager = new CClientBuildingManager(this); + m_fireManager = new CClientFireManager(); m_bBeingDeleted = false; m_bGameUnloadedFlag = false; @@ -181,6 +182,9 @@ CClientManager::~CClientManager() delete m_pBuildingManager; m_pBuildingManager = nullptr; + + delete m_fireManager; + m_fireManager = nullptr; } // diff --git a/Client/mods/deathmatch/logic/CClientManager.h b/Client/mods/deathmatch/logic/CClientManager.h index 26f93f0015b..95e45820c1a 100644 --- a/Client/mods/deathmatch/logic/CClientManager.h +++ b/Client/mods/deathmatch/logic/CClientManager.h @@ -44,6 +44,7 @@ class CClientManager; #include "CClientModelManager.h" #include "CClientIMGManager.h" #include "CClientBuildingManager.h" +#include "CClientFireManager.h" class CClientProjectileManager; class CClientExplosionManager; @@ -98,6 +99,7 @@ class CClientManager CClientPointLightsManager* GetPointLightsManager() { return m_pPointLightsManager; } CClientIMGManager* GetIMGManager() { return m_pImgManager; } CClientBuildingManager* GetBuildingManager() const noexcept { return m_pBuildingManager; } + CClientFireManager* GetFireManager() const noexcept { return m_fireManager; } bool IsGameLoaded() { @@ -154,6 +156,7 @@ class CClientManager CClientIMGManager* m_pImgManager; CClientPacketRecorder* m_pPacketRecorder; CClientBuildingManager* m_pBuildingManager; + CClientFireManager* m_fireManager; bool m_bBeingDeleted; bool m_bGameUnloadedFlag; int m_iNumLowLODElements; diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index c6fea64065e..5af6329b6fb 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -4765,7 +4765,7 @@ void CPacketHandler::Packet_FireSync(NetBitStreamInterface& bitStream) // TODO: Ping compensate - g_pGame->GetFireManager()->StartFire(vecPosition, fSize); + g_pGame->GetFireManager()->StartFire(vecPosition, fSize, pCreator ? pCreator->GetGameEntity() : nullptr, 5000, 100); } } diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index e104e137404..c285cf484d1 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -4628,23 +4628,6 @@ bool CStaticFunctionDefinitions::DetonateSatchels() return false; } -bool CStaticFunctionDefinitions::CreateFire(CVector& vecPosition, float fSize) -{ - return g_pGame->GetFireManager()->StartFire(vecPosition, fSize) != NULL; -} - -bool CStaticFunctionDefinitions::ExtinguishFireInRadius(CVector& vecPosition, float fRadius) -{ - g_pGame->GetFireManager()->ExtinguishPoint(vecPosition, fRadius); - return true; -} - -bool CStaticFunctionDefinitions::ExtinguishAllFires() -{ - g_pGame->GetFireManager()->ExtinguishAllFires(); - return true; -} - bool CStaticFunctionDefinitions::PlaySoundFrontEnd(unsigned char ucSound) { g_pGame->GetAudioEngine()->PlayFrontEndSound(ucSound); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h index 0c20cbe9d6b..a8be6876b8a 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -330,11 +330,6 @@ class CStaticFunctionDefinitions static bool CreateExplosion(CVector& vecPosition, unsigned char ucType, bool bMakeSound, float fCamShake, bool bDamaging); static bool DetonateSatchels(); - // Fire funcs - static bool CreateFire(CVector& vecPosition, float fSize); - static bool ExtinguishFireInRadius(CVector& vecPosition, float fRadius); - static bool ExtinguishAllFires(); - // Light funcs static CClientPointLights* CreateLight(CResource& Resource, int iMode, const CVector& vecPosition, float fRadius, SColor color, CVector& vecDirection); static bool GetLightType(CClientPointLights* pLight, int& iMode); diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index bae91981974..00afbdee06a 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -350,6 +350,10 @@ inline SString GetClassTypeName(CClientPointLights*) { return "light"; } +inline SString GetClassTypeName(CClientFire*) +{ + return "fire"; +} inline SString GetClassTypeName(CGUIButton*) { diff --git a/Client/mods/deathmatch/logic/lua/LuaCommon.h b/Client/mods/deathmatch/logic/lua/LuaCommon.h index 71a90a6157d..95731a252b5 100644 --- a/Client/mods/deathmatch/logic/lua/LuaCommon.h +++ b/Client/mods/deathmatch/logic/lua/LuaCommon.h @@ -28,6 +28,7 @@ class CClientColShape; class CScriptFile; class CClientDFF; class CClientEntity; +class CClientFire; class CClientGUIElement; class CClientMarker; class CClientObject; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.cpp index 7d07f26bc49..2f65cb99da8 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.cpp @@ -9,12 +9,13 @@ *****************************************************************************/ #include "StdInc.h" #include "CLuaFireDefs.h" +#include "game/CFireManager.h" void CLuaFireDefs::LoadFunctions() { constexpr static const std::pair functions[]{ - {"createFire", CreateFire}, - {"extinguishFire", ExtinguishFire}, + {"createFire", ArgumentParserWarn}, + {"extinguishFire", ArgumentParserWarn}, }; // Add functions @@ -22,59 +23,20 @@ void CLuaFireDefs::LoadFunctions() CLuaCFunctions::AddFunction(name, func); } -int CLuaFireDefs::CreateFire(lua_State* luaVM) +CClientFire* CLuaFireDefs::CreateFire(CVector position, float size, std::optional lifetime, std::optional makeNoise) { - // bool createFire ( float x, float y, float z [, float size = 1.8 ] ) - CVector vecPosition; - float fSize; - - CScriptArgReader argStream(luaVM); - argStream.ReadVector3D(vecPosition); - argStream.ReadNumber(fSize, 1.8f); - - if (!argStream.HasErrors()) - { - if (CStaticFunctionDefinitions::CreateFire(vecPosition, fSize)) - { - lua_pushboolean(luaVM, true); - return 1; - } - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + CClientFire* fire = new CClientFire(m_pManager->GetFireManager(), position, size, lifetime.value_or(5000), makeNoise.value_or(true)); + return fire; } -int CLuaFireDefs::ExtinguishFire(lua_State* luaVM) +bool CLuaFireDefs::ExtinguishFire(std::optional position, std::optional radius) { - // bool extinguishFire ( [ float x, float y, float z [, float radius = 1.0 ] ] ) - CScriptArgReader argStream(luaVM); - - if (argStream.NextIsNone()) - { - lua_pushboolean(luaVM, CStaticFunctionDefinitions::ExtinguishAllFires()); - return 1; - } - - CVector vecPosition; - float fRadius; - - argStream.ReadVector3D(vecPosition); - argStream.ReadNumber(fRadius, 1.0f); - - if (!argStream.HasErrors()) + if (!position.has_value()) { - if (CStaticFunctionDefinitions::ExtinguishFireInRadius(vecPosition, fRadius)) - { - lua_pushboolean(luaVM, true); - return 1; - } + g_pGame->GetFireManager()->ExtinguishAllFires(); + return true; // backwards compatibilty } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - lua_pushboolean(luaVM, false); - return 1; + g_pGame->GetFireManager()->ExtinguishPoint(position.value(), radius.value_or(1.0f)); + return true; // backwards compatibilty } diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.h index 91ca3385a2c..0fa9fd959a5 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaFireDefs.h @@ -16,6 +16,6 @@ class CLuaFireDefs : public CLuaDefs public: static void LoadFunctions(); - LUA_DECLARE(CreateFire); - LUA_DECLARE(ExtinguishFire); + static CClientFire* CreateFire(CVector position, float size, std::optional lifetime, std::optional makeNoise); + static bool ExtinguishFire(std::optional position, std::optional radius); }; diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index bf1ea9c5bb1..7640b670068 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -136,12 +136,6 @@ DWORD RETURN_CPhysical_ApplyGravity = 0x543093; DWORD RETURN_CWorld_SetWorldOnFire = 0x56B989; #define HOOKPOS_CTaskSimplePlayerOnFire_ProcessPed 0x6336DA DWORD RETURN_CTaskSimplePlayerOnFire_ProcessPed = 0x6336E0; -#define HOOKPOS_CFire_ProcessFire 0x53AC1A -DWORD RETURN_CFire_ProcessFire = 0x53AC1F; -#define HOOKPOS_CExplosion_Update 0x7377D3 -DWORD RETURN_CExplosion_Update = 0x7377D8; -#define HOOKPOS_CWeapon_FireAreaEffect 0x73EBFE -DWORD RETURN_CWeapon_FireAreaEffect = 0x73EC03; #define CALL_RenderScene_Plants 0x53E103 #define HOOKPOS_RenderScene_end 0x53E159 @@ -391,7 +385,6 @@ BulletImpactHandler* m_pBulletImpactHandler = NULL; BulletFireHandler* m_pBulletFireHandler = NULL; DamageHandler* m_pDamageHandler = NULL; DeathHandler* m_pDeathHandler = NULL; -FireHandler* m_pFireHandler = NULL; ProjectileHandler* m_pProjectileHandler = NULL; ProjectileStopHandler* m_pProjectileStopHandler = NULL; ProcessCamHandler* m_pProcessCamHandler = NULL; @@ -459,9 +452,6 @@ void HOOK_UnoccupiedVehicleBurnCheck(); void HOOK_ApplyCarBlowHop(); void HOOK_CWorld_SetWorldOnFire(); void HOOK_CTaskSimplePlayerOnFire_ProcessPed(); -void HOOK_CFire_ProcessFire(); -void HOOK_CExplosion_Update(); -void HOOK_CWeapon_FireAreaEffect(); void HOOK_CGame_Process(); void HOOK_Idle(); void HOOK_RenderScene_Plants(); @@ -579,7 +569,6 @@ CMultiplayerSA::CMultiplayerSA() m_pBreakTowLinkHandler = NULL; m_pDrawRadarAreasHandler = NULL; m_pDamageHandler = NULL; - m_pFireHandler = NULL; m_pProjectileHandler = NULL; m_pProjectileStopHandler = NULL; @@ -668,9 +657,6 @@ void CMultiplayerSA::InitHooks() HookInstall(HOOKPOS_ApplyCarBlowHop, (DWORD)HOOK_ApplyCarBlowHop, 6); HookInstall(HOOKPOS_CWorld_SetWorldOnFire, (DWORD)HOOK_CWorld_SetWorldOnFire, 5); HookInstall(HOOKPOS_CTaskSimplePlayerOnFire_ProcessPed, (DWORD)HOOK_CTaskSimplePlayerOnFire_ProcessPed, 5); - HookInstall(HOOKPOS_CFire_ProcessFire, (DWORD)HOOK_CFire_ProcessFire, 5); - HookInstall(HOOKPOS_CExplosion_Update, (DWORD)HOOK_CExplosion_Update, 5); - HookInstall(HOOKPOS_CWeapon_FireAreaEffect, (DWORD)HOOK_CWeapon_FireAreaEffect, 5); HookInstall(HOOKPOS_CGame_Process, (DWORD)HOOK_CGame_Process, 10); HookInstall(HOOKPOS_Idle, (DWORD)HOOK_Idle, 10); HookInstall(HOOKPOS_CEventHandler_ComputeKnockOffBikeResponse, (DWORD)HOOK_CEventHandler_ComputeKnockOffBikeResponse, 7); @@ -1582,10 +1568,6 @@ void CMultiplayerSA::InitHooks() MemCpy((void*)0x7259B0, "\xDD\xD8\x90", 3); MemSet((void*)0x7258B8, 0x90, 6); - // Disable spreading fires (Moved from multiplayer_shotsync) - MemCpy((void*)0x53A23F, "\x33\xC0\x90\x90\x90", 5); - MemCpy((void*)0x53A00A, "\x33\xC0\x90\x90\x90", 5); - InitHooks_CrashFixHacks(); InitHooks_DeviceSelection(); @@ -2639,11 +2621,6 @@ void CMultiplayerSA::SetDeathHandler(DeathHandler* pDeathHandler) m_pDeathHandler = pDeathHandler; } -void CMultiplayerSA::SetFireHandler(FireHandler* pFireHandler) -{ - m_pFireHandler = pFireHandler; -} - void CMultiplayerSA::SetProcessCamHandler(ProcessCamHandler* pProcessCamHandler) { m_pProcessCamHandler = pProcessCamHandler; @@ -4357,7 +4334,6 @@ void CMultiplayerSA::Reset() DisableAllVehicleWeapons(false); m_pDamageHandler = NULL; m_pDeathHandler = NULL; - m_pFireHandler = NULL; m_pRender3DStuffHandler = NULL; m_pFxSystemDestructionHandler = NULL; } @@ -5815,66 +5791,6 @@ static void __declspec(naked) HOOK_CTaskSimplePlayerOnFire_ProcessPed() // clang-format on } -static void __declspec(naked) HOOK_CFire_ProcessFire() -{ - MTA_VERIFY_HOOK_LOCAL_SIZE; - - // Set the new fire's creator to the original fire's creator - // clang-format off - __asm - { - mov eax, 0x53A450 // CCreepingFire::TryToStartFireAtCoors - call eax - test eax, eax - jz fail - mov ecx, [esi+0x14] - mov [eax+0x14], ecx -fail: - jmp RETURN_CFire_ProcessFire - } - // clang-format on -} - -static void __declspec(naked) HOOK_CExplosion_Update() -{ - MTA_VERIFY_HOOK_LOCAL_SIZE; - - // Set the new fire's creator to the explosion's creator - // clang-format off - __asm - { - mov eax, 0x53A450 // CCreepingFire::TryToStartFireAtCoors - call eax - test eax, eax - jz fail - mov ecx, [esi-0x18] - mov [eax+0x14], ecx -fail: - jmp RETURN_CExplosion_Update - } - // clang-format on -} - -static void __declspec(naked) HOOK_CWeapon_FireAreaEffect() -{ - MTA_VERIFY_HOOK_LOCAL_SIZE; - - // Set the new fire's creator to the weapon's owner - // clang-format off - __asm - { - mov eax, 0x53A450 // CCreepingFire::TryToStartFireAtCoors - call eax - test eax, eax - jz fail - mov ecx, [esp+0x6C+4] - mov [eax+0x14], ecx -fail: - jmp RETURN_CWeapon_FireAreaEffect - } - // clang-format on -} - // --------------------------------------------------- // When the water is not customized, use the default render order so water through glass looks better diff --git a/Client/multiplayer_sa/CMultiplayerSA.h b/Client/multiplayer_sa/CMultiplayerSA.h index 77e95b4191a..2bab9bc5f36 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.h +++ b/Client/multiplayer_sa/CMultiplayerSA.h @@ -114,7 +114,6 @@ class CMultiplayerSA : public CMultiplayer void SetDeathHandler(DeathHandler* pDeathHandler); void SetProjectileHandler(ProjectileHandler* pProjectileHandler); void SetProjectileStopHandler(ProjectileStopHandler* pProjectileHandler); - void SetFireHandler(FireHandler* pFireHandler); void SetBreakTowLinkHandler(BreakTowLinkHandler* pBreakTowLinkHandler); void SetProcessCamHandler(ProcessCamHandler* pProcessCamHandler); void SetChokingHandler(ChokingHandler* pChokingHandler); diff --git a/Client/multiplayer_sa/multiplayer_shotsync.cpp b/Client/multiplayer_sa/multiplayer_shotsync.cpp index 531c6051755..65782a87c34 100644 --- a/Client/multiplayer_sa/multiplayer_shotsync.cpp +++ b/Client/multiplayer_sa/multiplayer_shotsync.cpp @@ -68,7 +68,6 @@ extern BulletImpactHandler* m_pBulletImpactHandler; extern BulletFireHandler* m_pBulletFireHandler; extern DamageHandler* m_pDamageHandler; extern DeathHandler* m_pDeathHandler; -extern FireHandler* m_pFireHandler; extern ProjectileHandler* m_pProjectileHandler; extern ProjectileStopHandler* m_pProjectileStopHandler; @@ -94,7 +93,6 @@ VOID InitShotsyncHooks() HookInstall(HOOKPOS_CWeapon__Fire_Sniper, (DWORD)HOOK_CWeapon__Fire_Sniper, 6); HookInstall(HOOKPOS_CEventDamage__AffectsPed, (DWORD)HOOK_CEventDamage__AffectsPed, 6); HookInstall(HOOKPOS_CEventVehicleExplosion__AffectsPed, (DWORD)HOOK_CEventVehicleExplosion__AffectsPed, 5); - HookInstall(HOOKPOS_CFireManager__StartFire, (DWORD)HOOK_CFireManager__StartFire, 6); HookInstall(HOOKPOS_CProjectileInfo__AddProjectile, (DWORD)HOOK_CProjectileInfo__AddProjectile, 7); HookInstall(HOOKPOS_CProjectile__CProjectile, (DWORD)HOOK_CProjectile__CProjectile, 7); HookInstall(HOOKPOS_IKChainManager_PointArm, (DWORD)HOOK_IKChainManager_PointArm, 7); @@ -916,47 +914,6 @@ static void __declspec(naked) HOOK_CEventDamage__AffectsPed() } } -// CFire* StartFire(CEntity *pBurningEntity, CEntity *pStartedFireEntity, float fFireSize=DEFAULT_FIRE_PARTICLE_SIZE, bool8 bExtinguishEnabled=TRUE, UInt32 -// ArgBurnTime = FIRE_AVERAGE_BURNTIME, Int8 NumGenerationsAllowed = 100); -static constexpr std::uintptr_t SKIP_CFireManager_StartFire = 0x53A0C5; -static constexpr std::uintptr_t RETURN_CFireManager_StartFire = 0x53A056; - -static void __declspec(naked) HOOK_CFireManager__StartFire() -{ - MTA_VERIFY_HOOK_LOCAL_SIZE; - - // clang-format off - __asm - { - push esi - push edi - mov edi, [esp+0Ch] - - mov eax, m_pFireHandler - test eax, eax - jz startFire - - push ecx - - push [esp+14h] - push edi - call m_pFireHandler - add esp, 8 - - pop ecx - - test al, al - jz abortCreatingFire - - startFire: - jmp RETURN_CFireManager_StartFire - - abortCreatingFire: - jmp SKIP_CFireManager_StartFire - } - // clang-format on -} - static CEntity* GetProjectileOwner(CPools* pPools) { CEntity* pOwner = nullptr; diff --git a/Client/multiplayer_sa/multiplayer_shotsync.h b/Client/multiplayer_sa/multiplayer_shotsync.h index 7ea90b17609..6858e0c58e0 100644 --- a/Client/multiplayer_sa/multiplayer_shotsync.h +++ b/Client/multiplayer_sa/multiplayer_shotsync.h @@ -27,7 +27,6 @@ #define HOOKRET_CWeapon__Fire_Sniper 0x7424D7 #define HOOKPOS_CEventDamage__AffectsPed 0x4B35A0 #define HOOKPOS_CEventVehicleExplosion__AffectsPed 0x4B0E58 -#define HOOKPOS_CFireManager__StartFire 0x53A050 #define HOOKPOS_CProjectileInfo__AddProjectile 0x737C80 #define HOOKPOS_CProjectile__CProjectile 0x5A4030 #define HOOKPOS_IKChainManager_PointArm 0x618B66 diff --git a/Client/sdk/game/CEntity.h b/Client/sdk/game/CEntity.h index 8f370fc6a76..885ada252b6 100644 --- a/Client/sdk/game/CEntity.h +++ b/Client/sdk/game/CEntity.h @@ -118,6 +118,8 @@ class CEntity virtual bool GetBonePosition(eBone boneId, CVector& position) = 0; virtual bool SetBonePosition(eBone boneId, const CVector& position) = 0; - virtual bool IsOnFire() = 0; + virtual bool IsOnFire() const = 0; virtual bool SetOnFire(bool onFire) = 0; + + virtual bool IsFireProof() const = 0; }; diff --git a/Client/sdk/game/CFire.h b/Client/sdk/game/CFire.h index 3e876acd0cb..1737eaa0984 100644 --- a/Client/sdk/game/CFire.h +++ b/Client/sdk/game/CFire.h @@ -1,6 +1,6 @@ /***************************************************************************** * - * PROJECT: Multi Theft Auto v1.0 + * PROJECT: Multi Theft Auto * LICENSE: See LICENSE in the top level directory * FILE: sdk/game/CFire.h * PURPOSE: Fire interface @@ -12,26 +12,30 @@ #pragma once class CEntity; -class CFireSAInterface; class CVector; +class CFireSAInterface; class CFire { public: - virtual void Extinguish() = 0; - virtual CVector* GetPosition() = 0; - virtual void SetPosition(CVector& vecPosition) = 0; - virtual void SetTimeToBurnOut(DWORD dwTime) = 0; - virtual DWORD GetTimeToBurnOut() = 0; - virtual CEntity* GetCreator() = 0; - virtual CEntity* GetEntityOnFire() = 0; - virtual void SetTarget(CEntity* entity) = 0; - virtual bool IsIgnited() = 0; - virtual void SetSilent(bool bSilent) = 0; - virtual bool IsBeingExtinguished() = 0; - virtual void Ignite() = 0; - virtual float GetStrength() = 0; - virtual void SetStrength(float fStrength) = 0; - virtual void SetNumGenerationsAllowed(char generations) = 0; - virtual CFireSAInterface* GetInterface() = 0; + virtual CFireSAInterface* GetInterface() noexcept = 0; + + // Always pass ProcessPedCall = false, unless you know what you're doing. This is only used when extinguishing fire on a ped, and it will prevent the + // PLAYER_ON_FIRE task from being aborted, which can cause crashes if the task is removed while it's being processed. + virtual void Extinguish(bool ProcessPedCall = false) = 0; + + virtual void SetPosition(const CVector& position, bool updateParticle = false) = 0; + virtual CVector& GetPosition() noexcept = 0; + + virtual void SetStrength(float strength, bool updateFX = true) = 0; + virtual float GetStrength() const noexcept = 0; + + virtual void SetLifetime(std::uint32_t lifetime) noexcept = 0; + virtual std::uint32_t GetLifetime() const noexcept = 0; + + // Created by lua script - createFire + virtual void SetCreatedByScript(bool createdByScript) noexcept = 0; + virtual bool IsCreatedByScript() const noexcept = 0; + + virtual void SetCreator(CEntity* creator) = 0; }; diff --git a/Client/sdk/game/CFireManager.h b/Client/sdk/game/CFireManager.h index 9e9197e74af..d570a30d796 100644 --- a/Client/sdk/game/CFireManager.h +++ b/Client/sdk/game/CFireManager.h @@ -1,6 +1,6 @@ /***************************************************************************** * - * PROJECT: Multi Theft Auto v1.0 + * PROJECT: Multi Theft Auto * LICENSE: See LICENSE in the top level directory * FILE: sdk/game/CFireManager.h * PURPOSE: Fire entity manager interface @@ -13,14 +13,28 @@ class CFire; class CVector; +class CEntity; + +using FireCreationHandler = bool (*)(CEntity* target, CEntity* creator); +using FireDestructionHandler = void (*)(CFire* fire); class CFireManager { public: - virtual void ExtinguishPoint(CVector& vecPosition, float fRadius) = 0; - // doesn't work, use below instead - // virtual CFire * StartFire ( CEntity * entityTarget, CEntity * entityCreator, float fSize )=0; - virtual CFire* StartFire(CVector& vecPosition, float fSize) = 0; - virtual void ExtinguishAllFires() = 0; - virtual CFire* GetFire(DWORD ID) = 0; + virtual CFire* StartFire(const CVector& position, float size, CEntity* creator, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed = 100, + bool makeNoise = true) = 0; + virtual CFire* StartFire(CEntity* target, CEntity* creator, std::uint32_t lifetime, std::uint8_t numGenerationsAllowed = 100, bool makeNoise = true) = 0; + virtual CFire* FindNearestFire(CVector* position, bool checkExtinguished, bool checkScript) = 0; + + virtual void ExtinguishPoint(const CVector& position, float radius) = 0; + virtual bool ExtinguishPointWithWater(const CVector& position, float radius, float waterStrength) = 0; + virtual void ExtinguishAllFires() = 0; + + virtual std::uint32_t GetNumFiresInRange(const CVector& position, float radius) const = 0; + virtual bool PlentyFiresAvailable() = 0; + + virtual void Update() = 0; + + virtual void SetFireCreationHandler(FireCreationHandler creationHandler) noexcept = 0; + virtual void SetFireDestructionHandler(FireDestructionHandler destructionHandler) noexcept = 0; }; diff --git a/Client/sdk/game/CFx.h b/Client/sdk/game/CFx.h index f71bfdf1ae1..03a13ed994a 100644 --- a/Client/sdk/game/CFx.h +++ b/Client/sdk/game/CFx.h @@ -16,10 +16,13 @@ class CEntity; class CVector; class CVehicle; struct RwColor; +class CFxSAInterface; class CFx { public: + virtual CFxSAInterface* GetInterface() = 0; + virtual void AddBlood(CVector& vecPosition, CVector& vecDirection, int iCount, float fBrightness) = 0; virtual void AddWood(CVector& vecPosition, CVector& vecDirection, int iCount, float fBrightness) = 0; virtual void AddSparks(CVector& vecPosition, CVector& vecDirection, float fForce, int iCount, CVector vecAcrossLine, unsigned char ucBlurIf0, float fSpread, diff --git a/Client/sdk/game/CGame.h b/Client/sdk/game/CGame.h index 974ff8e94eb..248a5d2857c 100644 --- a/Client/sdk/game/CGame.h +++ b/Client/sdk/game/CGame.h @@ -172,6 +172,8 @@ class __declspec(novtable) CGame virtual float GetFPS() = 0; virtual float GetTimeStep() = 0; virtual float GetOldTimeStep() = 0; + virtual float GetTimeStepInSeconds() = 0; + virtual float GetTimeStepInMS() = 0; virtual float GetTimeScale() = 0; virtual void Initialize() = 0; diff --git a/Client/sdk/game/CPed.h b/Client/sdk/game/CPed.h index 1d90ba466c8..0c5568d2e1b 100644 --- a/Client/sdk/game/CPed.h +++ b/Client/sdk/game/CPed.h @@ -278,9 +278,6 @@ class CPed : public virtual CPhysical virtual bool IsBleeding() const = 0; virtual void SetBleeding(bool bleeding) = 0; - virtual bool IsOnFire() const = 0; - virtual bool SetOnFire(bool onFire) = 0; - virtual bool GetStayInSamePlace() const = 0; virtual void SetStayInSamePlace(bool stay) = 0; @@ -312,4 +309,6 @@ class CPed : public virtual CPhysical virtual void Say(const ePedSpeechContext& speechId, float probability) = 0; virtual void SetInWaterFlags(bool inWater) = 0; + + virtual bool IsPedInControl() const = 0; }; diff --git a/Client/sdk/game/CPools.h b/Client/sdk/game/CPools.h index 55d6bacb242..b1e3c09f63b 100644 --- a/Client/sdk/game/CPools.h +++ b/Client/sdk/game/CPools.h @@ -113,4 +113,7 @@ class CPools virtual CDummyPool& GetDummyPool() noexcept = 0; virtual CTxdPool& GetTxdPool() noexcept = 0; virtual CPtrNodeSingleLinkPool& GetPtrNodeSingleLinkPool() noexcept = 0; + + virtual void GetVehiclesPool(void* pool) = 0; + virtual void GetObjectsPool(void* pool) = 0; }; diff --git a/Client/sdk/multiplayer/CMultiplayer.h b/Client/sdk/multiplayer/CMultiplayer.h index 5926e578a89..a6c7faf13cc 100644 --- a/Client/sdk/multiplayer/CMultiplayer.h +++ b/Client/sdk/multiplayer/CMultiplayer.h @@ -85,7 +85,6 @@ typedef void(BulletImpactHandler)(class CPed* pInitiator, class CEntity* pVictim typedef void(BulletFireHandler)(class CPed* pInitiator, const CVector* pvecStartPosition, const CVector* pvecEndPosition); typedef bool(DamageHandler)(class CPed* pDamagePed, class CEventDamage* pEvent); typedef void(DeathHandler)(class CPed* pKilledPed, unsigned char ucDeathReason, unsigned char ucBodyPart); -typedef bool(FireHandler)(class CEntitySAInterface* target, class CEntitySAInterface* creator); typedef bool(ProjectileStopHandler)(class CEntity* owner, enum eWeaponType weaponType, class CVector* origin, float fForce, class CVector* target, class CEntity* targetEntity); typedef void(ProjectileHandler)(class CEntity* owner, class CProjectile* projectile, class CProjectileInfo* projectileInfo, enum eWeaponType weaponType, @@ -223,7 +222,6 @@ class CMultiplayer virtual void SetBreakTowLinkHandler(BreakTowLinkHandler* pBreakTowLinkHandler) = 0; virtual void SetDamageHandler(DamageHandler* pDamageHandler) = 0; virtual void SetDeathHandler(DeathHandler* pDeathHandler) = 0; - virtual void SetFireHandler(FireHandler* pFireHandler) = 0; virtual void SetProcessCamHandler(ProcessCamHandler* pProcessCamHandler) = 0; virtual void SetChokingHandler(ChokingHandler* pChokingHandler) = 0; virtual void SetProjectileHandler(ProjectileHandler* pProjectileHandler) = 0; diff --git a/Shared/sdk/CVector.h b/Shared/sdk/CVector.h index fb55ede23b3..814f1e95b96 100644 --- a/Shared/sdk/CVector.h +++ b/Shared/sdk/CVector.h @@ -68,10 +68,12 @@ class CVector } float Length() const { return sqrt((fX * fX) + (fY * fY) + (fZ * fZ)); } + float Length2D() const { return sqrt(fX * fX + fY * fY); } // LengthSquared returns Length() without sqrt applied (i.e. returns x*x* + y*y + z*z). // This can be useful if you only want to compare lengths. float LengthSquared() const { return (fX * fX) + (fY * fY) + (fZ * fZ); } + float Length2DSquared() const { return fX * fX + fY * fY; } float DotProduct(const CVector* param) const { return fX * param->fX + fY * param->fY + fZ * param->fZ; }