diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index cb9c3163c1..9af8787afa 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -482,6 +482,12 @@ class CModelInfoSA : public CModelInfo return pInterface ? pInterface->pRwObject : NULL; } + void SetRwObject(RwObject* pRwObject) + { + if (m_pInterface) + m_pInterface->pRwObject = pRwObject; + } + // CModelInfoSA methods void MakePedModel(const char* szTexture); void MakeObjectModel(ushort usBaseModelID); diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index cc5d6c1e3b..81d1a7971c 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -782,9 +782,25 @@ void CVehicleSA::LockDoors(bool bLocked) } } +static DWORD GetCustomUpgradeParentModelID(DWORD dwModelID) +{ + CModelInfo* pModelInfo = pGame->GetModelInfo(dwModelID); + if (pModelInfo && pModelInfo->GetParentID() != 0) + { + unsigned int parentID = pModelInfo->GetParentID(); + if (parentID >= 1000 && parentID <= 1193) + { + return parentID; + } + } + return dwModelID; +} + void CVehicleSA::AddVehicleUpgrade(DWORD dwModelID) { - if (dwModelID >= 1000 && dwModelID <= 1193) + DWORD dwActualModelID = GetCustomUpgradeParentModelID(dwModelID); + + if (dwActualModelID >= 1000 && dwActualModelID <= 1193) { DWORD dwThis = (DWORD)m_pInterface; @@ -793,7 +809,7 @@ void CVehicleSA::AddVehicleUpgrade(DWORD dwModelID) __asm { mov ecx, dwThis - push dwModelID + push dwActualModelID call dwFunc } // clang-format on @@ -802,6 +818,8 @@ void CVehicleSA::AddVehicleUpgrade(DWORD dwModelID) void CVehicleSA::RemoveVehicleUpgrade(DWORD dwModelID) { + DWORD dwActualModelID = GetCustomUpgradeParentModelID(dwModelID); + DWORD dwThis = (DWORD)m_pInterface; DWORD dwFunc = FUNC_CVehicle_RemoveVehicleUpgrade; @@ -809,7 +827,7 @@ void CVehicleSA::RemoveVehicleUpgrade(DWORD dwModelID) __asm { mov ecx, dwThis - push dwModelID + push dwActualModelID call dwFunc } // clang-format on @@ -818,7 +836,7 @@ void CVehicleSA::RemoveVehicleUpgrade(DWORD dwModelID) // In the case of hydraulics and nitro, this function does not return false and the upgrade is never removed from the array for (std::int16_t& upgrade : GetVehicleInterface()->m_upgrades) { - if (upgrade == dwModelID) + if (upgrade == dwModelID || upgrade == dwActualModelID) { upgrade = -1; break; diff --git a/Client/mods/deathmatch/logic/CClientModel.cpp b/Client/mods/deathmatch/logic/CClientModel.cpp index c8c37d5929..251ecba080 100644 --- a/Client/mods/deathmatch/logic/CClientModel.cpp +++ b/Client/mods/deathmatch/logic/CClientModel.cpp @@ -87,6 +87,15 @@ bool CClientModel::Allocate(ushort usParentID) allocated = true; } break; + case eClientModelType::VEHICLE_UPGRADE: + { + if (CVehicleUpgrades::IsUpgrade(usParentID)) + { + pModelInfo->MakeObjectModel(usParentID); + return true; + } + break; + } default: return false; } @@ -140,6 +149,7 @@ void CClientModel::RestoreEntitiesUsingThisModel() case eClientModelType::CLUMP: case eClientModelType::TIMED_OBJECT: case eClientModelType::VEHICLE: + case eClientModelType::VEHICLE_UPGRADE: RestoreDFF(pModelInfo); return; case eClientModelType::TXD: @@ -257,6 +267,23 @@ void CClientModel::RestoreDFF(CModelInfo* pModelInfo) [usParentID](auto& element) { element.SetModelBlocking(usParentID, 255, 255); }); break; } + case eClientModelType::VEHICLE_UPGRADE: + { + CClientVehicleManager* pVehicleManager = g_pClientGame->GetManager()->GetVehicleManager(); + const auto usParentID = static_cast(g_pGame->GetModelInfo(m_iModelID)->GetParentID()); + + // Remove custom upgrade and restore parent + unloadModelsAndCallEvents(pVehicleManager->IterBegin(), pVehicleManager->IterEnd(), usParentID, + [=](auto& element) + { + element.GetUpgrades()->RemoveUpgrade(m_iModelID); + if (usParentID >= 1000 && usParentID <= 1193) + { + element.GetUpgrades()->AddUpgrade(usParentID, false); + } + }); + break; + } } g_pClientGame->GetManager()->GetDFFManager()->RestoreModel(modelId); diff --git a/Client/mods/deathmatch/logic/CClientModel.h b/Client/mods/deathmatch/logic/CClientModel.h index 86f6ce4ab3..013e866cca 100644 --- a/Client/mods/deathmatch/logic/CClientModel.h +++ b/Client/mods/deathmatch/logic/CClientModel.h @@ -20,6 +20,7 @@ enum class eClientModelType OBJECT, OBJECT_DAMAGEABLE, VEHICLE, + VEHICLE_UPGRADE, TIMED_OBJECT, CLUMP, TXD, diff --git a/Client/mods/deathmatch/logic/CVehicleUpgrades.cpp b/Client/mods/deathmatch/logic/CVehicleUpgrades.cpp index 441347611b..eee7be4985 100644 --- a/Client/mods/deathmatch/logic/CVehicleUpgrades.cpp +++ b/Client/mods/deathmatch/logic/CVehicleUpgrades.cpp @@ -42,14 +42,30 @@ CVehicleUpgrades::CVehicleUpgrades(CClientVehicle* pVehicle) m_usLastLocalAddNitroType = 0; } +static unsigned short GetCustomUpgradeParentModelID(unsigned short usModelID) +{ + auto* upgradeModelInfo = g_pGame->GetModelInfo(usModelID); + if (upgradeModelInfo && upgradeModelInfo->GetParentID() != 0) + { + unsigned short parentID = upgradeModelInfo->GetParentID(); + if (parentID >= 1000 && parentID <= 1193) + { + return parentID; + } + } + return usModelID; +} + bool CVehicleUpgrades::IsUpgrade(unsigned short usModel) { + usModel = GetCustomUpgradeParentModelID(usModel); + return (usModel >= 1000 && usModel <= 1193); } bool CVehicleUpgrades::IsUpgradeCompatible(unsigned short usUpgrade) { - unsigned short us = usUpgrade; + unsigned short us = GetCustomUpgradeParentModelID(usUpgrade); eClientVehicleType vehicleType = m_pVehicle->GetVehicleType(); // No upgrades for trains/boats @@ -451,6 +467,9 @@ bool CVehicleUpgrades::IsUpgradeCompatible(unsigned short usUpgrade) bool CVehicleUpgrades::GetSlotFromUpgrade(unsigned short us, unsigned char& ucSlot) { + // Check if this is a custom upgrade model + us = GetCustomUpgradeParentModelID(us); + if (us == 1011 || us == 1012 || us == 1111 || us == 1112 || us == 1142 || /* bonet */ us == 1143 || us == 1144 || us == 1145) { @@ -610,18 +629,53 @@ void CVehicleUpgrades::ForceAddUpgrade(unsigned short usUpgrade) CVehicle* pVehicle = m_pVehicle->GetGameVehicle(); if (pVehicle) { - // Grab the upgrade model + // Load the upgrade model CModelInfo* pModelInfo = g_pGame->GetModelInfo(usUpgrade); if (pModelInfo) { if (!g_pGame->IsASyncLoadingEnabled() || !pModelInfo->IsLoaded()) { - // Request and load now pModelInfo->Request(BLOCKING, "CVehicleUpgrades::ForceAddUpgrade"); } - // Add the upgrade - pVehicle->AddVehicleUpgrade(usUpgrade); + + // If this is a custom model with parent ID, swap RwObjects + unsigned short parentID = static_cast(pModelInfo->GetParentID()); + if (parentID != 0 && IsUpgrade(parentID)) + { + CModelInfo* pParentModelInfo = g_pGame->GetModelInfo(parentID); + if (pParentModelInfo) + { + if (!g_pGame->IsASyncLoadingEnabled() || !pParentModelInfo->IsLoaded()) + { + pParentModelInfo->Request(BLOCKING, "CVehicleUpgrades::ForceAddUpgrade (parent)"); + } + + // Wait for both to be loaded + if (pModelInfo->IsLoaded() && pParentModelInfo->IsLoaded()) + { + RwObject* pCustomRwObject = pModelInfo->GetRwObject(); + RwObject* pParentRwObject = pParentModelInfo->GetRwObject(); + + if (pCustomRwObject && pParentRwObject) + { + // Temporarily swap to custom RwObject ONLY during AddVehicleUpgrade call + pParentModelInfo->SetRwObject(pCustomRwObject); + pVehicle->AddVehicleUpgrade(usUpgrade); + pParentModelInfo->SetRwObject(pParentRwObject); + + // Early return since we already added the upgrade + m_SlotStates[ucSlot] = usUpgrade; + if (ucSlot == 12) + m_pVehicle->ResetWheelScale(); + + return; + } + } + } + } } + + pVehicle->AddVehicleUpgrade(usUpgrade); } // Add it to the slot diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 7ae83f8bfb..0aef2c7307 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -712,6 +712,7 @@ ADD_ENUM(eClientModelType::PED, "ped") ADD_ENUM(eClientModelType::OBJECT, "object") ADD_ENUM(eClientModelType::OBJECT_DAMAGEABLE, "object-damageable") ADD_ENUM(eClientModelType::VEHICLE, "vehicle") +ADD_ENUM(eClientModelType::VEHICLE_UPGRADE, "vehicle-upgrade") ADD_ENUM(eClientModelType::TIMED_OBJECT, "timed-object") ADD_ENUM(eClientModelType::CLUMP, "clump") IMPLEMENT_ENUM_CLASS_END("client-model-type") diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 8d9bc6ece5..9a68af053c 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -961,6 +961,7 @@ int CLuaEngineDefs::EngineRequestModel(lua_State* luaVM) constexpr int defaultClumpParentId = 3425; constexpr int defaultObjectParentId = 1337; constexpr int defaultDamageableObjectParentId = 994; + constexpr int defaultVehicleUpgradeId = 1025; switch (eModelType) { @@ -982,6 +983,9 @@ int CLuaEngineDefs::EngineRequestModel(lua_State* luaVM) case eClientModelType::VEHICLE: iParentID = static_cast(VehicleType::VT_LANDSTAL); break; + case eClientModelType::VEHICLE_UPGRADE: + iParentID = defaultVehicleUpgradeId; + break; default: break; } diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 592776ea51..3181299c84 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -240,6 +240,7 @@ class CModelInfo // Call this to make sure the custom vehicle models are being used after a load. virtual void MakeCustomModel() = 0; virtual RwObject* GetRwObject() = 0; + virtual void SetRwObject(RwObject* pRwObject) = 0; virtual void MakePedModel(const char* szTexture) = 0; virtual void MakeObjectModel(unsigned short usBaseID) = 0; virtual void MakeObjectDamageableModel(std::uint16_t baseID) = 0;