/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsTypes.h" #include "hsTemplates.h" #include "hsStream.h" #include "hsResMgr.h" #include "plGDispatch.h" #include "../pnKeyedObject/plKey.h" #include "../pnKeyedObject/plFixedKey.h" #include "../pnSceneObject/plSceneObject.h" #include "../plDrawable/plInstanceDrawInterface.h" #include "../pnMessage/plRefMsg.h" #include "../pnMessage/plPipeResMakeMsg.h" #include "../pfMessage/plClothingMsg.h" #include "../plMessage/plRenderMsg.h" #include "../plGImage/plMipmap.h" #include "../plPipeline/hsGDeviceRef.h" #include "../plPipeline/plRenderTarget.h" #include "plPipeline.h" #include "plClothingLayout.h" #include "plAvatarClothing.h" #include "plClothingSDLModifier.h" #include "../plGImage/hsCodecManager.h" #include "../plAvatar/plArmatureMod.h" #include "../plAvatar/plAvatarMgr.h" #include "../plAvatar/plArmatureEffects.h" #include "../pnNetCommon/plNetApp.h" #include "../pnMessage/plSDLModifierMsg.h" #include "../plMessage/plReplaceGeometryMsg.h" #include "../plDrawable/plDrawableSpans.h" #include "../plDrawable/plSharedMesh.h" #include "../plDrawable/plMorphSequence.h" #include "../plDrawable/plMorphSequenceSDLMod.h" #include "../plDrawable/plSpaceTree.h" #include "../plSurface/hsGMaterial.h" #include "../plSurface/plLayer.h" #include "../plMath/plRandom.h" #include "../plSDL/plSDL.h" #include "../plVault/plVault.h" #include "../plResMgr/plKeyFinder.h" #include "../plNetClientComm/plNetClientComm.h" plClothingItem::plClothingItem() : fName(nil), fGroup(0), fTileset(0), fType(0), fSortOrder(0), fDescription(nil), fCustomText(nil), fThumbnail(nil), fAccessory(nil), fAccessoryName(nil) { int i; fTextures.Reset(); fElementNames.Reset(); fElements.Reset(); for (i = 0; i < 3; i++) { fDefaultTint1[i] = fDefaultTint2[i] = 255; } for (i = 0; i < kMaxNumLODLevels; i++) fMeshes[i] = nil; } plClothingItem::~plClothingItem() { while (fElementNames.GetCount() > 0) delete [] fElementNames.Pop(); while (fTextures.GetCount() > 0) delete [] fTextures.Pop(); delete [] fName; delete [] fDescription; delete [] fCustomText; delete [] fAccessoryName; } hsBool plClothingItem::CanWearWith(plClothingItem *item) { // For now, you can only wear one shirt, one pair of pants, etc. // Except accessories, of which you're allowed one per tileset. return ((item->fType != fType) || (item->fType == plClothingMgr::kTypeAccessory && fType == plClothingMgr::kTypeAccessory)); //&& item->fTileset != fTileset)); } hsBool plClothingItem::WearBefore(plClothingItem *item) { // Accessories come last // Face comes first // Other stuff is arbitrary. int myPri; if (fType == plClothingMgr::kTypeFace) myPri = 0; else myPri = (fType == plClothingMgr::kTypeAccessory ? 2 : 1); int otherPri; if (item->fType == plClothingMgr::kTypeFace) otherPri = 0; else otherPri = (item->fType == plClothingMgr::kTypeAccessory ? 2 : 1); return myPri < otherPri; } hsBool plClothingItem::HasBaseAlpha() { int i; for (i = 0; i < fElements.GetCount(); i++) { plMipmap *tex = fTextures[i][plClothingElement::kLayerBase]; // if (tex && (tex->GetFlags() & (plMipmap::kAlphaBitFlag | plMipmap::kAlphaChannelFlag))) if (tex && (tex->GetFlags() & plMipmap::kAlphaChannelFlag)) return true; } return false; } hsBool plClothingItem::HasSameMeshes(plClothingItem *other) { int i; for (i = 0; i < kMaxNumLODLevels; i++) if (fMeshes[i] != other->fMeshes[i]) return false; return true; } void plClothingItem::Read(hsStream *s, hsResMgr *mgr) { hsKeyedObject::Read(s, mgr); fName = s->ReadSafeString(); fGroup = s->ReadByte(); fType = s->ReadByte(); fTileset = s->ReadByte(); fSortOrder = s->ReadByte(); fCustomText = s->ReadSafeString(); fDescription = s->ReadSafeString(); if (s->ReadBool()) mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // thumbnail int tileCount = s->ReadSwap32(); int i, j; for (i = 0; i < tileCount; i++) { fElementNames.Append(s->ReadSafeString()); int layerCount = s->ReadByte(); for (j = 0; j < layerCount; j++) { int layer = s->ReadByte(); mgr->ReadKeyNotifyMe(s, TRACKED_NEW plElementRefMsg(GetKey(), plRefMsg::kOnCreate, i, -1, nil, layer), plRefFlags::kActiveRef); // texture } } for (i = 0; i < kMaxNumLODLevels; i++) { if (s->ReadBool()) mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, i, -1), plRefFlags::kActiveRef); // shared mesh } fElements.SetCountAndZero(tileCount); if (plClothingMgr::GetClothingMgr()) { plGenRefMsg *msg = TRACKED_NEW plGenRefMsg(plClothingMgr::GetClothingMgr()->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(GetKey(), msg, plRefFlags::kActiveRef); } mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // forced accessory for (i = 0; i < 3; i++) { fDefaultTint1[i] = s->ReadByte(); fDefaultTint2[i] = s->ReadByte(); } } void plClothingItem::Write(hsStream *s, hsResMgr *mgr) { hsKeyedObject::Write(s, mgr); int i, j; s->WriteSafeString(fName); s->WriteByte(fGroup); s->WriteByte(fType); s->WriteByte(fTileset); s->WriteByte(fSortOrder); s->WriteSafeString(fCustomText); s->WriteSafeString(fDescription); s->WriteBool(fThumbnail != nil); if (fThumbnail != nil) mgr->WriteKey(s, fThumbnail->GetKey()); UInt32 texSkip = 0; for (i = 0; i < fTextures.GetCount(); i++) if (fTextures[i] == nil) texSkip++; s->WriteSwap32(fTextures.GetCount() - texSkip); for (i = 0; i < fTextures.GetCount(); i++) { if (fTextures[i] == nil) continue; s->WriteSafeString(fElementNames.Get(i)); int layerCount = 0; for (j = 0; j < plClothingElement::kLayerMax; j++) { // Run through once to get the count of valid layers if (fTextures[i][j] != nil) layerCount++; } s->WriteByte(layerCount); for (j = 0; j < plClothingElement::kLayerMax; j++) { if (fTextures[i][j] != nil) { s->WriteByte(j); mgr->WriteKey(s, fTextures[i][j]->GetKey()); } } } for (i = 0; i < kMaxNumLODLevels; i++) { s->WriteBool(fMeshes[i] != nil); if (fMeshes[i] != nil) mgr->WriteKey(s, fMeshes[i]->GetKey()); } // EXPORT ONLY plKey accessoryKey = nil; if (fAccessoryName) { char strBuf[512]; sprintf(strBuf, "CItm_%s", fAccessoryName); accessoryKey = plKeyFinder::Instance().StupidSearch("GlobalClothing", nil, plClothingItem::Index(), strBuf); if (accessoryKey == nil) { sprintf(strBuf, "Couldn't find accessory \"%s\". It won't show at runtime.", fAccessoryName); hsMessageBox(strBuf, GetKeyName(), hsMessageBoxNormal); } } mgr->WriteKey(s, accessoryKey); for (i = 0; i < 3; i++) { s->WriteByte(fDefaultTint1[i]); s->WriteByte(fDefaultTint2[i]); } } hsBool plClothingItem::MsgReceive(plMessage* msg) { plElementRefMsg *eMsg = plElementRefMsg::ConvertNoRef(msg); if (eMsg) { plMipmap *tex = plMipmap::ConvertNoRef(eMsg->GetRef()); if (tex) { if( eMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) { if (fTextures.GetCount() <= eMsg->fWhich) fTextures.ExpandAndZero(eMsg->fWhich + 1); if (fElementNames.GetCount() <= eMsg->fWhich) fElementNames.ExpandAndZero(eMsg->fWhich + 1); if (fElementNames.Get(eMsg->fWhich) == nil) fElementNames.Set(eMsg->fWhich, hsStrcpy(eMsg->fElementName)); if (fTextures.Get(eMsg->fWhich) == nil) { plMipmap **layers = TRACKED_NEW plMipmap*[plClothingElement::kLayerMax]; int i; for (i = 0; i < plClothingElement::kLayerMax; i++) layers[i] = nil; fTextures.Set(eMsg->fWhich, layers); } fTextures.Get(eMsg->fWhich)[eMsg->fLayer] = tex; } else if( eMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) { fTextures.Get(eMsg->fWhich)[eMsg->fLayer] = nil; } return true; } } plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg); if (refMsg) { plSharedMesh *mesh = plSharedMesh::ConvertNoRef(refMsg->GetRef()); if (mesh) { if (refMsg->fWhich >= 0 && refMsg->fWhich < kMaxNumLODLevels) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) fMeshes[refMsg->fWhich] = mesh; else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) fMeshes[refMsg->fWhich] = nil; } return true; } plMipmap *thumbnail = plMipmap::ConvertNoRef(refMsg->GetRef()); if (thumbnail) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) fThumbnail = thumbnail; else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) fThumbnail = nil; return true; } plClothingItem *accessory = plClothingItem::ConvertNoRef(refMsg->GetRef()); if (accessory) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) fAccessory = accessory; else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) fAccessory = nil; return true; } } return hsKeyedObject::MsgReceive(msg); } ///////////////////////////////////////////////////////////////////////////// hsBool plClosetItem::IsMatch(plClosetItem *other) { return (fItem == other->fItem && fOptions.IsMatch(&other->fOptions)); } ///////////////////////////////////////////////////////////////////////////// plClothingBase::plClothingBase() : fName(nil), fBaseTexture(nil), fLayoutName(nil) {} plClothingBase::~plClothingBase() { delete [] fName; delete [] fLayoutName; } void plClothingBase::Read(hsStream* s, hsResMgr* mgr) { hsKeyedObject::Read(s, mgr); fName = s->ReadSafeString(); if (s->ReadBool()) mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); fLayoutName = s->ReadSafeString(); } void plClothingBase::Write(hsStream* s, hsResMgr* mgr) { hsKeyedObject::Write(s, mgr); s->WriteSafeString(fName); s->WriteBool(fBaseTexture != nil); if (fBaseTexture != nil) mgr->WriteKey(s, fBaseTexture->GetKey()); s->WriteSafeString(fLayoutName); } hsBool plClothingBase::MsgReceive(plMessage* msg) { plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg); if (refMsg) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) { fBaseTexture = plMipmap::ConvertNoRef(refMsg->GetRef()); } else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) { fBaseTexture = nil; } return true; } return hsKeyedObject::MsgReceive(msg); } ///////////////////////////////////////////////////////////////////////////// plClothingOutfit::plClothingOutfit() : fTargetLayer(nil), fBase(nil), fGroup(0), fAvatar(nil), fSynchClients(false), fMaterial(nil), fVaultSaveEnabled(true), fMorphsInitDone(false) { fSkinTint.Set(1.f, 0.84, 0.71, 1.f); fItems.Reset(); int i; for (i = 0; i < plClothingLayout::kMaxTileset; i++) fDirtyItems.SetBit(i); for (i = 0; i < plClothingElement::kLayerSkinLast - plClothingElement::kLayerSkinFirst; i++) fSkinBlends[i] = 0.f; } plClothingOutfit::~plClothingOutfit() { fItems.Reset(); while (fOptions.GetCount() > 0) delete fOptions.Pop(); plgDispatch::Dispatch()->UnRegisterForExactType(plPreResourceMsg::Index(), GetKey()); } void plClothingOutfit::AddItem(plClothingItem *item, hsBool update /* = true */, hsBool broadcast /* = true */, hsBool netForce /* = false */) { if (fItems.Find(item) != fItems.kMissingIndex) return; plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kAddItem); msg->fItemKey = item->GetKey(); if (broadcast) msg->SetBCastFlag(plMessage::kNetPropagate); if ( netForce ) { // also doesn't make much sense to not net propagate netForced messages msg->SetBCastFlag(plMessage::kNetPropagate); msg->SetBCastFlag(plMessage::kNetForce); } if (update) msg->AddCommand(plClothingMsg::kUpdateTexture); plgDispatch::MsgSend(msg); } void plClothingOutfit::ForceUpdate(bool retry) { plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddCommand(plClothingMsg::kUpdateTexture); if (retry) msg->AddCommand(plClothingMsg::kRetry); // force a resend msg->Send(GetKey()); } void plClothingOutfit::RemoveItem(plClothingItem *item, hsBool update /* = true */, hsBool netForce /* = false */) { if (fItems.Find(item) == fItems.kMissingIndex) return; plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kRemoveItem); msg->SetBCastFlag(plMessage::kNetPropagate); if ( netForce ) msg->SetBCastFlag(plMessage::kNetForce); msg->fItemKey = item->GetKey(); if (update) msg->AddCommand(plClothingMsg::kUpdateTexture); plgDispatch::MsgSend(msg); } void plClothingOutfit::TintItem(plClothingItem *item, hsScalar red, hsScalar green, hsScalar blue, hsBool update /* = true */, hsBool broadcast /* = true */, hsBool netForce /* = false */, hsBool retry /* = true */, UInt8 layer /* = kLayerTint1 */) { plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kTintItem); msg->fItemKey = item->GetKey(); msg->fColor.Set(red, green, blue, 1.f); msg->fLayer = layer; if (broadcast) msg->SetBCastFlag(plMessage::kNetPropagate); if (netForce) { msg->SetBCastFlag(plMessage::kNetPropagate); msg->SetBCastFlag(plMessage::kNetForce); } if (update) msg->AddCommand(plClothingMsg::kUpdateTexture); if (retry) msg->AddCommand(plClothingMsg::kRetry); plgDispatch::MsgSend(msg); } void plClothingOutfit::TintSkin(hsScalar red, hsScalar green, hsScalar blue, hsBool update /* = true */, hsBool broadcast /* = true */) { plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kTintSkin); msg->fColor.Set(red, green, blue, 1.f); if (broadcast) msg->SetBCastFlag(plMessage::kNetPropagate); if (update) msg->AddCommand(plClothingMsg::kUpdateTexture); plgDispatch::MsgSend(msg); } void plClothingOutfit::MorphItem(plClothingItem *item, UInt8 layer, UInt8 delta, hsScalar weight, hsBool retry /* = true */) { plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kMorphItem); msg->fItemKey = item->GetKey(); msg->fLayer = layer; msg->fDelta = delta; msg->fWeight = weight; if (retry) msg->AddCommand(plClothingMsg::kRetry); plgDispatch::MsgSend(msg); } void plClothingOutfit::SetAge(hsScalar age, hsBool update /* = true */, hsBool broadcast /* = true */) { SetSkinBlend(age, plClothingElement::kLayerSkinBlend1, update, broadcast); } void plClothingOutfit::SetSkinBlend(hsScalar blend, UInt8 layer, hsBool update /* = true */, hsBool broadcast /* = true */) { plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kBlendSkin); msg->fLayer = layer; msg->fColor.Set(1.f, 1.f, 1.f, blend); if (broadcast) msg->SetBCastFlag(plMessage::kNetPropagate); if (update) msg->AddCommand(plClothingMsg::kUpdateTexture); plgDispatch::MsgSend(msg); } hsScalar plClothingOutfit::GetSkinBlend(UInt8 layer) { if (layer >= plClothingElement::kLayerSkinBlend1 && layer <= plClothingElement::kLayerSkinLast) return fSkinBlends[layer - plClothingElement::kLayerSkinBlend1]; return 0; } void plClothingOutfit::IAddItem(plClothingItem *item) { if (item->fGroup != fGroup) { // Trying to wear clothing from the wrong group, remove the ref. GetKey()->Release(item->GetKey()); return; } if (fItems.Find(item) == fItems.kMissingIndex) { // Remove any other item we have that can't be worn with this int i; for (i = fItems.GetCount() - 1; i >= 0; i--) { if (!item->CanWearWith(fItems[i])) { plClothingItem *goner = fItems[i]; if (goner->fAccessory) { GetKey()->Release(goner->fAccessory->GetKey()); IRemoveItem(goner->fAccessory); } GetKey()->Release(goner->GetKey()); IRemoveItem(goner); // Can't wait for the ref message to process } } for (i = 0; i < fItems.GetCount(); i++) { if (item->WearBefore(fItems[i])) break; } fItems.Insert(i, item); plClothingItemOptions *op = TRACKED_NEW plClothingItemOptions; fOptions.Insert(i, op); IInstanceSharedMeshes(item); fDirtyItems.SetBit(item->fTileset); // A bit of hackage for bare feet sound effects. if (item->fType == plClothingMgr::kTypeLeftFoot) { plArmatureEffectsMgr *mgr = fAvatar->GetArmatureEffects(); plArmatureEffectFootSound *soundEffect = nil; int num = mgr->GetNumEffects(); int i; for (i = 0; i < num; i++) { if (soundEffect = plArmatureEffectFootSound::ConvertNoRef(mgr->GetEffect(i))) break; } if (soundEffect) { if (!strcmp(item->fName, "03_MLFoot04_01") || !strcmp(item->fName, "03_FLFoot04_01")) soundEffect->SetFootType(plArmatureEffectFootSound::kFootTypeBare); else soundEffect->SetFootType(plArmatureEffectFootSound::kFootTypeShoe); } } } } void plClothingOutfit::IRemoveItem(plClothingItem *item) { // We may just be removing the ref... UInt32 index = fItems.Find(item); if (index != fItems.kMissingIndex) { fItems.Remove(index); delete fOptions.Get(index); fOptions.Remove(index); IRemoveSharedMeshes(item); fDirtyItems.SetBit(item->fTileset); } } hsBool plClothingOutfit::ITintItem(plClothingItem *item, hsColorRGBA color, UInt8 layer) { UInt32 index = fItems.Find(item); if (index != fItems.kMissingIndex) { if (layer == plClothingElement::kLayerTint1) fOptions[index]->fTint1 = color; if (layer == plClothingElement::kLayerTint2) fOptions[index]->fTint2 = color; fDirtyItems.SetBit(item->fTileset); if (fItems[index]->fAccessory) { plClothingItem *acc = fItems[index]->fAccessory; UInt32 accIndex = fItems.Find(acc); if (accIndex != fItems.kMissingIndex) { if (layer == plClothingElement::kLayerTint1) fOptions[accIndex]->fTint1 = color; if (layer == plClothingElement::kLayerTint2) fOptions[accIndex]->fTint2 = color; fDirtyItems.SetBit(acc->fTileset); } } return true; } return false; } hsColorRGBA plClothingOutfit::GetItemTint(plClothingItem *item, UInt8 layer /* = kLayerTint1 */) const { if (layer >= plClothingElement::kLayerSkinFirst && layer <= plClothingElement::kLayerSkinLast) return fSkinTint; UInt32 index = fItems.Find(item); if (index != fItems.kMissingIndex) { if (layer == plClothingElement::kLayerTint1) return fOptions[index]->fTint1; if (layer == plClothingElement::kLayerTint2) return fOptions[index]->fTint2; } hsColorRGBA color; color.Set(1.f, 1.f, 1.f, 1.f); return color; } hsBool plClothingOutfit::IMorphItem(plClothingItem *item, UInt8 layer, UInt8 delta, hsScalar weight) { UInt32 index = fItems.Find(item); if (index != fItems.kMissingIndex) { int i; for (i = 0; i < fAvatar->GetNumLOD(); i++) { if (item->fMeshes[i]->fMorphSet == nil) continue; const plSceneObject *so = fAvatar->GetClothingSO(i); if (!so) continue; plMorphSequence *seq = const_cast(plMorphSequence::ConvertNoRef(so->GetModifierByType(plMorphSequence::Index()))); plKey meshKey = item->fMeshes[i]->GetKey(); // Lower LOD objects don't have all the morphs, so check if this one is in range. if (seq && layer < seq->GetNumLayers(meshKey) && delta < seq->GetNumDeltas(layer, meshKey)) seq->SetWeight(layer, delta, weight, meshKey); } return true; } return false; } void plClothingOutfit::Read(hsStream* s, hsResMgr* mgr) { plSynchedObject::Read(s, mgr); fGroup = s->ReadByte(); mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // plClothingBase if (fGroup != plClothingMgr::kClothingBaseNoOptions) { mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // target layer mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1), plRefFlags::kActiveRef); // material } plgDispatch::Dispatch()->RegisterForExactType(plPreResourceMsg::Index(), GetKey()); //ReadItems(s, mgr, false); } void plClothingOutfit::Write(hsStream* s, hsResMgr* mgr) { plSynchedObject::Write(s, mgr); s->WriteByte(fGroup); mgr->WriteKey(s, fBase->GetKey()); if (fGroup != plClothingMgr::kClothingBaseNoOptions) { mgr->WriteKey(s, fTargetLayer->GetKey()); mgr->WriteKey(s, fMaterial->GetKey()); } //WriteItems(s, mgr); } void plClothingOutfit::StripAccessories() { int i; for (i = fItems.GetCount() - 1; i >= 0; i--) { if (fItems[i]->fType == plClothingMgr::kTypeAccessory) { GetKey()->Release(fItems[i]->GetKey()); IRemoveItem(fItems[i]); } } } void plClothingOutfit::IHandleMorphSDR(plStateDataRecord *sdr) { plSimpleStateVariable *lodVar = sdr->FindVar(plMorphSequenceSDLMod::kStrTargetID); if (!lodVar) return; UInt8 lod; lodVar->Get(&lod); const plSceneObject *so = fAvatar->GetClothingSO(lod); if (!so) return; plMorphSequenceSDLMod *morph = const_cast(plMorphSequenceSDLMod::ConvertNoRef(so->GetModifierByType(plMorphSequenceSDLMod::Index()))); if (morph) morph->SetCurrentStateFrom(sdr); } void plClothingOutfit::ReadFromVault() { SetupMorphSDL(); WearDefaultClothing(); RelVaultNode * rvn; if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef())) return; ARRAY(RelVaultNode*) nodes; rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes); for (unsigned i = 0; i < nodes.Count(); ++i) { VaultSDLNode sdl(nodes[i]); if (sdl.sdlDataLen) { hsRAMStream ram; ram.Write(sdl.sdlDataLen, sdl.sdlData); ram.Rewind(); char * sdlRecName = nil; int sdlRecVersion; plStateDataRecord::ReadStreamHeader(&ram, &sdlRecName, &sdlRecVersion); plStateDescriptor * desc = plSDLMgr::GetInstance()->FindDescriptor(sdlRecName, sdlRecVersion); if (desc) { plStateDataRecord * sdlDataRec = TRACKED_NEW plStateDataRecord(desc); if (sdlDataRec->Read(&ram, 0)) { if (!strcmp(sdlRecName, kSDLMorphSequence)) IHandleMorphSDR(sdlDataRec); else plClothingSDLModifier::HandleSingleSDR(sdlDataRec, this); } delete sdlDataRec; } delete [] sdlRecName; } nodes[i]->DecRef(); } fSynchClients = true; // set true if the next synch should be bcast ForceUpdate(true); rvn->DecRef(); } void plClothingOutfit::SaveCustomizations(hsBool retry /* = true */) { plClothingMsg *msg = TRACKED_NEW plClothingMsg(); msg->AddReceiver(GetKey()); msg->AddCommand(plClothingMsg::kSaveCustomizations); if (retry) msg->AddCommand(plClothingMsg::kRetry); msg->Send(); } void plClothingOutfit::WriteToVault() { if (!fVaultSaveEnabled) return; RelVaultNode * rvn; if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef())) return; ARRAY(plStateDataRecord*) SDRs; plStateDataRecord clothingSDR(kSDLClothing); fAvatar->GetClothingSDLMod()->PutCurrentStateIn(&clothingSDR); plSDStateVariable * clothesStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrWardrobe); for (unsigned i = 0; i < clothesStateDesc->GetCount(); ++i) SDRs.Add(clothesStateDesc->GetStateDataRecord(i)); plSDStateVariable * appearanceStateDesc = clothingSDR.FindSDVar(plClothingSDLModifier::kStrAppearance); // for skin tint SDRs.Add(appearanceStateDesc->GetStateDataRecord(0)); WriteToVault(SDRs); rvn->DecRef(); } void plClothingOutfit::WriteToVault(const ARRAY(plStateDataRecord*) & SDRs) { // We'll hit this case when the server asks us to save state for NPCs. if (fAvatar->GetTarget(0) != plNetClientApp::GetInstance()->GetLocalPlayer()) return; RelVaultNode * rvn; if (nil == (rvn = VaultGetAvatarOutfitFolderIncRef())) return; ARRAY(plStateDataRecord*) morphs; // Gather morph SDRs hsTArray morphsSDRs; plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphsSDRs); for (unsigned i = 0; i < morphsSDRs.GetCount(); ++i) { for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++) { if (fAvatar->GetClothingSO(j) == morphsSDRs[i]->GetTarget(0)) { plStateDataRecord * morphSDR = TRACKED_NEW plStateDataRecord(kSDLMorphSequence); plSimpleStateVariable * lodVar = morphSDR->FindVar(plMorphSequenceSDLMod::kStrTargetID); if (lodVar) lodVar->Set((int)j); morphsSDRs[i]->GetSDLMod()->PutCurrentStateIn(morphSDR); morphs.Add(morphSDR); } } } ARRAY(RelVaultNode*) templates; ARRAY(RelVaultNode*) actuals; ARRAY(RelVaultNode*) nodes; // Get all existing clothing SDRs rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes); // REF: Find const ARRAY(plStateDataRecord*) * arrs[] = { &SDRs, &morphs, }; for (unsigned arrIdx = 0; arrIdx < arrsize(arrs); ++arrIdx) { const ARRAY(plStateDataRecord*) * arr = arrs[arrIdx]; // Write all SDL to to the outfit folder, reusing existing nodes and creating new ones as necessary for (unsigned i = 0; i < arr->Count(); ++i) { RelVaultNode * node; if (nodes.Count()) { node = nodes[0]; nodes.DeleteUnordered(0); node->IncRef(); // REF: Work node->DecRef(); // REF: Find } else { RelVaultNode * templateNode = NEWZERO(RelVaultNode); templateNode->SetNodeType(plVault::kNodeType_SDL); templates.Add(templateNode); node = templateNode; node->IncRef(); // REF: Create node->IncRef(); // REF: Work } VaultSDLNode sdl(node); sdl.SetStateDataRecord((*arr)[i], 0); node->DecRef(); // REF: Work } } // Delete any leftover nodes { for (unsigned i = 0; i < nodes.Count(); ++i) { VaultDeleteNode(nodes[i]->nodeId); nodes[i]->DecRef(); // REF: Array }} // Create actual new nodes from their templates { for (unsigned i = 0; i < templates.Count(); ++i) { ENetError result; if (RelVaultNode * actual = VaultCreateNodeAndWaitIncRef(templates[i], &result)) { actuals.Add(actual); } templates[i]->DecRef(); // REF: Create }} // Add new nodes to outfit folder { for (unsigned i = 0; i < actuals.Count(); ++i) { VaultAddChildNodeAndWait(rvn->nodeId, actuals[i]->nodeId, NetCommGetPlayer()->playerInt); actuals[i]->DecRef(); // REF: Create }} // Cleanup morph SDRs {for (unsigned i = 0; i < morphs.Count(); ++i) { DEL(morphs[i]); }} rvn->DecRef(); } // XXX HACK. DON'T USE (this function exists for the temp console command Clothing.SwapClothTexHACK) void plClothingOutfit::DirtyTileset(int tileset) { fDirtyItems.SetBit(tileset); ForceUpdate(true); } void plClothingOutfit::IUpdate() { //GenerateTexture(); fAvatar->RefreshTree(); DirtySynchState(kSDLClothing, 0); plArmatureMod * avMod = plAvatarMgr::GetInstance()->GetLocalAvatar(); hsAssert(avMod,"plArmatureMod not found for local player"); if (avMod->GetClothingOutfit()==this) { // Let the GUI know we changed clothes plClothingUpdateBCMsg *BCMsg = TRACKED_NEW plClothingUpdateBCMsg(); BCMsg->SetSender(GetKey()); plgDispatch::MsgSend(BCMsg); } } void plClothingOutfit::WearDefaultClothing() { StripAccessories(); plClothingMgr *cMgr = plClothingMgr::GetClothingMgr(); hsTArrayitems; cMgr->GetItemsByGroup(fGroup, items); // Wear one thing of each type UInt32 i, j; for (i = 0; i < plClothingMgr::kMaxType; i++) { if (i == plClothingMgr::kTypeAccessory) continue; for (j = 0; j < items.GetCount(); j++) { if (items[j]->fType == i) { AddItem(items[j], false, false); if (i == plClothingMgr::kTypeHair || i == plClothingMgr::kTypeFace) { // Hair tint color TintItem(items[j], 0.5, 0.3, 0.2, false, false); } else { TintItem(items[j], items[j]->fDefaultTint1[0] / 255.f, items[j]->fDefaultTint1[1] / 255.f, items[j]->fDefaultTint1[2] / 255.f, false, false); } // Everyone can tint layer 2. Go nuts! TintItem(items[j], items[j]->fDefaultTint2[0] / 255.f, items[j]->fDefaultTint2[1] / 255.f, items[j]->fDefaultTint2[2] / 255.f, false, false, false, true, plClothingElement::kLayerTint2); break; } } } } void plClothingOutfit::WearDefaultClothingType(UInt32 clothingType) { plClothingMgr *cMgr = plClothingMgr::GetClothingMgr(); hsTArray items; cMgr->GetItemsByGroup(fGroup, items); UInt32 i; for (i=0; ifType == clothingType) { AddItem(items[i], false, false); if (clothingType == plClothingMgr::kTypeHair || clothingType == plClothingMgr::kTypeFace) { // Hair tint color TintItem(items[i], 0.5, 0.3, 0.2, false, false); } else { TintItem(items[i], items[i]->fDefaultTint1[0] / 255.f, items[i]->fDefaultTint1[1] / 255.f, items[i]->fDefaultTint1[2] / 255.f, false, false); } // Everyone can tint layer 2. Go nuts! TintItem(items[i], items[i]->fDefaultTint2[0] / 255.f, items[i]->fDefaultTint2[1] / 255.f, items[i]->fDefaultTint2[2] / 255.f, false, false, false, true, plClothingElement::kLayerTint2); break; } } } void plClothingOutfit::WearMaintainerOutfit() { fVaultSaveEnabled = false; WearDefaultClothing(); plClothingItem *item; if (fGroup == plClothingMgr::kClothingBaseMale) { item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MHAcc_SuitHelmet"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLHand_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MRHand_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MTorso_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLegs_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MLFoot_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_MRFoot_Suit"); if (item) AddItem(item, false, false); } else if (fGroup == plClothingMgr::kClothingBaseFemale) { item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FHair_SuitHelmet"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLHand_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FRHand_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FTorso_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLegs_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FLFoot_Suit"); if (item) AddItem(item, false, false); item = plClothingMgr::GetClothingMgr()->FindItemByName("03_FRFoot_Suit"); if (item) AddItem(item, false, false); } fSynchClients = true; ForceUpdate(true); } void plClothingOutfit::RemoveMaintainerOutfit() { ReadFromVault(); fVaultSaveEnabled = true; } static plRandom sRandom; void plClothingOutfit::WearRandomOutfit() { plClothingMgr *cMgr = plClothingMgr::GetClothingMgr(); hsTArrayitems; // Wear one thing of each type UInt32 i, j; for (i = 0; i < plClothingMgr::kMaxType; i++) { if (i == plClothingMgr::kTypeAccessory) continue; items.Reset(); cMgr->GetItemsByGroupAndType(fGroup, (UInt8)i, items); j = (UInt32)(sRandom.RandZeroToOne() * items.GetCount()); hsScalar r1 = sRandom.RandZeroToOne(); hsScalar g1 = sRandom.RandZeroToOne(); hsScalar b1 = sRandom.RandZeroToOne(); hsScalar r2 = sRandom.RandZeroToOne(); hsScalar g2 = sRandom.RandZeroToOne(); hsScalar b2 = sRandom.RandZeroToOne(); AddItem(items[j], false, false); TintItem(items[j], r1, g1, b1, false, false, false, true, 1); TintItem(items[j], r2, g2, b2, false, false, false, true, 2); plClothingItem *match = cMgr->GetLRMatch(items[j]); if (match) { AddItem(match, false, false); TintItem(match, r1, g1, b1, false, false, false, true, 1); TintItem(match, r2, g2, b2, false, false, false, true, 2); } } TintSkin(sRandom.RandZeroToOne(), sRandom.RandZeroToOne(), sRandom.RandZeroToOne()); } hsBool plClothingOutfit::ReadItems(hsStream* s, hsResMgr* mgr, hsBool broadcast /* = true */) { hsBool result = true; UInt32 numItems = s->ReadSwap32(); int i; for (i = 0; i < numItems; i++) { plKey key = mgr->ReadKey( s ); hsColorRGBA color; hsColorRGBA color2; color.Read(s); color2.Read(s); // Make sure to read everything in before hitting this and possibly skipping to // the next item, lest we disrupt the stream. if( key == nil ) { hsAssert( false, "Nil item in plClothingOutfit::ReadItems(). The vault probably contains a key with a plLocation that's moved since then. Tsk, tsk." ); result = false; continue; } plClothingItem *item = plClothingItem::ConvertNoRef( key->GetObjectPtr() ); AddItem(item, false, broadcast); TintItem(item, color.r, color.g, color.b, false, broadcast, false, true, plClothingElement::kLayerTint1); TintItem(item, color2.r, color2.g, color2.b, false, broadcast, false, true, plClothingElement::kLayerTint2); } return result; } void plClothingOutfit::WriteItems(hsStream *s, hsResMgr *mgr) { s->WriteSwap32(fItems.GetCount()); int i; for (i = 0; i < fItems.GetCount(); i++) { mgr->WriteKey(s, fItems.Get(i)->GetKey()); fOptions.Get(i)->fTint1.Write(s); fOptions.Get(i)->fTint2.Write(s); } } hsBool plClothingOutfit::MsgReceive(plMessage* msg) { plPreResourceMsg *preMsg = plPreResourceMsg::ConvertNoRef(msg); if (preMsg) { if (fAvatar && fGroup != plClothingMgr::kClothingBaseNoOptions) { plDrawable *spans = fAvatar->FindDrawable(); const hsBounds3Ext &bnds = spans->GetWorldBounds(); if (bnds.GetType() == kBoundsNormal) { // This is a bit hacky... The drawable code has just run through and updated // each span's bounds (see plDrawableSpans::IUpdateMatrixPaletteBoundsHack()) // but not the world bounds for the entire drawable. So we tell the space tree // to refresh. However, the pageTreeMgr would then get confused because the // space tree is no longer dirty (see plPageTreeMgr::IRefreshTree()), // causing the avatar to only draw if the origin is in view. // So we just force it dirty, and everyone's happy. spans->GetSpaceTree()->Refresh(); spans->GetSpaceTree()->MakeDirty(); // Where were we? Oh yeah... if this avatar is in view it needs a texture. Tell // the pipeline. if (preMsg->Pipeline()->TestVisibleWorld(spans->GetSpaceTree()->GetWorldBounds())) preMsg->Pipeline()->SubmitClothingOutfit(this); } } } plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg); if (refMsg) { plLayer *layer = plLayer::ConvertNoRef(refMsg->GetRef()); if (layer) { if (refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) fTargetLayer = layer; else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) fTargetLayer = nil; return true; } plClothingItem *item = plClothingItem::ConvertNoRef(refMsg->GetRef()); if (item) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest) ) { IAddItem(item); } else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) { IRemoveItem(item); } return true; } plClothingBase *base = plClothingBase::ConvertNoRef(refMsg->GetRef()); if (base) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) fBase = base; else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) fBase = nil; return true; } hsGMaterial *mat = hsGMaterial::ConvertNoRef(refMsg->GetRef()); if (mat) { if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) ) fMaterial = mat; else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) fMaterial = nil; } } // For now: Adding an item fires off a notify, and the item won't get added until the ref message // is processed, BUT we need to make sure that task doesn't require any more messages (like the // trickling of chains of AddViaNotifies), so that other messages received after it (like tint // commands) just have to check a kRetry flag, resend the message, and be confident that the item // will be present by the time their resent message is handled. // // Should we ever handle AddViaNotify in a separate thread, this will blow up. (Ok, we'll just have // bugs about the characters not having the right clothings options visible. No explosions.) plClothingMsg *cMsg = plClothingMsg::ConvertNoRef(msg); if (cMsg) { if (cMsg->GetCommand(plClothingMsg::kAddItem)) { plGenRefMsg *msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(cMsg->fItemKey, msg, plRefFlags::kActiveRef); plClothingItem *accessory = plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr())->fAccessory; if (accessory) { plGenRefMsg *msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(accessory->GetKey(), msg, plRefFlags::kActiveRef); } } if (cMsg->GetCommand(plClothingMsg::kRemoveItem)) { plClothingItem *item = plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()); if (item && item->fType == plClothingMgr::kTypeAccessory) GetKey()->Release(cMsg->fItemKey); } if (cMsg->GetCommand(plClothingMsg::kTintItem)) { if (!ITintItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fColor, cMsg->fLayer) && cMsg->GetCommand(plClothingMsg::kRetry)) { // We failed to tint because we're not yet wearing the item. // However, the kRetry flag is set, so we fire another tint command off TintItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fColor.r, cMsg->fColor.g, cMsg->fColor.b, cMsg->GetCommand(plClothingMsg::kUpdateTexture), false, false, false, cMsg->fLayer); return true; } } if (cMsg->GetCommand(plClothingMsg::kMorphItem)) { if (cMsg->GetCommand(plClothingMsg::kRetry)) MorphItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fLayer, cMsg->fDelta, cMsg->fWeight, false); else IMorphItem(plClothingItem::ConvertNoRef(cMsg->fItemKey->GetObjectPtr()), cMsg->fLayer, cMsg->fDelta, cMsg->fWeight); } if (cMsg->GetCommand(plClothingMsg::kTintSkin)) { fSkinTint = cMsg->fColor; int i, j; for (i = 0; i < fItems.GetCount(); i++) for (j = 0; j < fItems[i]->fElements.GetCount(); j++) if (fItems[i]->fTextures[j][plClothingElement::kLayerSkin] != nil) fDirtyItems.SetBit(fItems[i]->fTileset); } if (cMsg->GetCommand(plClothingMsg::kBlendSkin)) { hsScalar blend = cMsg->fColor.a; if (blend > 1.f) blend = 1.f; if (blend < 0.f) blend = 0.f; if (cMsg->fLayer >= plClothingElement::kLayerSkinBlend1 && cMsg->fLayer <= plClothingElement::kLayerSkinBlend6) { fSkinBlends[cMsg->fLayer - plClothingElement::kLayerSkinBlend1] = blend; int i, j; for (i = 0; i < fItems.GetCount(); i++) for (j = 0; j < fItems[i]->fElements.GetCount(); j++) if (fItems[i]->fTextures[j][cMsg->fLayer] != nil) fDirtyItems.SetBit(fItems[i]->fTileset); } } if (cMsg->GetCommand(plClothingMsg::kSaveCustomizations)) { if (cMsg->GetCommand(plClothingMsg::kRetry)) SaveCustomizations(false); else WriteToVault(); } // Make sure to check for an update last if (cMsg->GetCommand(plClothingMsg::kUpdateTexture)) { // If kUpdateTexture was not the only command, we need to resend the update // as a solo command, so that it happens after any other AddViaNotify messages if (cMsg->ResendUpdate()) { plClothingMsg *update = TRACKED_NEW plClothingMsg(); update->AddReceiver(GetKey()); update->AddCommand(plClothingMsg::kUpdateTexture); plgDispatch::MsgSend(update); } else IUpdate(); } return true; } return hsKeyedObject::MsgReceive(msg); } // // TESTING SDL // Send clothing sendState msg to object's plClothingSDLModifier // hsBool plClothingOutfit::DirtySynchState(const char* SDLStateName, UInt32 synchFlags) { plSynchEnabler ps(true); // make sure synching is enabled, since this happens during load synchFlags |= plSynchedObject::kForceFullSend; // TEMP if (fSynchClients) synchFlags |= plSynchedObject::kBCastToClients; fSynchClients=false; hsAssert(fAvatar, "nil fAvatar"); return fAvatar->GetTarget(0)->DirtySynchState(SDLStateName, synchFlags); } // Note: Currently the word "instance" is a lie. We just copy. In the future // we'll be good about this, but I wanted to get it working first. void plClothingOutfit::IInstanceSharedMeshes(plClothingItem *item) { if (fAvatar) fAvatar->ValidateMesh(); hsBool partialSort = item->fCustomText && strstr(item->fCustomText, "NeedsSort"); int i; for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++) { const plSceneObject *so = fAvatar->GetClothingSO(i); if (so != nil && item->fMeshes[i] != nil) { plInstanceDrawInterface *idi = const_cast(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface())); if (idi) idi->AddSharedMesh(item->fMeshes[i], fMaterial, !item->HasBaseAlpha(), i, partialSort); } } } void plClothingOutfit::IRemoveSharedMeshes(plClothingItem *item) { if (fAvatar == nil) return; int i; for (i = 0; i < plClothingItem::kMaxNumLODLevels; i++) { const plSceneObject *so = fAvatar->GetClothingSO(i); if (so != nil && item->fMeshes[i] != nil) { plInstanceDrawInterface *idi = const_cast(plInstanceDrawInterface::ConvertNoRef(so->GetDrawInterface())); if (idi) idi->RemoveSharedMesh(item->fMeshes[i]); } } } void plClothingOutfit::SetupMorphSDL() { if (!fMorphsInitDone) { hsTArray morphs; plMorphSequence::FindMorphMods(fAvatar->GetTarget(0), morphs); for (unsigned i = 0; i < morphs.GetCount(); ++i) { for (unsigned j = 0; j < fAvatar->GetNumLOD(); j++) { if (fAvatar->GetClothingSO(j) == morphs[i]->GetTarget(0)) { plMorphSequenceSDLMod* morph = morphs[i]->GetSDLMod(); if (morph) morph->SetIsAvatar(true); } } } fMorphsInitDone = true; } } ///////////////////////////////////////////////////////////////////////////// const char *plClothingMgr::GroupStrings[] = { "Male Clothing", "Female Clothing", "(No Clothing Options)" }; const char *plClothingMgr::TypeStrings[] = { "Pants", "Shirt", "LeftHand", "RightHand", "Face", "Hair", "LeftFoot", "RightFoot", "Accessory" }; plClothingMgr *plClothingMgr::fInstance = nil; plClothingMgr::plClothingMgr() { fLayouts.Reset(); fItems.Reset(); } plClothingMgr::~plClothingMgr() { while (fElements.GetCount() > 0) delete fElements.Pop(); while (fLayouts.GetCount() > 0) delete fLayouts.Pop(); while (fItems.GetCount() > 0) delete fItems.Pop(); } plClothingLayout *plClothingMgr::GetLayout(char *name) { int i; for (i = 0; i < fLayouts.GetCount(); i++) { if (!strcmp(fLayouts.Get(i)->fName, name)) return fLayouts.Get(i); } return nil; } plClothingElement *plClothingMgr::FindElementByName(char *name) { int i; for (i = 0; i < fElements.GetCount(); i++) { if (!strcmp(fElements.Get(i)->fName, name)) return fElements.Get(i); } return nil; } void plClothingMgr::AddItemsToCloset(hsTArray &items) { RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef(); if (!rvn) return; hsTArray closet; GetClosetItems(closet); ARRAY(RelVaultNode*) templates; for (unsigned i = 0; i < items.GetCount(); ++i) { bool match = false; for (unsigned j = 0; j < closet.GetCount(); ++j) { if (closet[j].IsMatch(&items[i])) { match = true; break; } } if (match) continue; plStateDataRecord rec(plClothingSDLModifier::GetClothingItemSDRName()); plClothingSDLModifier::PutSingleItemIntoSDR(&items[i], &rec); RelVaultNode * templateNode = NEWZERO(RelVaultNode); templateNode->IncRef(); templateNode->SetNodeType(plVault::kNodeType_SDL); VaultSDLNode sdl(templateNode); sdl.SetStateDataRecord(&rec); templates.Add(templateNode); } for (unsigned i = 0; i < templates.Count(); ++i) { ENetError result; if (RelVaultNode * actual = VaultCreateNodeAndWaitIncRef(templates[i], &result)) { VaultAddChildNodeAndWait( rvn->nodeId, actual->nodeId, NetCommGetPlayer()->playerInt ); actual->DecRef(); // REF: Create } templates[i]->DecRef(); // REF: Create } rvn->DecRef(); } void plClothingMgr::GetClosetItems(hsTArray &out) { RelVaultNode * rvn = VaultGetAvatarClosetFolderIncRef(); if (!rvn) return; ARRAY(RelVaultNode*) nodes; rvn->GetChildNodesIncRef(plVault::kNodeType_SDL, 1, &nodes); out.SetCount(nodes.Count()); for (unsigned i = 0; i < nodes.Count(); ++i) { VaultSDLNode sdl(nodes[i]); plStateDataRecord * rec = NEWZERO(plStateDataRecord); if (sdl.GetStateDataRecord(rec, 0)) plClothingSDLModifier::HandleSingleSDR(rec, nil, &out[i]); DEL(rec); } if (out.GetCount()) { for (int i = out.GetCount() - 1; i >= 0; i--) { if (out[i].fItem == nil) out.Remove(i); } } rvn->DecRef(); } void plClothingMgr::GetAllWithSameMesh(plClothingItem *item, hsTArray &out) { int i; for (i = 0; i < fItems.GetCount(); i++) { if (item->HasSameMeshes(fItems[i])) out.Append(fItems[i]); } } // Yes, it's a lame n^2 function. Show me that we have enough items for it // to matter and I'll speed it up. void plClothingMgr::FilterUniqueMeshes(hsTArray &items) { int i, j; for (i = items.GetCount() - 1; i >= 1; i--) { for (j = i - 1; j >= 0; j--) { if (items[i]->HasSameMeshes(items[j])) { items.Remove(i); break; } } } } plClothingItem *plClothingMgr::FindItemByName(const char *name) { if (!name) return nil; int i; for (i = 0; i < fItems.GetCount(); i++) { plClothingItem* item = fItems.Get(i); if (!strcmp(item->fName, name)) return item; } return nil; } void plClothingMgr::GetItemsByGroup(UInt8 group, hsTArray &out) { int i; for (i = 0; i < fItems.GetCount(); i++) { if (fItems.Get(i)->fGroup == group) out.Append(fItems.Get(i)); } } void plClothingMgr::GetItemsByGroupAndType(UInt8 group, UInt8 type, hsTArray &out) { int i; for (i = 0; i < fItems.GetCount(); i++) { if (fItems.Get(i)->fGroup == group && fItems.Get(i)->fType == type) out.Append(fItems.Get(i)); } } plClothingItem *plClothingMgr::GetLRMatch(plClothingItem *item) { int i; for (i = 0; i < fItems.GetCount(); i++) { if (IsLRMatch(item, fItems[i])) return fItems[i]; } // Couldn't find one. return nil; } hsBool plClothingMgr::IsLRMatch(plClothingItem *item1, plClothingItem *item2) { if (!item1 || !item2) return false; switch (item1->fType) { case kTypeLeftHand: if (item2->fType != kTypeRightHand) return false; break; case kTypeRightHand: if (item2->fType != kTypeLeftHand) return false; break; case kTypeLeftFoot: if (item2->fType != kTypeRightFoot) return false; break; case kTypeRightFoot: if (item2->fType != kTypeLeftFoot) return false; break; default: // if its not a matching kinda item, then there can be no match return false; } // Types check out fine, now compare textures if (item1->fTextures.GetCount() != item2->fTextures.GetCount()) return false; int i, j; for (i = 0; i < item1->fTextures.GetCount(); i++) { for (j = 0; j < plClothingElement::kLayerMax; j++) if (item1->fTextures[i][j] != item2->fTextures[i][j]) return false; } // Finally... we're not our own match return item1 != item2; } void plClothingMgr::Init() { fInstance = TRACKED_NEW plClothingMgr; fInstance->RegisterAs(kClothingMgr_KEY); fInstance->IInit(); } void plClothingMgr::IInit() { plClothingElement::GetElements(fElements); plClothingLayout *layout = TRACKED_NEW plClothingLayout("BasicHuman", 1024); layout->fElements.Append(FindElementByName("shirt-chest")); layout->fElements.Append(FindElementByName("shirt-sleeve")); layout->fElements.Append(FindElementByName("face")); layout->fElements.Append(FindElementByName("eyeball")); layout->fElements.Append(FindElementByName("shoe-top")); layout->fElements.Append(FindElementByName("shoe-bottom")); layout->fElements.Append(FindElementByName("pants")); layout->fElements.Append(FindElementByName("hand-LOD")); layout->fElements.Append(FindElementByName("hand-square")); layout->fElements.Append(FindElementByName("hand-wide")); fLayouts.Append(layout); } void plClothingMgr::DeInit() { if (fInstance) { fInstance->UnRegisterAs(kClothingMgr_KEY); fInstance = nil; } } hsBool plClothingMgr::MsgReceive(plMessage* msg) { plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef(msg); if (refMsg) { plClothingItem *item = plClothingItem::ConvertNoRef(refMsg->GetRef()); if (item) { if( refMsg->GetContext() & (plRefMsg::kOnCreate) ) { IAddItem(item); } else if( refMsg->GetContext() & (plRefMsg::kOnDestroy|plRefMsg::kOnRemove) ) { fItems.RemoveItem(item); } return true; } } return hsKeyedObject::MsgReceive(msg); } void plClothingMgr::IAddItem(plClothingItem *item) { hsBool allFound = true; int i, j; for (i = 0; i < item->fElementNames.GetCount(); i++) { for (j = 0; j < fElements.GetCount(); j++) { if (!strcmp(item->fElementNames.Get(i), fElements.Get(j)->fName)) { item->fElements.Set(i, fElements.Get(j)); break; } } if (j >= fElements.GetCount()) { allFound = false; break; } } if (allFound) { for (i = 0; i < fItems.GetCount(); i++) { if (fItems[i]->fSortOrder >= item->fSortOrder) break; } fItems.InsertAtIndex(i, item); } else hsAssert(false, "Couldn't match all elements of added clothing item."); } void plClothingMgr::ChangeAvatar(char *name) { plAvatarMgr::GetInstance()->UnLoadLocalPlayer(); plAvatarMgr::GetInstance()->LoadPlayer(name, nil); }