diff --git a/Client/game_sa/CDirectorySA.cpp b/Client/game_sa/CDirectorySA.cpp index 726353792ea..97b9c910e80 100644 --- a/Client/game_sa/CDirectorySA.cpp +++ b/Client/game_sa/CDirectorySA.cpp @@ -11,12 +11,73 @@ #include "StdInc.h" #include "CDirectorySA.h" +bool CDirectorySAInterface::AddEntry(DirectoryInfoSA& entry) +{ + if (m_numEntries >= m_capacity || GetModelEntry(entry.m_name)) + return false; + + entry.m_offset = 0; + + if (m_numEntries > 0) + { + DirectoryInfoSA* lastEntry = m_entries + m_numEntries - 1; + entry.m_offset = lastEntry->m_offset + lastEntry->m_streamingSize; + + if (entry.m_offset % 2048) + entry.m_offset += 2048 - (entry.m_offset % 2048); + } + + m_entries[m_numEntries++] = entry; + + return true; +} + +bool CDirectorySAInterface::RemoveEntry(const char* fileName) +{ + if (m_numEntries == 0) + return false; + + DirectoryInfoSA* entry = GetModelEntry(fileName); + + if (!entry) + return false; + + std::ptrdiff_t index = entry - m_entries; + + if (index < m_numEntries - 1) + { + DirectoryInfoSA* lastEntry = m_entries + m_numEntries - 1; + entry->m_offset = lastEntry->m_offset + lastEntry->m_sizeInArchive; + } + + m_numEntries--; + + if (index < m_numEntries) + std::memmove(entry, entry + 1, (m_numEntries - index) * sizeof(DirectoryInfoSA)); + + return true; +} + +DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(const char* fileName) +{ + if (m_numEntries == 0) + return nullptr; + + for (DirectoryInfoSA* it = m_entries; it != m_entries + m_numEntries; it++) + { + if (std::strcmp(it->m_name, fileName) == 0) + return it; + } + + return nullptr; +} + 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; @@ -28,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; } @@ -46,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 620db3938be..49d8812145a 100644 --- a/Client/game_sa/CDirectorySA.h +++ b/Client/game_sa/CDirectorySA.h @@ -12,22 +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* fileName); + + 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 ffae5f3dd37..8d797a4c092 100644 --- a/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp +++ b/Client/game_sa/CRenderWareSA.ClothesReplacing.cpp @@ -31,8 +31,9 @@ namespace std::unordered_map ms_ReplacementClothesFileDataMap; std::unordered_map ms_OriginalStreamingSizesMap; + std::unordered_map ms_ClothesFileDataMap; - bool bClothesReplacementChanged = false; + bool clothesReplacementChanged = false; struct SPlayerImgItem { @@ -78,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; } } @@ -107,7 +108,7 @@ void CRenderWareSA::ClothesRemoveReplacement(char* pFileData) } iter = ms_ReplacementClothesFileDataMap.erase(iter); - bClothesReplacementChanged = true; + clothesReplacementChanged = true; } else ++iter; @@ -123,11 +124,81 @@ void CRenderWareSA::ClothesRemoveReplacement(char* pFileData) //////////////////////////////////////////////////////////////// bool CRenderWareSA::HasClothesReplacementChanged() { - bool bResult = bClothesReplacementChanged; - bClothesReplacementChanged = false; + bool bResult = clothesReplacementChanged; + clothesReplacementChanged = false; return bResult; } +//////////////////////////////////////////////////////////////// +// +// CRenderWareSA::ClothesAddFile +// +// Add a file to the clothes directory +// +//////////////////////////////////////////////////////////////// +bool CRenderWareSA::ClothesAddFile(const char* fileData, std::size_t fileSize, const char* fileName) +{ + if (!fileData || !fileName) + return false; + + if (MapFind(ms_ClothesFileDataMap, fileName)) + return false; + + DirectoryInfoSA entry{}; + entry.m_streamingSize = GetSizeInBlocks(fileSize); + + 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; + + MapSet(ms_ClothesFileDataMap, fileName, const_cast(fileData)); + clothesReplacementChanged = true; + + return true; +} + +//////////////////////////////////////////////////////////////// +// +// CRenderWareSA::ClothesRemoveFile +// +// Remove a file from the clothes directory +// +//////////////////////////////////////////////////////////////// +bool CRenderWareSA::ClothesRemoveFile(char* fileData) +{ + if (!fileData) + return false; + + for (auto iter = ms_ClothesFileDataMap.begin(); iter != ms_ClothesFileDataMap.end();) + { + if (iter->second == fileData) + { + if (!g_clothesDirectory->RemoveEntry(iter->first.c_str())) + return false; + + iter = ms_ClothesFileDataMap.erase(iter); + clothesReplacementChanged = true; + } + else + ++iter; + } +} + +//////////////////////////////////////////////////////////////// +// +// CRenderWareSA::HasClothesFile +// +// Check if clothe file exits +// +//////////////////////////////////////////////////////////////// +bool CRenderWareSA::HasClothesFile(const char* fileName) const noexcept +{ + return fileName && MapFind(ms_ClothesFileDataMap, fileName); +} + //////////////////////////////////////////////////////////////// // // CStreaming_RequestModel_Mid @@ -143,35 +214,44 @@ __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 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++) + for (std::uint32_t 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* replacementFileData = nullptr; + int* playerImgFileId = MapFind(blockOffsetToFileIdMap, pImgGTAInfo->iBlockOffset); + + if (playerImgFileId) + replacementFileData = MapFindRef(ms_ReplacementClothesFileDataMap, *playerImgFileId); - int iPlayerImgFileId = *piPlayerImgFileId; + if (!replacementFileData) + { + std::string* fileName = MapFind(blockOffsetToFileNameMap, pImgGTAInfo->iBlockOffset); + + if (fileName) + replacementFileData = MapFindRef(ms_ClothesFileDataMap, *fileName); + } - // Do we have a replacement for this clothes texture? - char* pReplacementFileData = MapFindRef(ms_ReplacementClothesFileDataMap, iPlayerImgFileId); - if (!pReplacementFileData) + if (!replacementFileData) return false; // If bLoadingBigModel is set, try to get it unset @@ -187,7 +267,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 d79d9307206..c0f69c2979f 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* 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 07e718a8b92..3de11df6bc2 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::AddClothingModel(const std::string& modelName) +{ + if (modelName.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(), modelName.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..22fc3ebef9f 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 AddClothingModel(const std::string& modelName); bool ReplaceModel(unsigned short usModel, bool bAlphaTransparency); bool HasReplaced(unsigned short usModel); 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/CClientPed.cpp b/Client/mods/deathmatch/logic/CClientPed.cpp index b52e09b9db1..c2e1ce5c877 100644 --- a/Client/mods/deathmatch/logic/CClientPed.cpp +++ b/Client/mods/deathmatch/logic/CClientPed.cpp @@ -4063,6 +4063,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..43b99d2748d 100644 --- a/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp +++ b/Client/mods/deathmatch/logic/CClientPlayerClothes.cpp @@ -15,284 +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; -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) { @@ -348,7 +235,7 @@ void CClientPlayerClothes::InternalAddClothes(const SPlayerClothing* pClothing, { if (pClothing && !IsEmptyClothing(pClothing, ucType)) { - pPlayerPed->SetClothesTextureAndModel(pClothing->szTexture, pClothing->szModel, ucType); + pPlayerPed->SetClothesTextureAndModel(pClothing->texture.c_str(), pClothing->model.c_str(), ucType); } else { @@ -371,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 { @@ -384,6 +271,7 @@ bool CClientPlayerClothes::RemoveClothes(unsigned char ucType, bool bRemoveFromM { InternalAddClothes(NULL, ucType); } + return true; } return false; @@ -441,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) @@ -464,121 +349,163 @@ 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) { - switch (ucType) + for (auto& clothing : m_DefaultClothes[ucType]) { - case 0: - return g_TorsoClothing; - case 1: - return g_HairClothing; - case 2: - return g_LegsClothing; - case 3: - return g_ShoesClothing; - case 4: - return g_LeftUpperArmClothing; - case 5: - return g_LeftLowerArmClothing; - case 6: - return g_RightUpperArmClothing; - case 7: - return g_RightLowerArmClothing; - case 8: - return g_BackTopClothing; - case 9: - return g_LeftChestClothing; - case 10: - return g_RightChestClothing; - case 11: - return g_StomachClothing; - case 12: - return g_LowerBackClothing; - case 13: - return g_Extra1Clothing; - case 14: - return g_Extra2Clothing; - case 15: - return g_Extra3Clothing; - case 16: - return g_Extra4Clothing; - case 17: - return g_SuitClothing; + clothes.push_back(&clothing); + } + + 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) + if (!szTexture || !szModel || ucType >= PLAYER_CLOTHING_SLOTS) + return nullptr; + + std::vector pGroup = GetClothingGroup(ucType); + + if (pGroup.empty()) + return nullptr; + + for (const auto& clothing : pGroup) { - const SPlayerClothing* pGroup = GetClothingGroup(ucType); - int iMax = GetClothingGroupMax(ucType); - for (int i = 0; i < iMax; i++) + if (!stricmp(szTexture, clothing->texture.c_str()) && !stricmp(szModel, clothing->model.c_str())) { - const SPlayerClothing* pClothing = &pGroup[i]; - if (!stricmp(szTexture, pClothing->szTexture) && !stricmp(szModel, pClothing->szModel)) - { - return pClothing; - } + return clothing; } } - return NULL; + + return nullptr; } -const int CClientPlayerClothes::GetClothingGroupMax(unsigned char ucType) +bool CClientPlayerClothes::IsValidModel(unsigned short usModel) { - if (ucType < PLAYER_CLOTHING_SLOTS) + return usModel >= CLOTHES_MODEL_ID_FIRST && usModel <= CLOTHES_MODEL_ID_LAST; +} + +bool CClientPlayerClothes::AddClothingModel(const char* texture, const char* model, unsigned char clothingType) +{ + if (clothingType < PLAYER_CLOTHING_SLOTS) { - switch (ucType) + if (texture == nullptr || model == nullptr) + return false; + + std::string textureFile = std::string(texture) + ".txd"; + + if (!g_pGame->GetRenderWare()->HasClothesFile(textureFile.c_str())) + return false; + + std::string modelFile = std::string(texture) + ".dff"; + + if (!g_pGame->GetRenderWare()->HasClothesFile(modelFile.c_str())) + return false; + + auto& clothes = m_NewClothes[clothingType]; + + if (std::any_of(clothes.begin(), clothes.end(), [&](const SPlayerClothing& clothing) { + return !stricmp(texture, clothing.texture.c_str()) && !stricmp(model, clothing.model.c_str()); + })) { - case 0: - return TORSO_CLOTHING_MAX; - case 1: - return HAIR_CLOTHING_MAX; - case 2: - return LEGS_CLOTHING_MAX; - case 3: - return SHOES_CLOTHING_MAX; - case 4: - return LEFT_UPPER_ARM_CLOTHING_MAX; - case 5: - return LEFT_LOWER_ARM_CLOTHING_MAX; - case 6: - return RIGHT_UPPER_ARM_CLOTHING_MAX; - case 7: - return RIGHT_LOWER_ARM_CLOTHING_MAX; - case 8: - return BACK_TOP_CLOTHING_MAX; - case 9: - return LEFT_CHEST_CLOTHING_MAX; - case 10: - return RIGHT_CHEST_CLOTHING_MAX; - case 11: - return STOMACH_CLOTHING_MAX; - case 12: - return LOWER_BACK_CLOTHING_MAX; - case 13: - return EXTRA1_CLOTHING_MAX; - case 14: - return EXTRA2_CLOTHING_MAX; - case 15: - return EXTRA3_CLOTHING_MAX; - case 16: - return EXTRA4_CLOTHING_MAX; - case 17: - return SUIT_CLOTHING_MAX; + return false; } + + clothes.push_back({texture, model}); + return true; } - return 0; + return false; } -bool CClientPlayerClothes::IsValidModel(unsigned short usModel) +bool CClientPlayerClothes::RemoveClothingModel(const char* texture, const char* model, unsigned char clothingType) { - return usModel >= CLOTHES_MODEL_ID_FIRST && usModel <= CLOTHES_MODEL_ID_LAST; + if (clothingType < PLAYER_CLOTHING_SLOTS) + { + if (texture == nullptr || model == nullptr) + return false; + + auto& clothes = m_NewClothes[clothingType]; + + auto it = std::find_if(clothes.begin(), clothes.end(),[&](const SPlayerClothing& clothing) { + return !stricmp(texture, clothing.texture.c_str()) && !stricmp(model, clothing.model.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 (std::uint8_t clothingType = 0; clothingType < PLAYER_CLOTHING_SLOTS; clothingType++) + { + auto& clothes = m_NewClothes[clothingType]; + + if (clothes.empty() && !m_bHasClothesChanged) + continue; + + bool hasInvalidClothing = false; + const SPlayerClothing* pCurrent = m_Clothes[clothingType]; + + if (!m_bHasClothesChanged) + { + for (auto clothing = clothes.begin(); clothing != clothes.end();) + { + 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->texture == clothing->texture || pCurrent->model == clothing->model)) + { + hasInvalidClothing = true; + } + + clothing = clothes.erase(clothing); + } + else + ++clothing; + } + } + + if (pCurrent && !hasInvalidClothing && m_bHasClothesChanged) + { + const SPlayerClothing* pClothing = GetClothing(pCurrent->texture.c_str(), pCurrent->model.c_str(), clothingType); + + if (!pClothing) + { + hasInvalidClothing = true; + } + } + + if (hasInvalidClothing) + { + RemoveClothes(clothingType, true); + } + } + + m_bHasClothesChanged = false; } diff --git a/Client/mods/deathmatch/logic/CClientPlayerClothes.h b/Client/mods/deathmatch/logic/CClientPlayerClothes.h index eeb3e25f8f6..f01e3433fba 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 texture; + std::string model; }; class CClientPlayerClothes @@ -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); @@ -65,15 +67,21 @@ 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 std::vector GetClothingGroup(unsigned char ucType); + static bool IsValidModel(unsigned short usModel); + static bool HasClothesChanged(); + 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); CClientPed* m_pPlayerModel; - SFixedArray m_Clothes; - static SFixedArray m_GlobalClothes; - static bool m_bStaticInit; + 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 bool m_bStaticInit; }; diff --git a/Client/mods/deathmatch/logic/CClientTXD.cpp b/Client/mods/deathmatch/logic/CClientTXD.cpp index 8a37f87157d..b619586c917 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::AddClothingTexture(const std::string& modelName) +{ + if (modelName.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(), modelName.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..7b9be10dbff 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 AddClothingTexture(const 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/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 1e4d645dbe1..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; } @@ -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)->texture; + strOutModel = pPlayerClothing.at(ucIndex)->model; + + 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]->texture.c_str()) == 0) && + (szModel == NULL || strcmp(szModel, pPlayerClothing[ucIter]->model.c_str()) == 0)) { ucTypeReturn = ucType; ucIndexReturn = ucIter; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.BodyClothes.cpp index 9bfabb48f6a..534cc9ed589 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::AddClothingModel(lua_State* luaVM) +{ + std::uint8_t clothingType = 0; + SString texture, model; + CScriptArgReader argStream(luaVM); + argStream.ReadString(texture); + argStream.ReadString(model, ""); + argStream.ReadNumber(clothingType); + + if (!argStream.HasErrors()) + { + if (CClientPlayerClothes::AddClothingModel(texture, model, clothingType)) + { + lua_pushboolean(luaVM, true); + return 1; + } + } + + lua_pushboolean(luaVM, false); + return 1; +} + +int CLuaFunctionDefs::RemoveClothingModel(lua_State* luaVM) +{ + std::uint8_t clothingType = 0; + SString texture, model; + CScriptArgReader argStream(luaVM); + argStream.ReadString(texture); + argStream.ReadString(model, ""); + argStream.ReadNumber(clothingType); + + if (!argStream.HasErrors()) + { + if (CClientPlayerClothes::RemoveClothingModel(texture, model, clothingType)) + { + 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..a6592f9df04 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(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 c2a28809af0..53751c83eff 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}, + {"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 0ad88113b11..9171d156e76 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}, + {"engineAddClothingTXD", ArgumentParser}, {"engineReplaceCOL", EngineReplaceCOL}, {"engineRestoreCOL", EngineRestoreCOL}, {"engineReplaceModel", EngineReplaceModel}, + {"engineAddClothingModel", ArgumentParser}, {"engineRestoreModel", EngineRestoreModel}, {"engineReplaceAnimation", EngineReplaceAnimation}, {"engineRestoreAnimation", EngineRestoreAnimation}, @@ -649,6 +651,17 @@ int CLuaEngineDefs::EngineImportTXD(lua_State* luaVM) return 1; } +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->AddClothingTexture(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::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->AddClothingModel(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..9e762b53d2d 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 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); diff --git a/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp b/Client/multiplayer_sa/CMultiplayerSA_ClothesSpeedUp.cpp index b3ba54d1759..55aba2cf20a 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,75 @@ 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 +// + +static std::uint32_t g_playerImgEntries = 0xBBCDC8; +static std::uint16_t g_playerImgSize = 0x226; + +bool IsPlayerImgDirLoaded() +{ + // When player.img dir is loaded, it looks this this: + // 0x00BC12C0 00bbcdc8 00000226 + DWORD* ptr1 = (DWORD*)0xBC12C0; + + return ptr1[0] == g_playerImgEntries && ptr1[1] == g_playerImgSize; +} + +// Hook info +#define HOOKSIZE_LoadingPlayerImgDir 5 +#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() +{ + // hook from 005A69E3 5 bytes + _asm + { + pushad + call IsPlayerImgDirLoaded + cmp al, 0 + jnz skip + popad + + // Standard code to load img directory + mov eax, g_playerImgEntries + push eax + jmp RETURN_LoadingPlayerImgDirA + + // Skip loading img directory +skip: + popad + jmp RETURN_LoadingPlayerImgDirB + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Setup clothing directory size +// +////////////////////////////////////////////////////////////////////////////////////////// +bool SetClothingDirectorySize(int 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 + + g_playerImgEntries = reinterpret_cast(clothesDirectory); + g_playerImgSize = directorySize; + + return true; +} + ////////////////////////////////////////////////////////////////////////////////////////// // // Setup hooks for ClothesSpeedUp @@ -259,6 +286,8 @@ void _declspec(naked) HOOK_CStreamingLoadRequestedModels() ////////////////////////////////////////////////////////////////////////////////////////// void CMultiplayerSA::InitHooks_ClothesSpeedUp() { + SetClothingDirectorySize(2050); + EZHookInstall(CStreamingLoadRequestedModels); EZHookInstall(LoadingPlayerImgDir); EZHookInstall(CallCStreamingInfoAddToList); diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index 92686978b14..20139ded76a 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* fileData, std::size_t fileSize, const char* fileName) = 0; + virtual bool ClothesRemoveFile(char* fileData) = 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;