#include "component/CCharacterComponent.hpp" #include "component/Texture.hpp" #include "component/Util.hpp" #include "db/Db.hpp" #include "gx/Blp.hpp" #include "gx/Device.hpp" #include "gx/Texture.hpp" #include "model/CM2Model.hpp" #include "object/Types.hpp" #include "util/CStatus.hpp" #include "util/Unimplemented.hpp" #include #include #include uint32_t* CCharacterComponent::s_characterFacialHairStylesList; st_race* CCharacterComponent::s_chrVarArray; uint32_t CCharacterComponent::s_chrVarArrayLength; EGxTexFormat CCharacterComponent::s_gxFormat; ITEM_FUNC CCharacterComponent::s_itemFunc[]; uint32_t CCharacterComponent::s_mipLevels; PREP_FUNC CCharacterComponent::s_prepFunc[]; CompSectionInfo CCharacterComponent::s_sectionInfo[]; MipBits* CCharacterComponent::s_textureBuffer; MipBits* CCharacterComponent::s_textureBufferCompressed; uint32_t CCharacterComponent::s_textureSize; CompSectionInfo CCharacterComponent::s_sectionInfoRaw[] = { { 0, 0, 256, 128 }, // SECTION_ARM_UPPER { 0, 128, 256, 128 }, // SECTION_ARM_LOWER { 0, 256, 256, 64 }, // SECTION_HAND { 256, 0, 256, 128 }, // SECTION_TORSO_UPPER { 256, 128, 256, 64 }, // SECTION_TORSO_LOWER { 256, 192, 256, 128 }, // SECTION_LEG_UPPER { 256, 320, 256, 128 }, // SECTION_LEG_LOWER { 256, 448, 256, 64 }, // SECTION_FOOT { 0, 320, 256, 64 }, // SECTION_HEAD_UPPER { 0, 384, 256, 128 }, // SECTION_HEAD_LOWER }; const char* s_componentSections[] = { "ArmUpperTexture", "ArmLowerTexture", "HandTexture", "TorsoUpperTexture", "TorsoLowerTexture", "LegUpperTexture", "LegLowerTexture", "FootTexture" }; const char* s_fileDecorations[] = { "M", "F", "U" }; /** * Texture priorities for each item slot and component section. Determines order for pasting * textures in RenderPrep functions. Priority start and end for leg component sections is * adjusted in the corresponding RenderPrep functions. */ int32_t s_itemPriority[NUM_ITEM_SLOT][NUM_COMPONENT_SECTIONS] = { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // ITEMSLOT_0 { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // ITEMSLOT_1 { 0, 0, -1, 0, 0, -1, -1, -1, -1, -1 }, // ITEMSLOT_2 { 1, 1, -1, 1, 1, 1, 1, -1, -1, -1 }, // ITEMSLOT_3 { -1, -1, -1, -1, 5, 2, -1, -1, -1, -1 }, // ITEMSLOT_4 { -1, -1, -1, -1, -1, 0, 0, -1, -1, -1 }, // ITEMSLOT_5 { -1, -1, -1, -1, -1, -1, 2, 0, -1, -1 }, // ITEMSLOT_6 { -1, 2, -1, -1, -1, -1, -1, -1, -1, -1 }, // ITEMSLOT_7 { -1, 3, 0, -1, -1, -1, -1, -1, -1, -1 }, // ITEMSLOT_8 { -1, -1, -1, 4, 4, -1, -1, -1, -1, -1 }, // ITEMSLOT_9 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // ITEMSLOT_10 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // ITEMSLOT_11 }; /** * Total item texture priorities for each component section. */ #define SECTION_AU_ITEM_PRIORITIES 2 #define SECTION_AL_ITEM_PRIORITIES 7 #define SECTION_HA_ITEM_PRIORITIES 1 #define SECTION_TU_ITEM_PRIORITIES 5 #define SECTION_TL_ITEM_PRIORITIES 7 #define SECTION_LU_ITEM_PRIORITIES 3 #define SECTION_LL_ITEM_PRIORITIES 6 #define SECTION_FO_ITEM_PRIORITIES 1 #define SECTION_HU_ITEM_PRIORITIES 0 #define SECTION_HL_ITEM_PRIORITIES 0 int32_t s_bInRenderPrep = 0; char s_buffer[STORM_MAX_PATH]; uint32_t* s_componentHeap; char* s_pathEnd; char s_path[STORM_MAX_PATH]; char* s_pathEnd2; char s_path2[STORM_MAX_PATH]; CStatus s_status; #define TEXTURE_INDEX(section, texture) (3 * section + texture) int32_t CCharacterComponent::AddHandItem(CM2Model* model, const ItemDisplayInfoRec* displayRec, INVENTORY_SLOTS invSlot, SHEATHE_TYPE sheatheType, bool sheathed, bool shield, bool a7, int32_t visualID) { if (!model || !displayRec || invSlot > INVSLOT_TABARD) { return -1; } auto itemModelPath = "Item\\ObjectComponents\\Weapon\\"; auto itemTexturePath = "Item\\ObjectComponents\\Weapon\\"; GEOCOMPONENTLINKS itemLink; GEOCOMPONENTLINKS sheatheLink; if (invSlot == INVSLOT_MAINHAND) { itemLink = ATTACH_HANDR; sheatheLink = CCharacterComponent::GetSheatheLink(sheatheType, true); } else if (invSlot == INVSLOT_OFFHAND) { itemLink = ATTACH_HANDL; sheatheLink = CCharacterComponent::GetSheatheLink(sheatheType, false); } else if (invSlot == INVSLOT_RANGED) { if (a7) { itemLink = ATTACH_HANDR; sheatheLink = CCharacterComponent::GetSheatheLink(sheatheType, true); } else { itemLink = ATTACH_HANDL; sheatheLink = CCharacterComponent::GetSheatheLink(sheatheType, false); } } else { return -1; } if (shield) { itemModelPath = "Item\\ObjectComponents\\Shield\\"; itemTexturePath = "Item\\ObjectComponents\\Shield\\"; itemLink = ATTACH_SHIELD; } CCharacterComponent::RemoveLinkpt(model, itemLink); CCharacterComponent::RemoveLinkpt(model, sheatheLink); auto link = sheathed ? sheatheLink : itemLink; if (model->IsLoaded(0, 0) && !model->HasAttachment(link)) { return -1; } SStrPrintf(s_buffer, sizeof(s_buffer), "%s%s", itemModelPath, displayRec->m_modelName[0]); SStrCopy(s_pathEnd, s_buffer); SStrPrintf(s_buffer, sizeof(s_buffer), "%s%s.blp", itemTexturePath, displayRec->m_modelTexture[0]); SStrCopy(s_pathEnd2, s_buffer); if (visualID == 0) { visualID = displayRec->m_itemVisual; } CCharacterComponent::AddLink(model, link, s_path, s_path2, visualID, displayRec); return link; } void CCharacterComponent::AddLink(CM2Model* model, GEOCOMPONENTLINKS link, char const* modelPath, char const* texturePath, int32_t a5, const ItemDisplayInfoRec* displayRec) { // TODO } CCharacterComponent* CCharacterComponent::AllocComponent() { uint32_t memHandle; void* mem; if (!ObjectAlloc(*s_componentHeap, &memHandle, &mem, false)) { return nullptr; } auto component = new (mem) CCharacterComponent(); component->m_memHandle = memHandle; return component; } HTEXTURE CCharacterComponent::CreateTexture(const char* fileName, CStatus* status) { auto texFlags = CGxTexFlags(GxTex_LinearMipNearest, 0, 0, 0, 0, 0, 1); return TextureCreate(fileName, texFlags, status, 0); } GEOCOMPONENTLINKS CCharacterComponent::GetSheatheLink(SHEATHE_TYPE sheatheType, bool a2) { switch (sheatheType) { case SHEATHE_1: return a2 ? ATTACH_SHEATH_MAINHAND : ATTACH_SHEATH_OFFHAND; case SHEATHE_2: return a2 ? ATTACH_LARGEWEAPONLEFT : ATTACH_LARGEWEAPONRIGHT; case SHEATHE_3: return a2 ? ATTACH_HIPWEAPONLEFT : ATTACH_HIPWEAPONRIGHT; case SHEATHE_4: return ATTACH_SHEATH_SHIELD; default: return ATTACH_NONE; } } void CCharacterComponent::Initialize() { // TODO // TODO proper implementation CCharacterComponent::Initialize(GxTex_Rgb565, 9, 0, 0); } void CCharacterComponent::Initialize(EGxTexFormat textureFormat, uint32_t textureLevel, int32_t thread, int32_t compress) { if (!s_componentHeap) { auto heapId = static_cast(STORM_ALLOC(sizeof(uint32_t))); *heapId = ObjectAllocAddHeap(sizeof(CCharacterComponent), 32, "CCharacterComponent", true); s_componentHeap = heapId; } s_pathEnd = s_path; s_pathEnd2 = s_path2; CCharacterComponent::s_prepFunc[SECTION_ARM_UPPER] = &CCharacterComponent::RenderPrepAU; CCharacterComponent::s_prepFunc[SECTION_ARM_LOWER] = &CCharacterComponent::RenderPrepAL; CCharacterComponent::s_prepFunc[SECTION_HAND] = &CCharacterComponent::RenderPrepHA; CCharacterComponent::s_prepFunc[SECTION_TORSO_UPPER] = &CCharacterComponent::RenderPrepTU; CCharacterComponent::s_prepFunc[SECTION_TORSO_LOWER] = &CCharacterComponent::RenderPrepTL; CCharacterComponent::s_prepFunc[SECTION_LEG_UPPER] = &CCharacterComponent::RenderPrepLU; CCharacterComponent::s_prepFunc[SECTION_LEG_LOWER] = &CCharacterComponent::RenderPrepLL; CCharacterComponent::s_prepFunc[SECTION_FOOT] = &CCharacterComponent::RenderPrepFO; CCharacterComponent::s_prepFunc[SECTION_HEAD_UPPER] = &CCharacterComponent::RenderPrepHU; CCharacterComponent::s_prepFunc[SECTION_HEAD_LOWER] = &CCharacterComponent::RenderPrepHL; CCharacterComponent::s_itemFunc[SECTION_ARM_UPPER] = &CCharacterComponent::UpdateItemAU; CCharacterComponent::s_itemFunc[SECTION_ARM_LOWER] = &CCharacterComponent::UpdateItemAL; CCharacterComponent::s_itemFunc[SECTION_HAND] = &CCharacterComponent::UpdateItemHA; CCharacterComponent::s_itemFunc[SECTION_TORSO_UPPER] = &CCharacterComponent::UpdateItemTU; CCharacterComponent::s_itemFunc[SECTION_TORSO_LOWER] = &CCharacterComponent::UpdateItemTL; CCharacterComponent::s_itemFunc[SECTION_LEG_UPPER] = &CCharacterComponent::UpdateItemLU; CCharacterComponent::s_itemFunc[SECTION_LEG_LOWER] = &CCharacterComponent::UpdateItemLL; CCharacterComponent::s_itemFunc[SECTION_FOOT] = &CCharacterComponent::UpdateItemFO; CCharacterComponent::s_itemFunc[SECTION_HEAD_UPPER] = &CCharacterComponent::UpdateItemHU; CCharacterComponent::s_itemFunc[SECTION_HEAD_LOWER] = &CCharacterComponent::UpdateItemHL; // Clamp mip levels between 6 and 9 uint32_t mipLevels = std::min(std::max(textureLevel, 6u), 9u); // Cap mip levels to 8 if compression isn't enabled if (!compress && mipLevels > 8) { mipLevels = 8; } CCharacterComponent::s_mipLevels = mipLevels; CCharacterComponent::s_textureSize = 1 << mipLevels; // Scale section info to match mip levels for (int32_t i = 0; i < NUM_COMPONENT_SECTIONS; i++) { auto& info = CCharacterComponent::s_sectionInfo[i]; auto& infoRaw = CCharacterComponent::s_sectionInfoRaw[i]; info.pos.x = infoRaw.pos.x >> (9 - mipLevels); info.pos.y = infoRaw.pos.y >> (9 - mipLevels); info.size.x = infoRaw.size.x >> (9 - mipLevels); info.size.y = infoRaw.size.y >> (9 - mipLevels); } // TODO CCharacterComponent::s_gxFormat = textureFormat; // TODO CCharacterComponent::InitDbData(); // TODO CCharacterComponent::s_textureBuffer = TextureAllocMippedImg( PIXEL_ARGB8888, CCharacterComponent::s_textureSize, CCharacterComponent::s_textureSize ); // TODO } void CCharacterComponent::InitDbData() { uint32_t varArrayLength = (g_chrRacesDB.m_maxID + 1) * UNITSEX_NUM_SEXES; CCharacterComponent::s_chrVarArrayLength = varArrayLength; BuildComponentArray(varArrayLength, &CCharacterComponent::s_chrVarArray); CountFacialFeatures(varArrayLength, &CCharacterComponent::s_characterFacialHairStylesList); } void CCharacterComponent::Paste(void* srcTexture, MipBits* dstMips, const C2iVector& dstPos, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel) { uint32_t dstWidth = CCharacterComponent::s_textureSize * 4; auto srcPal = TextureCacheGetPal(srcTexture); if (!srcPal) { // TODO fill in with crappy green (0x00FF00FF) return; } switch (srcInfo.alphaSize) { case 0: CCharacterComponent::PasteOpaque( srcTexture, srcPal, dstMips, dstPos, dstWidth, srcPos, srcSize, srcInfo, srcMipLevel, -srcMipLevel ); break; case 1: CCharacterComponent::PasteTransparent1Bit( srcTexture, srcPal, dstMips, dstPos, dstWidth, srcPos, srcSize, srcInfo, srcMipLevel, -srcMipLevel ); break; case 4: CCharacterComponent::PasteTransparent4Bit( srcTexture, srcPal, dstMips, dstPos, dstWidth, srcPos, srcSize, srcInfo, srcMipLevel, -srcMipLevel ); break; case 8: CCharacterComponent::PasteTransparent8Bit( srcTexture, srcPal, dstMips, dstPos, dstWidth, srcPos, srcSize, srcInfo, srcMipLevel, -srcMipLevel ); break; default: // Do nothing break; } } void CCharacterComponent::PasteFromSkin(COMPONENT_SECTIONS section, void* srcTexture, MipBits* dstMips) { if (!TextureCacheHasMips(srcTexture)) { return; } auto& sectionInfo = CCharacterComponent::s_sectionInfo[section]; TCTEXTUREINFO srcInfo; TextureCacheGetInfo(srcTexture, srcInfo, 1); // Skin is always opaque srcInfo.alphaSize = 0; if (srcInfo.width >= CCharacterComponent::s_textureSize || srcInfo.height >= CCharacterComponent::s_textureSize ) { // Calculate source mip level appropriate for CCharacterComponent::s_textureSize int32_t srcMipLevel = 0; int32_t srcWidth = srcInfo.width; while (srcWidth > CCharacterComponent::s_textureSize) { srcWidth /= 2; srcMipLevel++; } CCharacterComponent::Paste(srcTexture, dstMips, sectionInfo.pos, sectionInfo.pos, sectionInfo.size, srcInfo, srcMipLevel); } else { CCharacterComponent::PasteScale(srcTexture, dstMips, sectionInfo.pos, sectionInfo.pos, sectionInfo.size, srcInfo); } } void CCharacterComponent::PasteOpaque(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) { // Prepare first mip level C2iVector curSrcSize = srcSize; C2iVector curSrcPos = srcPos; C2iVector curDstPos = dstPos; uint32_t curSrcWidth = srcInfo.width >> srcMipLevel; uint32_t curDstWidth = dstWidth; // Paste texture for each mip level for (int32_t curMipLevel = srcMipLevel; curMipLevel < srcInfo.mipCount; curMipLevel++) { auto srcMip = TextureCacheGetMip(srcTexture, curMipLevel); auto srcRow = &srcMip[(curSrcPos.y * curSrcWidth) + curSrcPos.x]; auto dstMip = reinterpret_cast(dstMips[curMipLevel + dstMipLevelOfs].mip[0]); auto dstRow = &dstMip[(curDstPos.y * curDstWidth) + (curDstPos.x * 4)]; // Calculate the end of the source region auto srcEnd = srcRow + curSrcWidth * curSrcSize.y; // Copy each row while (srcRow < srcEnd) { auto dst = reinterpret_cast(dstRow); for (uint32_t x = 0; x < curSrcSize.x; x++) { // Get palette entry auto& src = srcPal[srcRow[x]]; // Copy color from palette entry dst->b = src.b; dst->g = src.g; dst->r = src.r; dst->a = 0xFF; dst++; } // Move to next row srcRow += curSrcWidth; dstRow += curDstWidth; } // Prepare next mip level curSrcSize.x = std::max(1, curSrcSize.x / 2); curSrcSize.y = std::max(1, curSrcSize.y / 2); curSrcPos.x /= 2; curSrcPos.y /= 2; curDstPos.x /= 2; curDstPos.y /= 2; curDstWidth /= 2; curSrcWidth /= 2; } } void CCharacterComponent::PasteScale(void* srcTexture, MipBits* dstMips, const C2iVector& a3, const C2iVector& a4, const C2iVector& a5, TCTEXTUREINFO& srcInfo) { WHOA_UNIMPLEMENTED(); } void CCharacterComponent::PasteToSection(COMPONENT_SECTIONS section, void* srcTexture, MipBits* dstMips) { if (!TextureCacheHasMips(srcTexture)) { return; } auto& sectionInfo = CCharacterComponent::s_sectionInfo[section]; TCTEXTUREINFO srcInfo; TextureCacheGetInfo(srcTexture, srcInfo, 1); if (srcInfo.width >= sectionInfo.size.x || srcInfo.height >= sectionInfo.size.y) { // Calculate source mip level appropriate for section size int32_t srcMipLevel = 0; int32_t srcWidth = srcInfo.width; while (srcWidth > sectionInfo.size.x) { srcWidth /= 2; srcMipLevel++; } C2iVector srcPos = { 0, 0 }; CCharacterComponent::Paste(srcTexture, dstMips, sectionInfo.pos, srcPos, sectionInfo.size, srcInfo, srcMipLevel); } else { C2iVector srcPos = { 0, 0 }; CCharacterComponent::PasteScale(srcTexture, dstMips, sectionInfo.pos, srcPos, sectionInfo.size, srcInfo); } } void CCharacterComponent::PasteTransparent1Bit(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) { // Prepare first mip level C2iVector curSrcSize = srcSize; C2iVector curSrcPos = srcPos; C2iVector curDstPos = dstPos; uint32_t curSrcWidth = srcInfo.width >> srcMipLevel; uint32_t curDstWidth = dstWidth; uint32_t curSrcHeight = srcInfo.height >> srcMipLevel; // Paste texture for each mip level for (int32_t curMipLevel = srcMipLevel; curMipLevel < srcInfo.mipCount; curMipLevel++) { auto srcMip = TextureCacheGetMip(srcTexture, curMipLevel); auto srcRow = &srcMip[(curSrcPos.y * curSrcWidth) + curSrcPos.x]; auto srcAlphaRow = &srcMip[(curSrcWidth * curSrcHeight) + ((curSrcPos.y * curSrcWidth) + curSrcPos.x) / 8]; auto dstMip = reinterpret_cast(dstMips[curMipLevel + dstMipLevelOfs].mip[0]); auto dstRow = &dstMip[(curDstPos.y * curDstWidth) + (curDstPos.x * 4)]; // Calculate the end of the source region auto srcEnd = srcRow + curSrcWidth * curSrcSize.y; // Copy each row while (srcRow < srcEnd) { auto dst = reinterpret_cast(dstRow); for (uint32_t x = 0; x < curSrcSize.x; x++) { // Get palette entry auto& src = srcPal[srcRow[x]]; // Get alpha uint8_t packedAlpha = srcAlphaRow[x / 8]; uint8_t transparent = (packedAlpha & 1 << x % 8) == 0; // Blend src color with dst dst->b = transparent ? dst->b : src.b; dst->g = transparent ? dst->g : src.g; dst->r = transparent ? dst->r : src.r; dst->a = 0xFF; dst++; } // Move to next row srcRow += curSrcWidth; srcAlphaRow += curSrcWidth / 8; dstRow += curDstWidth; } // Prepare next mip level curSrcSize.x = std::max(1, curSrcSize.x / 2); curSrcSize.y = std::max(1, curSrcSize.y / 2); curSrcPos.x /= 2; curSrcPos.y /= 2; curDstPos.x /= 2; curDstPos.y /= 2; curDstWidth /= 2; curSrcWidth /= 2; curSrcHeight /= 2; } } void CCharacterComponent::PasteTransparent4Bit(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) { WHOA_UNIMPLEMENTED(); } void CCharacterComponent::PasteTransparent8Bit(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) { // Prepare first mip level C2iVector curSrcSize = srcSize; C2iVector curSrcPos = srcPos; C2iVector curDstPos = dstPos; uint32_t curSrcWidth = srcInfo.width >> srcMipLevel; uint32_t curDstWidth = dstWidth; uint32_t curSrcHeight = srcInfo.height >> srcMipLevel; // Paste texture for each mip level for (int32_t curMipLevel = srcMipLevel; curMipLevel < srcInfo.mipCount; curMipLevel++) { auto srcMip = TextureCacheGetMip(srcTexture, curMipLevel); auto srcRow = &srcMip[(curSrcPos.y * curSrcWidth) + curSrcPos.x]; auto srcAlphaRow = &srcMip[(curSrcWidth * curSrcHeight) + (curSrcPos.y * curSrcWidth) + curSrcPos.x]; auto dstMip = reinterpret_cast(dstMips[curMipLevel + dstMipLevelOfs].mip[0]); auto dstRow = &dstMip[(curDstPos.y * curDstWidth) + (curDstPos.x * 4)]; // Calculate the end of the source region auto srcEnd = srcRow + curSrcWidth * curSrcSize.y; // Copy each row while (srcRow < srcEnd) { auto dst = reinterpret_cast(dstRow); for (uint32_t x = 0; x < curSrcSize.x; x++) { // Get palette entry auto& src = srcPal[srcRow[x]]; // Get alpha uint8_t alpha = srcAlphaRow[x]; uint8_t invAlpha = 0xFF - alpha; // Blend src color with dst dst->b = (src.b * alpha + dst->b * invAlpha) / 0xFF; dst->g = (src.g * alpha + dst->g * invAlpha) / 0xFF; dst->r = (src.r * alpha + dst->r * invAlpha) / 0xFF; dst->a = 0xFF; dst++; } // Move to next row srcRow += curSrcWidth; srcAlphaRow += curSrcWidth; dstRow += curDstWidth; } // Prepare next mip level curSrcSize.x = std::max(1, curSrcSize.x / 2); curSrcSize.y = std::max(1, curSrcSize.y / 2); curSrcPos.x /= 2; curSrcPos.y /= 2; curDstPos.x /= 2; curDstPos.y /= 2; curDstWidth /= 2; curSrcWidth /= 2; curSrcHeight /= 2; } } void CCharacterComponent::RemoveLinkpt(CM2Model* model, GEOCOMPONENTLINKS link) { // TODO } void CCharacterComponent::UpdateBaseTexture(EGxTexCommand cmd, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) { auto component = static_cast(userArg); switch(cmd) { case GxTex_Lock: { if (!s_bInRenderPrep) { component->RenderPrepAll(); } break; } case GxTex_Latch: { if (component->m_textureFormat == GxTex_Dxt1) { // TODO STORM_ASSERT(false); } else { texelStrideInBytes = 4 * width; } // TODO conditional check on some member of component auto buffer = component->m_textureFormat == GxTex_Dxt1 ? CCharacterComponent::s_textureBufferCompressed : CCharacterComponent::s_textureBuffer; texels = buffer[mipLevel].mip[0]; break; } // TODO unknown command case 3: { // TODO break; } } } void CCharacterComponent::AddItem(ITEM_SLOT itemSlot, int32_t displayID, int32_t a4) { if (displayID <= 0) { return; } auto displayRec = g_itemDisplayInfoDB.GetRecord(displayID); if (!displayRec) { return; } this->AddItem(itemSlot, displayRec, a4); } void CCharacterComponent::AddItem(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, int32_t a4) { this->m_flags |= 0x4; this->m_items[itemSlot] = displayRec->m_ID; // Helm if (itemSlot == ITEMSLOT_0) { // TODO handle helm return; } // Shoulders if (itemSlot == ITEMSLOT_1) { // TODO handle shoulders return; } // Cape if (itemSlot == ITEMSLOT_10) { // TODO handle cape return; } // Unk if (itemSlot == ITEMSLOT_11) { // TODO handle unknown item return; } if (itemSlot == ITEMSLOT_3) { // TODO flag manipulation } bool isNPC = this->m_data.flags & 0x1; if (isNPC) { return; } // Items don't manipulate head component sections for (int32_t section = SECTION_ARM_UPPER; section <= SECTION_FOOT; section++) { if (*displayRec->m_texture[section] && s_itemPriority[itemSlot][section] != -1) { (this->*CCharacterComponent::s_itemFunc[section])(itemSlot, displayRec, true); } } } void CCharacterComponent::AddItemBySlot(INVENTORY_SLOTS invSlot, int32_t displayID, int32_t a4) { if (invSlot > EQUIPPED_LAST) { return; } STORM_ASSERT(displayID > 0); switch (invSlot) { case INVSLOT_HEAD: this->AddItem(ITEMSLOT_0, displayID, a4); break; case INVSLOT_SHOULDER: this->AddItem(ITEMSLOT_1, displayID, a4); break; case INVSLOT_BODY: this->AddItem(ITEMSLOT_2, displayID, a4); break; case INVSLOT_CHEST: this->AddItem(ITEMSLOT_3, displayID, a4); break; case INVSLOT_WAIST: this->AddItem(ITEMSLOT_4, displayID, a4); break; case INVSLOT_LEGS: this->AddItem(ITEMSLOT_5, displayID, a4); break; case INVSLOT_FEET: this->AddItem(ITEMSLOT_6, displayID, a4); break; case INVSLOT_WRIST: this->AddItem(ITEMSLOT_7, displayID, a4); break; case INVSLOT_HAND: this->AddItem(ITEMSLOT_8, displayID, a4); break; case INVSLOT_BACK: this->AddItem(ITEMSLOT_10, displayID, a4); break; case INVSLOT_TABARD: this->AddItem(ITEMSLOT_9, displayID, a4); break; default: break; } } void CCharacterComponent::ClearItemDisplay(COMPONENT_SECTIONS section, int32_t priority) { if (priority == -1) { return; } if (this->m_itemDisplays[section].texture[priority]) { TextureCacheDestroyTexture(this->m_itemDisplays[section].texture[priority]); this->m_itemDisplays[section].texture[priority] = nullptr; } this->m_itemDisplays[section].displayID[priority] = 0; this->m_itemDisplays[section].priorityDirty &= ~(1 << priority); } void CCharacterComponent::CreateBaseTexture() { auto dataFormat = this->m_textureFormat == GxTex_Dxt1 ? GxTex_Dxt1 : GxTex_Argb8888; CGxTexFlags flags = CGxTexFlags(GxTex_LinearMipLinear, 0, 0, 0, 0, 0, 1); if (GxDevApi() == GxApi_GLL) { flags.m_bit15 = 1; } auto baseTexture = TextureCreate( CCharacterComponent::s_textureSize, CCharacterComponent::s_textureSize, this->m_textureFormat, dataFormat, flags, this, &CCharacterComponent::UpdateBaseTexture, "CharacterBaseSkin", 1 ); this->m_baseTexture = baseTexture; this->m_data.model->ReplaceTexture(1, this->m_baseTexture); } void* CCharacterComponent::CreateTexture(const ItemDisplayInfoRec* displayRec, int32_t section) { SStrPrintf( s_path, STORM_MAX_PATH, "Item\\TextureComponents\\%s\\%s_%s.blp", s_componentSections[section], displayRec->m_texture[section], s_fileDecorations[2] ); // Substitute gender suffix if (!SFile::FileExists(s_path)) { s_path[SStrLen(s_path) - 5] = *s_fileDecorations[this->m_data.sexID]; } return TextureCacheCreateTexture(s_path); } void CCharacterComponent::GeosRenderPrep() { // Check for eye glow bool eyeGlow = false; if (this->m_data.classID == 6) { eyeGlow = true; } else { auto sectionsRec = this->GetSectionsRecord(VARIATION_FACE, this->m_data.faceID, this->m_data.skinColorID, nullptr); if (sectionsRec && sectionsRec->m_flags & 0x4) { eyeGlow = true; } } // Hide all geosets (0 - 2000) this->m_data.model->SetGeometryVisible(0, 2000, 0); // Show base skin geoset (0) this->m_data.model->SetGeometryVisible(0, 0, 1); // Show all enabled geosets for (int32_t geoset = 0; geoset < NUM_GEOSET; geoset++) { if (geoset == GEOSET_EYE_EFFECTS && eyeGlow) { this->m_data.model->SetGeometryVisible(1703, 1703, 1); } else { this->m_data.model->SetGeometryVisible(this->m_data.geosets[geoset], this->m_data.geosets[geoset], 1); } } // TODO this->m_flags &= ~0x4; } CharSectionsRec* CCharacterComponent::GetSectionsRecord(COMPONENT_VARIATIONS sectionIndex, int32_t variationIndex, int32_t colorIndex, bool* found) { return ComponentGetSectionsRecord( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, sectionIndex, variationIndex, colorIndex, found ); } int32_t CCharacterComponent::Init(ComponentData* data, const char* a3) { // If existing model is present, release it before copying in new data if (this->m_data.model) { this->m_data.model->Release(); } this->m_data = *data; // TODO this->SetSkinColor(this->m_data.skinColorID, false, true, a3); this->SetHairStyle(this->m_data.hairStyleID, a3); this->SetBeardStyle(this->m_data.facialHairStyleID, false, a3); return 1; } int32_t CCharacterComponent::ItemsLoaded(int32_t a2) { if (a2) { TCTEXTUREINFO info; for (int32_t section = 0; section < NUM_COMPONENT_SECTIONS; section++) { if (!(this->m_sectionDirty & (1 << section))) { continue; } auto& itemDisplay = this->m_itemDisplays[section]; for (int32_t priority = 0; priority < 7; priority++) { if (!itemDisplay.displayID[priority] && !itemDisplay.texture[priority]) { continue; } if (itemDisplay.displayID[priority] && !itemDisplay.texture[priority]) { auto displayRec = g_itemDisplayInfoDB.GetRecord(itemDisplay.displayID[priority]); itemDisplay.texture[priority] = this->CreateTexture(displayRec, section); } // Trigger texture load TextureCacheGetInfo(itemDisplay.texture[priority], info, 1); if (TextureCacheHasMips(itemDisplay.texture[priority])) { itemDisplay.priorityDirty |= (1 << priority); } else { itemDisplay.priorityDirty &= ~(1 << priority); } } } return 1; } // TODO return 1; } void CCharacterComponent::LoadBaseVariation(COMPONENT_VARIATIONS sectionIndex, int32_t textureIndex, int32_t variationIndex, int32_t colorIndex, COMPONENT_SECTIONS section, const char* a7) { int32_t index = TEXTURE_INDEX(sectionIndex, textureIndex); if (this->m_texture[index]) { TextureCacheDestroyTexture(this->m_texture[index]); } auto valid = ComponentValidateBase( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, sectionIndex, variationIndex, colorIndex ); if (!valid) { return; } auto sectionsRec = this->GetSectionsRecord(sectionIndex, variationIndex, colorIndex, nullptr); auto textureName = sectionsRec->m_textureName[textureIndex]; if (*textureName) { SStrCopy(s_pathEnd, textureName); this->m_texture[index] = TextureCacheCreateTexture(s_path); } this->m_sectionDirty |= 1 << section; // TODO this->m_flags &= ~0x8; } void CCharacterComponent::PrepSections() { // TODO } int32_t CCharacterComponent::RenderPrep(int32_t a2) { if (this->m_data.flags & 0x1) { if (this->m_flags & 0x4) { this->GeosRenderPrep(); } return 1; } // TODO if (a2) { // TODO this->VariationsLoaded(1); this->ItemsLoaded(1); this->m_flags |= 8u; this->RenderPrepSections(); // TODO this->Sub79F820(); return 1; } // TODO return 1; } void CCharacterComponent::RenderPrepAL() { auto& itemDisplay = this->m_itemDisplays[SECTION_ARM_LOWER]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_ARM_LOWER, skinTexture, CCharacterComponent::s_textureBuffer); // Item textures for (int32_t priority = 0; priority < SECTION_AL_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_ARM_LOWER, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepAU() { auto& itemDisplay = this->m_itemDisplays[SECTION_ARM_UPPER]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_ARM_UPPER, skinTexture, CCharacterComponent::s_textureBuffer); // Item textures for (int32_t priority = 0; priority < SECTION_AU_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_ARM_UPPER, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepFO() { auto& itemDisplay = this->m_itemDisplays[SECTION_FOOT]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_FOOT, skinTexture, CCharacterComponent::s_textureBuffer); // Item textures for (int32_t priority = 0; priority < SECTION_FO_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_FOOT, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepHA() { auto& itemDisplay = this->m_itemDisplays[SECTION_HAND]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_HAND, skinTexture, CCharacterComponent::s_textureBuffer); // Item textures for (int32_t priority = 0; priority < SECTION_HA_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_HAND, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepHL() { auto sectionsRec = this->GetSectionsRecord(VARIATION_SKIN, 0, this->m_data.skinColorID, nullptr); // Skin texture if (sectionsRec && sectionsRec->m_flags & 0x8) { auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_HEAD_LOWER, skinTexture, CCharacterComponent::s_textureBuffer); } // Face texture auto faceLowerTexture = this->m_texture[TEXTURE_INDEX(VARIATION_FACE, 0)]; if (faceLowerTexture) { CCharacterComponent::PasteToSection(SECTION_HEAD_LOWER, faceLowerTexture, CCharacterComponent::s_textureBuffer); } // Hair textures auto facialHairLowerTexture = this->m_texture[TEXTURE_INDEX(VARIATION_FACIAL_HAIR, 0)]; if (facialHairLowerTexture) { CCharacterComponent::PasteToSection(SECTION_HEAD_LOWER, facialHairLowerTexture, CCharacterComponent::s_textureBuffer); } auto hairLowerTexture = this->m_texture[TEXTURE_INDEX(VARIATION_HAIR, 1)]; if (hairLowerTexture) { CCharacterComponent::PasteToSection(SECTION_HEAD_LOWER, hairLowerTexture, CCharacterComponent::s_textureBuffer); } } void CCharacterComponent::RenderPrepHU() { auto sectionsRec = this->GetSectionsRecord(VARIATION_SKIN, 0, this->m_data.skinColorID, nullptr); // Skin texture if (sectionsRec && sectionsRec->m_flags & 0x8) { auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_HEAD_UPPER, skinTexture, CCharacterComponent::s_textureBuffer); } // Face texture auto faceUpperTexture = this->m_texture[TEXTURE_INDEX(VARIATION_FACE, 1)]; if (faceUpperTexture) { CCharacterComponent::PasteToSection(SECTION_HEAD_UPPER, faceUpperTexture, CCharacterComponent::s_textureBuffer); } // Hair textures auto facialHairUpperTexture = this->m_texture[TEXTURE_INDEX(VARIATION_FACIAL_HAIR, 1)]; if (facialHairUpperTexture) { CCharacterComponent::PasteToSection(SECTION_HEAD_UPPER, facialHairUpperTexture, CCharacterComponent::s_textureBuffer); } auto hairUpperTexture = this->m_texture[TEXTURE_INDEX(VARIATION_HAIR, 2)]; if (hairUpperTexture) { CCharacterComponent::PasteToSection(SECTION_HEAD_UPPER, hairUpperTexture, CCharacterComponent::s_textureBuffer); } } void CCharacterComponent::RenderPrepLL() { auto& itemDisplay = this->m_itemDisplays[SECTION_LEG_LOWER]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_LEG_LOWER, skinTexture, CCharacterComponent::s_textureBuffer); // Item textures auto firstPriority = 0; auto itemPriorities = SECTION_LL_ITEM_PRIORITIES; if (this->m_flags & 0x20) { firstPriority = 1; itemPriorities = SECTION_LL_ITEM_PRIORITIES - 2; } for (int32_t priority = firstPriority; priority < itemPriorities; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_LEG_LOWER, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepLU() { auto& itemDisplay = this->m_itemDisplays[SECTION_LEG_UPPER]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_LEG_UPPER, skinTexture, CCharacterComponent::s_textureBuffer); // Underwear texture if ((this->m_flags & 0x20) || !(itemDisplay.priorityDirty & ((1 << 0) | (1 << 1)))) { auto bottomUnderwearTexture = this->m_texture[TEXTURE_INDEX(VARIATION_UNDERWEAR, 0)]; if (bottomUnderwearTexture) { CCharacterComponent::PasteToSection(SECTION_LEG_UPPER, bottomUnderwearTexture, CCharacterComponent::s_textureBuffer); } } // Item textures auto firstPriority = (this->m_flags & 0x20) ? 1 : 0; for (int32_t priority = firstPriority; priority < SECTION_LU_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_LEG_UPPER, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepTL() { auto& itemDisplay = this->m_itemDisplays[SECTION_TORSO_LOWER]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_TORSO_LOWER, skinTexture, CCharacterComponent::s_textureBuffer); // Item textures for (int32_t priority = 0; priority < SECTION_TL_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_TORSO_LOWER, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepTU() { auto& itemDisplay = this->m_itemDisplays[SECTION_TORSO_UPPER]; // Skin texture auto skinTexture = this->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_TORSO_UPPER, skinTexture, CCharacterComponent::s_textureBuffer); // Underwear texture if (!(itemDisplay.priorityDirty & ((1 << 0) | (1 << 1) | (1 << 2)))) { auto topUnderwearTexture = this->m_texture[TEXTURE_INDEX(VARIATION_UNDERWEAR, 1)]; if (topUnderwearTexture) { CCharacterComponent::PasteToSection(SECTION_TORSO_UPPER, topUnderwearTexture, CCharacterComponent::s_textureBuffer); } } // Item textures for (int32_t priority = 0; priority < SECTION_TU_ITEM_PRIORITIES; priority++) { if (itemDisplay.priorityDirty & (1 << priority)) { CCharacterComponent::PasteToSection(SECTION_TORSO_UPPER, itemDisplay.texture[priority], CCharacterComponent::s_textureBuffer); } } } void CCharacterComponent::RenderPrepAll() { // TODO this->m_flags &= ~0x8; this->VariationsLoaded(1); this->ItemsLoaded(1); for (uint32_t i = 0; i < NUM_COMPONENT_SECTIONS; i++) { (this->*CCharacterComponent::s_prepFunc[i])(); } // TODO this->m_flags &= ~0x1; this->m_sectionDirty = 0; // TODO } void CCharacterComponent::RenderPrepSections() { s_bInRenderPrep = 1; if (this->m_flags & 0x4) { this->GeosRenderPrep(); } if (!this->m_baseTexture) { this->CreateBaseTexture(); } this->PrepSections(); this->m_flags &= ~0x1; this->m_sectionDirty = 0; // TODO s_bInRenderPrep = 0; } void CCharacterComponent::ReplaceExtraSkinTexture(const char* a2) { if (!ComponentValidateBase( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_SKIN, 0, this->m_data.hairColorID )) { return; } auto sectionsRec = this->GetSectionsRecord(VARIATION_SKIN, 0, this->m_data.skinColorID, nullptr); if (!*sectionsRec->m_textureName[1]) { return; } SStrCopy(s_pathEnd, sectionsRec->m_textureName[1]); auto extraSkinTexture = CCharacterComponent::CreateTexture(s_path, &s_status); if (extraSkinTexture) { this->m_data.model->ReplaceTexture(8, extraSkinTexture); HandleClose(extraSkinTexture); } } void CCharacterComponent::ReplaceHairTexture(int32_t hairStyleID, const char* a3) { if (!ComponentValidateBase( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_HAIR, hairStyleID, this->m_data.hairColorID )) { return; } auto sectionsRec = this->GetSectionsRecord(VARIATION_HAIR, hairStyleID, this->m_data.hairColorID, nullptr); if (!*sectionsRec->m_textureName[0]) { return; } SStrCopy(s_pathEnd, sectionsRec->m_textureName[0]); auto hairTexture = CCharacterComponent::CreateTexture(s_path, &s_status); if (hairTexture) { this->m_data.model->ReplaceTexture(6, hairTexture); HandleClose(hairTexture); } } void CCharacterComponent::SetBeardStyle(int32_t facialHairStyleID, bool a3, const char* a4) { auto listIndex = this->m_data.raceID * 2 + this->m_data.sexID; if (facialHairStyleID < 0 || facialHairStyleID > CCharacterComponent::s_characterFacialHairStylesList[listIndex]) { return; } this->m_data.facialHairStyleID = facialHairStyleID; auto facialHairStyleRec = ComponentGetFacialHairStyleRecord(&this->m_data); if (facialHairStyleRec) { this->m_data.geosets[1] = 100 + facialHairStyleRec->m_geoset[0]; this->m_data.geosets[2] = 200 + facialHairStyleRec->m_geoset[2]; this->m_data.geosets[3] = 300 + facialHairStyleRec->m_geoset[1]; this->m_data.geosets[16] = 1600 + facialHairStyleRec->m_geoset[3]; this->m_data.geosets[17] = 1700 + facialHairStyleRec->m_geoset[4]; this->m_flags |= 0x4; if ( (facialHairStyleRec->m_geoset[0] || facialHairStyleRec->m_geoset[1] || facialHairStyleRec->m_geoset[2]) && (!this->m_texture[TEXTURE_INDEX(VARIATION_HAIR, 1)] || !this->m_texture[TEXTURE_INDEX(VARIATION_HAIR, 2)]) ) { this->ReplaceHairTexture(1, a4); } } bool isNPC = this->m_data.flags & 0x1; if (!isNPC) { if (a3) { this->LoadBaseVariation(VARIATION_FACIAL_HAIR, 0, this->m_data.facialHairStyleID, this->m_data.hairColorID, SECTION_HEAD_LOWER, a4); this->LoadBaseVariation(VARIATION_FACIAL_HAIR, 1, this->m_data.facialHairStyleID, this->m_data.hairColorID, SECTION_HEAD_UPPER, a4); } this->m_sectionDirty |= (1 << SECTION_HEAD_LOWER) | (1 << SECTION_HEAD_UPPER); // TODO component request logic this->m_flags &= ~0x8; } } void CCharacterComponent::SetFace(int32_t faceID, bool a3, const char* a4) { bool isNPC = this->m_data.flags & 0x1; if (isNPC) { return; } auto skinSectionsRec = this->GetSectionsRecord(VARIATION_SKIN, 0, this->m_data.skinColorID, nullptr); if (skinSectionsRec && skinSectionsRec->m_flags & 0x8) { return; } if (!ComponentValidateBase(CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_FACE, faceID, this->m_data.skinColorID)) { return; } this->m_data.faceID = faceID; this->LoadBaseVariation(VARIATION_FACE, 0, this->m_data.faceID, this->m_data.skinColorID, SECTION_HEAD_LOWER, a4); this->LoadBaseVariation(VARIATION_FACE, 1, this->m_data.faceID, this->m_data.skinColorID, SECTION_HEAD_UPPER, a4); if (a3) { this->LoadBaseVariation(VARIATION_HAIR, 1, this->m_data.hairStyleID, this->m_data.hairColorID, SECTION_HEAD_LOWER, a4); this->LoadBaseVariation(VARIATION_HAIR, 2, this->m_data.hairStyleID, this->m_data.hairColorID, SECTION_HEAD_UPPER, a4); } this->m_flags |= 0x4; this->m_sectionDirty |= (1 << SECTION_HEAD_LOWER) | (1 << SECTION_HEAD_UPPER); // TODO this->m_flags &= ~0x8; } void CCharacterComponent::SetHairColor(int32_t hairColorID, bool a3, const char* a4) { if (!ComponentValidateBase( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_HAIR, this->m_data.hairStyleID, hairColorID )) { return; } this->m_data.hairColorID = hairColorID; auto raceRec = g_chrRacesDB.GetRecord(this->m_data.raceID); if (this->m_data.sexID == UNITSEX_MALE && this->m_data.hairStyleID == 0 && raceRec->m_flags & 0x8) { this->ReplaceHairTexture(1, a4); } this->ReplaceHairTexture(this->m_data.hairStyleID, a4); bool isNPC = this->m_data.flags & 0x1; if (!isNPC) { if (a3) { this->LoadBaseVariation(VARIATION_HAIR, 1, this->m_data.hairStyleID, this->m_data.hairColorID, SECTION_HEAD_LOWER, a4); this->LoadBaseVariation(VARIATION_HAIR, 2, this->m_data.hairStyleID, this->m_data.hairColorID, SECTION_HEAD_UPPER, a4); } this->LoadBaseVariation(VARIATION_FACIAL_HAIR, 0, this->m_data.facialHairStyleID, this->m_data.hairColorID, SECTION_HEAD_LOWER, a4); this->LoadBaseVariation(VARIATION_FACIAL_HAIR, 1, this->m_data.facialHairStyleID, this->m_data.hairColorID, SECTION_HEAD_UPPER, a4); } } void CCharacterComponent::SetHairStyle(int32_t hairStyleID, const char* a3) { if (!ComponentValidateBase( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_HAIR, hairStyleID, this->m_data.hairColorID )) { return; } this->m_data.hairStyleID = hairStyleID; auto hairGeoset = ComponentGetHairGeoset(&this->m_data); this->m_data.geosets[0] = hairGeoset; bool isNPC = this->m_data.flags & 0x1; if (!isNPC) { this->LoadBaseVariation(VARIATION_HAIR, 1, this->m_data.hairStyleID, this->m_data.hairColorID, SECTION_HEAD_LOWER, a3); this->LoadBaseVariation(VARIATION_HAIR, 2, this->m_data.hairStyleID, this->m_data.hairColorID, SECTION_HEAD_UPPER, a3); } this->SetHairColor(this->m_data.hairColorID, false, a3); this->m_flags |= 0x4; this->m_sectionDirty |= (1 << SECTION_HEAD_LOWER) | (1 << SECTION_HEAD_UPPER); // TODO component request logic this->m_flags &= ~0x8; } void CCharacterComponent::SetSkinColor(int32_t skinColorID, bool a3, bool a4, const char* a5) { bool isNPC = this->m_data.flags & 0x1; this->m_data.skinColorID = skinColorID; if (isNPC) { return; } if (!ComponentValidateBase(CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_SKIN, 0, skinColorID)) { return; } auto numColors = ComponentGetNumColors( CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_SKIN, 0 ); auto sectionsRec = this->GetSectionsRecord(VARIATION_SKIN, 0, skinColorID, nullptr); if (skinColorID < numColors && sectionsRec && !(sectionsRec->m_flags & 0x8)) { auto underwearRec = this->GetSectionsRecord(VARIATION_UNDERWEAR, 0, skinColorID, nullptr); auto t0 = TEXTURE_INDEX(VARIATION_UNDERWEAR, 0); auto t1 = TEXTURE_INDEX(VARIATION_UNDERWEAR, 1); if (this->m_texture[t0]) { TextureCacheDestroyTexture(this->m_texture[t0]); this->m_texture[t0] = nullptr; } if (this->m_texture[t1]) { TextureCacheDestroyTexture(this->m_texture[t1]); this->m_texture[t1] = nullptr; } if (*underwearRec->m_textureName[0]) { SStrCopy(s_pathEnd, underwearRec->m_textureName[0]); this->m_texture[t0] = TextureCacheCreateTexture(s_path); STORM_ASSERT(this->m_texture[t0]); } if (*underwearRec->m_textureName[1]) { SStrCopy(s_pathEnd, underwearRec->m_textureName[1]); this->m_texture[t1] = TextureCacheCreateTexture(s_path); STORM_ASSERT(this->m_texture[t1]); } } this->ReplaceExtraSkinTexture(a5); this->LoadBaseVariation( VARIATION_SKIN, 0, 0, this->m_data.skinColorID, SECTION_TORSO_UPPER, a5 ); this->SetFace(this->m_data.faceID, a3, a5); if (a4) { this->m_flags |= 0x1; // TODO component request logic this->m_flags &= ~0x8; } this->m_sectionDirty = -1; // TODO component request logic this->m_flags &= ~0x8; } void CCharacterComponent::UpdateItem(ITEM_SLOT itemSlot, COMPONENT_SECTIONS section, const ItemDisplayInfoRec* displayRec, bool update) { auto priority = s_itemPriority[itemSlot][section]; if (section == SECTION_ARM_LOWER) { if (displayRec && displayRec->m_geosetGroup[0]) { if (itemSlot == ITEMSLOT_3) { priority = 5; } else if (itemSlot == ITEMSLOT_8) { priority = 6; } } } if (section == SECTION_LEG_LOWER) { if (itemSlot == ITEMSLOT_3 && displayRec && displayRec->m_geosetGroup[2]) { priority = 4; this->m_flags |= 0x4; } if (itemSlot == ITEMSLOT_5 && displayRec && displayRec->m_geosetGroup[2]) { // TODO this->m_flags |= 0x4; } if (itemSlot == ITEMSLOT_6 && displayRec && displayRec->m_geosetGroup[0]) { priority = 3; this->m_flags |= 0x4; } } if (section == SECTION_FOOT) { if (this->m_flags & 0x10) { update = false; } } if (update) { if (!this->UpdateItemDisplay(section, displayRec, priority)) { return; } } else { this->ClearItemDisplay(section, priority); if (section == SECTION_LEG_LOWER) { // TODO } } if (priority != -1) { this->m_sectionDirty |= (1 << section); // TODO component request logic this->m_flags &= ~0x8; } } void CCharacterComponent::UpdateItemAL(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_ARM_LOWER, displayRec, update); } void CCharacterComponent::UpdateItemAU(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_ARM_UPPER, displayRec, update); } void CCharacterComponent::UpdateItemFO(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_FOOT, displayRec, update); } void CCharacterComponent::UpdateItemHA(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_HAND, displayRec, update); } void CCharacterComponent::UpdateItemHL(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { // No item displays for head sections } void CCharacterComponent::UpdateItemHU(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { // No item displays for head sections } void CCharacterComponent::UpdateItemLL(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_LEG_LOWER, displayRec, update); } void CCharacterComponent::UpdateItemLU(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_LEG_UPPER, displayRec, update); } void CCharacterComponent::UpdateItemTL(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_TORSO_LOWER, displayRec, update); } void CCharacterComponent::UpdateItemTU(ITEM_SLOT itemSlot, const ItemDisplayInfoRec* displayRec, bool update) { this->UpdateItem(itemSlot, SECTION_TORSO_UPPER, displayRec, update); } int32_t CCharacterComponent::UpdateItemDisplay(COMPONENT_SECTIONS section, const ItemDisplayInfoRec* newDisplayRec, int32_t priority) { bool isNPC = this->m_data.flags & 0x1; if (isNPC) { return 0; } if (this->m_itemDisplays[section].displayID[priority]) { auto curDisplayID = this->m_itemDisplays[section].displayID[priority]; auto curDisplayRec = g_itemDisplayInfoDB.GetRecord(curDisplayID); // New display is same as old display if (curDisplayRec && curDisplayRec->m_texture[section] == newDisplayRec->m_texture[section]) { return 0; } } this->m_itemDisplays[section].priorityDirty &= ~(1 << priority); if (this->m_itemDisplays[section].texture[priority]) { TextureCacheDestroyTexture(this->m_itemDisplays[section].texture[priority]); this->m_itemDisplays[section].texture[priority] = nullptr; } this->m_itemDisplays[section].displayID[priority] = newDisplayRec->m_ID; return 1; } int32_t CCharacterComponent::VariationsLoaded(int32_t a2) { TCTEXTUREINFO info; for (int32_t v = 0; v < NUM_COMPONENT_VARIATIONS; v++) { for (int32_t t = 0; t < 3; t++) { auto texture = this->m_texture[TEXTURE_INDEX(v, t)]; if (texture && !TextureCacheGetInfo(texture, info, a2)) { return 0; } } } this->m_flags &= ~0x2; return 1; }