/*==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() : 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; } 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, 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", "", 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]); } } 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.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); } ///////////////////////////////////////////////////////////////////////////// bool 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); } 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(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, 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 (!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); } } 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 (index != fItems.kMissingIndex) { int i; for (i = 0; i < fAvatar->GetNumLOD(); i++) { if (item->fMeshes[i]->fMorphSet == nil) continue; const plSceneObject *so = fAvatar->GetClothingSO(i); if (!so) continue; plMorphSequence *seq = const_cast(plMorphSequence::ConvertNoRef(so->GetModifierByType(plMorphSequence::Index()))); plKey meshKey = item->fMeshes[i]->GetKey(); // Lower LOD objects don't have all the morphs, so check if this one is in range. if (seq && layer < seq->GetNumLayers(meshKey) && delta < seq->GetNumDeltas(layer, meshKey)) seq->SetWeight(layer, delta, weight, meshKey); } return true; } return false; } void plClothingOutfit::Read(hsStream* s, hsResMgr* mgr) { plSynchedObject::Read(s, mgr); fGroup = s->ReadByte(); mgr->ReadKeyNotifyMe(s, 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); } 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.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; } } nodes[i]->DecRef(); } fSynchClients = true; // set true if the next synch should be bcast ForceUpdate(true); rvn->DecRef(); } 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; RelVaultNode * rvn; if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef())) return; ARRAY(plStateDataRecord*) SDRs; plStateDataRecord clothingSDR(kSDLClothing); fAvatar->GetClothingSDLMod()->PutCurrentStateIn(&clothingSDR); plSDStateVariable * clothesStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrWardrobe); for (unsigned i = 0; i < clothesStateDesc->GetCount(); ++i) SDRs.Add(clothesStateDesc->GetStateDataRecord(i)); plSDStateVariable * appearanceStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrAppearance); // for skin tint SDRs.Add(appearanceStateDesc->GetStateDataRecord(0)); WriteToVault(SDRs); rvn->DecRef(); } void plClothingOutfit::WriteToVault(const ARRAY(plStateDataRecord*) & SDRs) { // We'll hit this case when the server asks us to save state for NPCs. if (fAvatar->GetTarget(0) != plNetClientApp::GetInstance()->GetLocalPlayer()) return; RelVaultNode * rvn; if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef())) return; ARRAY(plStateDataRecord*) morphs; // Gather morph SDRs hsTArray morphsSDRs; plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphsSDRs); for (unsigned i = 0; i < morphsSDRs.GetCount(); ++i) { for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++) { if (fAvatar->GetClothingSO(j) == morphsSDRs[i]->GetTarget(0)) { plStateDataRecord * morphSDR = 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 = new 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]->GetNodeId()); 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->GetNodeId(), actuals[i]->GetNodeId(), 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(); 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() { ReadFromVault(); 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 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(); bool partialSort = item->fCustomText && strstr(item->fCustomText, "NeedsSort"); int i; for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++) { const plSceneObject *so = fAvatar->GetClothingSO(i); if (so != nil && item->fMeshes[i] != nil) { plInstanceDrawInterface *idi = const_cast(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface())); if (idi) idi->AddSharedMesh(item->fMeshes[i], fMaterial, !item->HasBaseAlpha(), i, partialSort); } } } void plClothingOutfit::IRemoveSharedMeshes(plClothingItem *item) { if (fAvatar == nil) return; int i; for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++) { const plSceneObject *so = fAvatar->GetClothingSO(i); if (so != nil && item->fMeshes[i] != nil) { plInstanceDrawInterface *idi = const_cast(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface())); if (idi) idi->RemoveSharedMesh(item->fMeshes[i]); } } } void plClothingOutfit::SetupMorphSDL() { if (!fMorphsInitDone) { hsTArray morphs; plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphs); for (unsigned i = 0; i < morphs.GetCount(); ++i) { for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++) { if (fAvatar->GetClothingSO(j) == morphs[i]->GetTarget(0)) { plMorphSequenceSDLMod* morph = morphs[i]->GetSDLMod(); if (morph) morph->SetIsAvatar(true); } } } fMorphsInitDone = true; } } ///////////////////////////////////////////////////////////////////////////// const char *plClothingMgr::GroupStrings[] = { "Male Clothing", "Female Clothing", "(No Clothing Options)" }; const char *plClothingMgr::TypeStrings[] = { "Pants", "Shirt", "LeftHand", "RightHand", "Face", "Hair", "LeftFoot", "RightFoot", "Accessory" }; plClothingMgr *plClothingMgr::fInstance = nil; plClothingMgr::plClothingMgr() { fLayouts.Reset(); fItems.Reset(); } plClothingMgr::~plClothingMgr() { while (fElements.GetCount() > 0) delete fElements.Pop(); while (fLayouts.GetCount() > 0) delete fLayouts.Pop(); while (fItems.GetCount() > 0) delete fItems.Pop(); } plClothingLayout *plClothingMgr::GetLayout(char *name) { int i; for (i = 0; i < fLayouts.GetCount(); i++) { if (!strcmp(fLayouts.Get(i)->fName, name)) return fLayouts.Get(i); } return nil; } plClothingElement *plClothingMgr::FindElementByName(const char *name) { int i; for (i = 0; i < fElements.GetCount(); i++) { if (!strcmp(fElements.Get(i)->fName, name)) return fElements.Get(i); } return nil; } void plClothingMgr::AddItemsToCloset(hsTArray &items) { RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef(); if (!rvn) return; hsTArray closet; GetClosetItems(closet); ARRAY(RelVaultNode*) templates; for (unsigned i = 0; i < items.GetCount(); ++i) { bool match = false; for (unsigned j = 0; j < closet.GetCount(); ++j) { if (closet[j].IsMatch(&items[i])) { match = true; break; } } if (match) continue; plStateDataRecord rec(plClothingSDLModifier::GetClothingItemSDRName()); plClothingSDLModifier::PutSingleItemIntoSDR(&items[i], &rec); RelVaultNode * templateNode = new 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->GetNodeId(), actual->GetNodeId(), NetCommGetPlayer()->playerInt ); actual->DecRef(); // REF: Create } templates[i]->DecRef(); // REF: Create } rvn->DecRef(); } void plClothingMgr::GetClosetItems(hsTArray &out) { RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef(); if (!rvn) return; ARRAY(RelVaultNode*) nodes; rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes); out.SetCount(nodes.Count()); for (unsigned i = 0; i < nodes.Count(); ++i) { VaultSDLNode sdl(nodes[i]); plStateDataRecord * rec = 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); } } rvn->DecRef(); } void plClothingMgr::GetAllWithSameMesh(plClothingItem *item, hsTArray &out) { int i; for (i = 0; i < fItems.GetCount(); i++) { if (item->HasSameMeshes(fItems[i])) out.Append(fItems[i]); } } // Yes, it's a lame n^2 function. Show me that we have enough items for it // to matter and I'll speed it up. void plClothingMgr::FilterUniqueMeshes(hsTArray &items) { int i, j; for (i = items.GetCount() - 1; i >= 1; i--) { for (j = i - 1; j >= 0; j--) { if (items[i]->HasSameMeshes(items[j])) { items.Remove(i); break; } } } } plClothingItem *plClothingMgr::FindItemByName(const char *name) { if (!name) return nil; int i; for (i = 0; i < fItems.GetCount(); i++) { plClothingItem* item = fItems.Get(i); if (!strcmp(item->fName, name)) return item; } return nil; } void plClothingMgr::GetItemsByGroup(uint8_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 (!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); }