/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "HeadSpin.h"
#include "hsTemplates.h"
#include "hsStream.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnSceneObject/plSceneObject.h"
#include "plDrawable/plInstanceDrawInterface.h"
#include "pnMessage/plRefMsg.h"
#include "pnMessage/plPipeResMakeMsg.h"
#include "pfMessage/plClothingMsg.h"
#include "plMessage/plRenderMsg.h"
#include "plGImage/plMipmap.h"
#include "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() : 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, 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, nil, 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)
    {
        plString strBuf = plString::Format("CItm_%s", fAccessoryName);
        accessoryKey = plKeyFinder::Instance().StupidSearch("GlobalClothing", nil, plClothingItem::Index(), strBuf);
        if (accessoryKey == nil)
        {
            strBuf = plString::Format("Couldn't find accessory \"%s\". 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]);
    }
}

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 = 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, 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 = 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, hsBool update /* = true */, hsBool 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,
                                hsBool update /* = true */, hsBool broadcast /* = true */, hsBool netForce /* = false */,
                                hsBool 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,
                                hsBool update /* = true */, hsBool 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,
                                 hsBool 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, hsBool update /* = true */, hsBool broadcast /* = true */)
{
    SetSkinBlend(age, plClothingElement::kLayerSkinBlend1, update, broadcast);
}

void plClothingOutfit::SetSkinBlend(float blend, uint8_t layer, hsBool update /* = true */, hsBool 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++)
            {
                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_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);
    }
}

hsBool 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;
}

hsBool plClothingOutfit::IMorphItem(plClothingItem *item, uint8_t layer, uint8_t delta, float weight)
{
    uint32_t index = fItems.Find(item);
    if (index != fItems.kMissingIndex)
    {
        int i;
        for (i = 0; i < fAvatar->GetNumLOD(); i++)
        {
            if (item->fMeshes[i]->fMorphSet == nil)
                continue;

            const plSceneObject *so = fAvatar->GetClothingSO(i);
            if (!so)
                continue;

            plMorphSequence *seq = const_cast<plMorphSequence*>(plMorphSequence::ConvertNoRef(so->GetModifierByType(plMorphSequence::Index())));
            plKey meshKey = item->fMeshes[i]->GetKey();

            // Lower LOD objects don't have all the morphs, so check if this one is in range.
            if (seq && layer < seq->GetNumLayers(meshKey) && delta < seq->GetNumDeltas(layer, meshKey))
                seq->SetWeight(layer, delta, weight, meshKey);
        }
        return true;
    }
    
    return false;       
}

void plClothingOutfit::Read(hsStream* s, hsResMgr* mgr)
{
    plSynchedObject::Read(s, mgr);

    fGroup = s->ReadByte();
    mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // plClothingBase  

    if (fGroup != plClothingMgr::kClothingBaseNoOptions)
    {
        mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // target layer
        mgr->ReadKeyNotifyMe(s, new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // material    
    }
    plgDispatch::Dispatch()->RegisterForExactType(plPreResourceMsg::Index(), GetKey());
    //ReadItems(s, mgr, false);
}

void plClothingOutfit::Write(hsStream* s, hsResMgr* mgr)
{
    plSynchedObject::Write(s, mgr);

    s->WriteByte(fGroup);
    mgr->WriteKey(s, fBase->GetKey());
    
    if (fGroup != plClothingMgr::kClothingBaseNoOptions)
    {
        mgr->WriteKey(s, fTargetLayer->GetKey());
        mgr->WriteKey(s, fMaterial->GetKey());
    }
    //WriteItems(s, mgr);
}

void plClothingOutfit::StripAccessories()
{
    int i;
    for (i = fItems.GetCount() - 1; i >= 0; i--)
    {
        if (fItems[i]->fType == plClothingMgr::kTypeAccessory)
        {
            GetKey()->Release(fItems[i]->GetKey());
            IRemoveItem(fItems[i]);
        }
    }
}

void plClothingOutfit::IHandleMorphSDR(plStateDataRecord *sdr)
{
    plSimpleStateVariable *lodVar = sdr->FindVar(plMorphSequenceSDLMod::kStrTargetID);
    if (!lodVar)
        return;

    uint8_t lod;
    lodVar->Get(&lod);

    const plSceneObject *so = fAvatar->GetClothingSO(lod);
    if (!so)
        return;

    plMorphSequenceSDLMod *morph = const_cast<plMorphSequenceSDLMod*>(plMorphSequenceSDLMod::ConvertNoRef(so->GetModifierByType(plMorphSequenceSDLMod::Index())));
    if (morph)
        morph->SetCurrentStateFrom(sdr);
}

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 = 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 = new plClothingMsg();
    msg->AddReceiver(GetKey());
    msg->AddCommand(plClothingMsg::kSaveCustomizations);
    if (retry)
        msg->AddCommand(plClothingMsg::kRetry);

    msg->Send();
}

