Skip to content
17 changes: 17 additions & 0 deletions Client/game_sa/CModelInfoSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Client/game_sa/CModelInfoSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 28 additions & 4 deletions Client/game_sa/CVehicleSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,37 +765,61 @@ 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;

DWORD dwFunc = FUNC_CVehicle_AddVehicleUpgrade;
__asm
{
mov ecx, dwThis
push dwModelID
push dwActualModelID
call dwFunc
}
}
}

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
}

// GTA SA only does this when CVehicle::ClearVehicleUpgradeFlags returns false.
// 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;
Expand Down
26 changes: 26 additions & 0 deletions Client/mods/deathmatch/logic/CClientModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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<unsigned short>(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);
Expand Down
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class eClientModelType
OBJECT,
OBJECT_DAMAGEABLE,
VEHICLE,
VEHICLE_UPGRADE,
TIMED_OBJECT,
CLUMP,
TXD,
Expand Down
64 changes: 60 additions & 4 deletions Client/mods/deathmatch/logic/CVehicleUpgrades.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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<unsigned short>(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -967,6 +968,9 @@ int CLuaEngineDefs::EngineRequestModel(lua_State* luaVM)
case eClientModelType::VEHICLE:
iParentID = static_cast<int>(VehicleType::VT_LANDSTAL);
break;
case eClientModelType::VEHICLE_UPGRADE:
iParentID = defaultVehicleUpgradeId;
break;
default:
break;
}
Expand Down
2 changes: 2 additions & 0 deletions Client/sdk/game/CModelInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down