From fcef9e4a6eea3838fad47d3ea501fd060829aad6 Mon Sep 17 00:00:00 2001 From: Walace Date: Sat, 22 Feb 2025 14:14:41 -0300 Subject: [PATCH 01/17] Adds functions for adding new CJ clothes - engineAddClotheModel - engineAddClotheTXD - addClotheModel - removeClotheModel --- Client/game_sa/CDirectorySA.cpp | 65 +++++ Client/game_sa/CDirectorySA.h | 4 + .../CRenderWareSA.ClothesReplacing.cpp | 109 ++++++++- Client/game_sa/CRenderWareSA.h | 3 + Client/mods/deathmatch/logic/CClientDFF.cpp | 20 ++ Client/mods/deathmatch/logic/CClientDFF.h | 1 + Client/mods/deathmatch/logic/CClientPed.cpp | 1 + .../deathmatch/logic/CClientPlayerClothes.cpp | 223 +++++++++++++++--- .../deathmatch/logic/CClientPlayerClothes.h | 11 +- Client/mods/deathmatch/logic/CClientTXD.cpp | 23 ++ Client/mods/deathmatch/logic/CClientTXD.h | 1 + .../logic/CStaticFunctionDefinitions.cpp | 16 ++ .../logic/CStaticFunctionDefinitions.h | 2 + .../lua/CLuaFunctionDefs.BodyClothes.cpp | 44 ++++ .../deathmatch/logic/lua/CLuaFunctionDefs.h | 2 + .../mods/deathmatch/logic/lua/CLuaManager.cpp | 2 + .../logic/luadefs/CLuaEngineDefs.cpp | 24 ++ .../deathmatch/logic/luadefs/CLuaEngineDefs.h | 3 + Client/sdk/game/CRenderWare.h | 3 + 19 files changed, 507 insertions(+), 50 deletions(-) diff --git a/Client/game_sa/CDirectorySA.cpp b/Client/game_sa/CDirectorySA.cpp index 726353792ea..eec768ce8f0 100644 --- a/Client/game_sa/CDirectorySA.cpp +++ b/Client/game_sa/CDirectorySA.cpp @@ -11,6 +11,71 @@ #include "StdInc.h" #include "CDirectorySA.h" +bool CDirectorySAInterface::AddEntry(DirectoryInfoSA& entry) +{ + if (m_nNumEntries >= m_nCapacity) + return false; + + if (GetModelEntry(entry.m_szName)) + return false; + + entry.m_nOffset = 0; + + if (m_nNumEntries > 0) + { + DirectoryInfoSA* lastEntry = m_pEntries + m_nNumEntries - 1; + entry.m_nOffset = lastEntry->m_nOffset + lastEntry->m_nStreamingSize; + + if (entry.m_nOffset % 2048) + entry.m_nOffset += 2048 - (entry.m_nOffset % 2048); + } + + m_pEntries[m_nNumEntries] = entry; + m_nNumEntries++; + + return true; +} + +bool CDirectorySAInterface::RemoveEntry(const char* pFileName) +{ + if (m_nNumEntries <= 0) + return false; + + DirectoryInfoSA* entry = GetModelEntry(pFileName); + + if (!entry) + return false; + + std::uint32_t index = entry - m_pEntries; + + if (index < m_nNumEntries - 1) + { + DirectoryInfoSA* lastEntry = m_pEntries + m_nNumEntries - 1; + entry->m_nOffset = lastEntry->m_nOffset + lastEntry->m_nSizeInArchive; + } + + m_nNumEntries--; + + if (index < m_nNumEntries) + memmove(entry, entry + 1, (m_nNumEntries - index) * sizeof(DirectoryInfoSA)); + + return true; +} + +DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* pFileName) +{ + if (m_nNumEntries <= 0) + return nullptr; + + for (DirectoryInfoSA* it = m_pEntries; it != m_pEntries + m_nNumEntries; it++) + { + if (strcmp(it->m_szName, pFileName) == 0) + return it; + } + + return nullptr; +} + DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(std::uint16_t modelId) { if (m_nNumEntries <= 0) diff --git a/Client/game_sa/CDirectorySA.h b/Client/game_sa/CDirectorySA.h index 620db3938be..42273f670b0 100644 --- a/Client/game_sa/CDirectorySA.h +++ b/Client/game_sa/CDirectorySA.h @@ -21,6 +21,10 @@ struct DirectoryInfoSA class CDirectorySAInterface { public: + bool AddEntry(DirectoryInfoSA& entry); + bool RemoveEntry(const char* pFileName); + + DirectoryInfoSA* GetModelEntry(const char* pFileName); DirectoryInfoSA* GetModelEntry(std::uint16_t modelId); bool SetModelStreamingSize(std::uint16_t modelId, std::uint16_t size); std::uint16_t GetModelStreamingSize(std::uint16_t modelId); diff --git a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp index ffae5f3dd37..313ac8bc41c 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -31,6 +31,7 @@ namespace std::unordered_map ms_ReplacementClothesFileDataMap; std::unordered_map ms_OriginalStreamingSizesMap; + std::unordered_map ms_ClothesFileDataMap; bool bClothesReplacementChanged = false; @@ -128,6 +129,83 @@ bool CRenderWareSA::HasClothesReplacementChanged() return bResult; } +//////////////////////////////////////////////////////////////// +// +// CRenderWareSA::ClothesAddFile +// +// Add a file to the clothes directory +// +//////////////////////////////////////////////////////////////// +bool CRenderWareSA::ClothesAddFile(const char* pFileData, size_t fileSize, const char* pFileName) +{ + if (!pFileData || !pFileName) + return false; + + if (MapFind(ms_ClothesFileDataMap, pFileName)) + return false; + + DirectoryInfoSA entry{}; + entry.m_nStreamingSize = GetSizeInBlocks(fileSize); + strncpy(entry.m_szName, pFileName, sizeof(entry.m_szName)); + + if (!g_clothesDirectory->AddEntry(entry)) + return false; + + MapSet(ms_ClothesFileDataMap, pFileName, (char*)pFileData); + bClothesReplacementChanged = true; + + return true; +} + +//////////////////////////////////////////////////////////////// +// +// CRenderWareSA::ClothesRemoveFile +// +// Remove a file from the clothes directory +// +//////////////////////////////////////////////////////////////// +bool CRenderWareSA::ClothesRemoveFile(char* pFileData) +{ + if (!pFileData) + return false; + + for (auto iter = ms_ClothesFileDataMap.begin(); iter != ms_ClothesFileDataMap.end();) + { + if (iter->second == pFileData) + { + if (!g_clothesDirectory->RemoveEntry(iter->first.c_str())) + return false; + + iter = ms_ClothesFileDataMap.erase(iter); + bClothesReplacementChanged = true; + } + else + ++iter; + } +} + +//////////////////////////////////////////////////////////////// +// +// CRenderWareSA::HasClothesFile +// +// Check if clothe file exits +// +//////////////////////////////////////////////////////////////// +bool CRenderWareSA::HasClothesFile(const char* pFileName) +{ + if (!pFileName) + { + return false; + } + + if (!MapFind(ms_ClothesFileDataMap, pFileName)) + { + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////// // // CStreaming_RequestModel_Mid @@ -143,34 +221,47 @@ __declspec(noinline) bool _cdecl OnCStreaming_RequestModel_Mid(int flags, SImgGT return false; // Early out if no clothes textures to replace with - if (ms_ReplacementClothesFileDataMap.empty()) + if (ms_ReplacementClothesFileDataMap.empty() && ms_ClothesFileDataMap.empty()) return false; // Initialze lookup map if needed static std::map blockOffsetToFileIdMap; + static std::map blockOffsetToFileNameMap; if (blockOffsetToFileIdMap.empty()) { // Check is player.img dir has been loaded by GTA SPlayerImgItemArray* pItemArray = (SPlayerImgItemArray*)0x00BC12C0; - if (!pItemArray->pItems || pItemArray->uiArraySize != 542) + std::uint32_t maxArraySize = 542 + ms_ClothesFileDataMap.size(); + + if (!pItemArray->pItems || pItemArray->uiArraySize != maxArraySize) return false; for (uint i = 0; i < pItemArray->uiArraySize; i++) { SPlayerImgItem* pImgItem = &pItemArray->pItems[i]; MapSet(blockOffsetToFileIdMap, pImgItem->uiBlockOffset, i); + MapSet(blockOffsetToFileNameMap, pImgItem->uiBlockOffset, pImgItem->szName); } } - // Get player.img fileId by comparing the supplied BlockOffset with entries in the player.img dir - int* piPlayerImgFileId = MapFind(blockOffsetToFileIdMap, pImgGTAInfo->iBlockOffset); - if (!piPlayerImgFileId) - return false; + char* pReplacementFileData = nullptr; + int* piPlayerImgFileId = MapFind(blockOffsetToFileIdMap, pImgGTAInfo->iBlockOffset); + + if (piPlayerImgFileId) + { + pReplacementFileData = MapFindRef(ms_ReplacementClothesFileDataMap, *piPlayerImgFileId); + } + + if (!pReplacementFileData) + { + std::string* pFileName = MapFind(blockOffsetToFileNameMap, pImgGTAInfo->iBlockOffset); - int iPlayerImgFileId = *piPlayerImgFileId; + if (pFileName) + { + pReplacementFileData = MapFindRef(ms_ClothesFileDataMap, *pFileName); + } + } - // Do we have a replacement for this clothes texture? - char* pReplacementFileData = MapFindRef(ms_ReplacementClothesFileDataMap, iPlayerImgFileId); if (!pReplacementFileData) return false; diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index d79d9307206..d0d1e01f864 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -35,6 +35,9 @@ class CRenderWareSA : public CRenderWare void ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId); void ClothesRemoveReplacement(char* pFileData); bool HasClothesReplacementChanged(); + bool ClothesAddFile(const char* pFileData, size_t fileSize, const char* pFileName); + bool ClothesRemoveFile(char* pFileData); + bool HasClothesFile(const char* pFileName); // Reads and parses a TXD file specified by a path (szTXD) RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer); diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 07e718a8b92..5bbf3dd54c7 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -99,6 +99,23 @@ void CClientDFF::UnloadDFF() m_LoadedClumpInfoMap.clear(); } +bool CClientDFF::AddClotheModel(std::string strModelName) +{ + if (strModelName.empty()) + return false; + + if (m_RawDataBuffer.empty() && m_bIsRawData) + return false; + + if (m_RawDataBuffer.empty()) + { + if (!FileLoad(std::nothrow, m_strDffFilename, m_RawDataBuffer)) + return false; + } + + return g_pGame->GetRenderWare()->ClothesAddFile(m_RawDataBuffer.data(), m_RawDataBuffer.size(), strModelName.c_str()); +} + bool CClientDFF::ReplaceModel(unsigned short usModel, bool bAlphaTransparency) { // Record attempt in case it all goes wrong @@ -232,6 +249,9 @@ void CClientDFF::RestoreModels() InternalRestoreModel(*iter); } + // Remove all clothes models + g_pGame->GetRenderWare()->ClothesRemoveFile(m_RawDataBuffer.data()); + // Clear the list m_Replaced.clear(); } diff --git a/Client/mods/deathmatch/logic/CClientDFF.h b/Client/mods/deathmatch/logic/CClientDFF.h index 0a4db238940..bb2076a55ec 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.h +++ b/Client/mods/deathmatch/logic/CClientDFF.h @@ -37,6 +37,7 @@ class CClientDFF final : public CClientEntity bool Load(bool isRaw, SString input); + bool AddClotheModel(std::string strModelName); bool ReplaceModel(unsigned short usModel, bool bAlphaTransparency); bool HasReplaced(unsigned short usModel); diff --git a/Client/mods/deathmatch/logic/CClientPed.cpp b/Client/mods/deathmatch/logic/CClientPed.cpp index 85c5e0d51e7..49b801de9e0 100644 --- a/Client/mods/deathmatch/logic/CClientPed.cpp +++ b/Client/mods/deathmatch/logic/CClientPed.cpp @@ -4057,6 +4057,7 @@ void CClientPed::RebuildModel(bool bDelayChange) if (m_ulModel == 0) { // Adds only the neccesary textures + m_pClothes->RefreshClothes(); m_pClothes->AddAllToModel(); m_bPendingRebuildPlayer = true; diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp index 7a3ed663a46..90e56f934f6 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -291,8 +291,9 @@ static const SPlayerClothing g_SuitClothing[SUIT_CLOTHING_MAX + 1] = { {"balaclava", "balaclava"}, {"pimptr", "pimptr"}, {"garageleg", "garagetr"}, {"medictr", "medictr"}, {NULL, NULL}}; // This represents GTA's 1 clothing block -SFixedArray CClientPlayerClothes::m_GlobalClothes; -bool CClientPlayerClothes::m_bStaticInit = true; +SFixedArray CClientPlayerClothes::m_GlobalClothes; +std::array, PLAYER_CLOTHING_SLOTS> CClientPlayerClothes::m_NewClothes; +bool CClientPlayerClothes::m_bStaticInit = true; CClientPlayerClothes::CClientPlayerClothes(CClientPed* pPlayerModel) { @@ -468,45 +469,91 @@ const SPlayerClothing* CClientPlayerClothes::GetClothingGroup(unsigned char ucTy { if (ucType < PLAYER_CLOTHING_SLOTS) { + static std::vector clothingList; + const SPlayerClothing* clothingArray = nullptr; + size_t clothingSize = 0; + switch (ucType) { case 0: - return g_TorsoClothing; + clothingArray = g_TorsoClothing; + clothingSize = sizeof(g_TorsoClothing) / sizeof(g_TorsoClothing[0]); + break; case 1: - return g_HairClothing; + clothingArray = g_HairClothing; + clothingSize = sizeof(g_HairClothing) / sizeof(g_HairClothing[0]); + break; case 2: - return g_LegsClothing; + clothingArray = g_LegsClothing; + clothingSize = sizeof(g_LegsClothing) / sizeof(g_LegsClothing[0]); + break; case 3: - return g_ShoesClothing; + clothingArray = g_ShoesClothing; + clothingSize = sizeof(g_ShoesClothing) / sizeof(g_ShoesClothing[0]); + break; case 4: - return g_LeftUpperArmClothing; + clothingArray = g_LeftUpperArmClothing; + clothingSize = sizeof(g_LeftUpperArmClothing) / sizeof(g_LeftUpperArmClothing[0]); + break; case 5: - return g_LeftLowerArmClothing; + clothingArray = g_LeftLowerArmClothing; + clothingSize = sizeof(g_LeftLowerArmClothing) / sizeof(g_LeftLowerArmClothing[0]); + break; case 6: - return g_RightUpperArmClothing; + clothingArray = g_RightUpperArmClothing; + clothingSize = sizeof(g_RightUpperArmClothing) / sizeof(g_RightUpperArmClothing[0]); + break; case 7: - return g_RightLowerArmClothing; + clothingArray = g_RightLowerArmClothing; + clothingSize = sizeof(g_RightLowerArmClothing) / sizeof(g_RightLowerArmClothing[0]); + break; case 8: - return g_BackTopClothing; + clothingArray = g_BackTopClothing; + clothingSize = sizeof(g_BackTopClothing) / sizeof(g_BackTopClothing[0]); + break; case 9: - return g_LeftChestClothing; + clothingArray = g_LeftChestClothing; + clothingSize = sizeof(g_LeftChestClothing) / sizeof(g_LeftChestClothing[0]); + break; case 10: - return g_RightChestClothing; + clothingArray = g_RightChestClothing; + clothingSize = sizeof(g_RightChestClothing) / sizeof(g_RightChestClothing[0]); + break; case 11: - return g_StomachClothing; + clothingArray = g_StomachClothing; + clothingSize = sizeof(g_StomachClothing) / sizeof(g_StomachClothing[0]); + break; case 12: - return g_LowerBackClothing; + clothingArray = g_LowerBackClothing; + clothingSize = sizeof(g_LowerBackClothing) / sizeof(g_LowerBackClothing[0]); + break; case 13: - return g_Extra1Clothing; + clothingArray = g_Extra1Clothing; + clothingSize = sizeof(g_Extra1Clothing) / sizeof(g_Extra1Clothing[0]); + break; case 14: - return g_Extra2Clothing; + clothingArray = g_Extra2Clothing; + clothingSize = sizeof(g_Extra2Clothing) / sizeof(g_Extra2Clothing[0]); + break; case 15: - return g_Extra3Clothing; + clothingArray = g_Extra3Clothing; + clothingSize = sizeof(g_Extra3Clothing) / sizeof(g_Extra3Clothing[0]); + break; case 16: - return g_Extra4Clothing; + clothingArray = g_Extra4Clothing; + clothingSize = sizeof(g_Extra4Clothing) / sizeof(g_Extra4Clothing[0]); + break; case 17: - return g_SuitClothing; + clothingArray = g_SuitClothing; + clothingSize = sizeof(g_SuitClothing) / sizeof(g_SuitClothing[0]); + break; } + + clothingList.assign(clothingArray, clothingArray + clothingSize - 1); + clothingList.insert(clothingList.end(), m_NewClothes[ucType].begin(), m_NewClothes[ucType].end()); + clothingList.push_back({NULL, NULL}); + + return clothingList.data(); } return NULL; @@ -534,45 +581,67 @@ const int CClientPlayerClothes::GetClothingGroupMax(unsigned char ucType) { if (ucType < PLAYER_CLOTHING_SLOTS) { + int maxClothe = 0; + switch (ucType) { case 0: - return TORSO_CLOTHING_MAX; + maxClothe = TORSO_CLOTHING_MAX; + break; case 1: - return HAIR_CLOTHING_MAX; + maxClothe = HAIR_CLOTHING_MAX; + break; case 2: - return LEGS_CLOTHING_MAX; + maxClothe = LEGS_CLOTHING_MAX; + break; case 3: - return SHOES_CLOTHING_MAX; + maxClothe = SHOES_CLOTHING_MAX; + break; case 4: - return LEFT_UPPER_ARM_CLOTHING_MAX; + maxClothe = LEFT_UPPER_ARM_CLOTHING_MAX; + break; case 5: - return LEFT_LOWER_ARM_CLOTHING_MAX; + maxClothe = LEFT_LOWER_ARM_CLOTHING_MAX; + break; case 6: - return RIGHT_UPPER_ARM_CLOTHING_MAX; + maxClothe = RIGHT_UPPER_ARM_CLOTHING_MAX; + break; case 7: - return RIGHT_LOWER_ARM_CLOTHING_MAX; + maxClothe = RIGHT_LOWER_ARM_CLOTHING_MAX; + break; case 8: - return BACK_TOP_CLOTHING_MAX; + maxClothe = BACK_TOP_CLOTHING_MAX; + break; case 9: - return LEFT_CHEST_CLOTHING_MAX; + maxClothe = LEFT_CHEST_CLOTHING_MAX; + break; case 10: - return RIGHT_CHEST_CLOTHING_MAX; + maxClothe = RIGHT_CHEST_CLOTHING_MAX; + break; case 11: - return STOMACH_CLOTHING_MAX; + maxClothe = STOMACH_CLOTHING_MAX; + break; case 12: - return LOWER_BACK_CLOTHING_MAX; + maxClothe = LOWER_BACK_CLOTHING_MAX; + break; case 13: - return EXTRA1_CLOTHING_MAX; + maxClothe = EXTRA1_CLOTHING_MAX; + break; case 14: - return EXTRA2_CLOTHING_MAX; + maxClothe = EXTRA2_CLOTHING_MAX; + break; case 15: - return EXTRA3_CLOTHING_MAX; + maxClothe = EXTRA3_CLOTHING_MAX; + break; case 16: - return EXTRA4_CLOTHING_MAX; + maxClothe = EXTRA4_CLOTHING_MAX; + break; case 17: - return SUIT_CLOTHING_MAX; + maxClothe = SUIT_CLOTHING_MAX; + break; } + + return maxClothe + static_cast(m_NewClothes[ucType].size()); } return 0; @@ -582,3 +651,81 @@ bool CClientPlayerClothes::IsValidModel(unsigned short usModel) { return usModel >= CLOTHES_MODEL_ID_FIRST && usModel <= CLOTHES_MODEL_ID_LAST; } + +bool CClientPlayerClothes::AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +{ + if (ucType < PLAYER_CLOTHING_SLOTS) + { + if (szTexture == nullptr || szModel == nullptr) + return false; + + std::string textureFile = std::string(szTexture) + ".txd"; + + if (!g_pGame->GetRenderWare()->HasClothesFile(textureFile.c_str())) + return false; + + std::string modelFile = std::string(szTexture) + ".dff"; + + if (!g_pGame->GetRenderWare()->HasClothesFile(modelFile.c_str())) + return false; + + const SPlayerClothing* pClothing = GetClothing(szTexture, szModel, ucType); + + if (pClothing) + return false; + + SPlayerClothing clothing = {strdup(szTexture), strdup(szModel)}; + m_NewClothes[ucType].push_back(clothing); + + return true; + } + + return false; +} + +bool CClientPlayerClothes::RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +{ + if (ucType < PLAYER_CLOTHING_SLOTS) + { + if (szTexture == nullptr || szModel == nullptr) + return false; + + auto& clothes = m_NewClothes[ucType]; + + for (auto it = clothes.begin(); it != clothes.end(); ++it) + { + if (strcmp(it->szTexture, szTexture) == 0 && strcmp(it->szModel, szModel) == 0) + { + clothes.erase(it); + return true; + } + } + } + + return false; +} + +void CClientPlayerClothes::RefreshClothes() +{ + for (unsigned char ucType = 0; ucType < PLAYER_CLOTHING_SLOTS; ucType++) + { + auto& clothes = m_NewClothes[ucType]; + + if (clothes.empty()) + continue; + + for (auto it = clothes.begin(); it != clothes.end();) + { + std::string textureFile = std::string(it->szTexture) + ".txd"; + std::string modelFile = std::string(it->szModel) + ".dff"; + + if (!g_pGame->GetRenderWare()->HasClothesFile(textureFile.c_str()) || !g_pGame->GetRenderWare()->HasClothesFile(modelFile.c_str())) + { + RemoveClothes(ucType, true); + it = clothes.erase(it); + } + else + ++it; + } + } +} diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index eeb3e25f8f6..4f2caaf9908 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.h +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.h @@ -57,6 +57,8 @@ class CClientPlayerClothes bool RemoveClothes(unsigned char ucType, bool bRemoveFromModel = true); void AddAllToModel(); + void RefreshClothes(); + void RemoveAll(bool bRemoveFromModel = true); void DefaultClothes(bool bAddToModel = true); @@ -68,12 +70,15 @@ class CClientPlayerClothes static const SPlayerClothing* GetClothingGroup(unsigned char ucType); static const int GetClothingGroupMax(unsigned char ucType); static bool IsValidModel(unsigned short usModel); + static bool AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); private: static const SPlayerClothing* GetClothing(const char* szTexture, const char* szModel, unsigned char ucType); CClientPed* m_pPlayerModel; - SFixedArray m_Clothes; - static SFixedArray m_GlobalClothes; - static bool m_bStaticInit; + SFixedArray m_Clothes; + static SFixedArray m_GlobalClothes; + static std::array, PLAYER_CLOTHING_SLOTS> m_NewClothes; + static bool m_bStaticInit; }; diff --git a/Client/mods/deathmatch/logic/CClientTXD.cpp b/Client/mods/deathmatch/logic/CClientTXD.cpp index 8a37f87157d..8e3a90f885b 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.cpp +++ b/Client/mods/deathmatch/logic/CClientTXD.cpp @@ -30,6 +30,9 @@ CClientTXD::~CClientTXD() // Remove us from all the clothes replacement doo dah g_pGame->GetRenderWare()->ClothesRemoveReplacement(m_FileData.data()); + + // Remove us from all the clothes + g_pGame->GetRenderWare()->ClothesRemoveFile(m_FileData.data()); } bool CClientTXD::Load(bool isRaw, SString input, bool enableFiltering) @@ -50,6 +53,26 @@ bool CClientTXD::Load(bool isRaw, SString input, bool enableFiltering) } } +bool CClientTXD::AddClotheTexture(std::string strModelName) +{ + if (strModelName.empty()) + return false; + + if (m_FileData.empty() && m_bIsRawData) + return false; + + if (m_FileData.empty()) + { + SString strUseFilename; + if (!GetFilenameToUse(strUseFilename)) + return false; + if (!FileLoad(std::nothrow, strUseFilename, m_FileData)) + return false; + } + + return g_pGame->GetRenderWare()->ClothesAddFile(m_FileData.data(), m_FileData.size(), strModelName.c_str()); +} + bool CClientTXD::Import(unsigned short usModelID) { if (usModelID >= CLOTHES_TEX_ID_FIRST && usModelID <= CLOTHES_TEX_ID_LAST) diff --git a/Client/mods/deathmatch/logic/CClientTXD.h b/Client/mods/deathmatch/logic/CClientTXD.h index e4dcf54a789..03f71cf173f 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.h +++ b/Client/mods/deathmatch/logic/CClientTXD.h @@ -27,6 +27,7 @@ class CClientTXD final : public CClientEntity eClientEntityType GetType() const { return CCLIENTTXD; } bool Load(bool isRaw, SString input, bool enableFiltering); + bool AddClotheTexture(std::string strModelName); bool Import(unsigned short usModelID); static bool IsImportableModel(unsigned short usModelID); static bool IsTXDData(const SString& strData); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 1e4d645dbe1..293f6fec792 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2671,6 +2671,22 @@ bool CStaticFunctionDefinitions::GetClothesTypeName(unsigned char ucType, SStrin return false; } +bool CStaticFunctionDefinitions::AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +{ + if (!CClientPlayerClothes::AddClotheModel(szTexture, szModel, ucType)) + return false; + + return true; +} + +bool CStaticFunctionDefinitions::RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +{ + if (!CClientPlayerClothes::RemoveClotheModel(szTexture, szModel, ucType)) + return false; + + return true; +} + CClientPed* CStaticFunctionDefinitions::CreatePed(CResource& Resource, unsigned long ulModel, const CVector& vecPosition, float fRotation) { // Valid model? diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h index 89459aa9e2f..ba746c33d19 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -195,6 +195,8 @@ class CStaticFunctionDefinitions static bool GetClothesByTypeIndex(unsigned char ucType, unsigned char ucIndex, SString& strOutTexture, SString& strOutModel); static bool GetTypeIndexFromClothes(const char* szTexture, const char* szModel, unsigned char& ucTypeReturn, unsigned char& ucIndexReturn); static bool GetClothesTypeName(unsigned char ucType, SString& strOutName); + static bool AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); // Vehicle get funcs static CClientVehicle* CreateVehicle(CResource& Resource, unsigned short usModel, const CVector& vecPosition, const CVector& vecRotation, diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp index 9bfabb48f6a..2c57cf0f12d 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp @@ -102,3 +102,47 @@ int CLuaFunctionDefs::GetClothesTypeName(lua_State* luaVM) lua_pushboolean(luaVM, false); return 1; } + +int CLuaFunctionDefs::AddClotheModel(lua_State* luaVM) +{ + unsigned char ucType = 0; + SString strTexture, strModel; + CScriptArgReader argStream(luaVM); + argStream.ReadString(strTexture); + argStream.ReadString(strModel, ""); + argStream.ReadNumber(ucType); + + if (!argStream.HasErrors()) + { + if (CStaticFunctionDefinitions::AddClotheModel(strTexture, strModel, ucType)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + + lua_pushboolean(luaVM, false); + return 1; +} + +int CLuaFunctionDefs::RemoveClotheModel(lua_State* luaVM) +{ + unsigned char ucType = 0; + SString strTexture, strModel; + CScriptArgReader argStream(luaVM); + argStream.ReadString(strTexture); + argStream.ReadString(strModel, ""); + argStream.ReadNumber(ucType); + + if (!argStream.HasErrors()) + { + if (CStaticFunctionDefinitions::RemoveClotheModel(strTexture, strModel, ucType)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + + lua_pushboolean(luaVM, false); + return 1; +} diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h index 105754152f5..123ffeb8228 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h @@ -61,6 +61,8 @@ class CLuaFunctionDefs LUA_DECLARE(GetClothesByTypeIndex); LUA_DECLARE(GetTypeIndexFromClothes); LUA_DECLARE(GetClothesTypeName); + LUA_DECLARE(AddClotheModel); + LUA_DECLARE(RemoveClotheModel); // Cursor funcs LUA_DECLARE(GetCursorPosition); diff --git a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp index c2a28809af0..7b27874df6a 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp @@ -185,6 +185,8 @@ void CLuaManager::LoadCFunctions() {"getClothesByTypeIndex", CLuaFunctionDefs::GetClothesByTypeIndex}, {"getTypeIndexFromClothes", CLuaFunctionDefs::GetTypeIndexFromClothes}, {"getClothesTypeName", CLuaFunctionDefs::GetClothesTypeName}, + {"addClotheModel", CLuaFunctionDefs::AddClotheModel}, + {"removeClotheModel", CLuaFunctionDefs::RemoveClotheModel}, // Cursor funcs {"getCursorPosition", CLuaFunctionDefs::GetCursorPosition}, diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 0ad88113b11..2dc8af81374 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -80,9 +80,11 @@ void CLuaEngineDefs::LoadFunctions() {"engineLoadDFF", EngineLoadDFF}, {"engineLoadIFP", EngineLoadIFP}, {"engineImportTXD", EngineImportTXD}, + {"engineAddClotheTXD", ArgumentParser}, {"engineReplaceCOL", EngineReplaceCOL}, {"engineRestoreCOL", EngineRestoreCOL}, {"engineReplaceModel", EngineReplaceModel}, + {"engineAddClotheModel", ArgumentParser}, {"engineRestoreModel", EngineRestoreModel}, {"engineReplaceAnimation", EngineReplaceAnimation}, {"engineRestoreAnimation", EngineRestoreAnimation}, @@ -649,6 +651,17 @@ int CLuaEngineDefs::EngineImportTXD(lua_State* luaVM) return 1; } +bool CLuaEngineDefs::EngineAddClotheTXD(CClientTXD* pTXD, std::string strModelName) +{ + if (strModelName.find(".txd") == std::string::npos) + throw std::invalid_argument(SString("Invalid file name specified (%*s)", (int)strModelName.length(), strModelName.data())); + + if (!pTXD->AddClotheTexture(strModelName)) + throw std::invalid_argument(SString("Texture already added (%*s)", (int)strModelName.length(), strModelName.data())); + + return true; +} + CClientIMG* CLuaEngineDefs::EngineLoadIMG(lua_State* const luaVM, std::string strRelativeFilePath) { CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM); @@ -827,6 +840,17 @@ int CLuaEngineDefs::EngineReplaceModel(lua_State* luaVM) return 1; } +bool CLuaEngineDefs::EngineAddClotheModel(CClientDFF* pDFF, std::string strModelName) +{ + if (strModelName.find(".dff") == std::string::npos) + throw std::invalid_argument(SString("Invalid file name specified (%*s)", (int)strModelName.length(), strModelName.data())); + + if (!pDFF->AddClotheModel(strModelName)) + throw std::invalid_argument(SString("Model already added (%*s)", (int)strModelName.length(), strModelName.data())); + + return true; +} + int CLuaEngineDefs::EngineRestoreModel(lua_State* luaVM) { // Grab the model ID diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index a67ecfc68d0..0692f831efb 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -58,6 +58,9 @@ class CLuaEngineDefs : public CLuaDefs LUA_DECLARE(EngineSetObjectGroupPhysicalProperty) LUA_DECLARE(EngineGetObjectGroupPhysicalProperty) LUA_DECLARE(EngineRestoreObjectGroupPhysicalProperties) + + static bool EngineAddClotheModel(CClientDFF* pDff, std::string strModelName); + static bool EngineAddClotheTXD(CClientTXD* pTxd, std::string strModelName); static uint EngineGetModelFlags(uint uiModelID); static bool EngineSetModelFlags(uint uiModelID, uint uiFlags, std::optional bIdeFlags); static bool EngineGetModelFlag(uint uiModelID, eModelIdeFlag eFlag); diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index 92686978b14..dfee65545e2 100644 --- a/Client/sdk/game/CRenderWare.h +++ b/Client/sdk/game/CRenderWare.h @@ -79,6 +79,9 @@ class CRenderWare virtual void ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId) = 0; virtual void ClothesRemoveReplacement(char* pFileData) = 0; virtual bool HasClothesReplacementChanged() = 0; + virtual bool ClothesAddFile(const char* pFileData, size_t fileSize, const char* pFileName) = 0; + virtual bool ClothesRemoveFile(char* pFileData) = 0; + virtual bool HasClothesFile(const char* pFileName) = 0; virtual RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer) = 0; virtual RpClump* ReadDFF(const SString& strFilename, const SString& buffer, unsigned short usModelID, bool bLoadEmbeddedCollisions) = 0; virtual CColModel* ReadCOL(const SString& buffer) = 0; From 904382c0efa910d416098d030e249015d1f5a5c7 Mon Sep 17 00:00:00 2001 From: Walace Date: Sat, 22 Feb 2025 16:13:57 -0300 Subject: [PATCH 02/17] Fix spelling error --- Client/mods/deathmatch/logic/CClientDFF.cpp | 2 +- Client/mods/deathmatch/logic/CClientDFF.h | 2 +- .../mods/deathmatch/logic/CClientPlayerClothes.cpp | 4 ++-- Client/mods/deathmatch/logic/CClientPlayerClothes.h | 5 +++-- Client/mods/deathmatch/logic/CClientTXD.cpp | 2 +- Client/mods/deathmatch/logic/CClientTXD.h | 2 +- .../deathmatch/logic/CStaticFunctionDefinitions.cpp | 8 ++++---- .../deathmatch/logic/CStaticFunctionDefinitions.h | 4 ++-- .../logic/lua/CLuaFunctionDefs.BodyClothes.cpp | 8 ++++---- Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h | 4 ++-- Client/mods/deathmatch/logic/lua/CLuaManager.cpp | 4 ++-- .../mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp | 12 ++++++------ .../mods/deathmatch/logic/luadefs/CLuaEngineDefs.h | 4 ++-- 13 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 5bbf3dd54c7..a49ad127919 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -99,7 +99,7 @@ void CClientDFF::UnloadDFF() m_LoadedClumpInfoMap.clear(); } -bool CClientDFF::AddClotheModel(std::string strModelName) +bool CClientDFF::AddClothingModel(std::string strModelName) { if (strModelName.empty()) return false; diff --git a/Client/mods/deathmatch/logic/CClientDFF.h b/Client/mods/deathmatch/logic/CClientDFF.h index bb2076a55ec..da3652e1d33 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.h +++ b/Client/mods/deathmatch/logic/CClientDFF.h @@ -37,7 +37,7 @@ class CClientDFF final : public CClientEntity bool Load(bool isRaw, SString input); - bool AddClotheModel(std::string strModelName); + bool AddClothingModel(std::string strModelName); bool ReplaceModel(unsigned short usModel, bool bAlphaTransparency); bool HasReplaced(unsigned short usModel); diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp index 90e56f934f6..6a8cc044297 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -652,7 +652,7 @@ bool CClientPlayerClothes::IsValidModel(unsigned short usModel) return usModel >= CLOTHES_MODEL_ID_FIRST && usModel <= CLOTHES_MODEL_ID_LAST; } -bool CClientPlayerClothes::AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +bool CClientPlayerClothes::AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) { if (ucType < PLAYER_CLOTHING_SLOTS) { @@ -683,7 +683,7 @@ bool CClientPlayerClothes::AddClotheModel(const char* szTexture, const char* szM return false; } -bool CClientPlayerClothes::RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +bool CClientPlayerClothes::RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) { if (ucType < PLAYER_CLOTHING_SLOTS) { diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index 4f2caaf9908..8ef3222b582 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.h +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.h @@ -70,8 +70,9 @@ class CClientPlayerClothes static const SPlayerClothing* GetClothingGroup(unsigned char ucType); static const int GetClothingGroupMax(unsigned char ucType); static bool IsValidModel(unsigned short usModel); - static bool AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); - static bool RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); + private: static const SPlayerClothing* GetClothing(const char* szTexture, const char* szModel, unsigned char ucType); diff --git a/Client/mods/deathmatch/logic/CClientTXD.cpp b/Client/mods/deathmatch/logic/CClientTXD.cpp index 8e3a90f885b..1dd63bf22c7 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.cpp +++ b/Client/mods/deathmatch/logic/CClientTXD.cpp @@ -53,7 +53,7 @@ bool CClientTXD::Load(bool isRaw, SString input, bool enableFiltering) } } -bool CClientTXD::AddClotheTexture(std::string strModelName) +bool CClientTXD::AddClothingTexture(std::string strModelName) { if (strModelName.empty()) return false; diff --git a/Client/mods/deathmatch/logic/CClientTXD.h b/Client/mods/deathmatch/logic/CClientTXD.h index 03f71cf173f..e882f86b3c1 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.h +++ b/Client/mods/deathmatch/logic/CClientTXD.h @@ -27,7 +27,7 @@ class CClientTXD final : public CClientEntity eClientEntityType GetType() const { return CCLIENTTXD; } bool Load(bool isRaw, SString input, bool enableFiltering); - bool AddClotheTexture(std::string strModelName); + bool AddClothingTexture(std::string strModelName); bool Import(unsigned short usModelID); static bool IsImportableModel(unsigned short usModelID); static bool IsTXDData(const SString& strData); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 293f6fec792..66411bc8a15 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2671,17 +2671,17 @@ bool CStaticFunctionDefinitions::GetClothesTypeName(unsigned char ucType, SStrin return false; } -bool CStaticFunctionDefinitions::AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +bool CStaticFunctionDefinitions::AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) { - if (!CClientPlayerClothes::AddClotheModel(szTexture, szModel, ucType)) + if (!CClientPlayerClothes::AddClothingModel(szTexture, szModel, ucType)) return false; return true; } -bool CStaticFunctionDefinitions::RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType) +bool CStaticFunctionDefinitions::RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) { - if (!CClientPlayerClothes::RemoveClotheModel(szTexture, szModel, ucType)) + if (!CClientPlayerClothes::RemoveClothingModel(szTexture, szModel, ucType)) return false; return true; diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h index ba746c33d19..f283a2f8a78 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -195,8 +195,8 @@ class CStaticFunctionDefinitions static bool GetClothesByTypeIndex(unsigned char ucType, unsigned char ucIndex, SString& strOutTexture, SString& strOutModel); static bool GetTypeIndexFromClothes(const char* szTexture, const char* szModel, unsigned char& ucTypeReturn, unsigned char& ucIndexReturn); static bool GetClothesTypeName(unsigned char ucType, SString& strOutName); - static bool AddClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); - static bool RemoveClotheModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); // Vehicle get funcs static CClientVehicle* CreateVehicle(CResource& Resource, unsigned short usModel, const CVector& vecPosition, const CVector& vecRotation, diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp index 2c57cf0f12d..dd36b8e84da 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp @@ -103,7 +103,7 @@ int CLuaFunctionDefs::GetClothesTypeName(lua_State* luaVM) return 1; } -int CLuaFunctionDefs::AddClotheModel(lua_State* luaVM) +int CLuaFunctionDefs::AddClothingModel(lua_State* luaVM) { unsigned char ucType = 0; SString strTexture, strModel; @@ -114,7 +114,7 @@ int CLuaFunctionDefs::AddClotheModel(lua_State* luaVM) if (!argStream.HasErrors()) { - if (CStaticFunctionDefinitions::AddClotheModel(strTexture, strModel, ucType)) + if (CStaticFunctionDefinitions::AddClothingModel(strTexture, strModel, ucType)) { lua_pushboolean(luaVM, true); return 1; @@ -125,7 +125,7 @@ int CLuaFunctionDefs::AddClotheModel(lua_State* luaVM) return 1; } -int CLuaFunctionDefs::RemoveClotheModel(lua_State* luaVM) +int CLuaFunctionDefs::RemoveClothingModel(lua_State* luaVM) { unsigned char ucType = 0; SString strTexture, strModel; @@ -136,7 +136,7 @@ int CLuaFunctionDefs::RemoveClotheModel(lua_State* luaVM) if (!argStream.HasErrors()) { - if (CStaticFunctionDefinitions::RemoveClotheModel(strTexture, strModel, ucType)) + if (CStaticFunctionDefinitions::RemoveClothingModel(strTexture, strModel, ucType)) { lua_pushboolean(luaVM, true); return 1; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h index 123ffeb8228..a6592f9df04 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h @@ -61,8 +61,8 @@ class CLuaFunctionDefs LUA_DECLARE(GetClothesByTypeIndex); LUA_DECLARE(GetTypeIndexFromClothes); LUA_DECLARE(GetClothesTypeName); - LUA_DECLARE(AddClotheModel); - LUA_DECLARE(RemoveClotheModel); + LUA_DECLARE(AddClothingModel); + LUA_DECLARE(RemoveClothingModel); // Cursor funcs LUA_DECLARE(GetCursorPosition); diff --git a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp index 7b27874df6a..53751c83eff 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp @@ -185,8 +185,8 @@ void CLuaManager::LoadCFunctions() {"getClothesByTypeIndex", CLuaFunctionDefs::GetClothesByTypeIndex}, {"getTypeIndexFromClothes", CLuaFunctionDefs::GetTypeIndexFromClothes}, {"getClothesTypeName", CLuaFunctionDefs::GetClothesTypeName}, - {"addClotheModel", CLuaFunctionDefs::AddClotheModel}, - {"removeClotheModel", CLuaFunctionDefs::RemoveClotheModel}, + {"addClothingModel", CLuaFunctionDefs::AddClothingModel}, + {"removeClothingModel", CLuaFunctionDefs::RemoveClothingModel}, // Cursor funcs {"getCursorPosition", CLuaFunctionDefs::GetCursorPosition}, diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 2dc8af81374..9171d156e76 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -80,11 +80,11 @@ void CLuaEngineDefs::LoadFunctions() {"engineLoadDFF", EngineLoadDFF}, {"engineLoadIFP", EngineLoadIFP}, {"engineImportTXD", EngineImportTXD}, - {"engineAddClotheTXD", ArgumentParser}, + {"engineAddClothingTXD", ArgumentParser}, {"engineReplaceCOL", EngineReplaceCOL}, {"engineRestoreCOL", EngineRestoreCOL}, {"engineReplaceModel", EngineReplaceModel}, - {"engineAddClotheModel", ArgumentParser}, + {"engineAddClothingModel", ArgumentParser}, {"engineRestoreModel", EngineRestoreModel}, {"engineReplaceAnimation", EngineReplaceAnimation}, {"engineRestoreAnimation", EngineRestoreAnimation}, @@ -651,12 +651,12 @@ int CLuaEngineDefs::EngineImportTXD(lua_State* luaVM) return 1; } -bool CLuaEngineDefs::EngineAddClotheTXD(CClientTXD* pTXD, std::string strModelName) +bool CLuaEngineDefs::EngineAddClothingTXD(CClientTXD* pTXD, std::string strModelName) { if (strModelName.find(".txd") == std::string::npos) throw std::invalid_argument(SString("Invalid file name specified (%*s)", (int)strModelName.length(), strModelName.data())); - if (!pTXD->AddClotheTexture(strModelName)) + if (!pTXD->AddClothingTexture(strModelName)) throw std::invalid_argument(SString("Texture already added (%*s)", (int)strModelName.length(), strModelName.data())); return true; @@ -840,12 +840,12 @@ int CLuaEngineDefs::EngineReplaceModel(lua_State* luaVM) return 1; } -bool CLuaEngineDefs::EngineAddClotheModel(CClientDFF* pDFF, std::string strModelName) +bool CLuaEngineDefs::EngineAddClothingModel(CClientDFF* pDFF, std::string strModelName) { if (strModelName.find(".dff") == std::string::npos) throw std::invalid_argument(SString("Invalid file name specified (%*s)", (int)strModelName.length(), strModelName.data())); - if (!pDFF->AddClotheModel(strModelName)) + if (!pDFF->AddClothingModel(strModelName)) throw std::invalid_argument(SString("Model already added (%*s)", (int)strModelName.length(), strModelName.data())); return true; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index 0692f831efb..9e762b53d2d 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -59,8 +59,8 @@ class CLuaEngineDefs : public CLuaDefs LUA_DECLARE(EngineGetObjectGroupPhysicalProperty) LUA_DECLARE(EngineRestoreObjectGroupPhysicalProperties) - static bool EngineAddClotheModel(CClientDFF* pDff, std::string strModelName); - static bool EngineAddClotheTXD(CClientTXD* pTxd, std::string strModelName); + static bool EngineAddClothingModel(CClientDFF* pDff, std::string strModelName); + static bool EngineAddClothingTXD(CClientTXD* pTxd, std::string strModelName); static uint EngineGetModelFlags(uint uiModelID); static bool EngineSetModelFlags(uint uiModelID, uint uiFlags, std::optional bIdeFlags); static bool EngineGetModelFlag(uint uiModelID, eModelIdeFlag eFlag); From 17a19f5ea2d6f058975066401e372a7760bd26e7 Mon Sep 17 00:00:00 2001 From: Walace Date: Sat, 8 Mar 2025 15:11:17 -0300 Subject: [PATCH 03/17] Refactor clothing list and fix crash --- .../logic/CClientModelCacheManager.cpp | 2 +- .../deathmatch/logic/CClientPlayerClothes.cpp | 732 ++++++------------ .../deathmatch/logic/CClientPlayerClothes.h | 18 +- .../logic/CStaticFunctionDefinitions.cpp | 34 +- 4 files changed, 284 insertions(+), 502 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientModelCacheManager.cpp b/Client/mods/deathmatch/logic/CClientModelCacheManager.cpp index 6fdc3c9581d..57a4a172df2 100644 --- a/Client/mods/deathmatch/logic/CClientModelCacheManager.cpp +++ b/Client/mods/deathmatch/logic/CClientModelCacheManager.cpp @@ -139,7 +139,7 @@ void CClientModelCacheManagerImpl::DoPulse() DoPulseVehicleModels(); // Handle regeneration of possibly replaced clothes textures - if (g_pGame->GetRenderWare()->HasClothesReplacementChanged()) + if (g_pGame->GetRenderWare()->HasClothesReplacementChanged() || CClientPlayerClothes::HasClothesChanged()) { g_pMultiplayer->FlushClothesCache(); diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp index 6a8cc044297..f4cad45f530 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -15,285 +15,171 @@ static const SPlayerClothingType g_clothesNames[PLAYER_CLOTHING_SLOTS] = { {"Right Lower Arm"}, {"Back Top"}, {"Left Chest"}, {"Right Chest"}, {"Stomach"}, {"Lower Back"}, {"Extra1"}, {"Extra2"}, {"Extra3"}, {"Extra4"}, {"Suit"}}; -static const SPlayerClothing g_TorsoClothing[TORSO_CLOTHING_MAX + 1] = {{"player_torso", "torso"}, - {"vestblack", "vest"}, - {"vest", "vest"}, - {"tshirt2horiz", "tshirt2"}, - {"tshirtwhite", "tshirt"}, - {"tshirtilovels", "tshirt"}, - {"tshirtblunts", "tshirt"}, - {"shirtbplaid", "shirtb"}, - {"shirtbcheck", "shirtb"}, - {"field", "field"}, - {"tshirterisyell", "tshirt"}, - {"tshirterisorn", "tshirt"}, - {"trackytop2eris", "trackytop1"}, - {"bbjackrim", "bbjack"}, - {"bballjackrstar", "bbjack"}, - {"baskballdrib", "baskball"}, - {"baskballrim", "baskball"}, - {"sixtyniners", "tshirt"}, - {"bandits", "baseball"}, - {"tshirtprored", "tshirt"}, - {"tshirtproblk", "tshirt"}, - {"trackytop1pro", "trackytop1"}, - {"hockeytop", "sweat"}, - {"bbjersey", "sleevt"}, - {"shellsuit", "trackytop1"}, - {"tshirtheatwht", "tshirt"}, - {"tshirtbobomonk", "tshirt"}, - {"tshirtbobored", "tshirt"}, - {"tshirtbase5", "tshirt"}, - {"tshirtsuburb", "tshirt"}, - {"hoodyamerc", "hoodya"}, - {"hoodyabase5", "hoodya"}, - {"hoodyarockstar", "hoodya"}, - {"wcoatblue", "wcoat"}, - {"coach", "coach"}, - {"coachsemi", "coach"}, - {"sweatrstar", "sweat"}, - {"hoodyAblue", "hoodyA"}, - {"hoodyAblack", "hoodyA"}, - {"hoodyAgreen", "hoodyA"}, - {"sleevtbrown", "sleevt"}, - {"shirtablue", "shirta"}, - {"shirtayellow", "shirta"}, - {"shirtagrey", "shirta"}, - {"shirtbgang", "shirtb"}, - {"tshirtzipcrm", "tshirt"}, - {"tshirtzipgry", "tshirt"}, - {"denimfade", "denim"}, - {"bowling", "hawaii"}, - {"hoodjackbeige", "hoodjack"}, - {"baskballloc", "baskball"}, - {"tshirtlocgrey", "tshirt"}, - {"tshirtmaddgrey", "tshirt"}, - {"tshirtmaddgrn", "tshirt"}, - {"suit1grey", "suit1"}, - {"suit1blk", "suit1"}, - {"leather", "leather"}, - {"painter", "painter"}, - {"hawaiiwht", "hawaii"}, - {"hawaiired", "hawaii"}, - {"sportjack", "trackytop1"}, - {"suit1red", "suit1"}, - {"suit1blue", "suit1"}, - {"suit1yellow", "suit1"}, - {"suit2grn", "suit2"}, - {"tuxedo", "suit2"}, - {"suit1gang", "suit1"}, - {"letter", "sleevt"}, - {NULL, NULL}}; - -static const SPlayerClothing g_HairClothing[HAIR_CLOTHING_MAX + 1] = {{"player_face", "head"}, {"hairblond", "head"}, - {"hairred", "head"}, {"hairblue", "head"}, - {"hairgreen", "head"}, {"hairpink", "head"}, - {"bald", "head"}, {"baldbeard", "head"}, - {"baldtash", "head"}, {"baldgoatee", "head"}, - {"highfade", "head"}, {"highafro", "highafro"}, - {"wedge", "wedge"}, {"slope", "slope"}, - {"jhericurl", "jheri"}, {"cornrows", "cornrows"}, - {"cornrowsb", "cornrows"}, {"tramline", "tramline"}, - {"groovecut", "groovecut"}, {"mohawk", "mohawk"}, - {"mohawkblond", "mohawk"}, {"mohawkpink", "mohawk"}, - {"mohawkbeard", "mohawk"}, {"afro", "afro"}, - {"afrotash", "afro"}, {"afrobeard", "afro"}, - {"afroblond", "afro"}, {"flattop", "flattop"}, - {"elvishair", "elvishair"}, {"beard", "head"}, - {"tash", "head"}, {"goatee", "head"}, - {"afrogoatee", "afro"}, {NULL, NULL}}; - -static const SPlayerClothing g_LegsClothing[LEGS_CLOTHING_MAX + 1] = {{"player_legs", "legs"}, {"worktrcamogrn", "worktr"}, - {"worktrcamogry", "worktr"}, {"worktrgrey", "worktr"}, - {"worktrkhaki", "worktr"}, {"tracktr", "tracktr"}, - {"tracktreris", "tracktr"}, {"jeansdenim", "jeans"}, - {"legsblack", "legs"}, {"legsheart", "legs"}, - {"biegetr", "chinosb"}, {"tracktrpro", "tracktr"}, - {"tracktrwhstr", "tracktr"}, {"tracktrblue", "tracktr"}, - {"tracktrgang", "tracktr"}, {"bbshortwht", "boxingshort"}, - {"boxshort", "boxingshort"}, {"bbshortred", "boxingshort"}, - {"shellsuittr", "tracktr"}, {"shortsgrey", "shorts"}, - {"shortskhaki", "shorts"}, {"chongergrey", "chonger"}, - {"chongergang", "chonger"}, {"chongerred", "chonger"}, - {"chongerblue", "chonger"}, {"shortsgang", "shorts"}, - {"denimsgang", "jeans"}, {"denimsred", "jeans"}, - {"chinosbiege", "chinosb"}, {"chinoskhaki", "chinosb"}, - {"cutoffchinos", "shorts"}, {"cutoffchinosblue", "shorts"}, - {"chinosblack", "chinosb"}, {"chinosblue", "chinosb"}, - {"leathertr", "leathertr"}, {"leathertrchaps", "leathertr"}, - {"suit1trgrey", "suit1tr"}, {"suit1trblk", "suit1tr"}, - {"cutoffdenims", "shorts"}, {"suit1trred", "suit1tr"}, - {"suit1trblue", "suit1tr"}, {"suit1tryellow", "suit1tr"}, - {"suit1trgreen", "suit1tr"}, {"suit1trblk2", "suit1tr"}, - {"suit1trgang", "suit1tr"}, {NULL, NULL}}; - -static const SPlayerClothing g_ShoesClothing[SHOES_CLOTHING_MAX + 1] = {{"foot", "feet"}, - {"cowboyboot2", "biker"}, - {"bask2semi", "bask1"}, - {"bask1eris", "bask1"}, - {"sneakerbincgang", "sneaker"}, - {"sneakerbincblu", "sneaker"}, - {"sneakerbincblk", "sneaker"}, - {"sandal", "flipflop"}, - {"sandalsock", "flipflop"}, - {"flipflop", "flipflop"}, - {"hitop", "bask1"}, - {"convproblk", "conv"}, - {"convproblu", "conv"}, - {"convprogrn", "conv"}, - {"sneakerprored", "sneaker"}, - {"sneakerproblu", "sneaker"}, - {"sneakerprowht", "sneaker"}, - {"bask1prowht", "bask1"}, - {"bask1problk", "bask1"}, - {"boxingshoe", "biker"}, - {"convheatblk", "conv"}, - {"convheatred", "conv"}, - {"convheatorn", "conv"}, - {"sneakerheatwht", "sneaker"}, - {"sneakerheatgry", "sneaker"}, - {"sneakerheatblk", "sneaker"}, - {"bask2heatwht", "bask1"}, - {"bask2heatband", "bask1"}, - {"timbergrey", "bask1"}, - {"timberred", "bask1"}, - {"timberfawn", "bask1"}, - {"timberhike", "bask1"}, - {"cowboyboot", "biker"}, - {"biker", "biker"}, - {"snakeskin", "biker"}, - {"shoedressblk", "shoe"}, - {"shoedressbrn", "shoe"}, - {"shoespatz", "shoe"}, - {NULL, NULL}}; - -static const SPlayerClothing g_LeftUpperArmClothing[LEFT_UPPER_ARM_CLOTHING_MAX + 1] = { - {"4weed", "4WEED"}, {"4rip", "4RIP"}, {"4spider", "4SPIDER"}, {NULL, NULL}}; - -static const SPlayerClothing g_LeftLowerArmClothing[LEFT_LOWER_ARM_CLOTHING_MAX + 1] = { - {"5gun", "5GUN"}, {"5cross", "5CROSS"}, {"5cross2", "5CROSS2"}, {"5cross3", "5CROSS3"}, {NULL, NULL}}; - -static const SPlayerClothing g_RightUpperArmClothing[RIGHT_UPPER_ARM_CLOTHING_MAX + 1] = { - {"6aztec", "6AZTEC"}, {"6crown", "6CROWN"}, {"6clown", "6CLOWN"}, {"6africa", "6AFRICA"}, {NULL, NULL}}; - -static const SPlayerClothing g_RightLowerArmClothing[RIGHT_LOWER_ARM_CLOTHING_MAX + 1] = { - {"7cross", "7CROSS"}, {"7cross2", "7CROSS2"}, {"7cross3", "7CROSS3"}, {"7mary", "7MARY"}, {NULL, NULL}}; - -static const SPlayerClothing g_BackTopClothing[BACK_TOP_CLOTHING_MAX + 1] = { - {"8sa", "8SA"}, {"8sa2", "8SA2"}, {"8sa3", "8SA3"}, {"8westside", "8WESTSD"}, {"8santos", "8SANTOS"}, {"8poker", "8POKER"}, {"8gun", "8GUN"}, {NULL, NULL}}; - -static const SPlayerClothing g_LeftChestClothing[LEFT_CHEST_CLOTHING_MAX + 1] = { - {"9crown", "9CROWN"}, {"9gun", "9GUN"}, {"9gun2", "9GUN2"}, {"9homeboy", "9HOMBY"}, {"9bullet", "9BULLT"}, {"9rasta", "9RASTA"}, {NULL, NULL}}; - -static const SPlayerClothing g_RightChestClothing[RIGHT_CHEST_CLOTHING_MAX + 1] = { - {"10ls", "10LS"}, {"10ls2", "10LS2"}, {"10ls3", "10LS3"}, {"10ls4", "10LS4"}, {"10ls5", "10LS5"}, {"10og", "10OG"}, {"10weed", "10WEED"}, {NULL, NULL}}; - -static const SPlayerClothing g_StomachClothing[STOMACH_CLOTHING_MAX + 1] = { - {"11grove", "11GROVE"}, {"11grove2", "11GROV2"}, {"11grove3", "11GROV3"}, {"11dice", "11DICE"}, - {"11dice2", "11DICE2"}, {"11jail", "11JAIL"}, {"11godsgift", "11GGIFT"}, {NULL, NULL}}; - -static const SPlayerClothing g_LowerBackClothing[LOWER_BACK_CLOTHING_MAX + 1] = {{"12angels", "12ANGEL"}, - {"12mayabird", "12MAYBR"}, - {"12dagger", "12DAGER"}, - {"12bandit", "12BNDIT"}, - {"12cross7", "12CROSS"}, - {"12mayaface", "12MYFAC"}, - {NULL, NULL}}; - -static const SPlayerClothing g_Extra1Clothing[EXTRA1_CLOTHING_MAX + 1] = { - {"dogtag", "neck"}, {"neckafrica", "neck"}, {"stopwatch", "neck"}, {"necksaints", "neck"}, {"neckhash", "neck"}, {"necksilver", "neck2"}, - {"neckgold", "neck2"}, {"neckropes", "neck2"}, {"neckropeg", "neck2"}, {"neckls", "neck"}, {"neckdollar", "neck"}, {"neckcross", "neck"}, - {NULL, NULL}}; - -static const SPlayerClothing g_Extra2Clothing[EXTRA2_CLOTHING_MAX + 1] = {{"watchpink", "watch"}, - {"watchyellow", "watch"}, - {"watchpro", "watch"}, - {"watchpro2", "watch"}, - {"watchsub1", "watch"}, - {"watchsub2", "watch"}, - {"watchzip1", "watch"}, - {"watchzip2", "watch"}, - {"watchgno", "watch"}, - {"watchgno2", "watch"}, - {"watchcro", "watch"}, - {"watchcro2", "watch"}, - {NULL, NULL}}; - -static const SPlayerClothing g_Extra3Clothing[EXTRA3_CLOTHING_MAX + 1] = { - {"groucho", "grouchos"}, {"zorro", "zorromask"}, {"eyepatch", "eyepatch"}, - {"glasses01", "glasses01"}, {"glasses04", "glasses04"}, {"bandred3", "bandmask"}, - {"bandblue3", "bandmask"}, {"bandgang3", "bandmask"}, {"bandblack3", "bandmask"}, - {"glasses01dark", "glasses01"}, {"glasses04dark", "glasses04"}, {"glasses03", "glasses03"}, - {"glasses03red", "glasses03"}, {"glasses03blue", "glasses03"}, {"glasses03dark", "glasses03"}, - {"glasses05dark", "glasses03"}, {"glasses05", "glasses03"}, {NULL, NULL}}; - -static const SPlayerClothing g_Extra4Clothing[EXTRA4_CLOTHING_MAX + 1] = {{"bandred", "bandana"}, - {"bandblue", "bandana"}, - {"bandgang", "bandana"}, - {"bandblack", "bandana"}, - {"bandred2", "bandknots"}, - {"bandblue2", "bandknots"}, - {"bandblack2", "bandknots"}, - {"bandgang2", "bandknots"}, - {"capknitgrn", "capknit"}, - {"captruck", "captruck"}, - {"cowboy", "cowboy"}, - {"hattiger", "cowboy"}, - {"helmet", "helmet"}, - {"moto", "moto"}, - {"boxingcap", "boxingcap"}, - {"hockey", "hockeymask"}, - {"capgang", "cap"}, - {"capgangback", "capback"}, - {"capgangside", "capside"}, - {"capgangover", "capovereye"}, - {"capgangup", "caprimup"}, - {"bikerhelmet", "bikerhelmet"}, - {"capred", "cap"}, - {"capredback", "capback"}, - {"capredside", "capside"}, - {"capredover", "capovereye"}, - {"capredup", "caprimup"}, - {"capblue", "cap"}, - {"capblueback", "capback"}, - {"capblueside", "capside"}, - {"capblueover", "capovereye"}, - {"capblueup", "caprimup"}, - {"skullyblk", "skullycap"}, - {"skullygrn", "skullycap"}, - {"hatmancblk", "hatmanc"}, - {"hatmancplaid", "hatmanc"}, - {"capzip", "cap"}, - {"capzipback", "capback"}, - {"capzipside", "capside"}, - {"capzipover", "capovereye"}, - {"capzipup", "caprimup"}, - {"beretred", "beret"}, - {"beretblk", "beret"}, - {"capblk", "cap"}, - {"capblkback", "capback"}, - {"capblkside", "capside"}, - {"capblkover", "capovereye"}, - {"capblkup", "caprimup"}, - {"trilbydrk", "trilby"}, - {"trilbylght", "trilby"}, - {"bowler", "bowler"}, - {"bowlerred", "bowler"}, - {"bowlerblue", "bowler"}, - {"bowleryellow", "bowler"}, - {"boater", "boater"}, - {"bowlergang", "bowler"}, - {"boaterblk", "boater"}, - {NULL, NULL}}; - -static const SPlayerClothing g_SuitClothing[SUIT_CLOTHING_MAX + 1] = { - {"gimpleg", "gimpleg"}, {"valet", "valet"}, {"countrytr", "countrytr"}, {"croupier", "valet"}, {"policetr", "policetr"}, - {"balaclava", "balaclava"}, {"pimptr", "pimptr"}, {"garageleg", "garagetr"}, {"medictr", "medictr"}, {NULL, NULL}}; +SFixedArray, PLAYER_CLOTHING_SLOTS> CClientPlayerClothes::m_DefaultClothes +{ + //Torso Clothing + std::vector { + {"player_torso", "torso"}, {"vestblack", "vest"}, {"vest", "vest"}, {"tshirt2horiz", "tshirt2"}, {"tshirtwhite", "tshirt"}, + {"tshirtilovels", "tshirt"}, {"tshirtblunts", "tshirt"}, {"shirtbplaid", "shirtb"}, {"shirtbcheck", "shirtb"}, + {"field", "field"}, {"tshirterisyell", "tshirt"}, {"tshirterisorn", "tshirt"}, {"trackytop2eris", "trackytop1"}, + {"bbjackrim", "bbjack"}, {"bballjackrstar", "bbjack"}, {"baskballdrib", "baskball"}, {"baskballrim", "baskball"}, + {"sixtyniners", "tshirt"}, {"bandits", "baseball"}, {"tshirtprored", "tshirt"}, {"tshirtproblk", "tshirt"}, + {"trackytop1pro", "trackytop1"}, {"hockeytop", "sweat"}, {"bbjersey", "sleevt"}, {"shellsuit", "trackytop1"}, + {"tshirtheatwht", "tshirt"}, {"tshirtbobomonk", "tshirt"}, {"tshirtbobored", "tshirt"}, {"tshirtbase5", "tshirt"}, + {"tshirtsuburb", "tshirt"}, {"hoodyamerc", "hoodya"}, {"hoodyabase5", "hoodya"}, {"hoodyarockstar", "hoodya"}, + {"wcoatblue", "wcoat"}, {"coach", "coach"}, {"coachsemi", "coach"}, {"sweatrstar", "sweat"}, {"hoodyAblue", "hoodyA"}, + {"hoodyAblack", "hoodyA"}, {"hoodyAgreen", "hoodyA"}, {"sleevtbrown", "sleevt"}, {"shirtablue", "shirta"}, {"shirtayellow", "shirta"}, + {"shirtagrey", "shirta"}, {"shirtbgang", "shirtb"}, {"tshirtzipcrm", "tshirt"}, {"tshirtzipgry", "tshirt"}, {"denimfade", "denim"}, + {"bowling", "hawaii"}, {"hoodjackbeige", "hoodjack"}, {"baskballloc", "baskball"}, {"tshirtlocgrey", "tshirt"}, + {"tshirtmaddgrey", "tshirt"}, {"tshirtmaddgrn", "tshirt"}, {"suit1grey", "suit1"}, {"suit1blk", "suit1"}, {"leather", "leather"}, + {"painter", "painter"}, {"hawaiiwht", "hawaii"}, {"hawaiired", "hawaii"}, {"sportjack", "trackytop1"}, {"suit1red", "suit1"}, + {"suit1blue", "suit1"}, {"suit1yellow", "suit1"}, {"suit2grn", "suit2"}, {"tuxedo", "suit2"}, {"suit1gang", "suit1"}, {"letter", "sleevt"} + }, + + //Hair Clothing + std::vector { + {"player_face", "head"}, {"hairblond", "head"}, {"hairred", "head"}, {"hairblue", "head"}, {"hairgreen", "head"}, {"hairpink", "head"}, + {"bald", "head"}, {"baldbeard", "head"}, {"baldtash", "head"}, {"baldgoatee", "head"}, {"highfade", "head"}, {"highafro", "highafro"}, + {"wedge", "wedge"}, {"slope", "slope"}, {"jhericurl", "jheri"}, {"cornrows", "cornrows"}, {"cornrowsb", "cornrows"}, {"tramline", "tramline"}, + {"groovecut", "groovecut"}, {"mohawk", "mohawk"}, {"mohawkblond", "mohawk"}, {"mohawkpink", "mohawk"}, + {"mohawkbeard", "mohawk"}, {"afro", "afro"}, {"afrotash", "afro"}, {"afrobeard", "afro"}, {"afroblond", "afro"}, {"flattop", "flattop"}, + {"elvishair", "elvishair"}, {"beard", "head"}, {"tash", "head"}, {"goatee", "head"}, {"afrogoatee", "afro"} + }, + + //Legs Clothing + std::vector { + {"player_legs", "legs"}, {"worktrcamogrn", "worktr"}, {"worktrcamogry", "worktr"}, {"worktrgrey", "worktr"}, + {"worktrkhaki", "worktr"}, {"tracktr", "tracktr"}, {"tracktreris", "tracktr"}, {"jeansdenim", "jeans"}, + {"legsblack", "legs"}, {"legsheart", "legs"}, {"biegetr", "chinosb"}, {"tracktrpro", "tracktr"}, + {"tracktrwhstr", "tracktr"}, {"tracktrblue", "tracktr"}, {"tracktrgang", "tracktr"}, {"bbshortwht", "boxingshort"}, + {"boxshort", "boxingshort"}, {"bbshortred", "boxingshort"}, {"shellsuittr", "tracktr"}, {"shortsgrey", "shorts"}, + {"shortskhaki", "shorts"}, {"chongergrey", "chonger"}, {"chongergang", "chonger"}, {"chongerred", "chonger"}, + {"chongerblue", "chonger"}, {"shortsgang", "shorts"}, {"denimsgang", "jeans"}, {"denimsred", "jeans"}, + {"chinosbiege", "chinosb"}, {"chinoskhaki", "chinosb"}, {"cutoffchinos", "shorts"}, {"cutoffchinosblue", "shorts"}, + {"chinosblack", "chinosb"}, {"chinosblue", "chinosb"}, {"leathertr", "leathertr"}, {"leathertrchaps", "leathertr"}, + {"suit1trgrey", "suit1tr"}, {"suit1trblk", "suit1tr"}, {"cutoffdenims", "shorts"}, {"suit1trred", "suit1tr"}, + {"suit1trblue", "suit1tr"}, {"suit1tryellow", "suit1tr"}, {"suit1trgreen", "suit1tr"}, {"suit1trblk2", "suit1tr"}, + {"suit1trgang", "suit1tr"} + }, + + //Shoes Clothing + std::vector { + {"foot", "feet"}, {"cowboyboot2", "biker"}, {"bask2semi", "bask1"}, {"bask1eris", "bask1"}, {"sneakerbincgang", "sneaker"}, {"sneakerbincblu", "sneaker"}, + {"sneakerbincblk", "sneaker"}, {"sandal", "flipflop"}, {"sandalsock", "flipflop"}, {"flipflop", "flipflop"}, {"hitop", "bask1"}, {"convproblk", "conv"}, + {"convproblu", "conv"}, {"convprogrn", "conv"}, {"sneakerprored", "sneaker"}, {"sneakerproblu", "sneaker"}, {"sneakerprowht", "sneaker"}, + {"bask1prowht", "bask1"}, {"bask1problk", "bask1"}, {"boxingshoe", "biker"}, {"convheatblk", "conv"}, {"convheatred", "conv"}, {"convheatorn", "conv"}, + {"sneakerheatwht", "sneaker"}, {"sneakerheatgry", "sneaker"}, {"sneakerheatblk", "sneaker"}, {"bask2heatwht", "bask1"}, {"bask2heatband", "bask1"}, + {"timbergrey", "bask1"}, {"timberred", "bask1"}, {"timberfawn", "bask1"}, {"timberhike", "bask1"}, {"cowboyboot", "biker"}, {"biker", "biker"}, + {"snakeskin", "biker"}, {"shoedressblk", "shoe"}, {"shoedressbrn", "shoe"}, {"shoespatz", "shoe"} + }, + + //Left Upper Arm Clothing + std::vector { + {"4weed", "4WEED"}, {"4rip", "4RIP"}, {"4spider", "4SPIDER"} + }, + + //Left Lower Arm Clothing + std::vector { + {"5gun", "5GUN"}, {"5cross", "5CROSS"}, {"5cross2", "5CROSS2"}, + {"5cross3", "5CROSS3"} + }, + + //Right Upper Arm Clothing + std::vector { + {"6aztec", "6AZTEC"}, {"6crown", "6CROWN"}, {"6clown", "6CLOWN"}, + {"6africa", "6AFRICA"} + }, + + //Right LowerA rm Clothing + std::vector { + {"7cross", "7CROSS"}, {"7cross2", "7CROSS2"}, {"7cross3", "7CROSS3"}, + {"7mary", "7MARY"} + }, + + //Back Top Clothing + std::vector { + {"8sa", "8SA"}, {"8sa2", "8SA2"}, {"8sa3", "8SA3"}, + {"8westside", "8WESTSD"}, {"8santos", "8SANTOS"}, + {"8poker", "8POKER"}, {"8gun", "8GUN"} + }, + + //Left Chest Clothing + std::vector { + {"9crown", "9CROWN"}, {"9gun", "9GUN"}, {"9gun2", "9GUN2"}, + {"9homeboy", "9HOMBY"}, {"9bullet", "9BULLT"}, + {"9rasta", "9RASTA"} + }, + + //Right Chest Clothing + std::vector { + {"10ls", "10LS"}, {"10ls2", "10LS2"}, {"10ls3", "10LS3"}, + {"10ls4", "10LS4"}, {"10ls5", "10LS5"}, {"10og", "10OG"}, + {"10weed", "10WEED"} + }, + + //Stomach Clothing + std::vector { + {"11grove", "11GROVE"}, {"11grove2", "11GROV2"}, {"11grove3", "11GROV3"}, {"11dice", "11DICE"}, + {"11dice2", "11DICE2"}, {"11jail", "11JAIL"}, {"11godsgift", "11GGIFT"} + }, + + //Lower Back Clothing + std::vector { + {"12angels", "12ANGEL"}, {"12mayabird", "12MAYBR"}, {"12dagger", "12DAGER"}, + {"12bandit", "12BNDIT"}, {"12cross7", "12CROSS"}, {"12mayaface", "12MYFAC"}, + }, + + //Extra1 Clothing + std::vector { + {"dogtag", "neck"}, {"neckafrica", "neck"}, {"stopwatch", "neck"}, {"necksaints", "neck"}, {"neckhash", "neck"}, {"necksilver", "neck2"}, + {"neckgold", "neck2"}, {"neckropes", "neck2"}, {"neckropeg", "neck2"}, {"neckls", "neck"}, {"neckdollar", "neck"}, {"neckcross", "neck"} + }, + + //Extra2 Clothing + std::vector { + {"watchpink", "watch"}, {"watchyellow", "watch"}, {"watchpro", "watch"}, {"watchpro2", "watch"}, {"watchsub1", "watch"}, + {"watchsub2", "watch"}, {"watchzip1", "watch"}, {"watchzip2", "watch"}, {"watchgno", "watch"}, {"watchgno2", "watch"}, + {"watchcro", "watch"}, {"watchcro2", "watch"} + }, + + //Extra3 Clothing + std::vector { + {"groucho", "grouchos"}, {"zorro", "zorromask"}, {"eyepatch", "eyepatch"}, + {"glasses01", "glasses01"}, {"glasses04", "glasses04"}, {"bandred3", "bandmask"}, + {"bandblue3", "bandmask"}, {"bandgang3", "bandmask"}, {"bandblack3", "bandmask"}, + {"glasses01dark", "glasses01"}, {"glasses04dark", "glasses04"}, {"glasses03", "glasses03"}, + {"glasses03red", "glasses03"}, {"glasses03blue", "glasses03"}, {"glasses03dark", "glasses03"}, + {"glasses05dark", "glasses03"}, {"glasses05", "glasses03"} + }, + + //Extra4 Clothing + std::vector { + {"bandred", "bandana"}, {"bandblue", "bandana"}, {"bandgang", "bandana"}, {"bandblack", "bandana"}, {"bandred2", "bandknots"}, + {"bandblue2", "bandknots"}, {"bandblack2", "bandknots"}, {"bandgang2", "bandknots"}, {"capknitgrn", "capknit"}, {"captruck", "captruck"}, + {"cowboy", "cowboy"}, {"hattiger", "cowboy"}, {"helmet", "helmet"}, {"moto", "moto"}, {"boxingcap", "boxingcap"}, {"hockey", "hockeymask"}, {"capgang", "cap"}, + {"capgangback", "capback"}, {"capgangside", "capside"}, {"capgangover", "capovereye"}, {"capgangup", "caprimup"}, {"bikerhelmet", "bikerhelmet"}, + {"capred", "cap"}, {"capredback", "capback"}, {"capredside", "capside"}, {"capredover", "capovereye"}, {"capredup", "caprimup"}, {"capblue", "cap"}, + {"capblueback", "capback"}, {"capblueside", "capside"}, {"capblueover", "capovereye"}, {"capblueup", "caprimup"}, {"skullyblk", "skullycap"}, + {"skullygrn", "skullycap"}, {"hatmancblk", "hatmanc"}, {"hatmancplaid", "hatmanc"}, {"capzip", "cap"}, {"capzipback", "capback"}, {"capzipside", "capside"}, + {"capzipover", "capovereye"}, {"capzipup", "caprimup"}, {"beretred", "beret"}, {"beretblk", "beret"}, {"capblk", "cap"}, {"capblkback", "capback"}, + {"capblkside", "capside"}, {"capblkover", "capovereye"}, {"capblkup", "caprimup"}, {"trilbydrk", "trilby"}, {"trilbylght", "trilby"}, + {"bowler", "bowler"}, {"bowlerred", "bowler"}, {"bowlerblue", "bowler"}, {"bowleryellow", "bowler"}, {"boater", "boater"}, {"bowlergang", "bowler"}, + {"boaterblk", "boater"} + }, + + // Suit Clothing + std::vector { + {"gimpleg", "gimpleg"}, {"valet", "valet"}, {"countrytr", "countrytr"}, {"croupier", "valet"}, + {"policetr", "policetr"}, {"balaclava", "balaclava"}, {"pimptr", "pimptr"}, + {"garageleg", "garagetr"}, {"medictr", "medictr"} + } +}; // This represents GTA's 1 clothing block -SFixedArray CClientPlayerClothes::m_GlobalClothes; -std::array, PLAYER_CLOTHING_SLOTS> CClientPlayerClothes::m_NewClothes; -bool CClientPlayerClothes::m_bStaticInit = true; +SFixedArray CClientPlayerClothes::m_GlobalClothes; +SFixedArray, PLAYER_CLOTHING_SLOTS> CClientPlayerClothes::m_NewClothes; +bool CClientPlayerClothes::m_bStaticInit = true; +bool CClientPlayerClothes::m_bHasClothesChanged = false; CClientPlayerClothes::CClientPlayerClothes(CClientPed* pPlayerModel) { @@ -349,7 +235,7 @@ void CClientPlayerClothes::InternalAddClothes(const SPlayerClothing* pClothing, { if (pClothing && !IsEmptyClothing(pClothing, ucType)) { - pPlayerPed->SetClothesTextureAndModel(pClothing->szTexture, pClothing->szModel, ucType); + pPlayerPed->SetClothesTextureAndModel(pClothing->szTexture.c_str(), pClothing->szModel.c_str(), ucType); } else { @@ -372,8 +258,8 @@ bool CClientPlayerClothes::RemoveClothes(unsigned char ucType, bool bRemoveFromM // Can we replace them with empty-type clothes (eg: player_torso) if (HasEmptyClothing(ucType)) { - const SPlayerClothing* pGroup = GetClothingGroup(ucType); - m_Clothes[ucType] = &pGroup[0]; + const std::vector pGroup = GetClothingGroup(ucType); + m_Clothes[ucType] = pGroup.at(0); } else { @@ -385,6 +271,7 @@ bool CClientPlayerClothes::RemoveClothes(unsigned char ucType, bool bRemoveFromM { InternalAddClothes(NULL, ucType); } + return true; } return false; @@ -442,18 +329,15 @@ bool CClientPlayerClothes::HasEmptyClothing(unsigned char ucType) bool CClientPlayerClothes::IsEmptyClothing(const SPlayerClothing* pClothing, unsigned char ucType) { - if (pClothing) - { - if (ucType <= 3) - { - const SPlayerClothing* pGroup = GetClothingGroup(ucType); - if (pClothing == &pGroup[0]) - { - return true; - } - } - } - return false; + if (!pClothing || ucType > 3) + return false; + + const std::vector pGroup = GetClothingGroup(ucType); + + if (pGroup.empty()) + return false; + + return pClothing == pGroup.at(0); } const char* CClientPlayerClothes::GetClothingName(unsigned char ucType) @@ -465,186 +349,45 @@ const char* CClientPlayerClothes::GetClothingName(unsigned char ucType) return NULL; } -const SPlayerClothing* CClientPlayerClothes::GetClothingGroup(unsigned char ucType) +std::vector CClientPlayerClothes::GetClothingGroup(unsigned char ucType) { + std::vector clothes; + if (ucType < PLAYER_CLOTHING_SLOTS) { - static std::vector clothingList; - const SPlayerClothing* clothingArray = nullptr; - size_t clothingSize = 0; - - switch (ucType) + for (auto& clothing : m_DefaultClothes[ucType]) { - case 0: - clothingArray = g_TorsoClothing; - clothingSize = sizeof(g_TorsoClothing) / sizeof(g_TorsoClothing[0]); - break; - case 1: - clothingArray = g_HairClothing; - clothingSize = sizeof(g_HairClothing) / sizeof(g_HairClothing[0]); - break; - case 2: - clothingArray = g_LegsClothing; - clothingSize = sizeof(g_LegsClothing) / sizeof(g_LegsClothing[0]); - break; - case 3: - clothingArray = g_ShoesClothing; - clothingSize = sizeof(g_ShoesClothing) / sizeof(g_ShoesClothing[0]); - break; - case 4: - clothingArray = g_LeftUpperArmClothing; - clothingSize = sizeof(g_LeftUpperArmClothing) / sizeof(g_LeftUpperArmClothing[0]); - break; - case 5: - clothingArray = g_LeftLowerArmClothing; - clothingSize = sizeof(g_LeftLowerArmClothing) / sizeof(g_LeftLowerArmClothing[0]); - break; - case 6: - clothingArray = g_RightUpperArmClothing; - clothingSize = sizeof(g_RightUpperArmClothing) / sizeof(g_RightUpperArmClothing[0]); - break; - case 7: - clothingArray = g_RightLowerArmClothing; - clothingSize = sizeof(g_RightLowerArmClothing) / sizeof(g_RightLowerArmClothing[0]); - break; - case 8: - clothingArray = g_BackTopClothing; - clothingSize = sizeof(g_BackTopClothing) / sizeof(g_BackTopClothing[0]); - break; - case 9: - clothingArray = g_LeftChestClothing; - clothingSize = sizeof(g_LeftChestClothing) / sizeof(g_LeftChestClothing[0]); - break; - case 10: - clothingArray = g_RightChestClothing; - clothingSize = sizeof(g_RightChestClothing) / sizeof(g_RightChestClothing[0]); - break; - case 11: - clothingArray = g_StomachClothing; - clothingSize = sizeof(g_StomachClothing) / sizeof(g_StomachClothing[0]); - break; - case 12: - clothingArray = g_LowerBackClothing; - clothingSize = sizeof(g_LowerBackClothing) / sizeof(g_LowerBackClothing[0]); - break; - case 13: - clothingArray = g_Extra1Clothing; - clothingSize = sizeof(g_Extra1Clothing) / sizeof(g_Extra1Clothing[0]); - break; - case 14: - clothingArray = g_Extra2Clothing; - clothingSize = sizeof(g_Extra2Clothing) / sizeof(g_Extra2Clothing[0]); - break; - case 15: - clothingArray = g_Extra3Clothing; - clothingSize = sizeof(g_Extra3Clothing) / sizeof(g_Extra3Clothing[0]); - break; - case 16: - clothingArray = g_Extra4Clothing; - clothingSize = sizeof(g_Extra4Clothing) / sizeof(g_Extra4Clothing[0]); - break; - case 17: - clothingArray = g_SuitClothing; - clothingSize = sizeof(g_SuitClothing) / sizeof(g_SuitClothing[0]); - break; + clothes.push_back(&clothing); } - clothingList.assign(clothingArray, clothingArray + clothingSize - 1); - clothingList.insert(clothingList.end(), m_NewClothes[ucType].begin(), m_NewClothes[ucType].end()); - clothingList.push_back({NULL, NULL}); - - return clothingList.data(); + for (auto& clothing : m_NewClothes[ucType]) + { + clothes.push_back(&clothing); + } } - return NULL; + return clothes; } const SPlayerClothing* CClientPlayerClothes::GetClothing(const char* szTexture, const char* szModel, unsigned char ucType) { - if (szTexture && szModel && ucType < PLAYER_CLOTHING_SLOTS) - { - const SPlayerClothing* pGroup = GetClothingGroup(ucType); - int iMax = GetClothingGroupMax(ucType); - for (int i = 0; i < iMax; i++) - { - const SPlayerClothing* pClothing = &pGroup[i]; - if (!stricmp(szTexture, pClothing->szTexture) && !stricmp(szModel, pClothing->szModel)) - { - return pClothing; - } - } - } - return NULL; -} + if (!szTexture || !szModel || ucType >= PLAYER_CLOTHING_SLOTS) + return nullptr; -const int CClientPlayerClothes::GetClothingGroupMax(unsigned char ucType) -{ - if (ucType < PLAYER_CLOTHING_SLOTS) - { - int maxClothe = 0; + std::vector pGroup = GetClothingGroup(ucType); + + if (pGroup.empty()) + return nullptr; - switch (ucType) + for (const auto& clothing : pGroup) + { + if (!stricmp(szTexture, clothing->szTexture.c_str()) && !stricmp(szModel, clothing->szModel.c_str())) { - case 0: - maxClothe = TORSO_CLOTHING_MAX; - break; - case 1: - maxClothe = HAIR_CLOTHING_MAX; - break; - case 2: - maxClothe = LEGS_CLOTHING_MAX; - break; - case 3: - maxClothe = SHOES_CLOTHING_MAX; - break; - case 4: - maxClothe = LEFT_UPPER_ARM_CLOTHING_MAX; - break; - case 5: - maxClothe = LEFT_LOWER_ARM_CLOTHING_MAX; - break; - case 6: - maxClothe = RIGHT_UPPER_ARM_CLOTHING_MAX; - break; - case 7: - maxClothe = RIGHT_LOWER_ARM_CLOTHING_MAX; - break; - case 8: - maxClothe = BACK_TOP_CLOTHING_MAX; - break; - case 9: - maxClothe = LEFT_CHEST_CLOTHING_MAX; - break; - case 10: - maxClothe = RIGHT_CHEST_CLOTHING_MAX; - break; - case 11: - maxClothe = STOMACH_CLOTHING_MAX; - break; - case 12: - maxClothe = LOWER_BACK_CLOTHING_MAX; - break; - case 13: - maxClothe = EXTRA1_CLOTHING_MAX; - break; - case 14: - maxClothe = EXTRA2_CLOTHING_MAX; - break; - case 15: - maxClothe = EXTRA3_CLOTHING_MAX; - break; - case 16: - maxClothe = EXTRA4_CLOTHING_MAX; - break; - case 17: - maxClothe = SUIT_CLOTHING_MAX; - break; + return clothing; } - - return maxClothe + static_cast(m_NewClothes[ucType].size()); } - return 0; + return nullptr; } bool CClientPlayerClothes::IsValidModel(unsigned short usModel) @@ -669,14 +412,16 @@ bool CClientPlayerClothes::AddClothingModel(const char* szTexture, const char* s if (!g_pGame->GetRenderWare()->HasClothesFile(modelFile.c_str())) return false; - const SPlayerClothing* pClothing = GetClothing(szTexture, szModel, ucType); + auto& clothes = m_NewClothes[ucType]; - if (pClothing) + if (std::any_of(clothes.begin(), clothes.end(), [&](const SPlayerClothing& clothing) { + return !stricmp(szTexture, clothing.szTexture.c_str()) && !stricmp(szModel, clothing.szModel.c_str()); + })) + { return false; + } - SPlayerClothing clothing = {strdup(szTexture), strdup(szModel)}; - m_NewClothes[ucType].push_back(clothing); - + clothes.push_back({szTexture, szModel}); return true; } @@ -692,40 +437,75 @@ bool CClientPlayerClothes::RemoveClothingModel(const char* szTexture, const char auto& clothes = m_NewClothes[ucType]; - for (auto it = clothes.begin(); it != clothes.end(); ++it) - { - if (strcmp(it->szTexture, szTexture) == 0 && strcmp(it->szModel, szModel) == 0) - { - clothes.erase(it); - return true; - } - } + auto it = std::find_if(clothes.begin(), clothes.end(),[&](const SPlayerClothing& clothing) { + return !stricmp(szTexture, clothing.szTexture.c_str()) && !stricmp(szModel, clothing.szModel.c_str()); + }); + + if (it == clothes.end()) + return false; + + clothes.erase(it); + m_bHasClothesChanged = true; + + return true; } return false; } +bool CClientPlayerClothes::HasClothesChanged() +{ + return m_bHasClothesChanged; +} + void CClientPlayerClothes::RefreshClothes() { for (unsigned char ucType = 0; ucType < PLAYER_CLOTHING_SLOTS; ucType++) { auto& clothes = m_NewClothes[ucType]; - if (clothes.empty()) + if (clothes.empty() && !m_bHasClothesChanged) continue; - for (auto it = clothes.begin(); it != clothes.end();) + bool hasInvalidClothing = false; + const SPlayerClothing* pCurrent = m_Clothes[ucType]; + + if (!m_bHasClothesChanged) { - std::string textureFile = std::string(it->szTexture) + ".txd"; - std::string modelFile = std::string(it->szModel) + ".dff"; + for (auto clothing = clothes.begin(); clothing != clothes.end();) + { + std::string fileTXD = clothing->szTexture + ".txd"; + std::string fileDFF = clothing->szModel + ".dff"; + + if (!g_pGame->GetRenderWare()->HasClothesFile(fileTXD.c_str()) || !g_pGame->GetRenderWare()->HasClothesFile(fileDFF.c_str())) + { + if (pCurrent && (pCurrent->szTexture == clothing->szTexture || pCurrent->szModel == clothing->szModel)) + { + hasInvalidClothing = true; + } + + clothing = clothes.erase(clothing); + } + else + ++clothing; + } + } + + if (pCurrent && !hasInvalidClothing && m_bHasClothesChanged) + { + const SPlayerClothing* pClothing = GetClothing(pCurrent->szTexture.c_str(), pCurrent->szModel.c_str(), ucType); - if (!g_pGame->GetRenderWare()->HasClothesFile(textureFile.c_str()) || !g_pGame->GetRenderWare()->HasClothesFile(modelFile.c_str())) + if (!pClothing) { - RemoveClothes(ucType, true); - it = clothes.erase(it); + hasInvalidClothing = true; } - else - ++it; + } + + if (hasInvalidClothing) + { + RemoveClothes(ucType, true); } } + + m_bHasClothesChanged = false; } diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index 8ef3222b582..e00805deff1 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.h +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.h @@ -41,8 +41,8 @@ struct SPlayerClothingType struct SPlayerClothing { - const char* szTexture; - const char* szModel; + std::string szTexture; + std::string szModel; }; class CClientPlayerClothes @@ -67,11 +67,11 @@ class CClientPlayerClothes static bool IsEmptyClothing(const SPlayerClothing* pClothing, unsigned char ucType); static const char* GetClothingName(unsigned char ucType); - static const SPlayerClothing* GetClothingGroup(unsigned char ucType); - static const int GetClothingGroupMax(unsigned char ucType); - static bool IsValidModel(unsigned short usModel); - static bool AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); - static bool RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); + static std::vector GetClothingGroup(unsigned char ucType); + static bool IsValidModel(unsigned short usModel); + static bool HasClothesChanged(); + static bool AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); private: static const SPlayerClothing* GetClothing(const char* szTexture, const char* szModel, unsigned char ucType); @@ -79,7 +79,9 @@ class CClientPlayerClothes CClientPed* m_pPlayerModel; SFixedArray m_Clothes; + static bool m_bHasClothesChanged; + static SFixedArray, PLAYER_CLOTHING_SLOTS> m_DefaultClothes; + static SFixedArray, PLAYER_CLOTHING_SLOTS> m_NewClothes; static SFixedArray m_GlobalClothes; - static std::array, PLAYER_CLOTHING_SLOTS> m_NewClothes; static bool m_bStaticInit; }; diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 66411bc8a15..8d27beaa29d 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2618,18 +2618,18 @@ bool CStaticFunctionDefinitions::GetBodyPartName(unsigned char ucID, SString& st bool CStaticFunctionDefinitions::GetClothesByTypeIndex(unsigned char ucType, unsigned char ucIndex, SString& strOutTexture, SString& strOutModel) { - const SPlayerClothing* pPlayerClothing = CClientPlayerClothes::GetClothingGroup(ucType); - if (pPlayerClothing) - { - if (ucIndex < CClientPlayerClothes::GetClothingGroupMax(ucType)) - { - strOutTexture = pPlayerClothing[ucIndex].szTexture; - strOutModel = pPlayerClothing[ucIndex].szModel; - return true; - } - } + std::vector pPlayerClothing = CClientPlayerClothes::GetClothingGroup(ucType); - return false; + if (pPlayerClothing.empty()) + return false; + + if (ucIndex > (pPlayerClothing.size() - 1)) + return false; + + strOutTexture = pPlayerClothing.at(ucIndex)->szTexture; + strOutModel = pPlayerClothing.at(ucIndex)->szModel; + + return true; } bool CStaticFunctionDefinitions::GetTypeIndexFromClothes(const char* szTexture, const char* szModel, unsigned char& ucTypeReturn, unsigned char& ucIndexReturn) @@ -2639,13 +2639,13 @@ bool CStaticFunctionDefinitions::GetTypeIndexFromClothes(const char* szTexture, for (unsigned char ucType = 0; ucType < PLAYER_CLOTHING_SLOTS; ucType++) { - const SPlayerClothing* pPlayerClothing = CClientPlayerClothes::GetClothingGroup(ucType); - if (pPlayerClothing) - { - for (unsigned char ucIter = 0; pPlayerClothing[ucIter].szTexture != NULL; ucIter++) + std::vector pPlayerClothing = CClientPlayerClothes::GetClothingGroup(ucType); + + if (!pPlayerClothing.empty()) { + for (unsigned char ucIter = 0; ucIter < pPlayerClothing.size(); ucIter++) { - if ((szTexture == NULL || strcmp(szTexture, pPlayerClothing[ucIter].szTexture) == 0) && - (szModel == NULL || strcmp(szModel, pPlayerClothing[ucIter].szModel) == 0)) + if ((szTexture == NULL || strcmp(szTexture, pPlayerClothing[ucIter]->szTexture.c_str()) == 0) && + (szModel == NULL || strcmp(szModel, pPlayerClothing[ucIter]->szModel.c_str()) == 0)) { ucTypeReturn = ucType; ucIndexReturn = ucIter; From c8e631924d4107434451095b08417696deee7f60 Mon Sep 17 00:00:00 2001 From: Walace Date: Mon, 17 Mar 2025 10:58:47 -0300 Subject: [PATCH 04/17] Add new clothing directory size --- .../CMultiplayerSA_ClothesSpeedUp.cpp | 120 +++++++++++------- 1 file changed, 77 insertions(+), 43 deletions(-) diff --git a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp index b3ba54d1759..1e58fafc2df 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include "../Client/game_sa/CDirectorySA.h" DWORD FUNC_CStreamingInfoAddToList = 0x407480; DWORD FUNC_CStreamingConvertBufferToObject = 0x40C6B0; @@ -54,49 +55,6 @@ void CMultiplayerSA::SetFastClothesLoading(EFastClothesLoading fastClothesLoadin ms_PlayerImgCachePtr = NULL; } -// -// Skip loading the directory data from player.img if it has already been loaded. -// Speeds up clothes a bit, but is only part of a solution - The actual files from inside player.img are still loaded each time -// -bool _cdecl IsPlayerImgDirLoaded() -{ - // When player.img dir is loaded, it looks this this: - // 0x00BC12C0 00bbcdc8 00000226 - DWORD* ptr1 = (DWORD*)0x00BC12C0; - if (ptr1[0] == 0x00BBCDC8 && ptr1[1] == 0x0000226) - { - return true; - } - return false; -} - -// Hook info -#define HOOKPOS_LoadingPlayerImgDir 0x5A69E3 -#define HOOKSIZE_LoadingPlayerImgDir 5 -DWORD RETURN_LoadingPlayerImgDirA = 0x5A69E8; -DWORD RETURN_LoadingPlayerImgDirB = 0x5A6A06; -void _declspec(naked) HOOK_LoadingPlayerImgDir() -{ - // hook from 005A69E3 5 bytes - _asm - { - pushad - call IsPlayerImgDirLoaded - cmp al, 0 - jnz skip - popad - - // Standard code to load img directory - push 0BBCDC8h - jmp RETURN_LoadingPlayerImgDirA - - // Skip loading img directory -skip: - popad - jmp RETURN_LoadingPlayerImgDirB - } -} - //////////////////////////////////////////////// // // Hook CStreaming::RequestFile @@ -252,6 +210,80 @@ void _declspec(naked) HOOK_CStreamingLoadRequestedModels() } } +// +// Skip loading the directory data from player.img if it has already been loaded. +// Speeds up clothes a bit, but is only part of a solution - The actual files from inside player.img are still loaded each time +// + +uint32_t g_pPlayerImgEntries = 0xBBCDC8; +uint16_t g_nPlayerImgSize = 0x226; + +bool _cdecl IsPlayerImgDirLoaded() +{ + // When player.img dir is loaded, it looks this this: + // 0x00BC12C0 00bbcdc8 00000226 + DWORD* ptr1 = (DWORD*)0xBC12C0; + if (ptr1[0] == g_pPlayerImgEntries && ptr1[1] == g_nPlayerImgSize) + { + return true; + } + return false; +} + +// Hook info +#define HOOKSIZE_LoadingPlayerImgDir 5 +#define HOOKPOS_LoadingPlayerImgDir 0x5A69E3 // 005A69D6 -> CClothesBuilder::CreateSkinnedClump -> playerImgEntries +DWORD RETURN_LoadingPlayerImgDirA = 0x5A69E8; // push 00000226 { 550 } +DWORD RETURN_LoadingPlayerImgDirB = 0x5A6A06; // return of CreateSkinnedClump function + +void _declspec(naked) HOOK_LoadingPlayerImgDir() +{ + // hook from 005A69E3 5 bytes + _asm + { + pushad + call IsPlayerImgDirLoaded + cmp al, 0 + jnz skip + popad + + // Standard code to load img directory + mov eax, g_pPlayerImgEntries + push eax + jmp RETURN_LoadingPlayerImgDirA + + // Skip loading img directory +skip: + popad + jmp RETURN_LoadingPlayerImgDirB + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Setup clothing directory size +// +////////////////////////////////////////////////////////////////////////////////////////// +bool _cdecl SetClothingDirectorySize(int iCapacity) +{ + DirectoryInfoSA* clothesDirectory = (DirectoryInfoSA*)malloc(sizeof(DirectoryInfoSA) * iCapacity); + + if (clothesDirectory) + { + // CClothesBuilder::LoadCdDirectory(void) + MemPut(0x5A4190 + 1, reinterpret_cast(clothesDirectory)); // push offset _playerImgEntries; headers + MemPut(0x5A4195 + 1, iCapacity); // push 550 ; count + MemPut(0x5A69E8 + 1, iCapacity); // push 550 ; count + + g_pPlayerImgEntries = reinterpret_cast(clothesDirectory); + g_nPlayerImgSize = iCapacity; + + return true; + } + + return false; +} + ////////////////////////////////////////////////////////////////////////////////////////// // // Setup hooks for ClothesSpeedUp @@ -259,6 +291,8 @@ void _declspec(naked) HOOK_CStreamingLoadRequestedModels() ////////////////////////////////////////////////////////////////////////////////////////// void CMultiplayerSA::InitHooks_ClothesSpeedUp() { + SetClothingDirectorySize(2050); + EZHookInstall(CStreamingLoadRequestedModels); EZHookInstall(LoadingPlayerImgDir); EZHookInstall(CallCStreamingInfoAddToList); From 53189932430c213243feef67403ceb9c4258bca4 Mon Sep 17 00:00:00 2001 From: Walace Date: Mon, 17 Mar 2025 10:58:56 -0300 Subject: [PATCH 05/17] Update CDirectorySA.cpp --- Client/game_sa/CDirectorySA.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/game_sa/CDirectorySA.cpp b/Client/game_sa/CDirectorySA.cpp index eec768ce8f0..4231a9d7d93 100644 --- a/Client/game_sa/CDirectorySA.cpp +++ b/Client/game_sa/CDirectorySA.cpp @@ -30,8 +30,7 @@ bool CDirectorySAInterface::AddEntry(DirectoryInfoSA& entry) entry.m_nOffset += 2048 - (entry.m_nOffset % 2048); } - m_pEntries[m_nNumEntries] = entry; - m_nNumEntries++; + m_pEntries[m_nNumEntries++] = entry; return true; } From 43186ea21c369d2c2209ce2fc3b45172be49c7f4 Mon Sep 17 00:00:00 2001 From: Walace Date: Wed, 19 Mar 2025 17:22:30 -0300 Subject: [PATCH 06/17] Update CDirectorySA - Removes hungarian notation - Changes the types of some variables --- Client/game_sa/CDirectorySA.cpp | 60 +++++++++---------- Client/game_sa/CDirectorySA.h | 20 +++---- .../CRenderWareSA.ClothesReplacing.cpp | 4 +- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/Client/game_sa/CDirectorySA.cpp b/Client/game_sa/CDirectorySA.cpp index 4231a9d7d93..35b5b49dd2f 100644 --- a/Client/game_sa/CDirectorySA.cpp +++ b/Client/game_sa/CDirectorySA.cpp @@ -13,62 +13,59 @@ bool CDirectorySAInterface::AddEntry(DirectoryInfoSA& entry) { - if (m_nNumEntries >= m_nCapacity) + if (m_numEntries >= m_capacity || GetModelEntry(entry.m_name)) return false; - if (GetModelEntry(entry.m_szName)) - return false; - - entry.m_nOffset = 0; + entry.m_offset = 0; - if (m_nNumEntries > 0) + if (m_numEntries > 0) { - DirectoryInfoSA* lastEntry = m_pEntries + m_nNumEntries - 1; - entry.m_nOffset = lastEntry->m_nOffset + lastEntry->m_nStreamingSize; + DirectoryInfoSA* lastEntry = m_entries + m_numEntries - 1; + entry.m_offset = lastEntry->m_offset + lastEntry->m_streamingSize; - if (entry.m_nOffset % 2048) - entry.m_nOffset += 2048 - (entry.m_nOffset % 2048); + if (entry.m_offset % 2048) + entry.m_offset += 2048 - (entry.m_offset % 2048); } - m_pEntries[m_nNumEntries++] = entry; + m_entries[m_numEntries++] = entry; return true; } -bool CDirectorySAInterface::RemoveEntry(const char* pFileName) +bool CDirectorySAInterface::RemoveEntry(const char* fileName) { - if (m_nNumEntries <= 0) + if (m_numEntries <= 0) return false; - DirectoryInfoSA* entry = GetModelEntry(pFileName); + DirectoryInfoSA* entry = GetModelEntry(fileName); if (!entry) return false; - std::uint32_t index = entry - m_pEntries; + std::ptrdiff_t index = entry - m_entries; - if (index < m_nNumEntries - 1) + if (index < m_numEntries - 1) { - DirectoryInfoSA* lastEntry = m_pEntries + m_nNumEntries - 1; - entry->m_nOffset = lastEntry->m_nOffset + lastEntry->m_nSizeInArchive; + DirectoryInfoSA* lastEntry = m_entries + m_numEntries - 1; + entry->m_offset = lastEntry->m_offset + lastEntry->m_sizeInArchive; } - m_nNumEntries--; + m_numEntries--; - if (index < m_nNumEntries) - memmove(entry, entry + 1, (m_nNumEntries - index) * sizeof(DirectoryInfoSA)); + if (index < m_numEntries) + std::memmove(entry, entry + 1, (m_numEntries - index) * sizeof(DirectoryInfoSA)); return true; } -DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* pFileName) +DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* fileName) { - if (m_nNumEntries <= 0) + if (m_numEntries <= 0) return nullptr; - for (DirectoryInfoSA* it = m_pEntries; it != m_pEntries + m_nNumEntries; it++) + for (DirectoryInfoSA* it = m_entries; it != m_entries + m_numEntries; it++) { - if (strcmp(it->m_szName, pFileName) == 0) + if (std::strcmp(it->m_name, fileName) == 0) return it; } @@ -77,10 +74,10 @@ DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* pFileName) DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(std::uint16_t modelId) { - if (m_nNumEntries <= 0) + if (m_numEntries <= 0) return nullptr; - DirectoryInfoSA* entry = m_pEntries + modelId; + DirectoryInfoSA* entry = m_entries + modelId; if (!entry) return nullptr; @@ -92,13 +89,10 @@ bool CDirectorySAInterface::SetModelStreamingSize(std::uint16_t modelId, std::ui { DirectoryInfoSA* entry = GetModelEntry(modelId); - if (!entry) - return false; - - if (entry->m_nStreamingSize == size) + if (!entry || entry->m_streamingSize == size) return false; - entry->m_nStreamingSize = size; + entry->m_streamingSize = size; return true; } @@ -110,5 +104,5 @@ std::uint16_t CDirectorySAInterface::GetModelStreamingSize(std::uint16_t modelId if (!entry) return 0; - return entry->m_nStreamingSize; + return entry->m_streamingSize; } diff --git a/Client/game_sa/CDirectorySA.h b/Client/game_sa/CDirectorySA.h index 42273f670b0..49d8812145a 100644 --- a/Client/game_sa/CDirectorySA.h +++ b/Client/game_sa/CDirectorySA.h @@ -12,26 +12,26 @@ struct DirectoryInfoSA { - std::uint32_t m_nOffset; - std::uint16_t m_nStreamingSize; - std::uint16_t m_nSizeInArchive; - char m_szName[24]; + std::uint32_t m_offset; + std::uint16_t m_streamingSize; + std::uint16_t m_sizeInArchive; + char m_name[24]; }; class CDirectorySAInterface { public: bool AddEntry(DirectoryInfoSA& entry); - bool RemoveEntry(const char* pFileName); + bool RemoveEntry(const char* fileName); - DirectoryInfoSA* GetModelEntry(const char* pFileName); + DirectoryInfoSA* GetModelEntry(const char* fileName); DirectoryInfoSA* GetModelEntry(std::uint16_t modelId); bool SetModelStreamingSize(std::uint16_t modelId, std::uint16_t size); std::uint16_t GetModelStreamingSize(std::uint16_t modelId); private: - DirectoryInfoSA* m_pEntries{}; - std::uint32_t m_nCapacity{}; - std::uint32_t m_nNumEntries{}; - bool m_bOwnsEntries{}; + DirectoryInfoSA* m_entries{}; + std::uint32_t m_capacity{}; + std::uint32_t m_numEntries{}; + bool m_ownsEntries{}; }; diff --git a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp index 313ac8bc41c..4fcae9ecf39 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -145,8 +145,8 @@ bool CRenderWareSA::ClothesAddFile(const char* pFileData, size_t fileSize, const return false; DirectoryInfoSA entry{}; - entry.m_nStreamingSize = GetSizeInBlocks(fileSize); - strncpy(entry.m_szName, pFileName, sizeof(entry.m_szName)); + entry.m_streamingSize = GetSizeInBlocks(fileSize); + strncpy(entry.m_name, pFileName, sizeof(entry.m_name)); if (!g_clothesDirectory->AddEntry(entry)) return false; From 3f1cf67ac5291436c1f0b11bc40666489c5f8eef Mon Sep 17 00:00:00 2001 From: Walace Date: Wed, 19 Mar 2025 17:35:56 -0300 Subject: [PATCH 07/17] Update CRenderWareSA.ClothesReplacing --- .../CRenderWareSA.ClothesReplacing.cpp | 25 ++++++++----------- Client/game_sa/CRenderWareSA.h | 6 ++--- Client/sdk/game/CRenderWare.h | 6 ++--- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp index 4fcae9ecf39..5fc31ad937a 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -136,22 +136,22 @@ bool CRenderWareSA::HasClothesReplacementChanged() // Add a file to the clothes directory // //////////////////////////////////////////////////////////////// -bool CRenderWareSA::ClothesAddFile(const char* pFileData, size_t fileSize, const char* pFileName) +bool CRenderWareSA::ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName) { - if (!pFileData || !pFileName) + if (!fileData || !fileName) return false; - if (MapFind(ms_ClothesFileDataMap, pFileName)) + if (MapFind(ms_ClothesFileDataMap, fileName)) return false; DirectoryInfoSA entry{}; entry.m_streamingSize = GetSizeInBlocks(fileSize); - strncpy(entry.m_name, pFileName, sizeof(entry.m_name)); + std::strncpy(entry.m_name, fileName, sizeof(entry.m_name)); if (!g_clothesDirectory->AddEntry(entry)) return false; - MapSet(ms_ClothesFileDataMap, pFileName, (char*)pFileData); + MapSet(ms_ClothesFileDataMap, fileName, (char*)fileData); bClothesReplacementChanged = true; return true; @@ -164,14 +164,14 @@ bool CRenderWareSA::ClothesAddFile(const char* pFileData, size_t fileSize, const // Remove a file from the clothes directory // //////////////////////////////////////////////////////////////// -bool CRenderWareSA::ClothesRemoveFile(char* pFileData) +bool CRenderWareSA::ClothesRemoveFile(char* fileData) { - if (!pFileData) + if (!fileData) return false; for (auto iter = ms_ClothesFileDataMap.begin(); iter != ms_ClothesFileDataMap.end();) { - if (iter->second == pFileData) + if (iter->second == fileData) { if (!g_clothesDirectory->RemoveEntry(iter->first.c_str())) return false; @@ -191,14 +191,9 @@ bool CRenderWareSA::ClothesRemoveFile(char* pFileData) // Check if clothe file exits // //////////////////////////////////////////////////////////////// -bool CRenderWareSA::HasClothesFile(const char* pFileName) +bool CRenderWareSA::HasClothesFile(const char* fileName) { - if (!pFileName) - { - return false; - } - - if (!MapFind(ms_ClothesFileDataMap, pFileName)) + if (!fileName || !MapFind(ms_ClothesFileDataMap, fileName)) { return false; } diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index d0d1e01f864..3784b172bd2 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -35,9 +35,9 @@ class CRenderWareSA : public CRenderWare void ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId); void ClothesRemoveReplacement(char* pFileData); bool HasClothesReplacementChanged(); - bool ClothesAddFile(const char* pFileData, size_t fileSize, const char* pFileName); - bool ClothesRemoveFile(char* pFileData); - bool HasClothesFile(const char* pFileName); + bool ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName); + bool ClothesRemoveFile(char* fileData); + bool HasClothesFile(const char* fileName); // Reads and parses a TXD file specified by a path (szTXD) RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer); diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index dfee65545e2..8443de136e6 100644 --- a/Client/sdk/game/CRenderWare.h +++ b/Client/sdk/game/CRenderWare.h @@ -79,9 +79,9 @@ class CRenderWare virtual void ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId) = 0; virtual void ClothesRemoveReplacement(char* pFileData) = 0; virtual bool HasClothesReplacementChanged() = 0; - virtual bool ClothesAddFile(const char* pFileData, size_t fileSize, const char* pFileName) = 0; - virtual bool ClothesRemoveFile(char* pFileData) = 0; - virtual bool HasClothesFile(const char* pFileName) = 0; + virtual bool ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName) = 0; + virtual bool ClothesRemoveFile(char* fileData) = 0; + virtual bool HasClothesFile(const char* fileName) = 0; virtual RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer) = 0; virtual RpClump* ReadDFF(const SString& strFilename, const SString& buffer, unsigned short usModelID, bool bLoadEmbeddedCollisions) = 0; virtual CColModel* ReadCOL(const SString& buffer) = 0; From 43b922c6037b742fa75b9e77d2c1835eb9d08feb Mon Sep 17 00:00:00 2001 From: Walace Date: Wed, 19 Mar 2025 17:40:04 -0300 Subject: [PATCH 08/17] Update CMultiplayerSA_ClothesSpeedUp.cpp --- .../CMultiplayerSA_ClothesSpeedUp.cpp | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp index 1e58fafc2df..0b970a43666 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp @@ -215,26 +215,23 @@ void _declspec(naked) HOOK_CStreamingLoadRequestedModels() // Speeds up clothes a bit, but is only part of a solution - The actual files from inside player.img are still loaded each time // -uint32_t g_pPlayerImgEntries = 0xBBCDC8; -uint16_t g_nPlayerImgSize = 0x226; +std::uint32_t g_playerImgEntries = 0xBBCDC8; +std::uint16_t g_playerImgSize = 0x226; bool _cdecl IsPlayerImgDirLoaded() { // When player.img dir is loaded, it looks this this: // 0x00BC12C0 00bbcdc8 00000226 DWORD* ptr1 = (DWORD*)0xBC12C0; - if (ptr1[0] == g_pPlayerImgEntries && ptr1[1] == g_nPlayerImgSize) - { - return true; - } - return false; + + return ptr1[0] == g_playerImgEntries && ptr1[1] == g_playerImgSize; } // Hook info #define HOOKSIZE_LoadingPlayerImgDir 5 -#define HOOKPOS_LoadingPlayerImgDir 0x5A69E3 // 005A69D6 -> CClothesBuilder::CreateSkinnedClump -> playerImgEntries -DWORD RETURN_LoadingPlayerImgDirA = 0x5A69E8; // push 00000226 { 550 } -DWORD RETURN_LoadingPlayerImgDirB = 0x5A6A06; // return of CreateSkinnedClump function +#define HOOKPOS_LoadingPlayerImgDir 0x5A69E3 // 005A69D6 -> CClothesBuilder::CreateSkinnedClump -> playerImgEntries +static constexpr std::uintptr_t RETURN_LoadingPlayerImgDirA = 0x5A69E8; // push 00000226 { 550 } +static constexpr std::uintptr_t RETURN_LoadingPlayerImgDirB = 0x5A6A06; // return of CreateSkinnedClump function void _declspec(naked) HOOK_LoadingPlayerImgDir() { @@ -248,7 +245,7 @@ void _declspec(naked) HOOK_LoadingPlayerImgDir() popad // Standard code to load img directory - mov eax, g_pPlayerImgEntries + mov eax, g_playerImgEntries push eax jmp RETURN_LoadingPlayerImgDirA @@ -264,24 +261,22 @@ void _declspec(naked) HOOK_LoadingPlayerImgDir() // Setup clothing directory size // ////////////////////////////////////////////////////////////////////////////////////////// -bool _cdecl SetClothingDirectorySize(int iCapacity) +bool SetClothingDirectorySize(int directorySize) { - DirectoryInfoSA* clothesDirectory = (DirectoryInfoSA*)malloc(sizeof(DirectoryInfoSA) * iCapacity); + DirectoryInfoSA* clothesDirectory = (DirectoryInfoSA*)malloc(sizeof(DirectoryInfoSA) * directorySize); - if (clothesDirectory) - { - // CClothesBuilder::LoadCdDirectory(void) - MemPut(0x5A4190 + 1, reinterpret_cast(clothesDirectory)); // push offset _playerImgEntries; headers - MemPut(0x5A4195 + 1, iCapacity); // push 550 ; count - MemPut(0x5A69E8 + 1, iCapacity); // push 550 ; count + if (!clothesDirectory) + return false; - g_pPlayerImgEntries = reinterpret_cast(clothesDirectory); - g_nPlayerImgSize = iCapacity; + // CClothesBuilder::LoadCdDirectory(void) + MemPut(0x5A4190 + 1, reinterpret_cast(clothesDirectory)); // push offset _playerImgEntries; headers + MemPut(0x5A4195 + 1, directorySize); // push 550 ; count + MemPut(0x5A69E8 + 1, directorySize); // push 550 ; count - return true; - } + g_playerImgEntries = reinterpret_cast(clothesDirectory); + g_playerImgSize = directorySize; - return false; + return true; } ////////////////////////////////////////////////////////////////////////////////////////// From 0886f1fc814c91beb604edc9a7dd36a159914a5d Mon Sep 17 00:00:00 2001 From: Walace Date: Thu, 20 Mar 2025 14:51:41 -0300 Subject: [PATCH 09/17] Removed clothing model functions from CStaticFunctionDefinitions --- .../logic/CStaticFunctionDefinitions.cpp | 16 ---------------- .../logic/CStaticFunctionDefinitions.h | 2 -- .../logic/lua/CLuaFunctionDefs.BodyClothes.cpp | 4 ++-- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 8d27beaa29d..a06e6e38f84 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -2671,22 +2671,6 @@ bool CStaticFunctionDefinitions::GetClothesTypeName(unsigned char ucType, SStrin return false; } -bool CStaticFunctionDefinitions::AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) -{ - if (!CClientPlayerClothes::AddClothingModel(szTexture, szModel, ucType)) - return false; - - return true; -} - -bool CStaticFunctionDefinitions::RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) -{ - if (!CClientPlayerClothes::RemoveClothingModel(szTexture, szModel, ucType)) - return false; - - return true; -} - CClientPed* CStaticFunctionDefinitions::CreatePed(CResource& Resource, unsigned long ulModel, const CVector& vecPosition, float fRotation) { // Valid model? diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h index f283a2f8a78..89459aa9e2f 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -195,8 +195,6 @@ class CStaticFunctionDefinitions static bool GetClothesByTypeIndex(unsigned char ucType, unsigned char ucIndex, SString& strOutTexture, SString& strOutModel); static bool GetTypeIndexFromClothes(const char* szTexture, const char* szModel, unsigned char& ucTypeReturn, unsigned char& ucIndexReturn); static bool GetClothesTypeName(unsigned char ucType, SString& strOutName); - static bool AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); - static bool RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); // Vehicle get funcs static CClientVehicle* CreateVehicle(CResource& Resource, unsigned short usModel, const CVector& vecPosition, const CVector& vecRotation, diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp index dd36b8e84da..5ffc017c69b 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp @@ -114,7 +114,7 @@ int CLuaFunctionDefs::AddClothingModel(lua_State* luaVM) if (!argStream.HasErrors()) { - if (CStaticFunctionDefinitions::AddClothingModel(strTexture, strModel, ucType)) + if (CClientPlayerClothes::AddClothingModel(strTexture, strModel, ucType)) { lua_pushboolean(luaVM, true); return 1; @@ -136,7 +136,7 @@ int CLuaFunctionDefs::RemoveClothingModel(lua_State* luaVM) if (!argStream.HasErrors()) { - if (CStaticFunctionDefinitions::RemoveClothingModel(strTexture, strModel, ucType)) + if (CClientPlayerClothes::RemoveClothingModel(strTexture, strModel, ucType)) { lua_pushboolean(luaVM, true); return 1; From e4cc7d9d1bc92be8d6397833ad54e3e97e20ffc4 Mon Sep 17 00:00:00 2001 From: Walace Date: Thu, 20 Mar 2025 16:20:43 -0300 Subject: [PATCH 10/17] Update due to code revision --- .../CRenderWareSA.ClothesReplacing.cpp | 41 ++++++++----------- Client/game_sa/CRenderWareSA.h | 6 +-- Client/mods/deathmatch/logic/CClientDFF.cpp | 6 +-- Client/mods/deathmatch/logic/CClientDFF.h | 2 +- .../deathmatch/logic/CClientPlayerClothes.cpp | 36 ++++++++-------- .../deathmatch/logic/CClientPlayerClothes.h | 4 +- Client/mods/deathmatch/logic/CClientTXD.cpp | 6 +-- Client/mods/deathmatch/logic/CClientTXD.h | 2 +- .../lua/CLuaFunctionDefs.BodyClothes.cpp | 24 +++++------ .../CMultiplayerSA_ClothesSpeedUp.cpp | 10 ++--- Client/sdk/game/CRenderWare.h | 2 +- 11 files changed, 65 insertions(+), 74 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp index 5fc31ad937a..b5db1352c0f 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -151,7 +151,7 @@ bool CRenderWareSA::ClothesAddFile(const char* fileData, std::size_t fileSize, c if (!g_clothesDirectory->AddEntry(entry)) return false; - MapSet(ms_ClothesFileDataMap, fileName, (char*)fileData); + MapSet(ms_ClothesFileDataMap, fileName, const_cast(fileData)); bClothesReplacementChanged = true; return true; @@ -191,14 +191,9 @@ bool CRenderWareSA::ClothesRemoveFile(char* fileData) // Check if clothe file exits // //////////////////////////////////////////////////////////////// -bool CRenderWareSA::HasClothesFile(const char* fileName) +bool CRenderWareSA::HasClothesFile(const char* fileName) const noexcept { - if (!fileName || !MapFind(ms_ClothesFileDataMap, fileName)) - { - return false; - } - - return true; + return fileName && MapFind(ms_ClothesFileDataMap, fileName); } //////////////////////////////////////////////////////////////// @@ -220,8 +215,8 @@ __declspec(noinline) bool _cdecl OnCStreaming_RequestModel_Mid(int flags, SImgGT return false; // Initialze lookup map if needed - static std::map blockOffsetToFileIdMap; - static std::map blockOffsetToFileNameMap; + static std::map blockOffsetToFileIdMap; + static std::map blockOffsetToFileNameMap; if (blockOffsetToFileIdMap.empty()) { // Check is player.img dir has been loaded by GTA @@ -231,7 +226,7 @@ __declspec(noinline) bool _cdecl OnCStreaming_RequestModel_Mid(int flags, SImgGT if (!pItemArray->pItems || pItemArray->uiArraySize != maxArraySize) return false; - for (uint i = 0; i < pItemArray->uiArraySize; i++) + for (std::uint32_t i = 0; i < pItemArray->uiArraySize; i++) { SPlayerImgItem* pImgItem = &pItemArray->pItems[i]; MapSet(blockOffsetToFileIdMap, pImgItem->uiBlockOffset, i); @@ -239,25 +234,21 @@ __declspec(noinline) bool _cdecl OnCStreaming_RequestModel_Mid(int flags, SImgGT } } - char* pReplacementFileData = nullptr; - int* piPlayerImgFileId = MapFind(blockOffsetToFileIdMap, pImgGTAInfo->iBlockOffset); + char* replacementFileData = nullptr; + int* playerImgFileId = MapFind(blockOffsetToFileIdMap, pImgGTAInfo->iBlockOffset); - if (piPlayerImgFileId) - { - pReplacementFileData = MapFindRef(ms_ReplacementClothesFileDataMap, *piPlayerImgFileId); - } + if (playerImgFileId) + replacementFileData = MapFindRef(ms_ReplacementClothesFileDataMap, *playerImgFileId); - if (!pReplacementFileData) + if (!replacementFileData) { - std::string* pFileName = MapFind(blockOffsetToFileNameMap, pImgGTAInfo->iBlockOffset); + std::string* fileName = MapFind(blockOffsetToFileNameMap, pImgGTAInfo->iBlockOffset); - if (pFileName) - { - pReplacementFileData = MapFindRef(ms_ClothesFileDataMap, *pFileName); - } + if (fileName) + replacementFileData = MapFindRef(ms_ClothesFileDataMap, *fileName); } - if (!pReplacementFileData) + if (!replacementFileData) return false; // If bLoadingBigModel is set, try to get it unset @@ -273,7 +264,7 @@ __declspec(noinline) bool _cdecl OnCStreaming_RequestModel_Mid(int flags, SImgGT // Set results iReturnFileId = ((char*)pImgGTAInfo - (char*)CStreaming__ms_aInfoForModel) / 20; - pReturnBuffer = pReplacementFileData; + pReturnBuffer = replacementFileData; // Update flags pImgGTAInfo->uiLoadflag = 3; diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index 3784b172bd2..c0f69c2979f 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -35,9 +35,9 @@ class CRenderWareSA : public CRenderWare void ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId); void ClothesRemoveReplacement(char* pFileData); bool HasClothesReplacementChanged(); - bool ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName); - bool ClothesRemoveFile(char* fileData); - bool HasClothesFile(const char* fileName); + bool ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName) override; + bool ClothesRemoveFile(char* fileData) override; + bool HasClothesFile(const char* fileName) const noexcept override; // Reads and parses a TXD file specified by a path (szTXD) RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer); diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index a49ad127919..f7b40a07299 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -99,9 +99,9 @@ void CClientDFF::UnloadDFF() m_LoadedClumpInfoMap.clear(); } -bool CClientDFF::AddClothingModel(std::string strModelName) +bool CClientDFF::AddClothingModel(std::string modelName) { - if (strModelName.empty()) + if (modelName.empty()) return false; if (m_RawDataBuffer.empty() && m_bIsRawData) @@ -113,7 +113,7 @@ bool CClientDFF::AddClothingModel(std::string strModelName) return false; } - return g_pGame->GetRenderWare()->ClothesAddFile(m_RawDataBuffer.data(), m_RawDataBuffer.size(), strModelName.c_str()); + return g_pGame->GetRenderWare()->ClothesAddFile(m_RawDataBuffer.data(), m_RawDataBuffer.size(), modelName.c_str()); } bool CClientDFF::ReplaceModel(unsigned short usModel, bool bAlphaTransparency) diff --git a/Client/mods/deathmatch/logic/CClientDFF.h b/Client/mods/deathmatch/logic/CClientDFF.h index da3652e1d33..aa7848b8e54 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.h +++ b/Client/mods/deathmatch/logic/CClientDFF.h @@ -37,7 +37,7 @@ class CClientDFF final : public CClientEntity bool Load(bool isRaw, SString input); - bool AddClothingModel(std::string strModelName); + bool AddClothingModel(std::string modelName); bool ReplaceModel(unsigned short usModel, bool bAlphaTransparency); bool HasReplaced(unsigned short usModel); diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp index f4cad45f530..4616d421c4d 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -395,50 +395,50 @@ bool CClientPlayerClothes::IsValidModel(unsigned short usModel) return usModel >= CLOTHES_MODEL_ID_FIRST && usModel <= CLOTHES_MODEL_ID_LAST; } -bool CClientPlayerClothes::AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) +bool CClientPlayerClothes::AddClothingModel(const char* texture, const char* model, unsigned char clothingType) { - if (ucType < PLAYER_CLOTHING_SLOTS) + if (clothingType < PLAYER_CLOTHING_SLOTS) { - if (szTexture == nullptr || szModel == nullptr) + if (texture == nullptr || model == nullptr) return false; - std::string textureFile = std::string(szTexture) + ".txd"; + std::string textureFile = std::string(texture) + ".txd"; if (!g_pGame->GetRenderWare()->HasClothesFile(textureFile.c_str())) return false; - std::string modelFile = std::string(szTexture) + ".dff"; + std::string modelFile = std::string(texture) + ".dff"; if (!g_pGame->GetRenderWare()->HasClothesFile(modelFile.c_str())) return false; - auto& clothes = m_NewClothes[ucType]; + auto& clothes = m_NewClothes[clothingType]; if (std::any_of(clothes.begin(), clothes.end(), [&](const SPlayerClothing& clothing) { - return !stricmp(szTexture, clothing.szTexture.c_str()) && !stricmp(szModel, clothing.szModel.c_str()); + return !stricmp(texture, clothing.szTexture.c_str()) && !stricmp(model, clothing.szModel.c_str()); })) { return false; } - clothes.push_back({szTexture, szModel}); + clothes.push_back({texture, model}); return true; } return false; } -bool CClientPlayerClothes::RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType) +bool CClientPlayerClothes::RemoveClothingModel(const char* texture, const char* model, unsigned char clothingType) { - if (ucType < PLAYER_CLOTHING_SLOTS) + if (clothingType < PLAYER_CLOTHING_SLOTS) { - if (szTexture == nullptr || szModel == nullptr) + if (texture == nullptr || model == nullptr) return false; - auto& clothes = m_NewClothes[ucType]; + auto& clothes = m_NewClothes[clothingType]; auto it = std::find_if(clothes.begin(), clothes.end(),[&](const SPlayerClothing& clothing) { - return !stricmp(szTexture, clothing.szTexture.c_str()) && !stricmp(szModel, clothing.szModel.c_str()); + return !stricmp(texture, clothing.szTexture.c_str()) && !stricmp(model, clothing.szModel.c_str()); }); if (it == clothes.end()) @@ -460,15 +460,15 @@ bool CClientPlayerClothes::HasClothesChanged() void CClientPlayerClothes::RefreshClothes() { - for (unsigned char ucType = 0; ucType < PLAYER_CLOTHING_SLOTS; ucType++) + for (std::uint8_t clothingType = 0; clothingType < PLAYER_CLOTHING_SLOTS; clothingType++) { - auto& clothes = m_NewClothes[ucType]; + auto& clothes = m_NewClothes[clothingType]; if (clothes.empty() && !m_bHasClothesChanged) continue; bool hasInvalidClothing = false; - const SPlayerClothing* pCurrent = m_Clothes[ucType]; + const SPlayerClothing* pCurrent = m_Clothes[clothingType]; if (!m_bHasClothesChanged) { @@ -493,7 +493,7 @@ void CClientPlayerClothes::RefreshClothes() if (pCurrent && !hasInvalidClothing && m_bHasClothesChanged) { - const SPlayerClothing* pClothing = GetClothing(pCurrent->szTexture.c_str(), pCurrent->szModel.c_str(), ucType); + const SPlayerClothing* pClothing = GetClothing(pCurrent->szTexture.c_str(), pCurrent->szModel.c_str(), clothingType); if (!pClothing) { @@ -503,7 +503,7 @@ void CClientPlayerClothes::RefreshClothes() if (hasInvalidClothing) { - RemoveClothes(ucType, true); + RemoveClothes(clothingType, true); } } diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index e00805deff1..0ba33648452 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.h +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.h @@ -70,8 +70,8 @@ class CClientPlayerClothes static std::vector GetClothingGroup(unsigned char ucType); static bool IsValidModel(unsigned short usModel); static bool HasClothesChanged(); - static bool AddClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); - static bool RemoveClothingModel(const char* szTexture, const char* szModel, unsigned char ucType); + static bool AddClothingModel(const char* texture, const char* model, unsigned char clothingType); + static bool RemoveClothingModel(const char* texture, const char* model, unsigned char clothingType); private: static const SPlayerClothing* GetClothing(const char* szTexture, const char* szModel, unsigned char ucType); diff --git a/Client/mods/deathmatch/logic/CClientTXD.cpp b/Client/mods/deathmatch/logic/CClientTXD.cpp index 1dd63bf22c7..d0089629a15 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.cpp +++ b/Client/mods/deathmatch/logic/CClientTXD.cpp @@ -53,9 +53,9 @@ bool CClientTXD::Load(bool isRaw, SString input, bool enableFiltering) } } -bool CClientTXD::AddClothingTexture(std::string strModelName) +bool CClientTXD::AddClothingTexture(std::string modelName) { - if (strModelName.empty()) + if (modelName.empty()) return false; if (m_FileData.empty() && m_bIsRawData) @@ -70,7 +70,7 @@ bool CClientTXD::AddClothingTexture(std::string strModelName) return false; } - return g_pGame->GetRenderWare()->ClothesAddFile(m_FileData.data(), m_FileData.size(), strModelName.c_str()); + return g_pGame->GetRenderWare()->ClothesAddFile(m_FileData.data(), m_FileData.size(), modelName.c_str()); } bool CClientTXD::Import(unsigned short usModelID) diff --git a/Client/mods/deathmatch/logic/CClientTXD.h b/Client/mods/deathmatch/logic/CClientTXD.h index e882f86b3c1..07d36b098bf 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.h +++ b/Client/mods/deathmatch/logic/CClientTXD.h @@ -27,7 +27,7 @@ class CClientTXD final : public CClientEntity eClientEntityType GetType() const { return CCLIENTTXD; } bool Load(bool isRaw, SString input, bool enableFiltering); - bool AddClothingTexture(std::string strModelName); + bool AddClothingTexture(std::string modelName); bool Import(unsigned short usModelID); static bool IsImportableModel(unsigned short usModelID); static bool IsTXDData(const SString& strData); diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp index 5ffc017c69b..534cc9ed589 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp @@ -105,16 +105,16 @@ int CLuaFunctionDefs::GetClothesTypeName(lua_State* luaVM) int CLuaFunctionDefs::AddClothingModel(lua_State* luaVM) { - unsigned char ucType = 0; - SString strTexture, strModel; + std::uint8_t clothingType = 0; + SString texture, model; CScriptArgReader argStream(luaVM); - argStream.ReadString(strTexture); - argStream.ReadString(strModel, ""); - argStream.ReadNumber(ucType); + argStream.ReadString(texture); + argStream.ReadString(model, ""); + argStream.ReadNumber(clothingType); if (!argStream.HasErrors()) { - if (CClientPlayerClothes::AddClothingModel(strTexture, strModel, ucType)) + if (CClientPlayerClothes::AddClothingModel(texture, model, clothingType)) { lua_pushboolean(luaVM, true); return 1; @@ -127,16 +127,16 @@ int CLuaFunctionDefs::AddClothingModel(lua_State* luaVM) int CLuaFunctionDefs::RemoveClothingModel(lua_State* luaVM) { - unsigned char ucType = 0; - SString strTexture, strModel; + std::uint8_t clothingType = 0; + SString texture, model; CScriptArgReader argStream(luaVM); - argStream.ReadString(strTexture); - argStream.ReadString(strModel, ""); - argStream.ReadNumber(ucType); + argStream.ReadString(texture); + argStream.ReadString(model, ""); + argStream.ReadNumber(clothingType); if (!argStream.HasErrors()) { - if (CClientPlayerClothes::RemoveClothingModel(strTexture, strModel, ucType)) + if (CClientPlayerClothes::RemoveClothingModel(texture, model, clothingType)) { lua_pushboolean(luaVM, true); return 1; diff --git a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp index 0b970a43666..5fdfbd37197 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp @@ -218,7 +218,7 @@ void _declspec(naked) HOOK_CStreamingLoadRequestedModels() std::uint32_t g_playerImgEntries = 0xBBCDC8; std::uint16_t g_playerImgSize = 0x226; -bool _cdecl IsPlayerImgDirLoaded() +bool IsPlayerImgDirLoaded() { // When player.img dir is loaded, it looks this this: // 0x00BC12C0 00bbcdc8 00000226 @@ -263,15 +263,15 @@ void _declspec(naked) HOOK_LoadingPlayerImgDir() ////////////////////////////////////////////////////////////////////////////////////////// bool SetClothingDirectorySize(int directorySize) { - DirectoryInfoSA* clothesDirectory = (DirectoryInfoSA*)malloc(sizeof(DirectoryInfoSA) * directorySize); + DirectoryInfoSA* clothesDirectory = new DirectoryInfoSA[directorySize]; if (!clothesDirectory) return false; // CClothesBuilder::LoadCdDirectory(void) - MemPut(0x5A4190 + 1, reinterpret_cast(clothesDirectory)); // push offset _playerImgEntries; headers - MemPut(0x5A4195 + 1, directorySize); // push 550 ; count - MemPut(0x5A69E8 + 1, directorySize); // push 550 ; count + MemPut(0x5A4190 + 1, reinterpret_cast(clothesDirectory)); // push offset _playerImgEntries; headers + MemPut(0x5A4195 + 1, directorySize); // push 550 ; count + MemPut(0x5A69E8 + 1, directorySize); // push 550 ; count g_playerImgEntries = reinterpret_cast(clothesDirectory); g_playerImgSize = directorySize; diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index 8443de136e6..20139ded76a 100644 --- a/Client/sdk/game/CRenderWare.h +++ b/Client/sdk/game/CRenderWare.h @@ -81,7 +81,7 @@ class CRenderWare virtual bool HasClothesReplacementChanged() = 0; virtual bool ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName) = 0; virtual bool ClothesRemoveFile(char* fileData) = 0; - virtual bool HasClothesFile(const char* fileName) = 0; + virtual bool HasClothesFile(const char* fileName) const noexcept = 0; virtual RwTexDictionary* ReadTXD(const SString& strFilename, const SString& buffer) = 0; virtual RpClump* ReadDFF(const SString& strFilename, const SString& buffer, unsigned short usModelID, bool bLoadEmbeddedCollisions) = 0; virtual CColModel* ReadCOL(const SString& buffer) = 0; From 294bd7fc800d42d5868709dcc3b200e634410afd Mon Sep 17 00:00:00 2001 From: Walace Date: Thu, 20 Mar 2025 16:28:11 -0300 Subject: [PATCH 11/17] Fix crash when clothing has removed --- Client/mods/deathmatch/logic/CClientPlayerClothes.cpp | 2 +- Client/mods/deathmatch/logic/CClientPlayerClothes.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp index 4616d421c4d..91d5dd1be51 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -177,7 +177,7 @@ SFixedArray, PLAYER_CLOTHING_SLOTS> CClientPlayerCl // This represents GTA's 1 clothing block SFixedArray CClientPlayerClothes::m_GlobalClothes; -SFixedArray, PLAYER_CLOTHING_SLOTS> CClientPlayerClothes::m_NewClothes; +SFixedArray, PLAYER_CLOTHING_SLOTS> CClientPlayerClothes::m_NewClothes; bool CClientPlayerClothes::m_bStaticInit = true; bool CClientPlayerClothes::m_bHasClothesChanged = false; diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index 0ba33648452..29d2212be04 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.h +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.h @@ -81,7 +81,7 @@ class CClientPlayerClothes SFixedArray m_Clothes; static bool m_bHasClothesChanged; static SFixedArray, PLAYER_CLOTHING_SLOTS> m_DefaultClothes; - static SFixedArray, PLAYER_CLOTHING_SLOTS> m_NewClothes; + static SFixedArray, PLAYER_CLOTHING_SLOTS> m_NewClothes; static SFixedArray m_GlobalClothes; static bool m_bStaticInit; }; From fda03ceb32abc6f29d4c21803ad0640955b4327c Mon Sep 17 00:00:00 2001 From: Walace Date: Sat, 29 Mar 2025 14:52:20 -0300 Subject: [PATCH 12/17] Change AddClothingTexture/AddClothingModel to const std::string& --- Client/mods/deathmatch/logic/CClientDFF.cpp | 2 +- Client/mods/deathmatch/logic/CClientDFF.h | 2 +- Client/mods/deathmatch/logic/CClientTXD.cpp | 2 +- Client/mods/deathmatch/logic/CClientTXD.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index f7b40a07299..3de11df6bc2 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -99,7 +99,7 @@ void CClientDFF::UnloadDFF() m_LoadedClumpInfoMap.clear(); } -bool CClientDFF::AddClothingModel(std::string modelName) +bool CClientDFF::AddClothingModel(const std::string& modelName) { if (modelName.empty()) return false; diff --git a/Client/mods/deathmatch/logic/CClientDFF.h b/Client/mods/deathmatch/logic/CClientDFF.h index aa7848b8e54..22fc3ebef9f 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.h +++ b/Client/mods/deathmatch/logic/CClientDFF.h @@ -37,7 +37,7 @@ class CClientDFF final : public CClientEntity bool Load(bool isRaw, SString input); - bool AddClothingModel(std::string modelName); + bool AddClothingModel(const std::string& modelName); bool ReplaceModel(unsigned short usModel, bool bAlphaTransparency); bool HasReplaced(unsigned short usModel); diff --git a/Client/mods/deathmatch/logic/CClientTXD.cpp b/Client/mods/deathmatch/logic/CClientTXD.cpp index d0089629a15..b619586c917 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.cpp +++ b/Client/mods/deathmatch/logic/CClientTXD.cpp @@ -53,7 +53,7 @@ bool CClientTXD::Load(bool isRaw, SString input, bool enableFiltering) } } -bool CClientTXD::AddClothingTexture(std::string modelName) +bool CClientTXD::AddClothingTexture(const std::string& modelName) { if (modelName.empty()) return false; diff --git a/Client/mods/deathmatch/logic/CClientTXD.h b/Client/mods/deathmatch/logic/CClientTXD.h index 07d36b098bf..7b9be10dbff 100644 --- a/Client/mods/deathmatch/logic/CClientTXD.h +++ b/Client/mods/deathmatch/logic/CClientTXD.h @@ -27,7 +27,7 @@ class CClientTXD final : public CClientEntity eClientEntityType GetType() const { return CCLIENTTXD; } bool Load(bool isRaw, SString input, bool enableFiltering); - bool AddClothingTexture(std::string modelName); + bool AddClothingTexture(const std::string& modelName); bool Import(unsigned short usModelID); static bool IsImportableModel(unsigned short usModelID); static bool IsTXDData(const SString& strData); From 7952f16d54c9a9202e0b786c98f4e7fc08105a91 Mon Sep 17 00:00:00 2001 From: Walace Date: Sat, 29 Mar 2025 14:54:10 -0300 Subject: [PATCH 13/17] Change g_playerImgEntries and g_playerImgSize to static --- Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp index 5fdfbd37197..55aba2cf20a 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp @@ -215,8 +215,8 @@ void _declspec(naked) HOOK_CStreamingLoadRequestedModels() // Speeds up clothes a bit, but is only part of a solution - The actual files from inside player.img are still loaded each time // -std::uint32_t g_playerImgEntries = 0xBBCDC8; -std::uint16_t g_playerImgSize = 0x226; +static std::uint32_t g_playerImgEntries = 0xBBCDC8; +static std::uint16_t g_playerImgSize = 0x226; bool IsPlayerImgDirLoaded() { From b14cfbf5d71c2b685e00a4c9f5be88e184a36d4c Mon Sep 17 00:00:00 2001 From: Walace Date: Mon, 31 Mar 2025 01:04:14 -0300 Subject: [PATCH 14/17] Remove sz prefix from SPlayerClothing structure --- .../deathmatch/logic/CClientPlayerClothes.cpp | 16 ++++++++-------- .../mods/deathmatch/logic/CClientPlayerClothes.h | 4 ++-- .../logic/CStaticFunctionDefinitions.cpp | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp index 91d5dd1be51..43b99d2748d 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -235,7 +235,7 @@ void CClientPlayerClothes::InternalAddClothes(const SPlayerClothing* pClothing, { if (pClothing && !IsEmptyClothing(pClothing, ucType)) { - pPlayerPed->SetClothesTextureAndModel(pClothing->szTexture.c_str(), pClothing->szModel.c_str(), ucType); + pPlayerPed->SetClothesTextureAndModel(pClothing->texture.c_str(), pClothing->model.c_str(), ucType); } else { @@ -381,7 +381,7 @@ const SPlayerClothing* CClientPlayerClothes::GetClothing(const char* szTexture, for (const auto& clothing : pGroup) { - if (!stricmp(szTexture, clothing->szTexture.c_str()) && !stricmp(szModel, clothing->szModel.c_str())) + if (!stricmp(szTexture, clothing->texture.c_str()) && !stricmp(szModel, clothing->model.c_str())) { return clothing; } @@ -415,7 +415,7 @@ bool CClientPlayerClothes::AddClothingModel(const char* texture, const char* mod auto& clothes = m_NewClothes[clothingType]; if (std::any_of(clothes.begin(), clothes.end(), [&](const SPlayerClothing& clothing) { - return !stricmp(texture, clothing.szTexture.c_str()) && !stricmp(model, clothing.szModel.c_str()); + return !stricmp(texture, clothing.texture.c_str()) && !stricmp(model, clothing.model.c_str()); })) { return false; @@ -438,7 +438,7 @@ bool CClientPlayerClothes::RemoveClothingModel(const char* texture, const char* auto& clothes = m_NewClothes[clothingType]; auto it = std::find_if(clothes.begin(), clothes.end(),[&](const SPlayerClothing& clothing) { - return !stricmp(texture, clothing.szTexture.c_str()) && !stricmp(model, clothing.szModel.c_str()); + return !stricmp(texture, clothing.texture.c_str()) && !stricmp(model, clothing.model.c_str()); }); if (it == clothes.end()) @@ -474,12 +474,12 @@ void CClientPlayerClothes::RefreshClothes() { for (auto clothing = clothes.begin(); clothing != clothes.end();) { - std::string fileTXD = clothing->szTexture + ".txd"; - std::string fileDFF = clothing->szModel + ".dff"; + std::string fileTXD = clothing->texture + ".txd"; + std::string fileDFF = clothing->model + ".dff"; if (!g_pGame->GetRenderWare()->HasClothesFile(fileTXD.c_str()) || !g_pGame->GetRenderWare()->HasClothesFile(fileDFF.c_str())) { - if (pCurrent && (pCurrent->szTexture == clothing->szTexture || pCurrent->szModel == clothing->szModel)) + if (pCurrent && (pCurrent->texture == clothing->texture || pCurrent->model == clothing->model)) { hasInvalidClothing = true; } @@ -493,7 +493,7 @@ void CClientPlayerClothes::RefreshClothes() if (pCurrent && !hasInvalidClothing && m_bHasClothesChanged) { - const SPlayerClothing* pClothing = GetClothing(pCurrent->szTexture.c_str(), pCurrent->szModel.c_str(), clothingType); + const SPlayerClothing* pClothing = GetClothing(pCurrent->texture.c_str(), pCurrent->model.c_str(), clothingType); if (!pClothing) { diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index 29d2212be04..f01e3433fba 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.h +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.h @@ -41,8 +41,8 @@ struct SPlayerClothingType struct SPlayerClothing { - std::string szTexture; - std::string szModel; + std::string texture; + std::string model; }; class CClientPlayerClothes diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index a06e6e38f84..38a9a7721cf 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -1702,8 +1702,8 @@ bool CStaticFunctionDefinitions::GetPedClothes(CClientPed& Ped, unsigned char uc const SPlayerClothing* pClothing = Ped.GetClothes()->GetClothing(ucType); if (pClothing) { - strOutTexture = pClothing->szTexture; - strOutModel = pClothing->szModel; + strOutTexture = pClothing->texture; + strOutModel = pClothing->model; return true; } @@ -2626,8 +2626,8 @@ bool CStaticFunctionDefinitions::GetClothesByTypeIndex(unsigned char ucType, uns if (ucIndex > (pPlayerClothing.size() - 1)) return false; - strOutTexture = pPlayerClothing.at(ucIndex)->szTexture; - strOutModel = pPlayerClothing.at(ucIndex)->szModel; + strOutTexture = pPlayerClothing.at(ucIndex)->texture; + strOutModel = pPlayerClothing.at(ucIndex)->model; return true; } @@ -2644,8 +2644,8 @@ bool CStaticFunctionDefinitions::GetTypeIndexFromClothes(const char* szTexture, if (!pPlayerClothing.empty()) { for (unsigned char ucIter = 0; ucIter < pPlayerClothing.size(); ucIter++) { - if ((szTexture == NULL || strcmp(szTexture, pPlayerClothing[ucIter]->szTexture.c_str()) == 0) && - (szModel == NULL || strcmp(szModel, pPlayerClothing[ucIter]->szModel.c_str()) == 0)) + if ((szTexture == NULL || strcmp(szTexture, pPlayerClothing[ucIter]->texture.c_str()) == 0) && + (szModel == NULL || strcmp(szModel, pPlayerClothing[ucIter]->model.c_str()) == 0)) { ucTypeReturn = ucType; ucIndexReturn = ucIter; From 5ec40bc6c0b5c6ff66579513494b99e71a7c9f3b Mon Sep 17 00:00:00 2001 From: Walace Date: Mon, 31 Mar 2025 01:20:22 -0300 Subject: [PATCH 15/17] Add null terminator to entry.m_name in CRenderWareSA::ClothesAddFile --- Client/game_sa/CRenderWareSA.ClothesReplacing.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp index b5db1352c0f..2860e786b2f 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -146,7 +146,10 @@ bool CRenderWareSA::ClothesAddFile(const char* fileData, std::size_t fileSize, c DirectoryInfoSA entry{}; entry.m_streamingSize = GetSizeInBlocks(fileSize); - std::strncpy(entry.m_name, fileName, sizeof(entry.m_name)); + + std::size_t nameSize = sizeof(entry.m_name) - 1; + std::strncpy(entry.m_name, fileName, nameSize); + entry.m_name[nameSize] = '\0'; if (!g_clothesDirectory->AddEntry(entry)) return false; From c9154b34831f78375aff3e1afd0b3237d2dabc73 Mon Sep 17 00:00:00 2001 From: Walace Date: Mon, 31 Mar 2025 01:27:25 -0300 Subject: [PATCH 16/17] Add condition to check if m_numEntries is zero --- Client/game_sa/CDirectorySA.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/game_sa/CDirectorySA.cpp b/Client/game_sa/CDirectorySA.cpp index 35b5b49dd2f..97b9c910e80 100644 --- a/Client/game_sa/CDirectorySA.cpp +++ b/Client/game_sa/CDirectorySA.cpp @@ -34,7 +34,7 @@ bool CDirectorySAInterface::AddEntry(DirectoryInfoSA& entry) bool CDirectorySAInterface::RemoveEntry(const char* fileName) { - if (m_numEntries <= 0) + if (m_numEntries == 0) return false; DirectoryInfoSA* entry = GetModelEntry(fileName); @@ -60,7 +60,7 @@ bool CDirectorySAInterface::RemoveEntry(const char* fileName) DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* fileName) { - if (m_numEntries <= 0) + if (m_numEntries == 0) return nullptr; for (DirectoryInfoSA* it = m_entries; it != m_entries + m_numEntries; it++) @@ -74,7 +74,7 @@ DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* fileName) DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(std::uint16_t modelId) { - if (m_numEntries <= 0) + if (m_numEntries == 0) return nullptr; DirectoryInfoSA* entry = m_entries + modelId; From 912f28421ae43b12162bfbc3c59930bfd5c43137 Mon Sep 17 00:00:00 2001 From: Walace Date: Fri, 4 Apr 2025 17:41:28 -0300 Subject: [PATCH 17/17] Rename clothesReplacementChanged to remove Hungarian notation --- Client/game_sa/CRenderWareSA.ClothesReplacing.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp index 2860e786b2f..8d797a4c092 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -33,7 +33,7 @@ namespace std::unordered_map ms_OriginalStreamingSizesMap; std::unordered_map ms_ClothesFileDataMap; - bool bClothesReplacementChanged = false; + bool clothesReplacementChanged = false; struct SPlayerImgItem { @@ -79,7 +79,7 @@ void CRenderWareSA::ClothesAddReplacement(char* pFileData, size_t fileSize, usho MapSet(ms_OriginalStreamingSizesMap, usFileId, g_clothesDirectory->GetModelStreamingSize(usFileId)); g_clothesDirectory->SetModelStreamingSize(usFileId, GetSizeInBlocks(fileSize)); - bClothesReplacementChanged = true; + clothesReplacementChanged = true; } } @@ -108,7 +108,7 @@ void CRenderWareSA::ClothesRemoveReplacement(char* pFileData) } iter = ms_ReplacementClothesFileDataMap.erase(iter); - bClothesReplacementChanged = true; + clothesReplacementChanged = true; } else ++iter; @@ -124,8 +124,8 @@ void CRenderWareSA::ClothesRemoveReplacement(char* pFileData) //////////////////////////////////////////////////////////////// bool CRenderWareSA::HasClothesReplacementChanged() { - bool bResult = bClothesReplacementChanged; - bClothesReplacementChanged = false; + bool bResult = clothesReplacementChanged; + clothesReplacementChanged = false; return bResult; } @@ -155,7 +155,7 @@ bool CRenderWareSA::ClothesAddFile(const char* fileData, std::size_t fileSize, c return false; MapSet(ms_ClothesFileDataMap, fileName, const_cast(fileData)); - bClothesReplacementChanged = true; + clothesReplacementChanged = true; return true; } @@ -180,7 +180,7 @@ bool CRenderWareSA::ClothesRemoveFile(char* fileData) return false; iter = ms_ClothesFileDataMap.erase(iter); - bClothesReplacementChanged = true; + clothesReplacementChanged = true; } else ++iter;