diff --git a/Client/game_sa/CFileLoaderSA.cpp b/Client/game_sa/CFileLoaderSA.cpp
index 67cb476cf5..feb6d89be4 100644
--- a/Client/game_sa/CFileLoaderSA.cpp
+++ b/Client/game_sa/CFileLoaderSA.cpp
@@ -12,6 +12,7 @@
 #include "gamesa_renderware.h"
 #include "CFileLoaderSA.h"
 #include "CModelInfoSA.h"
+#include "CRenderWareSA.h"
 
 CFileLoaderSA::CFileLoaderSA()
 {
@@ -48,53 +49,6 @@ class CDamagableModelInfo
     void CDamagableModelInfo::SetDamagedAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CDamagableModelInfo*, RpAtomic*))0x4C48D0)(this, atomic); }
 };
 
-static char* GetFrameNodeName(RwFrame* frame)
-{
-    return ((char*(__cdecl*)(RwFrame*))0x72FB30)(frame);
-}
-
-// Originally there was a possibility for this function to cause buffer overflow
-// It should be fixed here.
-template <size_t OutBuffSize>
-void GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage)
-{
-    const auto nodeNameLen = strlen(nodeName);
-
-    const auto NodeNameEndsWith = [=](const char* with) {
-        const auto withLen = strlen(with);
-        // dassert(withLen <= nodeNameLen);
-        return withLen <= nodeNameLen /*dont bother checking otherwise, because it might cause a crash*/
-               && strncmp(nodeName + nodeNameLen - withLen, with, withLen) == 0;
-    };
-
-    // Copy `nodeName` into `outName` with `off` trimmed from the end
-    // Eg.: `dmg_dam` with `off = 4` becomes `dmg`
-    const auto TerminatedCopy = [&](size_t off) {
-        dassert(nodeNameLen - off < OutBuffSize);
-        strncpy_s(outName, nodeName,
-                  std::min(nodeNameLen - off, OutBuffSize - 1));            // By providing `OutBuffSize - 1` it is ensured the array will be null terminated
-    };
-
-    if (NodeNameEndsWith("_dam"))
-    {
-        outDamage = true;
-        TerminatedCopy(sizeof("_dam") - 1);
-    }
-    else
-    {
-        outDamage = false;
-        if (NodeNameEndsWith("_l0") || NodeNameEndsWith("_L0"))
-        {
-            TerminatedCopy(sizeof("_l0") - 1);
-        }
-        else
-        {
-            dassert(nodeNameLen < OutBuffSize);
-            strncpy_s(outName, OutBuffSize, nodeName, OutBuffSize - 1);
-        }
-    }
-}
-
 static void CVisibilityPlugins_SetAtomicRenderCallback(RpAtomic* pRpAtomic, RpAtomic* (*renderCB)(RpAtomic*))
 {
     return ((void(__cdecl*)(RpAtomic*, RpAtomic * (*renderCB)(RpAtomic*)))0x7328A0)(pRpAtomic, renderCB);
@@ -170,9 +124,9 @@ RpAtomic* CFileLoader_SetRelatedModelInfoCB(RpAtomic* atomic, SRelatedModelInfo*
     CBaseModelInfoSAInterface* pBaseModelInfo = CModelInfo_ms_modelInfoPtrs[gAtomicModelId];
     auto                       pAtomicModelInfo = reinterpret_cast<CAtomicModelInfo*>(pBaseModelInfo);
     RwFrame*                   pOldFrame = reinterpret_cast<RwFrame*>(atomic->object.object.parent);
-    char*                      frameNodeName = GetFrameNodeName(pOldFrame);
+    char*                      frameNodeName = CRenderWareSA::GetFrameNodeName(pOldFrame);
     bool                       bDamage = false;
-    GetNameAndDamage(frameNodeName, name, bDamage);
+    CRenderWareSA::GetNameAndDamage(frameNodeName, name, bDamage);
     CVisibilityPlugins_SetAtomicRenderCallback(atomic, 0);
 
     RpAtomic* pOldAtomic = reinterpret_cast<RpAtomic*>(pBaseModelInfo->pRwObject);
diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp
index 715515f488..07f152eb71 100644
--- a/Client/game_sa/CModelInfoSA.cpp
+++ b/Client/game_sa/CModelInfoSA.cpp
@@ -35,6 +35,7 @@ std::map<CTimeInfoSAInterface*, CTimeInfoSAInterface*>                CModelInfo
 std::unordered_map<DWORD, unsigned short>                             CModelInfoSA::ms_OriginalObjectPropertiesGroups;
 std::unordered_map<DWORD, std::pair<float, float>>                    CModelInfoSA::ms_VehicleModelDefaultWheelSizes;
 std::map<unsigned short, int>                                         CModelInfoSA::ms_DefaultTxdIDMap;
+std::unordered_map<std::uint32_t, CBaseModelInfoSAInterface*>         CModelInfoSA::m_convertedModelInterfaces;
 
 union tIdeFlags
 {
@@ -1525,7 +1526,8 @@ bool CModelInfoSA::SetCustomModel(RpClump* pClump)
         case eModelInfoType::ATOMIC:
         case eModelInfoType::LOD_ATOMIC:
         case eModelInfoType::TIME:
-            success = pGame->GetRenderWare()->ReplaceAllAtomicsInModel(pClump, static_cast<unsigned short>(m_dwModelID));
+        case eModelInfoType::CLUMP:
+            success = pGame->GetRenderWare()->ReplaceObjectModel(pClump, static_cast<std::uint16_t>(m_dwModelID));
             break;
         default:
             break;
@@ -1543,6 +1545,26 @@ void CModelInfoSA::RestoreOriginalModel()
         pGame->GetStreaming()->RemoveModel(m_dwModelID);
     }
 
+    // Restore original interface if model was converted
+    if (MapContains(m_convertedModelInterfaces, m_dwModelID))
+    {
+        CBaseModelInfoSAInterface* currentInterface = ppModelInfo[m_dwModelID];
+        ppModelInfo[m_dwModelID] = MapGet(m_convertedModelInterfaces, m_dwModelID);
+
+        if (currentInterface)
+        {
+            ppModelInfo[m_dwModelID]->usNumberOfRefs = currentInterface->usNumberOfRefs;
+            ppModelInfo[m_dwModelID]->pColModel = currentInterface->pColModel;
+            ppModelInfo[m_dwModelID]->ucAlpha = currentInterface->ucAlpha;
+            ppModelInfo[m_dwModelID]->usFlags = currentInterface->usFlags;
+            ppModelInfo[m_dwModelID]->usTextureDictionary = currentInterface->usTextureDictionary;
+            
+            delete currentInterface;
+        }
+
+        MapRemove(m_convertedModelInterfaces, m_dwModelID);
+    }
+
     // Reset the stored custom vehicle clump
     m_pCustomClump = NULL;
 }
@@ -1812,6 +1834,144 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID)
     CopyStreamingInfoFromModel(usBaseID);
 }
 