void plClothingOutfit::WriteToVault()
{
    if (!fVaultSaveEnabled)
        return;

    RelVaultNode * rvn;
    if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef()))
        return;

    ARRAY(plStateDataRecord*) SDRs;
    
    plStateDataRecord clothingSDR(kSDLClothing);
    fAvatar->GetClothingSDLMod()->PutCurrentStateIn(&clothingSDR);
    plSDStateVariable * clothesStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrWardrobe);

    for (unsigned i = 0; i < clothesStateDesc->GetCount(); ++i)
        SDRs.Add(clothesStateDesc->GetStateDataRecord(i));

    plSDStateVariable * appearanceStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrAppearance); // for skin tint
    SDRs.Add(appearanceStateDesc->GetStateDataRecord(0));
    
    WriteToVault(SDRs);
    rvn->DecRef();
}

void plClothingOutfit::WriteToVault(const ARRAY(plStateDataRecord*) & SDRs)
{
    // We'll hit this case when the server asks us to save state for NPCs.
    if (fAvatar->GetTarget(0) != plNetClientApp::GetInstance()->GetLocalPlayer())
        return;

    RelVaultNode * rvn;
    if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef()))
        return;
        
    ARRAY(plStateDataRecord*)   morphs;

    // Gather morph SDRs    
    hsTArray<const plMorphSequence*> morphsSDRs;
    plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphsSDRs);
    for (unsigned i = 0; i < morphsSDRs.GetCount(); ++i) {
        for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++) {
            if (fAvatar->GetClothingSO(j) == morphsSDRs[i]->GetTarget(0)) {
                plStateDataRecord * morphSDR = 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) {
        delete 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 = new plClothingUpdateBCMsg();
        BCMsg->SetSender(GetKey());
        plgDispatch::MsgSend(BCMsg);
    }
}

void plClothingOutfit::WearDefaultClothing()
{
    StripAccessories();
    
    plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
    hsTArray<plClothingItem *>items;
    cMgr->GetItemsByGroup(fGroup, items);

    // Wear one thing of each type
    uint32_t i, j;
    for (i = 0; i < plClothingMgr::kMaxType; i++)
    {
        if (i == plClothingMgr::kTypeAccessory)
            continue;

        for (j = 0; j < items.GetCount(); j++)
        {
            if (items[j]->fType == i)
            {
                AddItem(items[j], false, false);
                if (i == plClothingMgr::kTypeHair || i == plClothingMgr::kTypeFace)
                {   
                    // Hair tint color
                    TintItem(items[j], 0.5, 0.3, 0.2, false, false);
                }
                else
                {
                    TintItem(items[j], items[j]->fDefaultTint1[0] / 255.f, items[j]->fDefaultTint1[1] / 255.f,
                             items[j]->fDefaultTint1[2] / 255.f, false, false);
                }

                // Everyone can tint layer 2. Go nuts!
                TintItem(items[j], items[j]->fDefaultTint2[0] / 255.f, items[j]->fDefaultTint2[1] / 255.f,
                         items[j]->fDefaultTint2[2] / 255.f, false, false, false, true, plClothingElement::kLayerTint2);                    
                break;
            }
        }
    }
}

void plClothingOutfit::WearDefaultClothingType(uint32_t clothingType)
{
    plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
    hsTArray<plClothingItem *> items;
    cMgr->GetItemsByGroup(fGroup, items);

    uint32_t i;
    for (i=0; i<items.GetCount(); i++)
    {
        if (items[i]->fType == clothingType)
        {
            AddItem(items[i], false, false);
            if (clothingType == plClothingMgr::kTypeHair || clothingType == plClothingMgr::kTypeFace)
            {
                // Hair tint color
                TintItem(items[i], 0.5, 0.3, 0.2, false, false);
            }
            else
            {
                TintItem(items[i], items[i]->fDefaultTint1[0] / 255.f, items[i]->fDefaultTint1[1] / 255.f,
                         items[i]->fDefaultTint1[2] / 255.f, false, false);
            }

            // Everyone can tint layer 2. Go nuts!
            TintItem(items[i], items[i]->fDefaultTint2[0] / 255.f, items[i]->fDefaultTint2[1] / 255.f,
                     items[i]->fDefaultTint2[2] / 255.f, false, false, false, true, plClothingElement::kLayerTint2);
            break;
        }
    }
}

