|
|
|
/*==LICENSE==*
|
|
|
|
|
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools
|
|
|
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|
|
|
or by snail mail at:
|
|
|
|
Cyan Worlds, Inc.
|
|
|
|
14617 N Newport Hwy
|
|
|
|
Mead, WA 99021
|
|
|
|
|
|
|
|
*==LICENSE==*/
|
|
|
|
#include "HeadSpin.h"
|
|
|
|
#include "hsTemplates.h"
|
|
|
|
#include "hsStream.h"
|
|
|
|
#include "hsResMgr.h"
|
|
|
|
#include "plgDispatch.h"
|
|
|
|
#include "pnKeyedObject/plKey.h"
|
|
|
|
#include "pnKeyedObject/plFixedKey.h"
|
|
|
|
#include "pnSceneObject/plSceneObject.h"
|
|
|
|
#include "plDrawable/plInstanceDrawInterface.h"
|
|
|
|
#include "pnMessage/plRefMsg.h"
|
|
|
|
#include "pnMessage/plPipeResMakeMsg.h"
|
|
|
|
#include "pfMessage/plClothingMsg.h"
|
|
|
|
#include "plMessage/plRenderMsg.h"
|
|
|
|
#include "plGImage/plMipmap.h"
|
|
|
|
#include "hsGDeviceRef.h"
|
|
|
|
#include "plPipeline/plRenderTarget.h"
|
|
|
|
#include "plPipeline.h"
|
|
|
|
#include "plClothingLayout.h"
|
|
|
|
#include "plAvatarClothing.h"
|
|
|
|
#include "plClothingSDLModifier.h"
|
|
|
|
#include "plGImage/hsCodecManager.h"
|
|
|
|
#include "plAvatar/plArmatureMod.h"
|
|
|
|
#include "plAvatar/plAvatarMgr.h"
|
|
|
|
#include "plAvatar/plArmatureEffects.h"
|
|
|
|
#include "pnNetCommon/plNetApp.h"
|
|
|
|
#include "pnMessage/plSDLModifierMsg.h"
|
|
|
|
#include "plMessage/plReplaceGeometryMsg.h"
|
|
|
|
#include "plDrawable/plDrawableSpans.h"
|
|
|
|
#include "plDrawable/plSharedMesh.h"
|
|
|
|
#include "plDrawable/plMorphSequence.h"
|
|
|
|
#include "plDrawable/plMorphSequenceSDLMod.h"
|
|
|
|
#include "plDrawable/plSpaceTree.h"
|
|
|
|
#include "plSurface/hsGMaterial.h"
|
|
|
|
#include "plSurface/plLayer.h"
|
|
|
|
#include "pnEncryption/plRandom.h"
|
|
|
|
#include "plSDL/plSDL.h"
|
|
|
|
#include "plVault/plVault.h"
|
|
|
|
#include "plResMgr/plKeyFinder.h"
|
|
|
|
#include "plNetClientComm/plNetClientComm.h"
|
|
|
|
|
|
|
|
|
|
|
|
plClothingItem::plClothingItem() : fGroup(0), fTileset(0), fType(0), fSortOrder(0),
|
|
|
|
fThumbnail(nil), fAccessory(nil)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
fTextures.Reset();
|
|
|
|
fElementNames.Reset();
|
|
|
|
fElements.Reset();
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
{
|
|
|
|
fDefaultTint1[i] = fDefaultTint2[i] = 255;
|
|
|
|
}
|
|
|
|
for (i = 0; i < kMaxNumLODLevels; i++)
|
|
|
|
fMeshes[i] = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingItem::~plClothingItem()
|
|
|
|
{
|
|
|
|
while (fTextures.GetCount() > 0)
|
|
|
|
delete [] fTextures.Pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingItem::CanWearWith(plClothingItem *item)
|
|
|
|
{
|
|
|
|
// For now, you can only wear one shirt, one pair of pants, etc.
|
|
|
|
// Except accessories, of which you're allowed one per tileset.
|
|
|
|
return ((item->fType != fType) ||
|
|
|
|
(item->fType == plClothingMgr::kTypeAccessory &&
|
|
|
|
fType == plClothingMgr::kTypeAccessory));
|
|
|
|
//&& item->fTileset != fTileset));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingItem::WearBefore(plClothingItem *item)
|
|
|
|
{
|
|
|
|
// Accessories come last
|
|
|
|
// Face comes first
|
|
|
|
// Other stuff is arbitrary.
|
|
|
|
|
|
|
|
int myPri;
|
|
|
|
if (fType == plClothingMgr::kTypeFace)
|
|
|
|
myPri = 0;
|
|
|
|
else
|
|
|
|
myPri = (fType == plClothingMgr::kTypeAccessory ? 2 : 1);
|
|
|
|
|
|
|
|
int otherPri;
|
|
|
|
if (item->fType == plClothingMgr::kTypeFace)
|
|
|
|
otherPri = 0;
|
|
|
|
else
|
|
|
|
otherPri = (item->fType == plClothingMgr::kTypeAccessory ? 2 : 1);
|
|
|
|
|
|
|
|
return myPri < otherPri;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingItem::HasBaseAlpha()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fElements.GetCount(); i++)
|
|
|
|
{
|
|
|
|
plMipmap *tex = fTextures[i][plClothingElement::kLayerBase];
|
|
|
|
// if (tex && (tex->GetFlags() & (plMipmap::kAlphaBitFlag | plMipmap::kAlphaChannelFlag)))
|
|
|
|
if (tex && (tex->GetFlags() & plMipmap::kAlphaChannelFlag))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingItem::HasSameMeshes(plClothingItem *other)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < kMaxNumLODLevels; i++)
|
|
|
|
if (fMeshes[i] != other->fMeshes[i])
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingItem::Read(hsStream *s, hsResMgr *mgr)
|
|
|
|
{
|
|
|
|
hsKeyedObject::Read(s, mgr);
|
|
|
|
|
|
|
|
fName = s->ReadSafeString();
|
|
|
|
fGroup = s->ReadByte();
|
|
|
|
fType = s->ReadByte();
|
|
|
|
fTileset = s->ReadByte();
|
|
|
|
fSortOrder = s->ReadByte();
|
|
|
|
|
|
|
|
fCustomText = s->ReadSafeString();
|
|
|
|
fDescription = s->ReadSafeString();
|
|
|
|
if (s->ReadBool())
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // thumbnail
|
|
|
|
|
|
|
|
int tileCount = s->ReadLE32();
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < tileCount; i++)
|
|
|
|
{
|
|
|
|
fElementNames.Append(s->ReadSafeString());
|
|
|
|
|
|
|
|
int layerCount = s->ReadByte();
|
|
|
|
for (j = 0; j < layerCount; j++)
|
|
|
|
{
|
|
|
|
int layer = s->ReadByte();
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plElementRefMsg(GetKey(), plRefMsg::kOnCreate, i, -1, plString::Null, layer), plRefFlags::kActiveRef); // texture
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < kMaxNumLODLevels; i++)
|
|
|
|
{
|
|
|
|
if (s->ReadBool())
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, i, -1), plRefFlags::kActiveRef); // shared mesh
|
|
|
|
}
|
|
|
|
|
|
|
|
fElements.SetCountAndZero(tileCount);
|
|
|
|
if (plClothingMgr::GetClothingMgr())
|
|
|
|
{
|
|
|
|
plGenRefMsg *msg = new plGenRefMsg(plClothingMgr::GetClothingMgr()->GetKey(), plRefMsg::kOnCreate, -1, -1);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(GetKey(), msg, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // forced accessory
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
{
|
|
|
|
fDefaultTint1[i] = s->ReadByte();
|
|
|
|
fDefaultTint2[i] = s->ReadByte();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingItem::Write(hsStream *s, hsResMgr *mgr)
|
|
|
|
{
|
|
|
|
hsKeyedObject::Write(s, mgr);
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
s->WriteSafeString(fName);
|
|
|
|
s->WriteByte(fGroup);
|
|
|
|
s->WriteByte(fType);
|
|
|
|
s->WriteByte(fTileset);
|
|
|
|
s->WriteByte(fSortOrder);
|
|
|
|
|
|
|
|
s->WriteSafeString(fCustomText);
|
|
|
|
s->WriteSafeString(fDescription);
|
|
|
|
s->WriteBool(fThumbnail != nil);
|
|
|
|
if (fThumbnail != nil)
|
|
|
|
mgr->WriteKey(s, fThumbnail->GetKey());
|
|
|
|
|
|
|
|
uint32_t texSkip = 0;
|
|
|
|
for (i = 0; i < fTextures.GetCount(); i++)
|
|
|
|
if (fTextures[i] == nil)
|
|
|
|
texSkip++;
|
|
|
|
|
|
|
|
s->WriteLE32(fTextures.GetCount() - texSkip);
|
|
|
|
for (i = 0; i < fTextures.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (fTextures[i] == nil)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
s->WriteSafeString(fElementNames.Get(i));
|
|
|
|
|
|
|
|
int layerCount = 0;
|
|
|
|
for (j = 0; j < plClothingElement::kLayerMax; j++)
|
|
|
|
{
|
|
|
|
// Run through once to get the count of valid layers
|
|
|
|
if (fTextures[i][j] != nil)
|
|
|
|
layerCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->WriteByte(layerCount);
|
|
|
|
for (j = 0; j < plClothingElement::kLayerMax; j++)
|
|
|
|
{
|
|
|
|
if (fTextures[i][j] != nil)
|
|
|
|
{
|
|
|
|
s->WriteByte(j);
|
|
|
|
mgr->WriteKey(s, fTextures[i][j]->GetKey());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < kMaxNumLODLevels; i++)
|
|
|
|
{
|
|
|
|
s->WriteBool(fMeshes[i] != nil);
|
|
|
|
if (fMeshes[i] != nil)
|
|
|
|
mgr->WriteKey(s, fMeshes[i]->GetKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
// EXPORT ONLY
|
|
|
|
plKey accessoryKey = nil;
|
|
|
|
if (!fAccessoryName.IsEmpty())
|
|
|
|
{
|
|
|
|
plString strBuf = plFormat("CItm_{}", fAccessoryName);
|
|
|
|
accessoryKey = plKeyFinder::Instance().StupidSearch("GlobalClothing", "", plClothingItem::Index(), strBuf);
|
|
|
|
if (accessoryKey == nil)
|
|
|
|
{
|
|
|
|
strBuf = plFormat("Couldn't find accessory \"{}\". It won't show at runtime.", fAccessoryName);
|
|
|
|
hsMessageBox(strBuf.c_str(), GetKeyName().c_str(), hsMessageBoxNormal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mgr->WriteKey(s, accessoryKey);
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
{
|
|
|
|
s->WriteByte(fDefaultTint1[i]);
|
|
|
|
s->WriteByte(fDefaultTint2[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingItem::MsgReceive(plMessage* msg)
|
|
|
|
{
|
|
|
|
plElementRefMsg *eMsg = plElementRefMsg::ConvertNoRef(msg);
|
|
|
|
if (eMsg)
|
|
|
|
{
|
|
|
|
plMipmap *tex = plMipmap::ConvertNoRef(eMsg->GetRef());
|
|
|
|
if (tex)
|
|
|
|
{
|
|
|
|
if( eMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
{
|
|
|
|
if (fTextures.GetCount() <= eMsg->fWhich)
|
|
|
|
fTextures.ExpandAndZero(eMsg->fWhich + 1);
|
|
|
|
if (fElementNames.GetCount() <= eMsg->fWhich)
|
|
|
|
fElementNames.Expand(eMsg->fWhich + 1);
|
|
|
|
|
|
|
|
if (fElementNames.Get(eMsg->fWhich).IsEmpty())
|
|
|
|
fElementNames.Set(eMsg->fWhich, eMsg->fElementName);
|
|
|
|
if (fTextures.Get(eMsg->fWhich) == nil)
|
|
|
|
{
|
|
|
|
plMipmap **layers = new plMipmap*[plClothingElement::kLayerMax];
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < plClothingElement::kLayerMax; i++)
|
|
|
|
layers[i] = nil;
|
|
|
|
fTextures.Set(eMsg->fWhich, layers);
|
|
|
|
}
|
|
|
|
|
|
|
|
fTextures.Get(eMsg->fWhich)[eMsg->fLayer] = tex;
|
|
|
|
}
|
|
|
|
else if( eMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
{
|
|
|
|
fTextures.Get(eMsg->fWhich)[eMsg->fLayer] = nil;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg);
|
|
|
|
if (refMsg)
|
|
|
|
{
|
|
|
|
plSharedMesh *mesh = plSharedMesh::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (mesh)
|
|
|
|
{
|
|
|
|
if (refMsg->fWhich >= 0 && refMsg->fWhich < kMaxNumLODLevels)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fMeshes[refMsg->fWhich] = mesh;
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
fMeshes[refMsg->fWhich] = nil;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
plMipmap *thumbnail = plMipmap::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (thumbnail)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fThumbnail = thumbnail;
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
fThumbnail = nil;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingItem *accessory = plClothingItem::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (accessory)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fAccessory = accessory;
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
fAccessory = nil;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hsKeyedObject::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool plClosetItem::IsMatch(plClosetItem *other)
|
|
|
|
{
|
|
|
|
return (fItem == other->fItem && fOptions.IsMatch(&other->fOptions));
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plClothingBase::plClothingBase() : fBaseTexture(nil) {}
|
|
|
|
|
|
|
|
void plClothingBase::Read(hsStream* s, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
hsKeyedObject::Read(s, mgr);
|
|
|
|
|
|
|
|
fName = s->ReadSafeString();
|
|
|
|
if (s->ReadBool())
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef);
|
|
|
|
fLayoutName = s->ReadSafeString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingBase::Write(hsStream* s, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
hsKeyedObject::Write(s, mgr);
|
|
|
|
|
|
|
|
s->WriteSafeString(fName);
|
|
|
|
s->WriteBool(fBaseTexture != nil);
|
|
|
|
if (fBaseTexture != nil)
|
|
|
|
mgr->WriteKey(s, fBaseTexture->GetKey());
|
|
|
|
s->WriteSafeString(fLayoutName);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingBase::MsgReceive(plMessage* msg)
|
|
|
|
{
|
|
|
|
plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg);
|
|
|
|
if (refMsg)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
{
|
|
|
|
fBaseTexture = plMipmap::ConvertNoRef(refMsg->GetRef());
|
|
|
|
}
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
{
|
|
|
|
fBaseTexture = nil;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hsKeyedObject::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plClothingOutfit::plClothingOutfit() :
|
|
|
|
fTargetLayer(nullptr), fBase(nullptr), fGroup(0), fAvatar(nullptr), fSynchClients(false), fMaterial(nullptr),
|
|
|
|
fVaultSaveEnabled(true), fMorphsInitDone(false)
|
|
|
|
{
|
|
|
|
fSkinTint.Set(1.f, 0.84, 0.71, 1.f);
|
|
|
|
fItems.Reset();
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < plClothingLayout::kMaxTileset; i++)
|
|
|
|
fDirtyItems.SetBit(i);
|
|
|
|
|
|
|
|
for (i = 0; i < plClothingElement::kLayerSkinLast - plClothingElement::kLayerSkinFirst; i++)
|
|
|
|
fSkinBlends[i] = 0.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingOutfit::~plClothingOutfit()
|
|
|
|
{
|
|
|
|
fItems.Reset();
|
|
|
|
while (fOptions.GetCount() > 0)
|
|
|
|
delete fOptions.Pop();
|
|
|
|
plgDispatch::Dispatch()->UnRegisterForExactType(plPreResourceMsg::Index(), GetKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::AddItem(plClothingItem *item, bool update /* = true */, bool broadcast /* = true */, bool netForce /* = false */)
|
|
|
|
{
|
|
|
|
if (fItems.Find(item) != fItems.kMissingIndex)
|
|
|
|
return;
|
|
|
|
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kAddItem);
|
|
|
|
msg->fItemKey = item->GetKey();
|
|
|
|
if (broadcast)
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
if ( netForce )
|
|
|
|
{
|
|
|
|
// also doesn't make much sense to not net propagate netForced messages
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
msg->SetBCastFlag(plMessage::kNetForce);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (update)
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
|
|
|
|
plgDispatch::MsgSend(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::ForceUpdate(bool retry)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
if (retry)
|
|
|
|
msg->AddCommand(plClothingMsg::kRetry); // force a resend
|
|
|
|
msg->Send(GetKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::RemoveItem(plClothingItem *item, bool update /* = true */, bool netForce /* = false */)
|
|
|
|
{
|
|
|
|
if (fItems.Find(item) == fItems.kMissingIndex)
|
|
|
|
return;
|
|
|
|
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kRemoveItem);
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
if ( netForce )
|
|
|
|
msg->SetBCastFlag(plMessage::kNetForce);
|
|
|
|
msg->fItemKey = item->GetKey();
|
|
|
|
if (update)
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
|
|
|
|
plgDispatch::MsgSend(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::TintItem(plClothingItem *item, float red, float green, float blue,
|
|
|
|
bool update /* = true */, bool broadcast /* = true */, bool netForce /* = false */,
|
|
|
|
bool retry /* = true */, uint8_t layer /* = kLayerTint1 */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kTintItem);
|
|
|
|
msg->fItemKey = item->GetKey();
|
|
|
|
msg->fColor.Set(red, green, blue, 1.f);
|
|
|
|
msg->fLayer = layer;
|
|
|
|
if (broadcast)
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
if (netForce)
|
|
|
|
{
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
msg->SetBCastFlag(plMessage::kNetForce);
|
|
|
|
}
|
|
|
|
if (update)
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
if (retry)
|
|
|
|
msg->AddCommand(plClothingMsg::kRetry);
|
|
|
|
plgDispatch::MsgSend(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::TintSkin(float red, float green, float blue,
|
|
|
|
bool update /* = true */, bool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kTintSkin);
|
|
|
|
msg->fColor.Set(red, green, blue, 1.f);
|
|
|
|
if (broadcast)
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
if (update)
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
|
|
|
|
plgDispatch::MsgSend(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::MorphItem(plClothingItem *item, uint8_t layer, uint8_t delta, float weight,
|
|
|
|
bool retry /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kMorphItem);
|
|
|
|
msg->fItemKey = item->GetKey();
|
|
|
|
msg->fLayer = layer;
|
|
|
|
msg->fDelta = delta;
|
|
|
|
msg->fWeight = weight;
|
|
|
|
if (retry)
|
|
|
|
msg->AddCommand(plClothingMsg::kRetry);
|
|
|
|
plgDispatch::MsgSend(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::SetAge(float age, bool update /* = true */, bool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
SetSkinBlend(age, plClothingElement::kLayerSkinBlend1, update, broadcast);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::SetSkinBlend(float blend, uint8_t layer, bool update /* = true */, bool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kBlendSkin);
|
|
|
|
msg->fLayer = layer;
|
|
|
|
msg->fColor.Set(1.f, 1.f, 1.f, blend);
|
|
|
|
if (broadcast)
|
|
|
|
msg->SetBCastFlag(plMessage::kNetPropagate);
|
|
|
|
if (update)
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
|
|
|
|
plgDispatch::MsgSend(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
float plClothingOutfit::GetSkinBlend(uint8_t layer)
|
|
|
|
{
|
|
|
|
if (layer >= plClothingElement::kLayerSkinBlend1 && layer <= plClothingElement::kLayerSkinLast)
|
|
|
|
return fSkinBlends[layer - plClothingElement::kLayerSkinBlend1];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::IAddItem(plClothingItem *item)
|
|
|
|
{
|
|
|
|
if (item->fGroup != fGroup)
|
|
|
|
{
|
|
|
|
// Trying to wear clothing from the wrong group, remove the ref.
|
|
|
|
GetKey()->Release(item->GetKey());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fItems.Find(item) == fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
// Remove any other item we have that can't be worn with this
|
|
|
|
int i;
|
|
|
|
for (i = fItems.GetCount() - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if (!item->CanWearWith(fItems[i]))
|
|
|
|
{
|
|
|
|
plClothingItem *goner = fItems[i];
|
|
|
|
if (goner->fAccessory)
|
|
|
|
{
|
|
|
|
GetKey()->Release(goner->fAccessory->GetKey());
|
|
|
|
IRemoveItem(goner->fAccessory);
|
|
|
|
}
|
|
|
|
GetKey()->Release(goner->GetKey());
|
|
|
|
IRemoveItem(goner); // Can't wait for the ref message to process
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (item->WearBefore(fItems[i]))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fItems.Insert(i, item);
|
|
|
|
plClothingItemOptions *op = new plClothingItemOptions;
|
|
|
|
fOptions.Insert(i, op);
|
|
|
|
IInstanceSharedMeshes(item);
|
|
|
|
fDirtyItems.SetBit(item->fTileset);
|
|
|
|
|
|
|
|
// A bit of hackage for bare feet sound effects.
|
|
|
|
if (item->fType == plClothingMgr::kTypeLeftFoot)
|
|
|
|
{
|
|
|
|
plArmatureEffectsMgr *mgr = fAvatar->GetArmatureEffects();
|
|
|
|
plArmatureEffectFootSound *soundEffect = nil;
|
|
|
|
int num = mgr->GetNumEffects();
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num; i++)
|
|
|
|
{
|
|
|
|
soundEffect = plArmatureEffectFootSound::ConvertNoRef(mgr->GetEffect(i));
|
|
|
|
if (soundEffect)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundEffect)
|
|
|
|
{
|
|
|
|
if (item->fName == "03_MLFoot04_01" || item->fName == "03_FLFoot04_01")
|
|
|
|
soundEffect->SetFootType(plArmatureEffectFootSound::kFootTypeBare);
|
|
|
|
else
|
|
|
|
soundEffect->SetFootType(plArmatureEffectFootSound::kFootTypeShoe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::IRemoveItem(plClothingItem *item)
|
|
|
|
{
|
|
|
|
// We may just be removing the ref...
|
|
|
|
uint32_t index = fItems.Find(item);
|
|
|
|
if (index != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
fItems.Remove(index);
|
|
|
|
delete fOptions.Get(index);
|
|
|
|
fOptions.Remove(index);
|
|
|
|
IRemoveSharedMeshes(item);
|
|
|
|
fDirtyItems.SetBit(item->fTileset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::ITintItem(plClothingItem *item, hsColorRGBA color, uint8_t layer)
|
|
|
|
{
|
|
|
|
uint32_t index = fItems.Find(item);
|
|
|
|
if (index != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
if (layer == plClothingElement::kLayerTint1)
|
|
|
|
fOptions[index]->fTint1 = color;
|
|
|
|
if (layer == plClothingElement::kLayerTint2)
|
|
|
|
fOptions[index]->fTint2 = color;
|
|
|
|
fDirtyItems.SetBit(item->fTileset);
|
|
|
|
|
|
|
|
if (fItems[index]->fAccessory)
|
|
|
|
{
|
|
|
|
plClothingItem *acc = fItems[index]->fAccessory;
|
|
|
|
uint32_t accIndex = fItems.Find(acc);
|
|
|
|
if (accIndex != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
if (layer == plClothingElement::kLayerTint1)
|
|
|
|
fOptions[accIndex]->fTint1 = color;
|
|
|
|
if (layer == plClothingElement::kLayerTint2)
|
|
|
|
fOptions[accIndex]->fTint2 = color;
|
|
|
|
fDirtyItems.SetBit(acc->fTileset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsColorRGBA plClothingOutfit::GetItemTint(plClothingItem *item, uint8_t layer /* = kLayerTint1 */) const
|
|
|
|
{
|
|
|
|
if (layer >= plClothingElement::kLayerSkinFirst &&
|
|
|
|
layer <= plClothingElement::kLayerSkinLast)
|
|
|
|
return fSkinTint;
|
|
|
|
|
|
|
|
uint32_t index = fItems.Find(item);
|
|
|
|
if (index != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
if (layer == plClothingElement::kLayerTint1)
|
|
|
|
return fOptions[index]->fTint1;
|
|
|
|
if (layer == plClothingElement::kLayerTint2)
|
|
|
|
return fOptions[index]->fTint2;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsColorRGBA color;
|
|
|
|
color.Set(1.f, 1.f, 1.f, 1.f);
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::IMorphItem(plClothingItem *item, uint8_t layer, uint8_t delta, float weight)
|
|
|
|
{
|
|
|
|
uint32_t index = fItems.Find(item);
|
|
|
|
if (fAvatar && index != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
for (uint8_t i = 0; i < fAvatar->GetNumLOD(); i++)
|
|
|
|
{
|
|
|
|
if (item->fMeshes[i]->fMorphSet == nil)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const plSceneObject *so = fAvatar->GetClothingSO(i);
|
|
|
|
if (!so)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
plMorphSequence *seq = const_cast<plMorphSequence*>(plMorphSequence::ConvertNoRef(so->GetModifierByType(plMorphSequence::Index())));
|
|
|
|
plKey meshKey = item->fMeshes[i]->GetKey();
|
|
|
|
|
|
|
|
// Lower LOD objects don't have all the morphs, so check if this one is in range.
|
|
|
|
if (seq && layer < seq->GetNumLayers(meshKey) && delta < seq->GetNumDeltas(layer, meshKey))
|
|
|
|
seq->SetWeight(layer, delta, weight, meshKey);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::Read(hsStream* s, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
plSynchedObject::Read(s, mgr);
|
|
|
|
|
|
|
|
fGroup = s->ReadByte();
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // plClothingBase
|
|
|
|
|
|
|
|
if (fGroup != plClothingMgr::kClothingBaseNoOptions)
|
|
|
|
{
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // target layer
|
|
|
|
mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // material
|
|
|
|
}
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plPreResourceMsg::Index(), GetKey());
|
|
|
|
//ReadItems(s, mgr, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::Write(hsStream* s, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
plSynchedObject::Write(s, mgr);
|
|
|
|
|
|
|
|
s->WriteByte(fGroup);
|
|
|
|
mgr->WriteKey(s, fBase->GetKey());
|
|
|
|
|
|
|
|
if (fGroup != plClothingMgr::kClothingBaseNoOptions)
|
|
|
|
{
|
|
|
|
mgr->WriteKey(s, fTargetLayer->GetKey());
|
|
|
|
mgr->WriteKey(s, fMaterial->GetKey());
|
|
|
|
}
|
|
|
|
//WriteItems(s, mgr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::StripAccessories()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = fItems.GetCount() - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if (fItems[i]->fType == plClothingMgr::kTypeAccessory)
|
|
|
|
{
|
|
|
|
GetKey()->Release(fItems[i]->GetKey());
|
|
|
|
IRemoveItem(fItems[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::IHandleMorphSDR(plStateDataRecord *sdr)
|
|
|
|
{
|
|
|
|
plSimpleStateVariable *lodVar = sdr->FindVar(plMorphSequenceSDLMod::kStrTargetID);
|
|
|
|
if (!lodVar)
|
|
|
|
return;
|
|
|
|
|
|
|
|
uint8_t lod;
|
|
|
|
lodVar->Get(&lod);
|
|
|
|
|
|
|
|
const plSceneObject *so = fAvatar->GetClothingSO(lod);
|
|
|
|
if (!so)
|
|
|
|
return;
|
|
|
|
|
|
|
|
plMorphSequenceSDLMod *morph = const_cast<plMorphSequenceSDLMod*>(plMorphSequenceSDLMod::ConvertNoRef(so->GetModifierByType(plMorphSequenceSDLMod::Index())));
|
|
|
|
if (morph)
|
|
|
|
morph->SetCurrentStateFrom(sdr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::ReadClothing()
|
|
|
|
{
|
|
|
|
// Have we set a clothing file? If that's the case, load from there.
|
|
|
|
if (fClothingFile.IsValid())
|
|
|
|
return IReadFromFile(fClothingFile);
|
|
|
|
else
|
|
|
|
return IReadFromVault();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::IReadFromVault()
|
|
|
|
{
|
|
|
|
SetupMorphSDL();
|
|
|
|
|
|
|
|
WearDefaultClothing();
|
|
|
|
|
|
|
|
hsRef<RelVaultNode> rvn = VaultGetAvatarOutfitFolder();
|
|
|
|
if (!rvn)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
RelVaultNode::RefList nodes;
|
|
|
|
rvn->GetChildNodes(plVault::kNodeType_SDL, 1, &nodes);
|
|
|
|
|
|
|
|
for (const hsRef<RelVaultNode> &node : nodes) {
|
|
|
|
VaultSDLNode sdl(node);
|
|
|
|
if (sdl.GetSDLDataLength()) {
|
|
|
|
hsRAMStream ram;
|
|
|
|
ram.Write(sdl.GetSDLDataLength(), sdl.GetSDLData());
|
|
|
|
ram.Rewind();
|
|
|
|
|
|
|
|
plString sdlRecName;
|
|
|
|
int sdlRecVersion;
|
|
|
|
plStateDataRecord::ReadStreamHeader(&ram, &sdlRecName, &sdlRecVersion);
|
|
|
|
plStateDescriptor * desc = plSDLMgr::GetInstance()->FindDescriptor(sdlRecName, sdlRecVersion);
|
|
|
|
if (desc) {
|
|
|
|
plStateDataRecord * sdlDataRec = new plStateDataRecord(desc);
|
|
|
|
if (sdlDataRec->Read(&ram, 0)) {
|
|
|
|
if (sdlRecName == kSDLMorphSequence)
|
|
|
|
IHandleMorphSDR(sdlDataRec);
|
|
|
|
else
|
|
|
|
plClothingSDLModifier::HandleSingleSDR(sdlDataRec, this);
|
|
|
|
}
|
|
|
|
delete sdlDataRec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fSynchClients = true; // set true if the next synch should be bcast
|
|
|
|
ForceUpdate(true);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::SaveCustomizations(bool retry /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = new plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kSaveCustomizations);
|
|
|
|
if (retry)
|
|
|
|
msg->AddCommand(plClothingMsg::kRetry);
|
|
|
|
|
|
|
|
msg->Send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WriteToVault()
|
|
|
|
{
|
|
|
|
if (!fVaultSaveEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
hsRef<RelVaultNode> rvn = VaultGetAvatarOutfitFolder();
|
|
|
|
if (!rvn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ARRAY(plStateDataRecord*) SDRs;
|
|
|
|
|
|
|
|
plStateDataRecord clothingSDR(kSDLClothing);
|
|
|
|
fAvatar->GetClothingSDLMod()->PutCurrentStateIn(&clothingSDR);
|
|
|
|
plSDStateVariable * clothesStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrWardrobe);
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < clothesStateDesc->GetCount(); ++i)
|
|
|
|
SDRs.Add(clothesStateDesc->GetStateDataRecord(i));
|
|
|
|
|
|
|
|
plSDStateVariable * appearanceStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrAppearance); // for skin tint
|
|
|
|
SDRs.Add(appearanceStateDesc->GetStateDataRecord(0));
|
|
|
|
|
|
|
|
WriteToVault(SDRs);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WriteToVault(const ARRAY(plStateDataRecord*) & SDRs)
|
|
|
|
{
|
|
|
|
// We'll hit this case when the server asks us to save state for NPCs.
|
|
|
|
if (fAvatar->GetTarget(0) != plNetClientApp::GetInstance()->GetLocalPlayer())
|
|
|
|
return;
|
|
|
|
|
|
|
|
hsRef<RelVaultNode> rvn = VaultGetAvatarOutfitFolder();
|
|
|
|
if (!rvn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ARRAY(plStateDataRecord*) morphs;
|
|
|
|
|
|
|
|
// Gather morph SDRs
|
|
|
|
hsTArray<const plMorphSequence*> morphsSDRs;
|
|
|
|
plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphsSDRs);
|
|
|
|
for (unsigned i = 0; i < morphsSDRs.GetCount(); ++i) {
|
|
|
|
for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++) {
|
|
|
|
if (fAvatar->GetClothingSO(j) == morphsSDRs[i]->GetTarget(0)) {
|
|
|
|
plStateDataRecord * morphSDR = new plStateDataRecord(kSDLMorphSequence);
|
|
|
|
plSimpleStateVariable * lodVar = morphSDR->FindVar(plMorphSequenceSDLMod::kStrTargetID);
|
|
|
|
if (lodVar)
|
|
|
|
lodVar->Set((int)j);
|
|
|
|
|
|
|
|
morphsSDRs[i]->GetSDLMod()->PutCurrentStateIn(morphSDR);
|
|
|
|
morphs.Add(morphSDR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RelVaultNode::RefList templates;
|
|
|
|
RelVaultNode::RefList actuals;
|
|
|
|
RelVaultNode::RefList nodes;
|
|
|
|
|
|
|
|
// Get all existing clothing SDRs
|
|
|
|
rvn->GetChildNodes(plVault::kNodeType_SDL, 1, &nodes); // REF: Find
|
|
|
|
|
|
|
|
const ARRAY(plStateDataRecord*) * arrs[] = {
|
|
|
|
&SDRs,
|
|
|
|
&morphs,
|
|
|
|
};
|
|
|
|
for (unsigned arrIdx = 0; arrIdx < arrsize(arrs); ++arrIdx) {
|
|
|
|
const ARRAY(plStateDataRecord*) * arr = arrs[arrIdx];
|
|
|
|
|
|
|
|
// Write all SDL to to the outfit folder, reusing existing nodes and creating new ones as necessary
|
|
|
|
for (unsigned i = 0; i < arr->Count(); ++i) {
|
|
|
|
hsRef<RelVaultNode> node;
|
|
|
|
if (!nodes.empty()) {
|
|
|
|
node = nodes.front();
|
|
|
|
nodes.pop_front();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node = new RelVaultNode;
|
|
|
|
node->SetNodeType(plVault::kNodeType_SDL);
|
|
|
|
templates.push_back(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
VaultSDLNode sdl(node);
|
|
|
|
sdl.SetStateDataRecord((*arr)[i], 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete any leftover nodes
|
|
|
|
for (const hsRef<RelVaultNode> &node : nodes)
|
|
|
|
VaultDeleteNode(node->GetNodeId());
|
|
|
|
|
|
|
|
// Create actual new nodes from their templates
|
|
|
|
for (const hsRef<RelVaultNode> &temp : templates) {
|
|
|
|
ENetError result;
|
|
|
|
if (hsRef<RelVaultNode> actual = VaultCreateNodeAndWait(temp, &result))
|
|
|
|
actuals.push_back(actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add new nodes to outfit folder
|
|
|
|
for (const hsRef<RelVaultNode> &act : actuals)
|
|
|
|
VaultAddChildNodeAndWait(rvn->GetNodeId(), act->GetNodeId(), NetCommGetPlayer()->playerInt);
|
|
|
|
|
|
|
|
// Cleanup morph SDRs
|
|
|
|
for (unsigned i = 0; i < morphs.Count(); ++i) {
|
|
|
|
delete morphs[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX HACK. DON'T USE (this function exists for the temp console command Clothing.SwapClothTexHACK)
|
|
|
|
void plClothingOutfit::DirtyTileset(int tileset)
|
|
|
|
{
|
|
|
|
fDirtyItems.SetBit(tileset);
|
|
|
|
ForceUpdate(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::IUpdate()
|
|
|
|
{
|
|
|
|
//GenerateTexture();
|
|
|
|
fAvatar->RefreshTree();
|
|
|
|
DirtySynchState(kSDLClothing, 0);
|
|
|
|
|
|
|
|
plArmatureMod * avMod = plAvatarMgr::GetInstance()->GetLocalAvatar();
|
|
|
|
hsAssert(avMod,"plArmatureMod not found for local player");
|
|
|
|
if (avMod->GetClothingOutfit()==this)
|
|
|
|
{
|
|
|
|
// Let the GUI know we changed clothes
|
|
|
|
plClothingUpdateBCMsg *BCMsg = new plClothingUpdateBCMsg();
|
|
|
|
BCMsg->SetSender(GetKey());
|
|
|
|
plgDispatch::MsgSend(BCMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WearDefaultClothing()
|
|
|
|
{
|
|
|
|
StripAccessories();
|
|
|
|
|
|
|
|
plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
|
|
|
|
hsTArray<plClothingItem *>items;
|
|
|
|
cMgr->GetItemsByGroup(fGroup, items);
|
|
|
|
|
|
|
|
// Wear one thing of each type
|
|
|
|
uint32_t i, j;
|
|
|
|
for (i = 0; i < plClothingMgr::kMaxType; i++)
|
|
|
|
{
|
|
|
|
if (i == plClothingMgr::kTypeAccessory)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (j = 0; j < items.GetCount(); j++)
|
|
|
|
{
|
|
|
|
if (items[j]->fType == i)
|
|
|
|
{
|
|
|
|
AddItem(items[j], false, false);
|
|
|
|
if (i == plClothingMgr::kTypeHair || i == plClothingMgr::kTypeFace)
|
|
|
|
{
|
|
|
|
// Hair tint color
|
|
|
|
TintItem(items[j], 0.5, 0.3, 0.2, false, false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TintItem(items[j], items[j]->fDefaultTint1[0] / 255.f, items[j]->fDefaultTint1[1] / 255.f,
|
|
|
|
items[j]->fDefaultTint1[2] / 255.f, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everyone can tint layer 2. Go nuts!
|
|
|
|
TintItem(items[j], items[j]->fDefaultTint2[0] / 255.f, items[j]->fDefaultTint2[1] / 255.f,
|
|
|
|
items[j]->fDefaultTint2[2] / 255.f, false, false, false, true, plClothingElement::kLayerTint2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WearDefaultClothingType(uint32_t clothingType)
|
|
|
|
{
|
|
|
|
plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
|
|
|
|
hsTArray<plClothingItem *> items;
|
|
|
|
cMgr->GetItemsByGroup(fGroup, items);
|
|
|
|
|
|
|
|
uint32_t i;
|
|
|
|
for (i=0; i<items.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (items[i]->fType == clothingType)
|
|
|
|
{
|
|
|
|
AddItem(items[i], false, false);
|
|
|
|
if (clothingType == plClothingMgr::kTypeHair || clothingType == plClothingMgr::kTypeFace)
|
|
|
|
{
|
|
|
|
// Hair tint color
|
|
|
|
TintItem(items[i], 0.5, 0.3, 0.2, false, false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TintItem(items[i], items[i]->fDefaultTint1[0] / 255.f, items[i]->fDefaultTint1[1] / 255.f,
|
|
|
|
items[i]->fDefaultTint1[2] / 255.f, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everyone can tint layer 2. Go nuts!
|
|
|
|
TintItem(items[i], items[i]->fDefaultTint2[0] / 255.f, items[i]->fDefaultTint2[1] / 255.f,
|
|
|
|
items[i]->fDefaultTint2[2] / 255.f, false, false, false, true, plClothingElement::kLayerTint2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WearMaintainerOutfit()
|
|
|
|
{
|
|
|
|
fVaultSaveEnabled = false;
|
|
|
|
|
|
|
|
WearDefaultClothing();
|
|
|
|
plClothingItem *item;
|
|
|
|
|
|
|
|
if (fGroup == plClothingMgr::kClothingBaseMale)
|
|
|
|
{
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MHAcc_SuitHelmet");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLHand_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MRHand_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MTorso_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLegs_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLFoot_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MRFoot_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
}
|
|
|
|
else if (fGroup == plClothingMgr::kClothingBaseFemale)
|
|
|
|
{
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FHair_SuitHelmet");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLHand_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FRHand_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FTorso_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLegs_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLFoot_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FRFoot_Suit");
|
|
|
|
if (item)
|
|
|
|
AddItem(item, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
fSynchClients = true;
|
|
|
|
ForceUpdate(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::RemoveMaintainerOutfit()
|
|
|
|
{
|
|
|
|
ReadClothing();
|
|
|
|
|
|
|
|
fVaultSaveEnabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static plRandom sRandom;
|
|
|
|
|
|
|
|
void plClothingOutfit::WearRandomOutfit()
|
|
|
|
{
|
|
|
|
plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
|
|
|
|
hsTArray<plClothingItem *>items;
|
|
|
|
|
|
|
|
// Wear one thing of each type
|
|
|
|
uint32_t i, j;
|
|
|
|
for (i = 0; i < plClothingMgr::kMaxType; i++)
|
|
|
|
{
|
|
|
|
if (i == plClothingMgr::kTypeAccessory)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
items.Reset();
|
|
|
|
cMgr->GetItemsByGroupAndType(fGroup, (uint8_t)i, items);
|
|
|
|
j = (uint32_t)(sRandom.RandZeroToOne() * items.GetCount());
|
|
|
|
|
|
|
|
float r1 = sRandom.RandZeroToOne();
|
|
|
|
float g1 = sRandom.RandZeroToOne();
|
|
|
|
float b1 = sRandom.RandZeroToOne();
|
|
|
|
float r2 = sRandom.RandZeroToOne();
|
|
|
|
float g2 = sRandom.RandZeroToOne();
|
|
|
|
float b2 = sRandom.RandZeroToOne();
|
|
|
|
|
|
|
|
AddItem(items[j], false, false);
|
|
|
|
TintItem(items[j], r1, g1, b1, false, false, false, true, 1);
|
|
|
|
TintItem(items[j], r2, g2, b2, false, false, false, true, 2);
|
|
|
|
|
|
|
|
plClothingItem *match = cMgr->GetLRMatch(items[j]);
|
|
|
|
if (match)
|
|
|
|
{
|
|
|
|
AddItem(match, false, false);
|
|
|
|
TintItem(match, r1, g1, b1, false, false, false, true, 1);
|
|
|
|
TintItem(match, r2, g2, b2, false, false, false, true, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TintSkin(sRandom.RandZeroToOne(), sRandom.RandZeroToOne(), sRandom.RandZeroToOne());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::ReadItems(hsStream* s, hsResMgr* mgr, bool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
uint32_t numItems = s->ReadLE32();
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < numItems; i++)
|
|
|
|
{
|
|
|
|
plKey key = mgr->ReadKey( s );
|
|
|
|
hsColorRGBA color;
|
|
|
|
hsColorRGBA color2;
|
|
|
|
color.Read(s);
|
|
|
|
color2.Read(s);
|
|
|
|
|
|
|
|
// Make sure to read everything in before hitting this and possibly skipping to
|
|
|
|
// the next item, lest we disrupt the stream.
|
|
|
|
if( key == nil )
|
|
|
|
{
|
|
|
|
hsAssert( false, "Nil item in plClothingOutfit::ReadItems(). The vault probably contains a key with a plLocation that's moved since then. Tsk, tsk." );
|
|
|
|
result = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingItem *item = plClothingItem::ConvertNoRef( key->GetObjectPtr() );
|
|
|
|
AddItem(item, false, broadcast);
|
|
|
|
TintItem(item, color.r, color.g, color.b, false, broadcast, false, true, plClothingElement::kLayerTint1);
|
|
|
|
TintItem(item, color2.r, color2.g, color2.b, false, broadcast, false, true, plClothingElement::kLayerTint2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WriteItems(hsStream *s, hsResMgr *mgr)
|
|
|
|
{
|
|
|
|
s->WriteLE32(fItems.GetCount());
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
mgr->WriteKey(s, fItems.Get(i)->GetKey());
|
|
|
|
fOptions.Get(i)->fTint1.Write(s);
|
|
|
|
fOptions.Get(i)->fTint2.Write(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::MsgReceive(plMessage* msg)
|
|
|
|
{
|
|
|
|
plPreResourceMsg *preMsg = plPreResourceMsg::ConvertNoRef(msg);
|
|
|
|
if (preMsg)
|
|
|
|
{
|
|
|
|
if (fAvatar && fGroup != plClothingMgr::kClothingBaseNoOptions)
|
|
|
|
{
|
|
|
|
plDrawable *spans = fAvatar->FindDrawable();
|
|
|
|
// This is a bit hacky... The drawable code has just run through and updated
|
|
|
|
// each span's bounds (see plDrawableSpans::IUpdateMatrixPaletteBoundsHack())
|
|
|
|
// but not the world bounds for the entire drawable. So we tell the space tree
|
|
|
|
// to refresh. However, the pageTreeMgr would then get confused because the
|
|
|
|
// space tree is no longer dirty (see plPageTreeMgr::IRefreshTree()),
|
|
|
|
// causing the avatar to only draw if the origin is in view.
|
|
|
|
// So we just force it dirty, and everyone's happy.
|
|
|
|
spans->GetSpaceTree()->Refresh();
|
|
|
|
spans->GetSpaceTree()->MakeDirty();
|
|
|
|
|
|
|
|
// Where were we? Oh yeah... if this avatar is in view it needs a texture. Tell
|
|
|
|
// the pipeline.
|
|
|
|
const hsBounds3Ext &bnds = spans->GetSpaceTree()->GetWorldBounds();
|
|
|
|
if ((bnds.GetType() == kBoundsNormal) && preMsg->Pipeline()->TestVisibleWorld(bnds))
|
|
|
|
preMsg->Pipeline()->SubmitClothingOutfit(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg);
|
|
|
|
if (refMsg)
|
|
|
|
{
|
|
|
|
plLayer *layer = plLayer::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (layer)
|
|
|
|
{
|
|
|
|
if (refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fTargetLayer = layer;
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
fTargetLayer = nil;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
plClothingItem *item = plClothingItem::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (item)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest) )
|
|
|
|
{
|
|
|
|
IAddItem(item);
|
|
|
|
}
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
{
|
|
|
|
IRemoveItem(item);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
plClothingBase *base = plClothingBase::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (base)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fBase = base;
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
fBase = nil;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsGMaterial *mat = hsGMaterial::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (mat)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
|
|
|
fMaterial = mat;
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
fMaterial = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For now: Adding an item fires off a notify, and the item won't get added until the ref message
|
|
|
|
// is processed, BUT we need to make sure that task doesn't require any more messages (like the
|
|
|
|
// trickling of chains of AddViaNotifies), so that other messages received after it (like tint
|
|
|
|
// commands) just have to check a kRetry flag, resend the message, and be confident that the item
|
|
|
|
// will be present by the time their resent message is handled.
|
|
|
|
//
|
|
|
|
// Should we ever handle AddViaNotify in a separate thread, this will blow up. (Ok, we'll just have
|
|
|
|
// bugs about the characters not having the right clothings options visible. No explosions.)
|
|
|
|
|
|
|
|
plClothingMsg *cMsg = plClothingMsg::ConvertNoRef(msg);
|
|
|
|
if (cMsg)
|
|
|
|
{
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kAddItem))
|
|
|
|
{
|
|
|
|
plGenRefMsg *msg = new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(cMsg->fItemKey, msg, plRefFlags::kActiveRef);
|
|
|
|
plClothingItem *accessory = plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr())->fAccessory;
|
|
|
|
if (accessory)
|
|
|
|
{
|
|
|
|
plGenRefMsg *msg = new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(accessory->GetKey(), msg, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kRemoveItem))
|
|
|
|
{
|
|
|
|
plClothingItem *item = plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr());
|
|
|
|
if (item && item->fType == plClothingMgr::kTypeAccessory)
|
|
|
|
GetKey()->Release(cMsg->fItemKey);
|
|
|
|
}
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kTintItem))
|
|
|
|
{
|
|
|
|
if (!ITintItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fColor, cMsg->fLayer) &&
|
|
|
|
cMsg->GetCommand(plClothingMsg::kRetry))
|
|
|
|
{
|
|
|
|
// We failed to tint because we're not yet wearing the item.
|
|
|
|
// However, the kRetry flag is set, so we fire another tint command off
|
|
|
|
|
|
|
|
TintItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()),
|
|
|
|
cMsg->fColor.r, cMsg->fColor.g, cMsg->fColor.b,
|
|
|
|
cMsg->GetCommand(plClothingMsg::kUpdateTexture), false, false, false, cMsg->fLayer);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kMorphItem))
|
|
|
|
{
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kRetry))
|
|
|
|
MorphItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fLayer, cMsg->fDelta, cMsg->fWeight, false);
|
|
|
|
else
|
|
|
|
IMorphItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fLayer, cMsg->fDelta, cMsg->fWeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kTintSkin))
|
|
|
|
{
|
|
|
|
fSkinTint = cMsg->fColor;
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
for (j = 0; j < fItems[i]->fElements.GetCount(); j++)
|
|
|
|
if (fItems[i]->fTextures[j][plClothingElement::kLayerSkin] != nil)
|
|
|
|
fDirtyItems.SetBit(fItems[i]->fTileset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kBlendSkin))
|
|
|
|
{
|
|
|
|
float blend = cMsg->fColor.a;
|
|
|
|
if (blend > 1.f)
|
|
|
|
blend = 1.f;
|
|
|
|
if (blend < 0.f)
|
|
|
|
blend = 0.f;
|
|
|
|
if (cMsg->fLayer >= plClothingElement::kLayerSkinBlend1 &&
|
|
|
|
cMsg->fLayer <= plClothingElement::kLayerSkinBlend6)
|
|
|
|
{
|
|
|
|
fSkinBlends[cMsg->fLayer - plClothingElement::kLayerSkinBlend1] = blend;
|
|
|
|
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
for (j = 0; j < fItems[i]->fElements.GetCount(); j++)
|
|
|
|
if (fItems[i]->fTextures[j][cMsg->fLayer] != nil)
|
|
|
|
fDirtyItems.SetBit(fItems[i]->fTileset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kSaveCustomizations))
|
|
|
|
{
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kRetry))
|
|
|
|
SaveCustomizations(false);
|
|
|
|
else
|
|
|
|
WriteToVault();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure to check for an update last
|
|
|
|
if (cMsg->GetCommand(plClothingMsg::kUpdateTexture))
|
|
|
|
{
|
|
|
|
// If kUpdateTexture was not the only command, we need to resend the update
|
|
|
|
// as a solo command, so that it happens after any other AddViaNotify messages
|
|
|
|
if (cMsg->ResendUpdate())
|
|
|
|
{
|
|
|
|
plClothingMsg *update = new plClothingMsg();
|
|
|
|
update->AddReceiver(GetKey());
|
|
|
|
update->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
plgDispatch::MsgSend(update);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
IUpdate();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hsKeyedObject::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// TESTING SDL
|
|
|
|
// Send clothing sendState msg to object's plClothingSDLModifier
|
|
|
|
//
|
|
|
|
bool plClothingOutfit::DirtySynchState(const plString& SDLStateName, uint32_t synchFlags)
|
|
|
|
{
|
|
|
|
plSynchEnabler ps(true); // make sure synching is enabled, since this happens during load
|
|
|
|
synchFlags |= plSynchedObject::kForceFullSend; // TEMP
|
|
|
|
if (fSynchClients)
|
|
|
|
synchFlags |= plSynchedObject::kBCastToClients;
|
|
|
|
fSynchClients=false;
|
|
|
|
hsAssert(fAvatar, "nil fAvatar");
|
|
|
|
return fAvatar->GetTarget(0)->DirtySynchState(SDLStateName, synchFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: Currently the uint16_t "instance" is a lie. We just copy. In the future
|
|
|
|
// we'll be good about this, but I wanted to get it working first.
|
|
|
|
void plClothingOutfit::IInstanceSharedMeshes(plClothingItem *item)
|
|
|
|
{
|
|
|
|
if (!fAvatar)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fAvatar->ValidateMesh();
|
|
|
|
|
|
|
|
bool partialSort = (item->fCustomText.Find("NeedsSort") >= 0);
|
|
|
|
for (int i = 0; i < plClothingItem::kMaxNumLODLevels; i++)
|
|
|
|
{
|
|
|
|
const plSceneObject *so = fAvatar->GetClothingSO(i);
|
|
|
|
if (so && item->fMeshes[i])
|
|
|
|
{
|
|
|
|
plInstanceDrawInterface *idi = const_cast<plInstanceDrawInterface*>(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface()));
|
|
|
|
if (idi)
|
|
|
|
idi->AddSharedMesh(item->fMeshes[i], fMaterial, !item->HasBaseAlpha(), i, partialSort);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::IRemoveSharedMeshes(plClothingItem *item)
|
|
|
|
{
|
|
|
|
if (fAvatar == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++)
|
|
|
|
{
|
|
|
|
const plSceneObject *so = fAvatar->GetClothingSO(i);
|
|
|
|
if (so != nil && item->fMeshes[i] != nil)
|
|
|
|
{
|
|
|
|
plInstanceDrawInterface *idi = const_cast<plInstanceDrawInterface*>(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface()));
|
|
|
|
if (idi)
|
|
|
|
idi->RemoveSharedMesh(item->fMeshes[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::SetupMorphSDL()
|
|
|
|
{
|
|
|
|
if (!fMorphsInitDone)
|
|
|
|
{
|
|
|
|
hsTArray<const plMorphSequence*> morphs;
|
|
|
|
plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphs);
|
|
|
|
for (unsigned i = 0; i < morphs.GetCount(); ++i)
|
|
|
|
{
|
|
|
|
for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++)
|
|
|
|
{
|
|
|
|
if (fAvatar->GetClothingSO(j) == morphs[i]->GetTarget(0))
|
|
|
|
{
|
|
|
|
plMorphSequenceSDLMod* morph = morphs[i]->GetSDLMod();
|
|
|
|
if (morph)
|
|
|
|
morph->SetIsAvatar(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fMorphsInitDone = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::WriteToFile(const plFileName &filename)
|
|
|
|
{
|
|
|
|
if (!filename.IsValid())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
hsRef<RelVaultNode> rvn = VaultGetAvatarOutfitFolder();
|
|
|
|
if (!rvn)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
hsUNIXStream S;
|
|
|
|
if (!S.Open(filename, "wb"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
S.WriteByte(fGroup);
|
|
|
|
|
|
|
|
RelVaultNode::RefList nodes;
|
|
|
|
rvn->GetChildNodes(plVault::kNodeType_SDL, 1, &nodes);
|
|
|
|
S.WriteLE32(nodes.size());
|
|
|
|
for (const hsRef<RelVaultNode> &node : nodes) {
|
|
|
|
VaultSDLNode sdl(node);
|
|
|
|
S.WriteLE32(sdl.GetSDLDataLength());
|
|
|
|
if (sdl.GetSDLDataLength())
|
|
|
|
S.Write(sdl.GetSDLDataLength(), sdl.GetSDLData());
|
|
|
|
}
|
|
|
|
|
|
|
|
S.Close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingOutfit::IReadFromFile(const plFileName &filename)
|
|
|
|
{
|
|
|
|
if (!filename.IsValid())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
hsUNIXStream S;
|
|
|
|
if (!S.Open(filename))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool isLocalAvatar = plAvatarMgr::GetInstance()->GetLocalAvatar()->GetClothingOutfit() == this;
|
|
|
|
|
|
|
|
uint8_t gender = S.ReadByte();
|
|
|
|
if (gender != fGroup) {
|
|
|
|
if (isLocalAvatar) {
|
|
|
|
if (gender == plClothingMgr::kClothingBaseMale)
|
|
|
|
plClothingMgr::ChangeAvatar("Male", filename);
|
|
|
|
else if (gender == plClothingMgr::kClothingBaseFemale)
|
|
|
|
plClothingMgr::ChangeAvatar("Female", filename);
|
|
|
|
}
|
|
|
|
S.Close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
StripAccessories();
|
|
|
|
|
|
|
|
uint32_t nodeCount = S.ReadLE32();
|
|
|
|
for (size_t i = 0; i < nodeCount; i++) {
|
|
|
|
uint32_t dataLen = S.ReadLE32();
|
|
|
|
if (dataLen) {
|
|
|
|
plString sdlRecName;
|
|
|
|
int sdlRecVersion;
|
|
|
|
plStateDataRecord::ReadStreamHeader(&S, &sdlRecName, &sdlRecVersion);
|
|
|
|
plStateDescriptor* desc = plSDLMgr::GetInstance()->FindDescriptor(sdlRecName, sdlRecVersion);
|
|
|
|
if (desc) {
|
|
|
|
plStateDataRecord sdlDataRec(desc);
|
|
|
|
if (sdlDataRec.Read(&S, 0)) {
|
|
|
|
if (sdlRecName == kSDLMorphSequence)
|
|
|
|
IHandleMorphSDR(&sdlDataRec);
|
|
|
|
else
|
|
|
|
plClothingSDLModifier::HandleSingleSDR(&sdlDataRec, this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
S.Close();
|
|
|
|
fSynchClients = true;
|
|
|
|
ForceUpdate(true);
|
|
|
|
SaveCustomizations(); // Sync with the vault
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
const char *plClothingMgr::GroupStrings[] =
|
|
|
|
{
|
|
|
|
"Male Clothing",
|
|
|
|
"Female Clothing",
|
|
|
|
"(No Clothing Options)"
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *plClothingMgr::TypeStrings[] =
|
|
|
|
{
|
|
|
|
"Pants",
|
|
|
|
"Shirt",
|
|
|
|
"LeftHand",
|
|
|
|
"RightHand",
|
|
|
|
"Face",
|
|
|
|
"Hair",
|
|
|
|
"LeftFoot",
|
|
|
|
"RightFoot",
|
|
|
|
"Accessory"
|
|
|
|
};
|
|
|
|
|
|
|
|
plClothingMgr *plClothingMgr::fInstance = nil;
|
|
|
|
|
|
|
|
plClothingMgr::plClothingMgr()
|
|
|
|
{
|
|
|
|
fLayouts.Reset();
|
|
|
|
fItems.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingMgr::~plClothingMgr()
|
|
|
|
{
|
|
|
|
while (fElements.GetCount() > 0)
|
|
|
|
delete fElements.Pop();
|
|
|
|
while (fLayouts.GetCount() > 0)
|
|
|
|
delete fLayouts.Pop();
|
|
|
|
while (fItems.GetCount() > 0)
|
|
|
|
delete fItems.Pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingLayout *plClothingMgr::GetLayout(const plString &name) const
|
|
|
|
{
|
|
|
|
for (int i = 0; i < fLayouts.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (fLayouts.Get(i)->fName == name)
|
|
|
|
return fLayouts.Get(i);
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingElement *plClothingMgr::FindElementByName(const plString &name) const
|
|
|
|
{
|
|
|
|
for (int i = 0; i < fElements.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (fElements.Get(i)->fName == name)
|
|
|
|
return fElements.Get(i);
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::AddItemsToCloset(hsTArray<plClosetItem> &items)
|
|
|
|
{
|
|
|
|
hsRef<RelVaultNode> rvn = VaultGetAvatarClosetFolder();
|
|
|
|
if (!rvn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
hsTArray<plClosetItem> closet;
|
|
|
|
GetClosetItems(closet);
|
|
|
|
|
|
|
|
RelVaultNode::RefList templates;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < items.GetCount(); ++i) {
|
|
|
|
bool match = false;
|
|
|
|
for (unsigned j = 0; j < closet.GetCount(); ++j) {
|
|
|
|
if (closet[j].IsMatch(&items[i]))
|
|
|
|
{
|
|
|
|
match = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (match)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
plStateDataRecord rec(plClothingSDLModifier::GetClothingItemSDRName());
|
|
|
|
plClothingSDLModifier::PutSingleItemIntoSDR(&items[i], &rec);
|
|
|
|
|
|
|
|
hsRef<RelVaultNode> templateNode = new RelVaultNode;
|
|
|
|
templateNode->SetNodeType(plVault::kNodeType_SDL);
|
|
|
|
|
|
|
|
VaultSDLNode sdl(templateNode);
|
|
|
|
sdl.SetStateDataRecord(&rec);
|
|
|
|
|
|
|
|
templates.push_back(templateNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const hsRef<RelVaultNode> &temp : templates) {
|
|
|
|
ENetError result;
|
|
|
|
if (hsRef<RelVaultNode> actual = VaultCreateNodeAndWait(temp, &result)) {
|
|
|
|
VaultAddChildNodeAndWait(
|
|
|
|
rvn->GetNodeId(),
|
|
|
|
actual->GetNodeId(),
|
|
|
|
NetCommGetPlayer()->playerInt
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::GetClosetItems(hsTArray<plClosetItem> &out)
|
|
|
|
{
|
|
|
|
hsRef<RelVaultNode> rvn = VaultGetAvatarClosetFolder();
|
|
|
|
if (!rvn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
RelVaultNode::RefList nodes;
|
|
|
|
rvn->GetChildNodes(plVault::kNodeType_SDL, 1, &nodes);
|
|
|
|
out.SetCount(nodes.size());
|
|
|
|
|
|
|
|
auto iter = nodes.begin();
|
|
|
|
for (unsigned i = 0; i < nodes.size(); ++i, ++iter) {
|
|
|
|
VaultSDLNode sdl(*iter);
|
|
|
|
plStateDataRecord * rec = new plStateDataRecord;
|
|
|
|
if (sdl.GetStateDataRecord(rec, 0))
|
|
|
|
plClothingSDLModifier::HandleSingleSDR(rec, nil, &out[i]);
|
|
|
|
delete rec;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out.GetCount()) {
|
|
|
|
for (int i = out.GetCount() - 1; i >= 0; i--) {
|
|
|
|
if (out[i].fItem == nil)
|
|
|
|
out.Remove(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::GetAllWithSameMesh(plClothingItem *item, hsTArray<plClothingItem*> &out)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (item->HasSameMeshes(fItems[i]))
|
|
|
|
out.Append(fItems[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Yes, it's a lame n^2 function. Show me that we have enough items for it
|
|
|
|
// to matter and I'll speed it up.
|
|
|
|
void plClothingMgr::FilterUniqueMeshes(hsTArray<plClothingItem*> &items)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
for (i = items.GetCount() - 1; i >= 1; i--)
|
|
|
|
{
|
|
|
|
for (j = i - 1; j >= 0; j--)
|
|
|
|
{
|
|
|
|
if (items[i]->HasSameMeshes(items[j]))
|
|
|
|
{
|
|
|
|
items.Remove(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingItem *plClothingMgr::FindItemByName(const plString &name) const
|
|
|
|
{
|
|
|
|
if (name.IsEmpty())
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
for (int i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
plClothingItem* item = fItems.Get(i);
|
|
|
|
if (item->fName == name)
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::GetItemsByGroup(uint8_t group, hsTArray<plClothingItem*> &out)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (fItems.Get(i)->fGroup == group)
|
|
|
|
out.Append(fItems.Get(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::GetItemsByGroupAndType(uint8_t group, uint8_t type, hsTArray<plClothingItem*> &out)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (fItems.Get(i)->fGroup == group && fItems.Get(i)->fType == type)
|
|
|
|
out.Append(fItems.Get(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingItem *plClothingMgr::GetLRMatch(plClothingItem *item)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (IsLRMatch(item, fItems[i]))
|
|
|
|
return fItems[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Couldn't find one.
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingMgr::IsLRMatch(plClothingItem *item1, plClothingItem *item2)
|
|
|
|
{
|
|
|
|
if (!item1 || !item2)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (item1->fType)
|
|
|
|
{
|
|
|
|
case kTypeLeftHand:
|
|
|
|
if (item2->fType != kTypeRightHand) return false;
|
|
|
|
break;
|
|
|
|
case kTypeRightHand:
|
|
|
|
if (item2->fType != kTypeLeftHand) return false;
|
|
|
|
break;
|
|
|
|
case kTypeLeftFoot:
|
|
|
|
if (item2->fType != kTypeRightFoot) return false;
|
|
|
|
break;
|
|
|
|
case kTypeRightFoot:
|
|
|
|
if (item2->fType != kTypeLeftFoot) return false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// if its not a matching kinda item, then there can be no match
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Types check out fine, now compare textures
|
|
|
|
if (item1->fTextures.GetCount() != item2->fTextures.GetCount()) return false;
|
|
|
|
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < item1->fTextures.GetCount(); i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < plClothingElement::kLayerMax; j++)
|
|
|
|
if (item1->fTextures[i][j] != item2->fTextures[i][j])
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally... we're not our own match
|
|
|
|
return item1 != item2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::Init()
|
|
|
|
{
|
|
|
|
fInstance = new plClothingMgr;
|
|
|
|
fInstance->RegisterAs(kClothingMgr_KEY);
|
|
|
|
fInstance->IInit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::IInit()
|
|
|
|
{
|
|
|
|
plClothingElement::GetElements(fElements);
|
|
|
|
plClothingLayout *layout = new plClothingLayout("BasicHuman", 1024);
|
|
|
|
layout->fElements.Append(FindElementByName("shirt-chest"));
|
|
|
|
layout->fElements.Append(FindElementByName("shirt-sleeve"));
|
|
|
|
layout->fElements.Append(FindElementByName("face"));
|
|
|
|
layout->fElements.Append(FindElementByName("eyeball"));
|
|
|
|
layout->fElements.Append(FindElementByName("shoe-top"));
|
|
|
|
layout->fElements.Append(FindElementByName("shoe-bottom"));
|
|
|
|
layout->fElements.Append(FindElementByName("pants"));
|
|
|
|
layout->fElements.Append(FindElementByName("hand-LOD"));
|
|
|
|
layout->fElements.Append(FindElementByName("hand-square"));
|
|
|
|
layout->fElements.Append(FindElementByName("hand-wide"));
|
|
|
|
|
|
|
|
fLayouts.Append(layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::DeInit()
|
|
|
|
{
|
|
|
|
if (fInstance)
|
|
|
|
{
|
|
|
|
fInstance->UnRegisterAs(kClothingMgr_KEY);
|
|
|
|
fInstance = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool plClothingMgr::MsgReceive(plMessage* msg)
|
|
|
|
{
|
|
|
|
plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg);
|
|
|
|
if (refMsg)
|
|
|
|
{
|
|
|
|
plClothingItem *item = plClothingItem::ConvertNoRef(refMsg->GetRef());
|
|
|
|
if (item)
|
|
|
|
{
|
|
|
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate) )
|
|
|
|
{
|
|
|
|
IAddItem(item);
|
|
|
|
}
|
|
|
|
else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
|
|
|
|
{
|
|
|
|
fItems.RemoveItem(item);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hsKeyedObject::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::IAddItem(plClothingItem *item)
|
|
|
|
{
|
|
|
|
bool allFound = true;
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < item->fElementNames.GetCount(); i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < fElements.GetCount(); j++)
|
|
|
|
{
|
|
|
|
if (item->fElementNames.Get(i) == fElements.Get(j)->fName)
|
|
|
|
{
|
|
|
|
item->fElements.Set(i, fElements.Get(j));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (j >= fElements.GetCount())
|
|
|
|
{
|
|
|
|
allFound = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allFound)
|
|
|
|
{
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (fItems[i]->fSortOrder >= item->fSortOrder)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
fItems.InsertAtIndex(i, item);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
hsAssert(false, "Couldn't match all elements of added clothing item.");
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::ChangeAvatar(const plString& name, const plFileName &clothingFile)
|
|
|
|
{
|
|
|
|
plAvatarMgr::GetInstance()->UnLoadLocalPlayer();
|
|
|
|
plAvatarMgr::GetInstance()->LoadPlayerFromFile(name, "", clothingFile);
|
|
|
|
}
|