mirror of
https://github.com/whoahq/whoa.git
synced 2026-02-01 00:02:45 +03:00
1802 lines
56 KiB
C++
1802 lines
56 KiB
C++
#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 "model/CM2Scene.hpp"
|
|
#include "object/Types.hpp"
|
|
#include "util/CStatus.hpp"
|
|
#include "util/Unimplemented.hpp"
|
|
#include <common/ObjectAlloc.hpp>
|
|
#include <storm/Memory.hpp>
|
|
#include <storm/String.hpp>
|
|
|
|
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* parent, GEOCOMPONENTLINKS link, char const* modelPath, char const* texturePath, int32_t visualID, const ItemDisplayInfoRec* displayRec) {
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
|
|
// Create item model
|
|
|
|
auto model = parent->m_scene->CreateModel(modelPath, 0);
|
|
|
|
if (!model) {
|
|
return;
|
|
}
|
|
|
|
// Create item texture
|
|
|
|
auto textureFlags = CGxTexFlags(GxTex_LinearMipNearest, 0, 0, 0, 0, 0, 1);
|
|
auto texture = TextureCreate(texturePath, textureFlags, &s_status, 0);
|
|
|
|
if (!texture) {
|
|
model->Release();
|
|
return;
|
|
}
|
|
|
|
// Replace item texture
|
|
|
|
model->ReplaceTexture(2, texture);
|
|
HandleClose(texture);
|
|
|
|
// Add item visual
|
|
|
|
if (visualID > 0) {
|
|
// TODO CCharacterComponent::ComponentUtilAddItemVisual(model, visualID);
|
|
}
|
|
|
|
// Attach item to parent
|
|
|
|
parent->DetachAllChildrenById(link);
|
|
model->AttachToParent(parent, link, nullptr, 0);
|
|
|
|
if (link == ATTACH_HANDR) {
|
|
CCharacterComponent::ComponentCloseFingers(parent, HAND_RIGHT);
|
|
} else if (link == ATTACH_HANDL) {
|
|
CCharacterComponent::ComponentCloseFingers(parent, HAND_LEFT);
|
|
}
|
|
|
|
// Replace item particle color
|
|
|
|
// TODO ReplaceParticleColor(displayRec->m_particleColorID, model);
|
|
|
|
model->Release();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void CCharacterComponent::ComponentCloseFingers(CM2Model* model, COMP_HAND_SLOT handSlot) {
|
|
uint32_t firstBone;
|
|
uint32_t lastBone;
|
|
|
|
if (handSlot == HAND_LEFT) {
|
|
firstBone = 13;
|
|
lastBone = 17;
|
|
} else {
|
|
firstBone = 8;
|
|
lastBone = 12;
|
|
}
|
|
|
|
for (uint32_t boneId = firstBone; boneId <= lastBone; boneId++) {
|
|
model->SetBoneSequence(boneId, 15, 0xFFFFFFFF, 0, 1.0f, 0, 1);
|
|
}
|
|
}
|
|
|
|
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<uint32_t*>(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<uint8_t*>(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<C4Pixel*>(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<uint8_t*>(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<C4Pixel*>(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<uint8_t*>(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<C4Pixel*>(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<CCharacterComponent*>(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;
|
|
}
|