+bool CModelInfoSA::ConvertToClump()
+{
+    if (GetModelType() == eModelInfoType::CLUMP)
+        return false;
+
+    // Get current interface
+    CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID];
+    if (!currentModelInterface)
+        return false;
+
+    m_lastInterfaceVTBL = currentModelInterface->VFTBL;
+
+    // Create new clump interface
+    CClumpModelInfoSAInterface* newClumpInterface = new CClumpModelInfoSAInterface();
+    MemCpyFast(newClumpInterface, currentModelInterface, sizeof(CBaseModelInfoSAInterface));
+    newClumpInterface->m_nAnimFileIndex = -1;
+
+    // We do not destroy or set pRwObject to nullptr here
+    // because our IsLoaded code expects the RwObject to exist.
+    // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel.
+
+    // Set CClumpModelInfo vtbl after copying data
+    newClumpInterface->VFTBL = reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CClumpModelInfo);
+
+    // Set new interface for ModelInfo
+    ppModelInfo[m_dwModelID] = newClumpInterface;
+
+    // Store original (only) interface
+    if (!MapContains(m_convertedModelInterfaces, m_dwModelID))
+        MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface);
+    else
+        m_lastConversionInterface = currentModelInterface;
+
+    return true;
+}
+
+bool CModelInfoSA::ConvertToAtomic(bool damageable)
+{
+    // Get current interface
+    CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID];
+    if (!currentModelInterface)
+        return false;
+
+    if (GetModelType() == eModelInfoType::ATOMIC && ((damageable && currentModelInterface->IsDamageAtomicVTBL()) || (!damageable && currentModelInterface->IsAtomicVTBL())))
+        return false;
+
+    m_lastInterfaceVTBL = currentModelInterface->VFTBL;
+
+    // Create new atomic interface
+    CAtomicModelInfoSAInterface* newAtomicInterface = nullptr;
+    CDamageableModelInfoSAInterface* newDamageableAtomicInterface = nullptr;
+
+    // We do not destroy or set pRwObject to nullptr here
+    // because our IsLoaded code expects the RwObject to exist.
+    // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel.
+
+    if (damageable)
+    {
+        newDamageableAtomicInterface = new CDamageableModelInfoSAInterface();
+        MemCpyFast(newDamageableAtomicInterface, currentModelInterface, sizeof(CDamageableModelInfoSAInterface));
+        newDamageableAtomicInterface->m_damagedAtomic = nullptr;
+
+        // Set CDamageAtomicModelInfo vtbl after copying data
+        newDamageableAtomicInterface->VFTBL = reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CDamageAtomicModelInfo);
+    }
+    else
+    {
+        newAtomicInterface = new CAtomicModelInfoSAInterface();
+        MemCpyFast(newAtomicInterface, currentModelInterface, sizeof(CAtomicModelInfoSAInterface));
+
+        // Set CAtomicModelInfo vtbl after copying data
+        newAtomicInterface->VFTBL = reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CAtomicModelInfo);
+    }
+
+    // Set new interface for ModelInfo
+    ppModelInfo[m_dwModelID] = damageable ? newDamageableAtomicInterface : newAtomicInterface;
+
+    // Store original (only) interface
+    if (!MapContains(m_convertedModelInterfaces, m_dwModelID))
+        MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface);
+    else
+        m_lastConversionInterface = currentModelInterface;
+
+    return true;
+}
+
+bool CModelInfoSA::ConvertToTimedObject()
+{
+    if (GetModelType() == eModelInfoType::TIME)
+        return false;
+
+    // Get current interface
+    CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID];
+    if (!currentModelInterface)
+        return false;
+
+    m_lastInterfaceVTBL = currentModelInterface->VFTBL;
+
+    // Create new interface
+    CTimeModelInfoSAInterface* newTimedInterface = new CTimeModelInfoSAInterface();
+    MemCpyFast(newTimedInterface, currentModelInterface, sizeof(CTimeModelInfoSAInterface));
+    newTimedInterface->timeInfo.m_wOtherTimeModel = 0;
+
+    // We do not destroy or set pRwObject to nullptr here
+    // because our IsLoaded code expects the RwObject to exist.
+    // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel.
+
+    // Set CTimeModelInfo vtbl after copying data
+    newTimedInterface->VFTBL = reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CTimeModelInfo);
+
+    // Set new interface for ModelInfo
+    ppModelInfo[m_dwModelID] = newTimedInterface;
+
+    // Store original (only) interface
+    if (!MapContains(m_convertedModelInterfaces, m_dwModelID))
+        MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface);
+    else
+        m_lastConversionInterface = currentModelInterface;
+
+    return true;
+}
+
+CBaseModelInfoSAInterface* CModelInfoSA::GetOriginalInterface() const
+{
+    if (!MapContains(m_convertedModelInterfaces, m_dwModelID))
+        return nullptr;
+
+    return MapGet(m_convertedModelInterfaces, m_dwModelID);
+}
+
+bool CModelInfoSA::IsSameModelType()
+{
+    if (!m_lastInterfaceVTBL)
+        m_lastInterfaceVTBL = m_pInterface->VFTBL;
+
+    return m_lastInterfaceVTBL == m_pInterface->VFTBL;
+}
+
 void CModelInfoSA::MakeVehicleAutomobile(ushort usBaseID)
 {
     CVehicleModelInfoSAInterface* m_pInterface = new CVehicleModelInfoSAInterface();
diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h
index 6d0e00c758..9ccbf43aae 100644
--- a/Client/game_sa/CModelInfoSA.h
+++ b/Client/game_sa/CModelInfoSA.h
@@ -64,6 +64,11 @@ static void* ARRAY_ModelInfo = *(void**)(0x403DA4 + 3);
 
 #define     VAR_CTempColModels_ModelPed1    0x968DF0
 
+#define VTBL_CClumpModelInfo 0x85BD30
+#define VTBL_CAtomicModelInfo 0x85BBF0
+#define VTBL_CDamageAtomicModelInfo 0x85BC30
+#define VTBL_CTimeModelInfo         0x85BCB0
+
 class CBaseModelInfoSAInterface;
 class CModelInfoSAInterface
 {
@@ -232,6 +237,13 @@ class CBaseModelInfoSAInterface
     // +726 = Word array as referenced in CVehicleModelInfo::GetVehicleUpgrade(int)
     // +762 = Array of WORD containing something relative to paintjobs
     // +772 = Anim file index
+
+    void DeleteRwObject() { ((void(__thiscall*)(void*))VFTBL->DeleteRwObject)(this); }
+
+    bool IsAtomicVTBL() const { return VFTBL == reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CAtomicModelInfo); }
+    bool IsDamageAtomicVTBL() const { return VFTBL == reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CDamageAtomicModelInfo); }
+    bool IsTimedObjectVTBL() const { return VFTBL == reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CTimeModelInfo); }
+    bool IsClumpVTBL() const { return VFTBL == reinterpret_cast<CBaseModelInfo_SA_VTBL*>(VTBL_CClumpModelInfo); }
 };
 static_assert(sizeof(CBaseModelInfoSAInterface) == 0x20, "Invalid size for CBaseModelInfoSAInterface");
 