void plClothingOutfit::WearMaintainerOutfit()
{
    fVaultSaveEnabled = false;

    WearDefaultClothing();
    plClothingItem *item;
    
    if (fGroup == plClothingMgr::kClothingBaseMale)
    {
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MHAcc_SuitHelmet");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLHand_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MRHand_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MTorso_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLegs_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLFoot_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MRFoot_Suit");
        if (item)
            AddItem(item, false, false);
    }
    else if (fGroup == plClothingMgr::kClothingBaseFemale)
    {
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FHair_SuitHelmet");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLHand_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FRHand_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FTorso_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLegs_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLFoot_Suit");
        if (item)
            AddItem(item, false, false);
        item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FRFoot_Suit");
        if (item)
            AddItem(item, false, false);
    }
    
    fSynchClients = true;
    ForceUpdate(true);
}

void plClothingOutfit::RemoveMaintainerOutfit()
{
    ReadFromVault();

    fVaultSaveEnabled = true;
}

static plRandom sRandom;

void plClothingOutfit::WearRandomOutfit()
{
    plClothingMgr *cMgr = plClothingMgr::GetClothingMgr();
    hsTArray<plClothingItem *>items;

    // Wear one thing of each type
    uint32_t i, j;
    for (i = 0; i < plClothingMgr::kMaxType; i++)
    {
        if (i == plClothingMgr::kTypeAccessory)
            continue;

        items.Reset();
        cMgr->GetItemsByGroupAndType(fGroup, (uint8_t)i, items);
        j = (uint32_t)(sRandom.RandZeroToOne() * items.GetCount());

        float r1 = sRandom.RandZeroToOne();
        float g1 = sRandom.RandZeroToOne();
        float b1 = sRandom.RandZeroToOne();
        float r2 = sRandom.RandZeroToOne();
        float g2 = sRandom.RandZeroToOne();
        float b2 = sRandom.RandZeroToOne();

        AddItem(items[j], false, false);
        TintItem(items[j], r1, g1, b1, false, false, false, true, 1);
        TintItem(items[j], r2, g2, b2, false, false, false, true, 2);

        plClothingItem *match = cMgr->GetLRMatch(items[j]);
        if (match)
        {
            AddItem(match, false, false);
            TintItem(match, r1, g1, b1, false, false, false, true, 1);
            TintItem(match, r2, g2, b2, false, false, false, true, 2);
        }
    }
    TintSkin(sRandom.RandZeroToOne(), sRandom.RandZeroToOne(), sRandom.RandZeroToOne());
}

hsBool plClothingOutfit::ReadItems(hsStream* s, hsResMgr* mgr, hsBool broadcast /* = true */)
{
    hsBool 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);
    }
}

