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