@@ -250,11 +262,28 @@ class CClumpModelInfoSAInterface : public CBaseModelInfoSAInterface
     union
     {
         char*    m_animFileName;
-        uint32_t m_nAnimFileIndex;
+        std::int32_t m_nAnimFileIndex;
     };
+
+    void DeleteRwObject() { ((void(__thiscall*)(CClumpModelInfoSAInterface*))0x4C4E70)(this); }
+    void SetClump(RpClump* clump) { ((void(__thiscall*)(CClumpModelInfoSAInterface*, RpClump*))0x4C4F70)(this, clump); }
+};
+
+class CAtomicModelInfoSAInterface : public CBaseModelInfoSAInterface
+{
+public:
+    void DeleteRwObject() { ((void(__thiscall*)(CAtomicModelInfoSAInterface*))0x4C4440)(this); }
+    void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfoSAInterface*, RpAtomic*))0x4C4360)(this, atomic); }
 };
 
-class CTimeModelInfoSAInterface : public CBaseModelInfoSAInterface
+class CLodAtomicModelInfoSAInterface : public CAtomicModelInfoSAInterface
+{
+public:
+    std::int16_t numChildren; // num child higher level LODs
+    std::int16_t numChildrenRendered; // num child higher level LODs that have been rendered
+};
+
+class CTimeModelInfoSAInterface : public CAtomicModelInfoSAInterface
 {
 public:
     CTimeInfoSAInterface timeInfo;
@@ -268,10 +297,12 @@ class CVehicleModelUpgradePosnDesc
 };
 static_assert(sizeof(CVehicleModelUpgradePosnDesc) == 0x20, "Invalid size of CVehicleModelUpgradePosnDesc class");
 
