You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1947 lines
54 KiB

/*==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(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, 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);
}
bool plClothingOutfit::ReadFromFile()
{
// 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();
RelVaultNode * rvn = VaultGetAvatarOutfitFolderIncRef();
if (!rvn)
return false;
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();
return true;
}
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(hsBool broadcast)
{
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;
}
}
}
if (broadcast) {
fSynchClients = true;
ForceUpdate(true);
}
}
void plClothingOutfit::WearDefaultClothingType(UInt32 clothingType, hsBool broadcast)
{
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;
}
}
if (broadcast) {
fSynchClients = true;
ForceUpdate(true);
}
}
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()
{
ReadFromFile();
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;
}
}
bool plClothingOutfit::WriteToFile(const plFileName &filename)
{
if (!filename.IsValid())
return false;
RelVaultNode* rvn = VaultGetAvatarOutfitFolderIncRef();
if (!rvn)
return false;
hsUNIXStream S;
if (!S.Open(filename, "wb")) {
rvn->DecRef();
return false;
}
S.WriteByte(fGroup);
ARRAY(RelVaultNode*) nodes;
rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes);
S.WriteLE32(nodes.Count());
for (size_t i = 0; i < nodes.Count(); i++) {
VaultSDLNode sdl(nodes[i]);
S.WriteLE32(sdl.GetSDLDataLength());
if (sdl.GetSDLDataLength())
S.Write(sdl.GetSDLDataLength(), sdl.GetSDLData());
nodes[i]->DecRef();
}
rvn->DecRef();
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(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);
}