hsBool plClothingOutfit::MsgReceive(plMessage* msg)
{
    plPreResourceMsg *preMsg = plPreResourceMsg::ConvertNoRef(msg);
    if (preMsg)
    {
        if (fAvatar && fGroup != plClothingMgr::kClothingBaseNoOptions)
        {
            plDrawable *spans = fAvatar->FindDrawable();
            // This is a bit hacky... The drawable code has just run through and updated
            // each span's bounds (see plDrawableSpans::IUpdateMatrixPaletteBoundsHack())
            // but not the world bounds for the entire drawable. So we tell the space tree
            // to refresh. However, the pageTreeMgr would then get confused because the
            // space tree is no longer dirty (see plPageTreeMgr::IRefreshTree()), 
            // causing the avatar to only draw if the origin is in view. 
            // So we just force it dirty, and everyone's happy.
            spans->GetSpaceTree()->Refresh();
            spans->GetSpaceTree()->MakeDirty();

            // Where were we? Oh yeah... if this avatar is in view it needs a texture. Tell
            // the pipeline.
            const hsBounds3Ext &bnds = spans->GetSpaceTree()->GetWorldBounds();
            if ((bnds.GetType() == kBoundsNormal) && preMsg->Pipeline()->TestVisibleWorld(bnds))
                preMsg->Pipeline()->SubmitClothingOutfit(this);
        }
    }
 
    plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg);
    if (refMsg)
    {
        plLayer *layer = plLayer::ConvertNoRef(refMsg->GetRef());
        if (layer)
        {
            if (refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
                fTargetLayer = layer;
            else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
                fTargetLayer = nil;
            
            return true;
        }
        plClothingItem *item = plClothingItem::ConvertNoRef(refMsg->GetRef());
        if (item)
        {
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest) )
            {
                IAddItem(item);
            }
            else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
            {
                IRemoveItem(item);
            }
            return true;
        }
        plClothingBase *base = plClothingBase::ConvertNoRef(refMsg->GetRef());
        if (base)
        {
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
                fBase = base;
            else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
                fBase = nil;
            
            return true;        
        }
        
        hsGMaterial *mat = hsGMaterial::ConvertNoRef(refMsg->GetRef());
        if (mat)
        {
            if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
                fMaterial = mat;
            else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) )
                fMaterial = nil;
        }
    }

    // For now: Adding an item fires off a notify, and the item won't get added until the ref message
    // is processed, BUT we need to make sure that task doesn't require any more messages (like the
    // trickling of chains of AddViaNotifies), so that other messages received after it (like tint
    // commands) just have to check a kRetry flag, resend the message, and be confident that the item
    // will be present by the time their resent message is handled.
    // 
    // Should we ever handle AddViaNotify in a separate thread, this will blow up. (Ok, we'll just have
    // bugs about the characters not having the right clothings options visible. No explosions.)

    plClothingMsg *cMsg = plClothingMsg::ConvertNoRef(msg);
    if (cMsg)
    {
        if (cMsg->GetCommand(plClothingMsg::kAddItem))
        {
            plGenRefMsg *msg = 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
//
hsBool plClothingOutfit::DirtySynchState(const char* 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)
        fAvatar->ValidateMesh();

    hsBool partialSort = item->fCustomText && strstr(item->fCustomText, "NeedsSort");
    int i;
    for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++)
    {
        const plSceneObject *so = fAvatar->GetClothingSO(i);
        if (so != nil && item->fMeshes[i] != nil)
        {
            plInstanceDrawInterface *idi = const_cast<plInstanceDrawInterface*>(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface()));
            if (idi)
                idi->AddSharedMesh(item->fMeshes[i], fMaterial, !item->HasBaseAlpha(), i, partialSort);
        }
    }
}

void plClothingOutfit::IRemoveSharedMeshes(plClothingItem *item)
{   
    if (fAvatar == nil)
        return;

    int i;
    for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++)
    {
        const plSceneObject *so = fAvatar->GetClothingSO(i);
        if (so != nil && item->fMeshes[i] != nil)
        {
            plInstanceDrawInterface *idi = const_cast<plInstanceDrawInterface*>(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface()));
            if (idi)
                idi->RemoveSharedMesh(item->fMeshes[i]);            
        }
    }
}

void plClothingOutfit::SetupMorphSDL()
{
    if (!fMorphsInitDone)
    {
        hsTArray<const plMorphSequence*> morphs;
        plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphs);
        for (unsigned i = 0; i < morphs.GetCount(); ++i)
        {
            for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++)
            {
                if (fAvatar->GetClothingSO(j) == morphs[i]->GetTarget(0))
                {
                    plMorphSequenceSDLMod* morph = morphs[i]->GetSDLMod();
                    if (morph)
                        morph->SetIsAvatar(true);
                }
            }
        }

        fMorphsInitDone = true;
    }
}


/////////////////////////////////////////////////////////////////////////////

const char *plClothingMgr::GroupStrings[] = 
{
    "Male Clothing",
    "Female Clothing",
    "(No Clothing Options)"
};

const char *plClothingMgr::TypeStrings[] =
{
    "Pants",
    "Shirt",
    "LeftHand",
    "RightHand",
    "Face",
    "Hair",
    "LeftFoot",
    "RightFoot",
    "Accessory"
};

plClothingMgr *plClothingMgr::fInstance = nil;

plClothingMgr::plClothingMgr()
{
    fLayouts.Reset();
    fItems.Reset();
}

plClothingMgr::~plClothingMgr()
{
    while (fElements.GetCount() > 0)
        delete fElements.Pop();
    while (fLayouts.GetCount() > 0)
        delete fLayouts.Pop();
    while (fItems.GetCount() > 0)
        delete fItems.Pop();
}

plClothingLayout *plClothingMgr::GetLayout(char *name)
{
    int i;
    for (i = 0; i < fLayouts.GetCount(); i++)
    {
        if (!strcmp(fLayouts.Get(i)->fName, name))
            return fLayouts.Get(i);
    }
    return nil;
}

plClothingElement *plClothingMgr::FindElementByName(char *name)
{
    int i;
    for (i = 0; i < fElements.GetCount(); i++)
    {
        if (!strcmp(fElements.Get(i)->fName, name))
            return fElements.Get(i);
    }
    return nil; 
}