-class CDamageableModelInfoSAInterface : public CBaseModelInfoSAInterface
+class CDamageableModelInfoSAInterface : public CAtomicModelInfoSAInterface
 {
 public:
-    void* m_damagedAtomic;
+    RpAtomic* m_damagedAtomic;
+
+    void SetDamagedAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CDamageableModelInfoSAInterface*, RpAtomic*))0x4C48D0)(this, atomic); }
 };
 
 class CVehicleModelVisualInfoSAInterface            // Not sure about this name. If somebody knows more, please change
@@ -356,6 +387,12 @@ class CModelInfoSA : public CModelInfo
     static std::map<unsigned short, int>                                         ms_DefaultTxdIDMap;
     SVehicleSupportedUpgrades                                                    m_ModelSupportedUpgrades;
 
+    void*                      m_lastInterfaceVTBL{nullptr};
+    CBaseModelInfoSAInterface* m_lastConversionInterface{nullptr};
+
+    // Store original model interfaces after type conersion 
+    static std::unordered_map<std::uint32_t, CBaseModelInfoSAInterface*> m_convertedModelInterfaces;
+
 public:
     CModelInfoSA();
 
@@ -489,6 +526,15 @@ class CModelInfoSA : public CModelInfo
     void           RestoreObjectPropertiesGroup();
     static void    RestoreAllObjectsPropertiesGroups();
 
+    // Model type conversion functions
+    bool ConvertToClump() override;
+    bool ConvertToAtomic(bool damageable) override;
+    bool ConvertToTimedObject() override;
+    CBaseModelInfoSAInterface* GetLastConversionInterface() const noexcept override { return m_lastConversionInterface; }
+    void                       SetLastConversionInterface(CBaseModelInfoSAInterface* lastInterace) noexcept override { m_lastConversionInterface = lastInterace; }
+    CBaseModelInfoSAInterface* GetOriginalInterface() const override;
+    bool                       IsSameModelType() override;
+
     // Vehicle towing functions
     bool IsTowableBy(CModelInfo* towingModel) override;
 
diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp
index 56f5ce34e4..c95e45cfa3 100644
--- a/Client/game_sa/CRenderWareSA.cpp
+++ b/Client/game_sa/CRenderWareSA.cpp
@@ -23,6 +23,7 @@
 #include "CRenderWareSA.ShaderMatching.h"
 #include "gamesa_renderware.h"
 #include "gamesa_renderware.hpp"
+#include "CVisibilityPluginsSA.h"
 
 extern CCoreInterface* g_pCore;
 extern CGameSA*        pGame;
@@ -476,52 +477,156 @@ unsigned int CRenderWareSA::LoadAtomics(RpClump* pClump, RpAtomicContainer* pAto
     return data.uiReplacements;
 }
 
-// Replaces all atomics for a specific model
-typedef struct
+struct SCDamageableModelinfo
 {
-    unsigned short usTxdID;
-    RpClump*       pClump;
-} SAtomicsReplacer;
-bool AtomicsReplacer(RpAtomic* pAtomic, void* data)
+    CBaseModelInfoSAInterface* baseInterface;
+    std::uint32_t              modelId;
+    RpClump*                   clump;
+};
+
+bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID)
 {
-    SAtomicsReplacer* pData = reinterpret_cast<SAtomicsReplacer*>(data);
-    SRelatedModelInfo relatedModelInfo = {0};
-    relatedModelInfo.pClump = pData->pClump;
-    relatedModelInfo.bDeleteOldRwObject = true;
-    CFileLoader_SetRelatedModelInfoCB(pAtomic, &relatedModelInfo);
+    CModelInfo* pModelInfo = pGame->GetModelInfo(modelID);
+    if (!pModelInfo)
+        return false;
 
-    // The above function adds a reference to the model's TXD by either
-    // calling CAtomicModelInfo::SetAtomic or CDamagableModelInfo::SetDamagedAtomic. Remove it again.
-    CTxdStore_RemoveRef(pData->usTxdID);
-    return true;
-}
+    CBaseModelInfoSAInterface* currentModelInterface = pModelInfo->GetInterface();
 
-bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usModelID)
-{
-    CModelInfo* pModelInfo = pGame->GetModelInfo(usModelID);
+    // Comparing two clumps here leads to an unknown crash during object creation (std::bad_alloc) in the SA code
+    if ((newClump == reinterpret_cast<RpClump*>(currentModelInterface->pRwObject) || DoContainTheSameGeometry(newClump, nullptr, reinterpret_cast<RpAtomic*>(currentModelInterface->pRwObject))) && pModelInfo->IsSameModelType())
+        return true;
 
-    if (pModelInfo)
+    // We need to make a copy here because DeleteRwObject removes atomics from our new clump
+    RpClump* clonedClump = RpClumpClone(newClump);
+    if (!clonedClump)
+        return false;
+
+    // Add extra reference to prevent the TXD from being unloaded after calling DeleteRwObject
+    bool extraRefAdded = false;
+    if (currentModelInterface->pRwObject)
     {
-        RpAtomic* pOldAtomic = (RpAtomic*)pModelInfo->GetRwObject();
+        CTxdStore_AddRef(currentModelInterface->usTextureDictionary);
+        extraRefAdded = true;
+    }
 
-        if (reinterpret_cast<RpClump*>(pOldAtomic) != pNew && !DoContainTheSameGeometry(pNew, NULL, pOldAtomic))
+    // Destroy RwObject from original interface after type conversion
+    CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface();
+    if (originalInterface && originalInterface->pRwObject)
+    {
+        originalInterface->DeleteRwObject();
+
+        // If the originalInterface has an existing RwObject, it points to the same one as copyCurrentModelInterface
+        currentModelInterface->pRwObject = nullptr;
+    }
+
+    // The user can convert the same model multiple times, but its original model interface is saved only once,
+    // so we remove the RwObject and the interface created during the last conversion
+    CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface();
+    if (lastConversionInterface)
+    {
+        if (lastConversionInterface->pRwObject == currentModelInterface->pRwObject)
+            currentModelInterface->pRwObject = nullptr;
+
+        lastConversionInterface->DeleteRwObject();
+        delete lastConversionInterface;
+
+        pModelInfo->SetLastConversionInterface(nullptr);
+    }
+
+    // Destroy current (old) RwObject
+    if (currentModelInterface->pRwObject)
+        currentModelInterface->DeleteRwObject();
+
+    // Check model type
+    switch (pModelInfo->GetModelType())
+    {
+        case eModelInfoType::ATOMIC:
         {
-            // Clone the clump that's to be replaced (FUNC_AtomicsReplacer removes the atomics from the source clump)
-            RpClump* pCopy = RpClumpClone(pNew);
+            RpAtomic* atomic = GetFirstAtomic(clonedClump);
+            if (!atomic)
+                return false;
 
-            // Replace the atomics
-            SAtomicsReplacer data;
-            data.usTxdID = ((CBaseModelInfoSAInterface**)ARRAY_ModelInfo)[usModelID]->usTextureDictionary;
-            data.pClump = pCopy;
+            // Create main root frame
+            RpAtomicSetFrame(atomic, RwFrameCreate());
 
-            MemPutFast<DWORD>((DWORD*)DWORD_AtomicsReplacerModelID, usModelID);
-            RpClumpForAllAtomics(pCopy, AtomicsReplacer, &data);
+            // Is damageable object?
+            if (currentModelInterface->IsDamageAtomicVTBL())
+            {
+                SCDamageableModelinfo damagedInfo{};
+                damagedInfo.modelId = modelID;
+                damagedInfo.baseInterface = currentModelInterface;
+                damagedInfo.clump = clonedClump;
+
+                RpClumpForAllAtomics(clonedClump,
+                [](RpAtomic* atomic, void* data) -> bool {
+                    auto* damagedInfo = static_cast<SCDamageableModelinfo*>(data);
+                    RwFrame* frame = RpGetFrame(atomic);
+                    if (!frame)
+                        return false;
+
+                    bool damage = false;
+                    char name[24];
+                    GetNameAndDamage(GetFrameNodeName(frame), name, damage);
+
+                    if (damage)
+                        static_cast<CDamageableModelInfoSAInterface*>(damagedInfo->baseInterface)->SetDamagedAtomic(atomic);
+                    else
+                        static_cast<CAtomicModelInfoSAInterface*>(damagedInfo->baseInterface)->SetAtomic(atomic);
+
+                    // Create main root frame
+                    RpAtomicSetFrame(atomic, RwFrameCreate());
+                    pGame->GetVisibilityPlugins()->SetAtomicId(atomic, damagedInfo->modelId);
+
+                    // Remove our atomic from temp clump
+                    RpClumpRemoveAtomic(damagedInfo->clump, atomic);
+                    return false;
+                }, &damagedInfo);
+            }
+            else // or standard object?
+            {
+                static_cast<CAtomicModelInfoSAInterface*>(currentModelInterface)->SetAtomic(atomic);
+                pGame->GetVisibilityPlugins()->SetAtomicId(atomic, modelID);
+
+                // Remove our atomic from temp clump
+                RpClumpRemoveAtomic(clonedClump, atomic);
+            }
+
+            // Destroy empty clump
+            RpClumpDestroy(clonedClump);
+            break;
+        }
+        case eModelInfoType::TIME:
+        {
+            RpAtomic* atomic = GetFirstAtomic(clonedClump);
+            if (!atomic)
+                return false;
 
-            // Get rid of the now empty copied clump
-            RpClumpDestroy(pCopy);
+            // Create main root frame
+            RpAtomicSetFrame(atomic, RwFrameCreate());
+
+            static_cast<CTimeModelInfoSAInterface*>(currentModelInterface)->SetAtomic(atomic);
+            pGame->GetVisibilityPlugins()->SetAtomicId(atomic, modelID);
+
+            // Remove our atomic from temp clump
+            RpClumpRemoveAtomic(clonedClump, atomic);
+
+            // Destroy empty clump
+            RpClumpDestroy(clonedClump);
+            break;
+        }
+        case eModelInfoType::CLUMP:
+        {
+            // We do not delete or clear the clump copy here
+            // The copy will be removed when DestroyRwObject is called in CClumpModelInfo
+            static_cast<CClumpModelInfoSAInterface*>(currentModelInterface)->SetClump(clonedClump);
+            break;
         }
     }
 
+    // Remove extra ref
+    if (extraRefAdded)
+        CTxdStore_RemoveRef(currentModelInterface->usTextureDictionary);
+ 
     return true;
 }
 
