|
|
|
/*==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 "hsTypes.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 "plPipeline/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 "plMath/plRandom.h"
|
|
|
|
#include "plSDL/plSDL.h"
|
|
|
|
#include "plVault/plVault.h"
|
|
|
|
#include "plResMgr/plKeyFinder.h"
|
|
|
|
#include "plNetClientComm/plNetClientComm.h"
|
|
|
|
|
|
|
|
|
|
|
|
plClothingItem::plClothingItem() : fName(nil), fGroup(0), fTileset(0), fType(0), fSortOrder(0),
|
|
|
|
fDescription(nil), fCustomText(nil), fThumbnail(nil),
|
|
|
|
fAccessory(nil), fAccessoryName(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 (fElementNames.GetCount() > 0)
|
|
|
|
delete [] fElementNames.Pop();
|
|
|
|
|
|
|
|
while (fTextures.GetCount() > 0)
|
|
|
|
delete [] fTextures.Pop();
|
|
|
|
|
|
|
|
delete [] fName;
|
|
|
|
delete [] fDescription;
|
|
|
|
delete [] fCustomText;
|
|
|
|
delete [] fAccessoryName;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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));
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // thumbnail
|
|
|
|
|
|
|
|
int tileCount = s->ReadSwap32();
|
|
|
|
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, TRACKED_NEW plElementRefMsg(GetKey(), plRefMsg::kOnCreate, i, -1, nil, layer), plRefFlags::kActiveRef); // texture
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < kMaxNumLODLevels; i++)
|
|
|
|
{
|
|
|
|
if (s->ReadBool())
|
|
|
|
mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, i, -1), plRefFlags::kActiveRef); // shared mesh
|
|
|
|
}
|
|
|
|
|
|
|
|
fElements.SetCountAndZero(tileCount);
|
|
|
|
if (plClothingMgr::GetClothingMgr())
|
|
|
|
{
|
|
|
|
plGenRefMsg *msg = TRACKED_NEW plGenRefMsg(plClothingMgr::GetClothingMgr()->GetKey(), plRefMsg::kOnCreate, -1, -1);
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(GetKey(), msg, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
mgr->ReadKeyNotifyMe(s, TRACKED_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 texSkip = 0;
|
|
|
|
for (i = 0; i < fTextures.GetCount(); i++)
|
|
|
|
if (fTextures[i] == nil)
|
|
|
|
texSkip++;
|
|
|
|
|
|
|
|
s->WriteSwap32(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)
|
|
|
|
{
|
|
|
|
char strBuf[512];
|
|
|
|
sprintf(strBuf, "CItm_%s", fAccessoryName);
|
|
|
|
accessoryKey = plKeyFinder::Instance().StupidSearch("GlobalClothing", nil, plClothingItem::Index(), strBuf);
|
|
|
|
if (accessoryKey == nil)
|
|
|
|
{
|
|
|
|
sprintf(strBuf, "Couldn't find accessory \"%s\". It won't show at runtime.", fAccessoryName);
|
|
|
|
hsMessageBox(strBuf, GetKeyName(), hsMessageBoxNormal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mgr->WriteKey(s, accessoryKey);
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
{
|
|
|
|
s->WriteByte(fDefaultTint1[i]);
|
|
|
|
s->WriteByte(fDefaultTint2[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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.ExpandAndZero(eMsg->fWhich + 1);
|
|
|
|
|
|
|
|
if (fElementNames.Get(eMsg->fWhich) == nil)
|
|
|
|
fElementNames.Set(eMsg->fWhich, hsStrcpy(eMsg->fElementName));
|
|
|
|
if (fTextures.Get(eMsg->fWhich) == nil)
|
|
|
|
{
|
|
|
|
plMipmap **layers = TRACKED_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
hsBool plClosetItem::IsMatch(plClosetItem *other)
|
|
|
|
{
|
|
|
|
return (fItem == other->fItem && fOptions.IsMatch(&other->fOptions));
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
plClothingBase::plClothingBase() : fName(nil), fBaseTexture(nil), fLayoutName(nil) {}
|
|
|
|
|
|
|
|
plClothingBase::~plClothingBase()
|
|
|
|
{
|
|
|
|
delete [] fName;
|
|
|
|
delete [] fLayoutName;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingBase::Read(hsStream* s, hsResMgr* mgr)
|
|
|
|
{
|
|
|
|
hsKeyedObject::Read(s, mgr);
|
|
|
|
|
|
|
|
fName = s->ReadSafeString();
|
|
|
|
if (s->ReadBool())
|
|
|
|
mgr->ReadKeyNotifyMe(s, TRACKED_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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(nil), fBase(nil), fGroup(0), fAvatar(nil), fSynchClients(false), fMaterial(nil), 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, hsBool update /* = true */, hsBool broadcast /* = true */, hsBool netForce /* = false */)
|
|
|
|
{
|
|
|
|
if (fItems.Find(item) != fItems.kMissingIndex)
|
|
|
|
return;
|
|
|
|
|
|
|
|
plClothingMsg *msg = TRACKED_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 = TRACKED_NEW plClothingMsg();
|
|
|
|
msg->AddCommand(plClothingMsg::kUpdateTexture);
|
|
|
|
if (retry)
|
|
|
|
msg->AddCommand(plClothingMsg::kRetry); // force a resend
|
|
|
|
msg->Send(GetKey());
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::RemoveItem(plClothingItem *item, hsBool update /* = true */, hsBool netForce /* = false */)
|
|
|
|
{
|
|
|
|
if (fItems.Find(item) == fItems.kMissingIndex)
|
|
|
|
return;
|
|
|
|
|
|
|
|
plClothingMsg *msg = TRACKED_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, hsScalar red, hsScalar green, hsScalar blue,
|
|
|
|
hsBool update /* = true */, hsBool broadcast /* = true */, hsBool netForce /* = false */,
|
|
|
|
hsBool retry /* = true */, UInt8 layer /* = kLayerTint1 */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = TRACKED_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(hsScalar red, hsScalar green, hsScalar blue,
|
|
|
|
hsBool update /* = true */, hsBool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = TRACKED_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 layer, UInt8 delta, hsScalar weight,
|
|
|
|
hsBool retry /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = TRACKED_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(hsScalar age, hsBool update /* = true */, hsBool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
SetSkinBlend(age, plClothingElement::kLayerSkinBlend1, update, broadcast);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::SetSkinBlend(hsScalar blend, UInt8 layer, hsBool update /* = true */, hsBool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = TRACKED_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);
|
|
|
|
}
|
|
|
|
|
|
|
|
hsScalar plClothingOutfit::GetSkinBlend(UInt8 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 = TRACKED_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++)
|
|
|
|
{
|
|
|
|
if (soundEffect = plArmatureEffectFootSound::ConvertNoRef(mgr->GetEffect(i)))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundEffect)
|
|
|
|
{
|
|
|
|
if (!strcmp(item->fName, "03_MLFoot04_01") || !strcmp(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 index = fItems.Find(item);
|
|
|
|
if (index != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
fItems.Remove(index);
|
|
|
|
delete fOptions.Get(index);
|
|
|
|
fOptions.Remove(index);
|
|
|
|
IRemoveSharedMeshes(item);
|
|
|
|
fDirtyItems.SetBit(item->fTileset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plClothingOutfit::ITintItem(plClothingItem *item, hsColorRGBA color, UInt8 layer)
|
|
|
|
{
|
|
|
|
UInt32 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 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 layer /* = kLayerTint1 */) const
|
|
|
|
{
|
|
|
|
if (layer >= plClothingElement::kLayerSkinFirst &&
|
|
|
|
layer <= plClothingElement::kLayerSkinLast)
|
|
|
|
return fSkinTint;
|
|
|
|
|
|
|
|
UInt32 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plClothingOutfit::IMorphItem(plClothingItem *item, UInt8 layer, UInt8 delta, hsScalar weight)
|
|
|
|
{
|
|
|
|
UInt32 index = fItems.Find(item);
|
|
|
|
if (index != fItems.kMissingIndex)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (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, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // plClothingBase
|
|
|
|
|
|
|
|
if (fGroup != plClothingMgr::kClothingBaseNoOptions)
|
|
|
|
{
|
|
|
|
mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // target layer
|
|
|
|
mgr->ReadKeyNotifyMe(s, TRACKED_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 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::ReadFromVault()
|
|
|
|
{
|
|
|
|
SetupMorphSDL();
|
|
|
|
|
|
|
|
WearDefaultClothing();
|
|
|
|
|
|
|
|
RelVaultNode * rvn;
|
|
|
|
if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ARRAY(RelVaultNode*) nodes;
|
|
|
|
rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes);
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < nodes.Count(); ++i) {
|
|
|
|
VaultSDLNode sdl(nodes[i]);
|
|
|
|
if (sdl.sdlDataLen) {
|
|
|
|
hsRAMStream ram;
|
|
|
|
ram.Write(sdl.sdlDataLen, sdl.sdlData);
|
|
|
|
ram.Rewind();
|
|
|
|
|
|
|
|
char * sdlRecName = nil;
|
|
|
|
int sdlRecVersion;
|
|
|
|
plStateDataRecord::ReadStreamHeader(&ram, &sdlRecName, &sdlRecVersion);
|
|
|
|
plStateDescriptor * desc = plSDLMgr::GetInstance()->FindDescriptor(sdlRecName, sdlRecVersion);
|
|
|
|
if (desc) {
|
|
|
|
plStateDataRecord * sdlDataRec = TRACKED_NEW plStateDataRecord(desc);
|
|
|
|
if (sdlDataRec->Read(&ram, 0)) {
|
|
|
|
if (!strcmp(sdlRecName, kSDLMorphSequence))
|
|
|
|
IHandleMorphSDR(sdlDataRec);
|
|
|
|
else
|
|
|
|
plClothingSDLModifier::HandleSingleSDR(sdlDataRec, this);
|
|
|
|
}
|
|
|
|
delete sdlDataRec;
|
|
|
|
}
|
|
|
|
delete [] sdlRecName;
|
|
|
|
}
|
|
|
|
nodes[i]->DecRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
fSynchClients = true; // set true if the next synch should be bcast
|
|
|
|
ForceUpdate(true);
|
|
|
|
|
|
|
|
rvn->DecRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::SaveCustomizations(hsBool retry /* = true */)
|
|
|
|
{
|
|
|
|
plClothingMsg *msg = TRACKED_NEW plClothingMsg();
|
|
|
|
msg->AddReceiver(GetKey());
|
|
|
|
msg->AddCommand(plClothingMsg::kSaveCustomizations);
|
|
|
|
if (retry)
|
|
|
|
msg->AddCommand(plClothingMsg::kRetry);
|
|
|
|
|
|
|
|
msg->Send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingOutfit::WriteToVault()
|
|
|
|
{
|
|
|
|
if (!fVaultSaveEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
RelVaultNode * rvn;
|
|
|
|
if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef()))
|
|
|
|
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);
|
|
|
|
rvn->DecRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
RelVaultNode * rvn;
|
|
|
|
if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef()))
|
|
|
|
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 = TRACKED_NEW plStateDataRecord(kSDLMorphSequence);
|
|
|
|
plSimpleStateVariable * lodVar = morphSDR->FindVar(plMorphSequenceSDLMod::kStrTargetID);
|
|
|
|
if (lodVar)
|
|
|
|
lodVar->Set((int)j);
|
|
|
|
|
|
|
|
morphsSDRs[i]->GetSDLMod()->PutCurrentStateIn(morphSDR);
|
|
|
|
morphs.Add(morphSDR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ARRAY(RelVaultNode*) templates;
|
|
|
|
ARRAY(RelVaultNode*) actuals;
|
|
|
|
ARRAY(RelVaultNode*) nodes;
|
|
|
|
|
|
|
|
// Get all existing clothing SDRs
|
|
|
|
rvn->GetChildNodesIncRef(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) {
|
|
|
|
RelVaultNode * node;
|
|
|
|
if (nodes.Count()) {
|
|
|
|
node = nodes[0];
|
|
|
|
nodes.DeleteUnordered(0);
|
|
|
|
node->IncRef(); // REF: Work
|
|
|
|
node->DecRef(); // REF: Find
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
RelVaultNode * templateNode = NEWZERO(RelVaultNode);
|
|
|
|
templateNode->SetNodeType(plVault::kNodeType_SDL);
|
|
|
|
templates.Add(templateNode);
|
|
|
|
node = templateNode;
|
|
|
|
node->IncRef(); // REF: Create
|
|
|
|
node->IncRef(); // REF: Work
|
|
|
|
}
|
|
|
|
|
|
|
|
VaultSDLNode sdl(node);
|
|
|
|
sdl.SetStateDataRecord((*arr)[i], 0);
|
|
|
|
node->DecRef(); // REF: Work
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete any leftover nodes
|
|
|
|
{ for (unsigned i = 0; i < nodes.Count(); ++i) {
|
|
|
|
VaultDeleteNode(nodes[i]->nodeId);
|
|
|
|
nodes[i]->DecRef(); // REF: Array
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Create actual new nodes from their templates
|
|
|
|
{ for (unsigned i = 0; i < templates.Count(); ++i) {
|
|
|
|
ENetError result;
|
|
|
|
if (RelVaultNode * actual = VaultCreateNodeAndWaitIncRef(templates[i], &result)) {
|
|
|
|
actuals.Add(actual);
|
|
|
|
}
|
|
|
|
templates[i]->DecRef(); // REF: Create
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Add new nodes to outfit folder
|
|
|
|
{ for (unsigned i = 0; i < actuals.Count(); ++i) {
|
|
|
|
VaultAddChildNodeAndWait(rvn->nodeId, actuals[i]->nodeId, NetCommGetPlayer()->playerInt);
|
|
|
|
actuals[i]->DecRef(); // REF: Create
|
|
|
|
}}
|
|
|
|
|
|
|
|
// Cleanup morph SDRs
|
|
|
|
{for (unsigned i = 0; i < morphs.Count(); ++i) {
|
|
|
|
DEL(morphs[i]);
|
|
|
|
}}
|
|
|
|
|
|
|
|
rvn->DecRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 = TRACKED_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 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 clothingType)
|
|
|
|
{
|
|
|
|
plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
|
|
|
|
hsTArray<plClothingItem *> items;
|
|
|
|
cMgr->GetItemsByGroup(fGroup, items);
|
|
|
|
|
|
|
|
UInt32 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()
|
|
|
|
{
|
|
|
|
ReadFromVault();
|
|
|
|
|
|
|
|
fVaultSaveEnabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static plRandom sRandom;
|
|
|
|
|
|
|
|
void plClothingOutfit::WearRandomOutfit()
|
|
|
|
{
|
|
|
|
plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
|
|
|
|
hsTArray<plClothingItem *>items;
|
|
|
|
|
|
|
|
// Wear one thing of each type
|
|
|
|
UInt32 i, j;
|
|
|
|
for (i = 0; i < plClothingMgr::kMaxType; i++)
|
|
|
|
{
|
|
|
|
if (i == plClothingMgr::kTypeAccessory)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
items.Reset();
|
|
|
|
cMgr->GetItemsByGroupAndType(fGroup, (UInt8)i, items);
|
|
|
|
j = (UInt32)(sRandom.RandZeroToOne() * items.GetCount());
|
|
|
|
|
|
|
|
hsScalar r1 = sRandom.RandZeroToOne();
|
|
|
|
hsScalar g1 = sRandom.RandZeroToOne();
|
|
|
|
hsScalar b1 = sRandom.RandZeroToOne();
|
|
|
|
hsScalar r2 = sRandom.RandZeroToOne();
|
|
|
|
hsScalar g2 = sRandom.RandZeroToOne();
|
|
|
|
hsScalar 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());
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plClothingOutfit::ReadItems(hsStream* s, hsResMgr* mgr, hsBool broadcast /* = true */)
|
|
|
|
{
|
|
|
|
hsBool result = true;
|
|
|
|
UInt32 numItems = s->ReadSwap32();
|
|
|
|
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->WriteSwap32(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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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 = TRACKED_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 = TRACKED_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))
|
|
|
|
{
|
|
|
|
hsScalar 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 = TRACKED_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
|
|
|
|
//
|
|
|
|
hsBool plClothingOutfit::DirtySynchState(const char* SDLStateName, UInt32 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 word "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)
|
|
|
|
fAvatar->ValidateMesh();
|
|
|
|
|
|
|
|
hsBool partialSort = item->fCustomText && strstr(item->fCustomText, "NeedsSort");
|
|
|
|
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->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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
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(char *name)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fLayouts.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (!strcmp(fLayouts.Get(i)->fName, name))
|
|
|
|
return fLayouts.Get(i);
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
plClothingElement *plClothingMgr::FindElementByName(char *name)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fElements.GetCount(); i++)
|
|
|
|
{
|
|
|
|
if (!strcmp(fElements.Get(i)->fName, name))
|
|
|
|
return fElements.Get(i);
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::AddItemsToCloset(hsTArray<plClosetItem> &items)
|
|
|
|
{
|
|
|
|
RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef();
|
|
|
|
if (!rvn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
hsTArray<plClosetItem> closet;
|
|
|
|
GetClosetItems(closet);
|
|
|
|
|
|
|
|
ARRAY(RelVaultNode*) 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);
|
|
|
|
|
|
|
|
RelVaultNode * templateNode = NEWZERO(RelVaultNode);
|
|
|
|
templateNode->IncRef();
|
|
|
|
templateNode->SetNodeType(plVault::kNodeType_SDL);
|
|
|
|
|
|
|
|
VaultSDLNode sdl(templateNode);
|
|
|
|
sdl.SetStateDataRecord(&rec);
|
|
|
|
|
|
|
|
templates.Add(templateNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < templates.Count(); ++i) {
|
|
|
|
ENetError result;
|
|
|
|
if (RelVaultNode * actual = VaultCreateNodeAndWaitIncRef(templates[i], &result)) {
|
|
|
|
VaultAddChildNodeAndWait(
|
|
|
|
rvn->nodeId,
|
|
|
|
actual->nodeId,
|
|
|
|
NetCommGetPlayer()->playerInt
|
|
|
|
);
|
|
|
|
actual->DecRef(); // REF: Create
|
|
|
|
}
|
|
|
|
templates[i]->DecRef(); // REF: Create
|
|
|
|
}
|
|
|
|
|
|
|
|
rvn->DecRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::GetClosetItems(hsTArray<plClosetItem> &out)
|
|
|
|
{
|
|
|
|
RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef();
|
|
|
|
if (!rvn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ARRAY(RelVaultNode*) nodes;
|
|
|
|
rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes);
|
|
|
|
out.SetCount(nodes.Count());
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < nodes.Count(); ++i) {
|
|
|
|
VaultSDLNode sdl(nodes[i]);
|
|
|
|
plStateDataRecord * rec = NEWZERO(plStateDataRecord);
|
|
|
|
if (sdl.GetStateDataRecord(rec, 0))
|
|
|
|
plClothingSDLModifier::HandleSingleSDR(rec, nil, &out[i]);
|
|
|
|
DEL(rec);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out.GetCount()) {
|
|
|
|
for (int i = out.GetCount() - 1; i >= 0; i--) {
|
|
|
|
if (out[i].fItem == nil)
|
|
|
|
out.Remove(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rvn->DecRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
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 char *name)
|
|
|
|
{
|
|
|
|
if (!name)
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fItems.GetCount(); i++)
|
|
|
|
{
|
|
|
|
plClothingItem* item = fItems.Get(i);
|
|
|
|
if (!strcmp(item->fName, name))
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::GetItemsByGroup(UInt8 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 group, UInt8 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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 = TRACKED_NEW plClothingMgr;
|
|
|
|
fInstance->RegisterAs(kClothingMgr_KEY);
|
|
|
|
fInstance->IInit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plClothingMgr::IInit()
|
|
|
|
{
|
|
|
|
plClothingElement::GetElements(fElements);
|
|
|
|
plClothingLayout *layout = TRACKED_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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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)
|
|
|
|
{
|
|
|
|
hsBool allFound = true;
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < item->fElementNames.GetCount(); i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < fElements.GetCount(); j++)
|
|
|
|
{
|
|
|
|
if (!strcmp(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(char *name)
|
|
|
|
{
|
|
|
|
plAvatarMgr::GetInstance()->UnLoadLocalPlayer();
|
|
|
|
plAvatarMgr::GetInstance()->LoadPlayer(name, nil);
|
|
|
|
}
|
|
|
|
|