void plClothingMgr::AddItemsToCloset(hsTArray<plClosetItem> &items)
{
    RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef();
    if (!rvn)
        return;
        
    hsTArray<plClosetItem> closet;
    GetClosetItems(closet);
    
    ARRAY(RelVaultNode*)    templates;
    
    for (unsigned i = 0; i < items.GetCount(); ++i) {
        bool match = false;
        for (unsigned j = 0; j < closet.GetCount(); ++j) {
            if (closet[j].IsMatch(&items[i]))
            {
                match = true;
                break;
            }
        }
        
        if (match)
            continue;

        plStateDataRecord rec(plClothingSDLModifier::GetClothingItemSDRName());
        plClothingSDLModifier::PutSingleItemIntoSDR(&items[i], &rec);
        
        RelVaultNode * templateNode = NEWZERO(RelVaultNode);
        templateNode->IncRef();
        templateNode->SetNodeType(plVault::kNodeType_SDL);
        
        VaultSDLNode sdl(templateNode);
        sdl.SetStateDataRecord(&rec);

        templates.Add(templateNode);
    }
    
    for (unsigned i = 0; i < templates.Count(); ++i) {
        ENetError result;
        if (RelVaultNode * actual = VaultCreateNodeAndWaitIncRef(templates[i], &result)) {
            VaultAddChildNodeAndWait(
                rvn->nodeId,
                actual->nodeId,
                NetCommGetPlayer()->playerInt
            );
            actual->DecRef(); // REF: Create
        }
        templates[i]->DecRef(); // REF: Create
    }
    
    rvn->DecRef();
}

void plClothingMgr::GetClosetItems(hsTArray<plClosetItem> &out)
{
    RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef();
    if (!rvn)
        return;

    ARRAY(RelVaultNode*)    nodes;
    rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes);
    out.SetCount(nodes.Count());
    
    for (unsigned i = 0; i < nodes.Count(); ++i) {
        VaultSDLNode sdl(nodes[i]);
        plStateDataRecord * rec = NEWZERO(plStateDataRecord);
        if (sdl.GetStateDataRecord(rec, 0))
            plClothingSDLModifier::HandleSingleSDR(rec, nil, &out[i]);
        delete rec;
    }

    if (out.GetCount()) {
        for (int i = out.GetCount() - 1; i >= 0; i--) {
            if (out[i].fItem == nil)
                out.Remove(i);
        }
    }
    
    rvn->DecRef();
}   

void plClothingMgr::GetAllWithSameMesh(plClothingItem *item, hsTArray<plClothingItem*> &out)
{
    int i;
    for (i = 0; i < fItems.GetCount(); i++)
    {
        if (item->HasSameMeshes(fItems[i]))
            out.Append(fItems[i]);
    }
}

// Yes, it's a lame n^2 function. Show me that we have enough items for it
// to matter and I'll speed it up.
void plClothingMgr::FilterUniqueMeshes(hsTArray<plClothingItem*> &items)
{
    int i, j;
    for (i = items.GetCount() - 1; i >= 1; i--)
    {
        for (j = i - 1; j >= 0; j--)
        {
            if (items[i]->HasSameMeshes(items[j]))
            {
                items.Remove(i);
                break;
            }
        }
    }
}

plClothingItem *plClothingMgr::FindItemByName(const char *name)
{
    if (!name)
        return nil;
    
    int i;
    for (i = 0; i < fItems.GetCount(); i++)
    {
        plClothingItem* item = fItems.Get(i);
        if (!strcmp(item->fName, name))
            return item;
    }
    return nil;
}

void plClothingMgr::GetItemsByGroup(uint8_t group, hsTArray<plClothingItem*> &out)
{
    int i;
    for (i = 0; i < fItems.GetCount(); i++)
    {
        if (fItems.Get(i)->fGroup == group)
            out.Append(fItems.Get(i));
    }
}

void plClothingMgr::GetItemsByGroupAndType(uint8_t group, uint8_t type, hsTArray<plClothingItem*> &out)
{
    int i;
    for (i = 0; i < fItems.GetCount(); i++)
    {
        if (fItems.Get(i)->fGroup == group && fItems.Get(i)->fType == type)
            out.Append(fItems.Get(i));
    }
}

plClothingItem *plClothingMgr::GetLRMatch(plClothingItem *item)
{
    int i;
    for (i = 0; i < fItems.GetCount(); i++)
    {
        if (IsLRMatch(item, fItems[i]))
            return fItems[i];
    }

    // Couldn't find one.
    return nil;
}

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 = 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;
    }
}   

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);
}