@@ -927,6 +1032,30 @@ RwFrame* CRenderWareSA::GetFrameFromName(RpClump* pRoot, SString strName)
     return RwFrameFindFrame(RpGetFrame(pRoot), strName);
 }
 
+RpAtomic* CRenderWareSA::GetFirstAtomic(RpClump* clump)
+{
+    return ((RpAtomic*(__cdecl*)(RpClump*))0x734820)(clump);
+}
+
+char* CRenderWareSA::GetFrameNodeName(RwFrame* frame)
+{
+    return ((char*(__cdecl*)(RwFrame*))0x72FB30)(frame);
+}
+
+std::uint32_t CRenderWareSA::RpGeometryGet2dFxCount(RpGeometry* geometry)
+{
+    if (!geometry)
+        return 0;
+
+    auto* plugin = RWPLUGINOFFSET(std::uint32_t, geometry, *(int*)0xC3A1E0); // g2dEffectPluginOffset
+    return plugin ? *plugin : 0;
+}
+
+RpAtomic* CRenderWareSA::Get2DEffectAtomic(RpClump* clump)
+{
+    return ((RpAtomic*(__cdecl*)(RpClump*))0x734880)(clump);
+}
+
 void CRenderWareSA::CMatrixToRwMatrix(const CMatrix& mat, RwMatrix& rwOutMatrix)
 {
     rwOutMatrix.right = (RwV3d&)mat.vRight;
diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h
index d79d930720..3be13b17a0 100644
--- a/Client/game_sa/CRenderWareSA.h
+++ b/Client/game_sa/CRenderWareSA.h
@@ -60,9 +60,6 @@ class CRenderWareSA : public CRenderWare
     // Loads all atomics from a clump into a container struct and returns the number of atomics it loaded
     unsigned int LoadAtomics(RpClump* pClump, RpAtomicContainer* pAtomics);
 
-    // Replaces all atomics for a specific model
-    bool ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usModelID) override;
-
     // Replaces all atomics in a clump
     void ReplaceAllAtomicsInClump(RpClump* pDst, RpAtomicContainer* pAtomics, unsigned int uiAtomics);
 
@@ -80,11 +77,12 @@ class CRenderWareSA : public CRenderWare
 
     // Replaces a CClumpModelInfo clump with a new clump
     bool ReplaceWeaponModel(RpClump* pNew, unsigned short usModelID) override;
-
     bool ReplacePedModel(RpClump* pNew, unsigned short usModelID) override;
-
     bool ReplaceModel(RpClump* pNew, unsigned short usModelID, DWORD dwSetClumpFunction);
 
+    // Replaces object model (clump or atomic)
+    bool ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) override;
+
     // Replaces dynamic parts of the vehicle (models that have two different versions: 'ok' and 'dam'), such as doors
     // szName should be without the part suffix (e.g. 'door_lf' or 'door_rf', and not 'door_lf_dummy')
     bool ReplacePartModels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szName);
@@ -122,6 +120,57 @@ class CRenderWareSA : public CRenderWare
     CModelTexturesInfo* GetModelTexturesInfo(ushort usModelId);
 
     RwFrame* GetFrameFromName(RpClump* pRoot, SString strName);
+    RpAtomic* GetFirstAtomic(RpClump* clump) override;
+
+    static char* GetFrameNodeName(RwFrame* frame);
+
+    std::uint32_t RpGeometryGet2dFxCount(RpGeometry* geometry) override;
+    RpAtomic* Get2DEffectAtomic(RpClump* clump) override;
+
+    // Originally there was a possibility for this function to cause buffer overflow
+    // It should be fixed here.
+    template <size_t OutBuffSize>
+    static void GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage)
+    {
+        const auto nodeNameLen = strlen(nodeName);
+
+        const auto NodeNameEndsWith = [=](const char* with)
+        {
+            const auto withLen = strlen(with);
+            // dassert(withLen <= nodeNameLen);
+            return withLen <= nodeNameLen /*dont bother checking otherwise, because it might cause a crash*/
+                   && strncmp(nodeName + nodeNameLen - withLen, with, withLen) == 0;
+        };
+
+        // Copy `nodeName` into `outName` with `off` trimmed from the end
+        // Eg.: `dmg_dam` with `off = 4` becomes `dmg`
+        const auto TerminatedCopy = [&](size_t off)
+        {
+            dassert(nodeNameLen - off < OutBuffSize);
+            strncpy_s(
+                outName, nodeName,
+                std::min(nodeNameLen - off, OutBuffSize - 1));            // By providing `OutBuffSize - 1` it is ensured the array will be null terminated
+        };
+
+        if (NodeNameEndsWith("_dam"))
+        {
+            outDamage = true;
+            TerminatedCopy(sizeof("_dam") - 1);
+        }
+        else
+        {
+            outDamage = false;
+            if (NodeNameEndsWith("_l0") || NodeNameEndsWith("_L0"))
+            {
+                TerminatedCopy(sizeof("_l0") - 1);
+            }
+            else
+            {
+                dassert(nodeNameLen < OutBuffSize);
+                strncpy_s(outName, OutBuffSize, nodeName, OutBuffSize - 1);
+            }
+        }
+    }
 
     static void  StaticSetHooks();
     static void  StaticSetClothesReplacingHooks();
