/*==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 .
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::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::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 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();
hsTArrayitems;
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 items;
cMgr->GetItemsByGroup(fGroup, items);
UInt32 i;
for (i=0; ifType == 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();
hsTArrayitems;
// 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();
const hsBounds3Ext &bnds = spans->GetWorldBounds();
if (bnds.GetType() == kBoundsNormal)
{
// 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.
if (preMsg->Pipeline()->TestVisibleWorld(spans->GetSpaceTree()->GetWorldBounds()))
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::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::ConvertNoRef(so->GetDrawInterface()));
if (idi)
idi->RemoveSharedMesh(item->fMeshes[i]);
}
}
}
void plClothingOutfit::SetupMorphSDL()
{
if (!fMorphsInitDone)
{
hsTArray 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 &items)
{
RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef();
if (!rvn)
return;
hsTArray 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 &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 &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 &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 &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 &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);
}