diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 3dca8615f12..f41e637056b 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -2087,6 +2087,23 @@ void CModelInfoSA::MakeVehicleAutomobile(ushort usBaseID) CopyStreamingInfoFromModel(usBaseID); } +void CModelInfoSA::MakeVehicleUpgradeModel(ushort usBaseID) +{ + CBaseModelInfoSAInterface* m_pInterface = new CBaseModelInfoSAInterface(); + + CBaseModelInfoSAInterface* pBaseObjectInfo = ppModelInfo[usBaseID]; + MemCpyFast(m_pInterface, pBaseObjectInfo, sizeof(CBaseModelInfoSAInterface)); + m_pInterface->usNumberOfRefs = 0; + m_pInterface->pRwObject = nullptr; + m_pInterface->usUnknown = 65535; + m_pInterface->usDynamicIndex = 65535; + + ppModelInfo[m_dwModelID] = m_pInterface; + + m_dwParentID = usBaseID; + CopyStreamingInfoFromModel(usBaseID); +} + void CModelInfoSA::DeallocateModel(void) { // Model IDs can be reused (engineRequestModel); do not let a previous model's stored default TXD diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index b778f808be1..f657c9dd6dc 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -471,12 +471,14 @@ class CModelInfoSA : public CModelInfo void SetModelID(DWORD dwModelID) { m_dwModelID = dwModelID; } RwObject* GetRwObject() { return m_pInterface ? m_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); void MakeObjectDamageableModel(std::uint16_t usBaseModelID) override; void MakeVehicleAutomobile(ushort usBaseModelID); + void MakeVehicleUpgradeModel(ushort usBaseModelID); void MakeTimedObjectModel(ushort usBaseModelID); void MakeClumpModel(ushort usBaseModelID); void DeallocateModel(void); diff --git a/Client/game_sa/CVehicleSA.cpp b/Client/game_sa/CVehicleSA.cpp index e5620f01439..64b32869cce 100644 --- a/Client/game_sa/CVehicleSA.cpp +++ b/Client/game_sa/CVehicleSA.cpp @@ -765,7 +765,19 @@ void CVehicleSA::LockDoors(bool bLocked) void CVehicleSA::AddVehicleUpgrade(DWORD dwModelID) { - if (dwModelID >= 1000 && dwModelID <= 1193) + DWORD dwActualModelID = dwModelID; + + CModelInfo* pModelInfo = pGame->GetModelInfo(dwModelID); + if (pModelInfo && pModelInfo->GetParentID() != 0) + { + unsigned int parentID = pModelInfo->GetParentID(); + if (parentID >= 1000 && parentID <= 1193) + { + dwActualModelID = parentID; + } + } + + if (dwActualModelID >= 1000 && dwActualModelID <= 1193) { DWORD dwThis = (DWORD)m_pInterface; @@ -773,7 +785,7 @@ void CVehicleSA::AddVehicleUpgrade(DWORD dwModelID) __asm { mov ecx, dwThis - push dwModelID + push dwActualModelID call dwFunc } } @@ -781,13 +793,25 @@ void CVehicleSA::AddVehicleUpgrade(DWORD dwModelID) void CVehicleSA::RemoveVehicleUpgrade(DWORD dwModelID) { + DWORD dwActualModelID = dwModelID; + + CModelInfo* pModelInfo = pGame->GetModelInfo(dwModelID); + if (pModelInfo && pModelInfo->GetParentID() != 0) + { + unsigned int parentID = pModelInfo->GetParentID(); + if (parentID >= 1000 && parentID <= 1193) + { + dwActualModelID = parentID; + } + } + DWORD dwThis = (DWORD)m_pInterface; DWORD dwFunc = FUNC_CVehicle_RemoveVehicleUpgrade; __asm { mov ecx, dwThis - push dwModelID + push dwActualModelID call dwFunc } @@ -795,7 +819,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 ce094ed027b..2f87d9a42b8 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->MakeVehicleUpgradeModel(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: @@ -251,6 +261,22 @@ 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 759890e0e57..0b3859226e8 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 72872162f48..744fb018061 100644 --- a/Client/mods/deathmatch/logic/CVehicleUpgrades.cpp +++ b/Client/mods/deathmatch/logic/CVehicleUpgrades.cpp @@ -52,6 +52,16 @@ bool CVehicleUpgrades::IsUpgradeCompatible(unsigned short usUpgrade) unsigned short us = usUpgrade; eClientVehicleType vehicleType = m_pVehicle->GetVehicleType(); + auto* upgradeModelInfo = g_pGame->GetModelInfo(us); + if (upgradeModelInfo && upgradeModelInfo->GetParentID() != 0) + { + unsigned short parentID = upgradeModelInfo->GetParentID(); + if (IsUpgrade(parentID)) + { + us = parentID; + } + } + // No upgrades for trains/boats if (vehicleType == CLIENTVEHICLE_TRAIN || vehicleType == CLIENTVEHICLE_BOAT) return false; @@ -452,6 +462,17 @@ bool CVehicleUpgrades::IsUpgradeCompatible(unsigned short usUpgrade) bool CVehicleUpgrades::GetSlotFromUpgrade(unsigned short us, unsigned char& ucSlot) { + // Check if this is a custom upgrade model + auto* upgradeModelInfo = g_pGame->GetModelInfo(us); + if (upgradeModelInfo && upgradeModelInfo->GetParentID() != 0) + { + unsigned short parentID = upgradeModelInfo->GetParentID(); + if (IsUpgrade(parentID)) + { + us = parentID; + } + } + if (us == 1011 || us == 1012 || us == 1111 || us == 1112 || us == 1142 || /* bonet */ us == 1143 || us == 1144 || us == 1145) { @@ -611,18 +632,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 f056360de43..eed5576186e 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 7236031084a..951e60f6198 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -946,6 +946,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) { @@ -967,6 +968,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 7e5cc8ec65e..e761236d4ad 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -240,10 +240,12 @@ 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; virtual void MakeVehicleAutomobile(unsigned short usBaseID) = 0; + virtual void MakeVehicleUpgradeModel(unsigned short usBaseID) = 0; virtual void MakeTimedObjectModel(unsigned short usBaseID) = 0; virtual void MakeClumpModel(unsigned short usBaseID) = 0;