diff --git a/Client/game_sa/CVisibilityPluginsSA.cpp b/Client/game_sa/CVisibilityPluginsSA.cpp
index b5fe2d92c6..c4cab5c173 100644
--- a/Client/game_sa/CVisibilityPluginsSA.cpp
+++ b/Client/game_sa/CVisibilityPluginsSA.cpp
@@ -54,6 +54,14 @@ int CVisibilityPluginsSA::GetAtomicId(RwObject* pAtomic)
     return iResult;
 }
 
+void CVisibilityPluginsSA::SetAtomicId(RpAtomic* atomic, int id)
+{
+    if (!atomic)
+        return;
+
+    ((void(__cdecl*)(RpAtomic*, int))FUNC_CVisibilityPlugins_SetAtomicId)(atomic, id);
+}
+
 bool CVisibilityPluginsSA::InsertEntityIntoEntityList(void* entity, float distance, void* callback)
 {
     return ((bool(_cdecl*)(void*, float, void*))FUNC_CVisibilityPlugins_InsertEntityIntoEntityList)(entity, distance, callback);
diff --git a/Client/game_sa/CVisibilityPluginsSA.h b/Client/game_sa/CVisibilityPluginsSA.h
index f4a583bcf2..81ffc736eb 100644
--- a/Client/game_sa/CVisibilityPluginsSA.h
+++ b/Client/game_sa/CVisibilityPluginsSA.h
@@ -15,12 +15,14 @@
 
 #define FUNC_CVisiblityPlugins_SetClumpAlpha    0x732B00
 #define FUNC_CVisibilityPlugins_GetAtomicId     0x732370
+#define FUNC_CVisibilityPlugins_SetAtomicId     0x732230
 
 class CVisibilityPluginsSA : public CVisibilityPlugins
 {
 public:
     void SetClumpAlpha(RpClump* pClump, int iAlpha);
     int  GetAtomicId(RwObject* pAtomic);
+    void SetAtomicId(RpAtomic* atomic, int id) override;
 
     bool InsertEntityIntoEntityList(void* entity, float distance, void* callback);
 };
diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
index 0ad88113b1..4a57ab30c5 100644
--- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
+++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
@@ -147,6 +147,7 @@ void CLuaEngineDefs::LoadFunctions()
         {"engineGetPoolUsedCapacity", ArgumentParser<EngineGetPoolUsedCapacity>},
         {"engineSetPoolCapacity", ArgumentParser<EngineSetPoolCapacity>},
         {"enginePreloadWorldArea", ArgumentParser<EnginePreloadWorldArea>},
+        {"engineConvertModelToType", ArgumentParser<EngineConvertModelToType>},
         
         // CLuaCFunctions::AddFunction ( "engineReplaceMatchingAtomics", EngineReplaceMatchingAtomics );
         // CLuaCFunctions::AddFunction ( "engineReplaceWheelAtomics", EngineReplaceWheelAtomics );
@@ -2568,3 +2569,34 @@ void CLuaEngineDefs::EnginePreloadWorldArea(CVector position, std::optional<Prel
     if (option == PreloadAreaOption::ALL || option == PreloadAreaOption::COLLISIONS)
         g_pGame->GetStreaming()->LoadSceneCollision(&position);
 }
+
+bool CLuaEngineDefs::EngineConvertModelToType(std::uint32_t model, eClientModelType type)
+{
+    if (!CClientObjectManager::IsValidModel(model))
+        throw LuaFunctionError("Invalid model");
+
+    if (type == eClientModelType::PED || type == eClientModelType::VEHICLE || type == eClientModelType::TXD)
+        throw LuaFunctionError("The argument 'model-type' is invalid");
+
+    CModelInfo* modelInfo = g_pGame->GetModelInfo(model);
+    if (!modelInfo)
+        return false;
+
+    // We need to stream out the model, otherwise it can crash
+    g_pClientGame->GetObjectManager()->RestreamObjects(model);
+    modelInfo->RestreamIPL();
+
+    switch (type)
+    {
+        case eClientModelType::OBJECT:
+            return modelInfo->ConvertToAtomic(false);
+        case eClientModelType::OBJECT_DAMAGEABLE:
+            return modelInfo->ConvertToAtomic(true);
+        case eClientModelType::CLUMP:
+            return modelInfo->ConvertToClump();
+        case eClientModelType::TIMED_OBJECT:
+            return modelInfo->ConvertToTimedObject();
+    }
+
+    return false;
+}
diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h
index a67ecfc68d..85d013a426 100644
--- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h
+++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h
@@ -94,6 +94,8 @@ class CLuaEngineDefs : public CLuaDefs
 
     static void EnginePreloadWorldArea(CVector position, std::optional<PreloadAreaOption> option);
 
+    static bool EngineConvertModelToType(std::uint32_t model, eClientModelType type);
+
 private:
     static void AddEngineColClass(lua_State* luaVM);
     static void AddEngineTxdClass(lua_State* luaVM);
diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h
index 8b27fc0304..d42589ee6f 100644
--- a/Client/sdk/game/CModelInfo.h
+++ b/Client/sdk/game/CModelInfo.h
@@ -192,6 +192,14 @@ class CModelInfo
     virtual bool IsAlphaTransparencyEnabled() = 0;
     virtual void ResetAlphaTransparency() = 0;
 
+    virtual bool ConvertToClump() = 0;
+    virtual bool ConvertToAtomic(bool damageable) = 0;
+    virtual bool ConvertToTimedObject() = 0;
+    virtual CBaseModelInfoSAInterface* GetLastConversionInterface() const noexcept = 0;
+    virtual void                       SetLastConversionInterface(CBaseModelInfoSAInterface* lastInterface) noexcept = 0;
+    virtual CBaseModelInfoSAInterface* GetOriginalInterface() const = 0;
+    virtual bool                       IsSameModelType() = 0;
+
     // ONLY use for CVehicleModelInfos
     virtual short        GetAvailableVehicleMod(unsigned short usSlot) = 0;
     virtual bool         IsUpgradeAvailable(eVehicleUpgradePosn posn) = 0;
diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h
index 92686978b1..e65c1c1208 100644
--- a/Client/sdk/game/CRenderWare.h
+++ b/Client/sdk/game/CRenderWare.h
@@ -26,6 +26,8 @@ struct RwMatrix;
 struct RwTexDictionary;
 struct RwTexture;
 struct RpClump;
+struct RpAtomic;
+struct RpGeometry;
 
 typedef CShaderItem CSHADERDUMMY;
 
@@ -87,7 +89,6 @@ class CRenderWare
     virtual void             DestroyTexture(RwTexture* pTex) = 0;
     virtual void             ReplaceCollisions(CColModel* pColModel, unsigned short usModelID) = 0;
     virtual unsigned int     LoadAtomics(RpClump* pClump, RpAtomicContainer* pAtomics) = 0;
-    virtual bool             ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usModelID) = 0;
     virtual void             ReplaceAllAtomicsInClump(RpClump* pDst, RpAtomicContainer* pAtomics, unsigned int uiAtomics) = 0;
     virtual void             ReplaceWheels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szWheel) = 0;
     virtual void             RepositionAtomic(RpClump* pDst, RpClump* pSrc, const char* szName) = 0;
@@ -95,6 +96,7 @@ class CRenderWare
     virtual bool             ReplaceVehicleModel(RpClump* pNew, unsigned short usModelID) = 0;
     virtual bool             ReplaceWeaponModel(RpClump* pNew, unsigned short usModelID) = 0;
     virtual bool             ReplacePedModel(RpClump* pNew, unsigned short usModelID) = 0;
+    virtual bool             ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) = 0;
     virtual bool             ReplacePartModels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szName) = 0;
     virtual void             PulseWorldTextureWatch() = 0;
     virtual void             GetModelTextureNames(std::vector<SString>& outNameList, ushort usModelID) = 0;
@@ -110,6 +112,11 @@ class CRenderWare
     virtual void     RemoveClientEntityRefs(CClientEntityBase* pClientEntity) = 0;
     virtual void     RemoveShaderRefs(CSHADERDUMMY* pShaderItem) = 0;
     virtual RwFrame* GetFrameFromName(RpClump* pRoot, SString strName) = 0;
+    virtual RpAtomic* GetFirstAtomic(RpClump* clump) = 0;
+
+    virtual std::uint32_t RpGeometryGet2dFxCount(RpGeometry* geometry) = 0;
+    virtual RpAtomic*     Get2DEffectAtomic(RpClump* clump) = 0;
+
     virtual bool     RightSizeTxd(const SString& strInTxdFilename, const SString& strOutTxdFilename, uint uiSizeLimit) = 0;
     virtual void     TxdForceUnload(ushort usTxdId, bool bDestroyTextures) = 0;
 
diff --git a/Client/sdk/game/CVisibilityPlugins.h b/Client/sdk/game/CVisibilityPlugins.h
index f2e948f65a..1cb16960c9 100644
--- a/Client/sdk/game/CVisibilityPlugins.h
+++ b/Client/sdk/game/CVisibilityPlugins.h
@@ -15,6 +15,7 @@
 #define ATOMIC_ID_FLAG_TWO_VERSIONS_DAMAGED     2
 
 struct RpClump;
+struct RpAtomic;
 struct RwObject;
 
 class CVisibilityPlugins
@@ -22,6 +23,7 @@ class CVisibilityPlugins
 public:
     virtual void SetClumpAlpha(RpClump* pClump, int iAlpha) = 0;
     virtual int  GetAtomicId(RwObject* pAtomic) = 0;
+    virtual void SetAtomicId(RpAtomic* atomic, int id) = 0;
 
     virtual bool InsertEntityIntoEntityList(void* entity, float distance, void* callback) = 0;
 };