Files
SingularityViewer/indra/newview/llappearancemgr.cpp
2013-05-26 05:45:00 +02:00

4399 lines
136 KiB
C++

/**
* @file llappearancemgr.cpp
* @brief Manager for initiating appearance changes on the viewer
*
* $LicenseInfo:firstyear=2004&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llagent.h"
#include "llagentcamera.h"
#include "llagentwearables.h"
#include "llappearancemgr.h"
#include "llattachmentsmgr.h"
#include "llcommandhandler.h"
#include "lleventtimer.h"
#include "llgesturemgr.h"
#include "llinventorybridge.h"
#include "llinventoryfunctions.h"
#include "llinventoryobserver.h"
#include "llnotificationsutil.h"
#include "lloutfitobserver.h"
//#include "lloutfitslist.h"
#include "llselectmgr.h"
//#include "llsidepanelappearance.h"
#include "llviewerobjectlist.h"
#include "llvoavatar.h"
#include "llvoavatarself.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llwearablelist.h"
#include "llsdutil.h"
#include "llinventorypanel.h"
#include "llfloatercustomize.h"
// [RLVa:KB] - Checked: 2011-05-22 (RLVa-1.3.1a)
#include "rlvhandler.h"
#include "rlvhelper.h"
#include "rlvlocks.h"
// [/RLVa:KB]
std::string self_av_string()
{
// On logout gAgentAvatarp can already be invalid
return isAgentAvatarValid() ? gAgentAvatarp->avString() : "";
}
// RAII thingy to guarantee that a variable gets reset when the Setter
// goes out of scope. More general utility would be handy - TODO:
// check boost.
class BoolSetter
{
public:
BoolSetter(bool& var):
mVar(var)
{
mVar = true;
}
~BoolSetter()
{
mVar = false;
}
private:
bool& mVar;
};
char ORDER_NUMBER_SEPARATOR('@');
class LLOutfitUnLockTimer: public LLEventTimer
{
public:
LLOutfitUnLockTimer(F32 period) : LLEventTimer(period)
{
// restart timer on BOF changed event
LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(
&LLOutfitUnLockTimer::reset, this));
stop();
}
/*virtual*/
BOOL tick()
{
if(mEventTimer.hasExpired())
{
LLAppearanceMgr::instance().setOutfitLocked(false);
}
return FALSE;
}
void stop() { mEventTimer.stop(); }
void start() { mEventTimer.start(); }
void reset() { mEventTimer.reset(); }
BOOL getStarted() { return mEventTimer.getStarted(); }
LLTimer& getEventTimer() { return mEventTimer;}
};
// support for secondlife:///app/appearance SLapps
/*class LLAppearanceHandler : public LLCommandHandler
{
public:
// requests will be throttled from a non-trusted browser
LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {}
bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
{
// support secondlife:///app/appearance/show, but for now we just
// make all secondlife:///app/appearance SLapps behave this way
if (!LLUI::sSettingGroups["config"]->getBOOL("EnableAppearance"))
{
LLNotificationsUtil::add("NoAppearance", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit"));
return true;
}
LLFloaterSidePanelContainer::showPanel("appearance", LLSD());
return true;
}
};
LLAppearanceHandler gAppearanceHandler;*/
LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name)
{
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
LLNameCategoryCollector has_name(name);
gInventory.collectDescendentsIf(parent_id,
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH,
has_name);
if (0 == cat_array.count())
return LLUUID();
else
{
LLViewerInventoryCategory *cat = cat_array.get(0);
if (cat)
return cat->getUUID();
else
{
llwarns << "null cat" << llendl;
return LLUUID();
}
}
}
// We want this to be much lower (e.g. 15.0 is usually fine), bumping
// up for now until we can diagnose some cases of very slow response
// to requests.
const F32 DEFAULT_RETRY_AFTER_INTERVAL = 300.0;
// Given the current back-end problems, retrying is causing too many
// duplicate items. Bump this back to 2 once they are resolved (or can
// leave at 0 if the operations become actually reliable).
const S32 DEFAULT_MAX_RETRIES = 0;
class LLCallAfterInventoryBatchMgr: public LLEventTimer
{
public:
LLCallAfterInventoryBatchMgr(const LLUUID& dst_cat_id,
const std::string& phase_name,
nullary_func_t on_completion_func,
nullary_func_t on_failure_func = no_op,
F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL,
S32 max_retries = DEFAULT_MAX_RETRIES
):
mDstCatID(dst_cat_id),
mTrackingPhase(phase_name),
mOnCompletionFunc(on_completion_func),
mOnFailureFunc(on_failure_func),
mRetryAfter(retry_after),
mMaxRetries(max_retries),
mPendingRequests(0),
mFailCount(0),
mCompletionOrFailureCalled(false),
mRetryCount(0),
LLEventTimer(5.0)
{
if (!mTrackingPhase.empty())
{
selfStartPhase(mTrackingPhase);
}
}
void addItems(LLInventoryModel::item_array_t& src_items)
{
for (LLInventoryModel::item_array_t::const_iterator it = src_items.begin();
it != src_items.end();
++it)
{
LLViewerInventoryItem* item = *it;
llassert(item);
addItem(item->getUUID());
}
}
// Request or re-request operation for specified item.
void addItem(const LLUUID& item_id)
{
LL_DEBUGS("Avatar") << "item_id " << item_id << llendl;
if (!requestOperation(item_id))
{
LL_DEBUGS("Avatar") << "item_id " << item_id << " requestOperation false, skipping" << llendl;
return;
}
mPendingRequests++;
// On a re-request, this will reset the timer.
mWaitTimes[item_id] = LLTimer();
if (mRetryCounts.find(item_id) == mRetryCounts.end())
{
mRetryCounts[item_id] = 0;
}
else
{
mRetryCounts[item_id]++;
}
}
virtual bool requestOperation(const LLUUID& item_id) = 0;
void onOp(const LLUUID& src_id, const LLUUID& dst_id, LLTimer timestamp)
{
if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateLateOpRate"))
{
llwarns << "Simulating late operation by punting handling to later" << llendl;
doAfterInterval(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,src_id,dst_id,timestamp),
mRetryAfter);
return;
}
mPendingRequests--;
F32 elapsed = timestamp.getElapsedTimeF32();
LL_DEBUGS("Avatar") << "op done, src_id " << src_id << " dst_id " << dst_id << " after " << elapsed << " seconds" << llendl;
if (mWaitTimes.find(src_id) == mWaitTimes.end())
{
// No longer waiting for this item - either serviced
// already or gave up after too many retries.
llwarns << "duplicate or late operation, src_id " << src_id << "dst_id " << dst_id
<< " elapsed " << elapsed << " after end " << (S32) mCompletionOrFailureCalled << llendl;
}
mTimeStats.push(elapsed);
mWaitTimes.erase(src_id);
if (mWaitTimes.empty() && !mCompletionOrFailureCalled)
{
onCompletionOrFailure();
}
}
void onCompletionOrFailure()
{
assert (!mCompletionOrFailureCalled);
mCompletionOrFailureCalled = true;
// Will never call onCompletion() if any item has been flagged as
// a failure - otherwise could wind up with corrupted
// outfit, involuntary nudity, etc.
reportStats();
if (!mTrackingPhase.empty())
{
selfStopPhase(mTrackingPhase);
}
if (!mFailCount)
{
onCompletion();
}
else
{
onFailure();
}
}
void onFailure()
{
llinfos << "failed" << llendl;
mOnFailureFunc();
}
void onCompletion()
{
llinfos << "done" << llendl;
mOnCompletionFunc();
}
// virtual
// Will be deleted after returning true - only safe to do this if all callbacks have fired.
BOOL tick()
{
// mPendingRequests will be zero if all requests have been
// responded to. mWaitTimes.empty() will be true if we have
// received at least one reply for each UUID. If requests
// have been dropped and retried, these will not necessarily
// be the same. Only safe to return true if all requests have
// been serviced, since it will result in this object being
// deleted.
bool all_done = (mPendingRequests==0);
if (!mWaitTimes.empty())
{
llwarns << "still waiting on " << mWaitTimes.size() << " items" << llendl;
for (std::map<LLUUID,LLTimer>::iterator it = mWaitTimes.begin();
it != mWaitTimes.end();)
{
// Use a copy of iterator because it may be erased/invalidated.
std::map<LLUUID,LLTimer>::iterator curr_it = it;
++it;
F32 time_waited = curr_it->second.getElapsedTimeF32();
S32 retries = mRetryCounts[curr_it->first];
if (time_waited > mRetryAfter)
{
if (retries < mMaxRetries)
{
LL_DEBUGS("Avatar") << "Waited " << time_waited <<
" for " << curr_it->first << ", retrying" << llendl;
mRetryCount++;
addItem(curr_it->first);
}
else
{
llwarns << "Giving up on " << curr_it->first << " after too many retries" << llendl;
mWaitTimes.erase(curr_it);
mFailCount++;
}
}
if (mWaitTimes.empty())
{
onCompletionOrFailure();
}
}
}
return all_done;
}
void reportStats()
{
LL_DEBUGS("Avatar") << "Phase: " << mTrackingPhase << llendl;
LL_DEBUGS("Avatar") << "mFailCount: " << mFailCount << llendl;
LL_DEBUGS("Avatar") << "mRetryCount: " << mRetryCount << llendl;
LL_DEBUGS("Avatar") << "Times: n " << mTimeStats.getCount() << " min " << mTimeStats.getMinValue() << " max " << mTimeStats.getMaxValue() << llendl;
LL_DEBUGS("Avatar") << "Mean " << mTimeStats.getMean() << " stddev " << mTimeStats.getStdDev() << llendl;
}
virtual ~LLCallAfterInventoryBatchMgr()
{
LL_DEBUGS("Avatar") << "deleting" << llendl;
}
protected:
std::string mTrackingPhase;
std::map<LLUUID,LLTimer> mWaitTimes;
std::map<LLUUID,S32> mRetryCounts;
LLUUID mDstCatID;
nullary_func_t mOnCompletionFunc;
nullary_func_t mOnFailureFunc;
F32 mRetryAfter;
S32 mMaxRetries;
S32 mPendingRequests;
S32 mFailCount;
S32 mRetryCount;
bool mCompletionOrFailureCalled;
LLViewerStats::StatsAccumulator mTimeStats;
};
class LLCallAfterInventoryCopyMgr: public LLCallAfterInventoryBatchMgr
{
public:
LLCallAfterInventoryCopyMgr(LLInventoryModel::item_array_t& src_items,
const LLUUID& dst_cat_id,
const std::string& phase_name,
nullary_func_t on_completion_func,
nullary_func_t on_failure_func = no_op,
F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL,
S32 max_retries = DEFAULT_MAX_RETRIES
):
LLCallAfterInventoryBatchMgr(dst_cat_id, phase_name, on_completion_func, on_failure_func, retry_after, max_retries)
{
addItems(src_items);
}
virtual bool requestOperation(const LLUUID& item_id)
{
LLViewerInventoryItem *item = gInventory.getItem(item_id);
llassert(item);
LL_DEBUGS("Avatar") << "copying item " << item_id << llendl;
if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateOpFailureRate"))
{
LL_DEBUGS("Avatar") << "simulating failure by not sending request for item " << item_id << llendl;
return true;
}
copy_inventory_item(
gAgent.getID(),
item->getPermissions().getOwner(),
item->getUUID(),
mDstCatID,
std::string(),
new LLBoostFuncInventoryCallback(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,item_id,_1,LLTimer()))
);
return true;
}
};
class LLCallAfterInventoryLinkMgr: public LLCallAfterInventoryBatchMgr
{
public:
LLCallAfterInventoryLinkMgr(LLInventoryModel::item_array_t& src_items,
const LLUUID& dst_cat_id,
const std::string& phase_name,
nullary_func_t on_completion_func,
nullary_func_t on_failure_func = no_op,
F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL,
S32 max_retries = DEFAULT_MAX_RETRIES
):
LLCallAfterInventoryBatchMgr(dst_cat_id, phase_name, on_completion_func, on_failure_func, retry_after, max_retries)
{
addItems(src_items);
}
virtual bool requestOperation(const LLUUID& item_id)
{
bool request_sent = false;
LLViewerInventoryItem *item = gInventory.getItem(item_id);
if (item)
{
if (item->getParentUUID() == mDstCatID)
{
LL_DEBUGS("Avatar") << "item " << item_id << " name " << item->getName() << " is already a child of " << mDstCatID << llendl;
return false;
}
LL_DEBUGS("Avatar") << "linking item " << item_id << " name " << item->getName() << " to " << mDstCatID << llendl;
// create an inventory item link.
if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateOpFailureRate"))
{
LL_DEBUGS("Avatar") << "simulating failure by not sending request for item " << item_id << llendl;
return true;
}
link_inventory_item(gAgent.getID(),
item->getLinkedUUID(),
mDstCatID,
item->getName(),
item->getActualDescription(),
LLAssetType::AT_LINK,
new LLBoostFuncInventoryCallback(
boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,item_id,_1,LLTimer())));
return true;
}
else
{
// create a base outfit link if appropriate.
LLViewerInventoryCategory *catp = gInventory.getCategory(item_id);
if (!catp)
{
llwarns << "link request failed, id not found as inventory item or category " << item_id << llendl;
return false;
}
const LLUUID cof = LLAppearanceMgr::instance().getCOF();
std::string new_outfit_name = "";
LLAppearanceMgr::instance().purgeBaseOutfitLink(cof);
if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
{
if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateOpFailureRate"))
{
LL_DEBUGS("Avatar") << "simulating failure by not sending request for item " << item_id << llendl;
return true;
}
LL_DEBUGS("Avatar") << "linking folder " << item_id << " name " << catp->getName() << " to cof " << cof << llendl;
link_inventory_item(gAgent.getID(), item_id, cof, catp->getName(), "",
LLAssetType::AT_LINK_FOLDER,
new LLBoostFuncInventoryCallback(
boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,item_id,_1,LLTimer())));
new_outfit_name = catp->getName();
request_sent = true;
}
LLAppearanceMgr::instance().updatePanelOutfitName(new_outfit_name);
}
return request_sent;
}
};
LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(bool update_base_outfit_ordering):
mFireCount(0),
mUpdateBaseOrder(update_base_outfit_ordering)
{
selfStartPhase("update_appearance_on_destroy");
}
LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy()
{
if (!LLApp::isExiting())
{
// speculative fix for MAINT-1150
LL_INFOS("Avatar") << self_av_string() << "done update appearance on destroy" << LL_ENDL;
selfStopPhase("update_appearance_on_destroy");
LLAppearanceMgr::instance().updateAppearanceFromCOF(mUpdateBaseOrder);
}
}
void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item)
{
LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item);
const std::string item_name = item ? item->getName() : "ITEM NOT FOUND";
#ifndef LL_RELEASE_FOR_DOWNLOAD
LL_DEBUGS("Avatar") << self_av_string() << "callback fired [ name:" << item_name << " UUID:" << inv_item << " count:" << mFireCount << " ] " << LL_ENDL;
#endif
mFireCount++;
}
struct LLFoundData
{
LLFoundData() :
mAssetType(LLAssetType::AT_NONE),
mWearableType(LLWearableType::WT_INVALID),
mWearable(NULL) {}
LLFoundData(const LLUUID& item_id,
const LLUUID& asset_id,
const std::string& name,
const LLAssetType::EType& asset_type,
const LLWearableType::EType& wearable_type,
const bool is_replacement = false
) :
mItemID(item_id),
mAssetID(asset_id),
mName(name),
mAssetType(asset_type),
mWearableType(wearable_type),
mIsReplacement(is_replacement),
mWearable( NULL ) {}
LLUUID mItemID;
LLUUID mAssetID;
std::string mName;
LLAssetType::EType mAssetType;
LLWearableType::EType mWearableType;
LLViewerWearable* mWearable;
bool mIsReplacement;
};
class LLWearableHoldingPattern
{
LOG_CLASS(LLWearableHoldingPattern);
public:
LLWearableHoldingPattern();
~LLWearableHoldingPattern();
bool pollFetchCompletion();
void onFetchCompletion();
bool isFetchCompleted();
bool isTimedOut();
void checkMissingWearables();
bool pollMissingWearables();
bool isMissingCompleted();
void recoverMissingWearable(LLWearableType::EType type);
void clearCOFLinksForMissingWearables();
void onWearableAssetFetch(LLViewerWearable *wearable);
void onAllComplete();
// [SL:KB] - Patch: Appearance-COFCorruption | Checked: 2010-04-14 (Catznip-3.0.0a) | Added: Catznip-2.0.0a
bool pollStopped();
// [/SL:KB]
typedef std::list<LLFoundData> found_list_t;
found_list_t& getFoundList();
void eraseTypeToLink(LLWearableType::EType type);
void eraseTypeToRecover(LLWearableType::EType type);
// void setObjItems(const LLInventoryModel::item_array_t& items);
void setGestItems(const LLInventoryModel::item_array_t& items);
bool isMostRecent();
void handleLateArrivals();
void resetTime(F32 timeout);
private:
found_list_t mFoundList;
// LLInventoryModel::item_array_t mObjItems;
LLInventoryModel::item_array_t mGestItems;
typedef std::set<S32> type_set_t;
type_set_t mTypesToRecover;
type_set_t mTypesToLink;
S32 mResolved;
LLTimer mWaitTime;
bool mFired;
typedef std::set<LLWearableHoldingPattern*> type_set_hp;
static type_set_hp sActiveHoldingPatterns;
bool mIsMostRecent;
std::set<LLViewerWearable*> mLateArrivals;
bool mIsAllComplete;
};
LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns;
LLWearableHoldingPattern::LLWearableHoldingPattern():
mResolved(0),
mFired(false),
mIsMostRecent(true),
mIsAllComplete(false)
{
if (sActiveHoldingPatterns.size()>0)
{
llinfos << "Creating LLWearableHoldingPattern when "
<< sActiveHoldingPatterns.size()
<< " other attempts are active."
<< " Flagging others as invalid."
<< llendl;
for (type_set_hp::iterator it = sActiveHoldingPatterns.begin();
it != sActiveHoldingPatterns.end();
++it)
{
(*it)->mIsMostRecent = false;
}
}
sActiveHoldingPatterns.insert(this);
//selfStartPhase("holding_pattern");
}
LLWearableHoldingPattern::~LLWearableHoldingPattern()
{
sActiveHoldingPatterns.erase(this);
if (isMostRecent())
{
selfStopPhase("holding_pattern");
}
}
bool LLWearableHoldingPattern::isMostRecent()
{
return mIsMostRecent;
}
LLWearableHoldingPattern::found_list_t& LLWearableHoldingPattern::getFoundList()
{
return mFoundList;
}
void LLWearableHoldingPattern::eraseTypeToLink(LLWearableType::EType type)
{
mTypesToLink.erase(type);
}
void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type)
{
mTypesToRecover.erase(type);
}
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-06-19 (Catznip-3.0.0a) | Added: Catznip-2.1.2a
//void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items)
//{
// mObjItems = items;
//}
void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items)
{
mGestItems = items;
}
bool LLWearableHoldingPattern::isFetchCompleted()
{
return (mResolved >= (S32)getFoundList().size()); // have everything we were waiting for?
}
bool LLWearableHoldingPattern::isTimedOut()
{
return mWaitTime.hasExpired();
}
void LLWearableHoldingPattern::checkMissingWearables()
{
if (!isMostRecent())
{
// runway why don't we actually skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
}
std::vector<S32> found_by_type(LLWearableType::WT_COUNT,0);
std::vector<S32> requested_by_type(LLWearableType::WT_COUNT,0);
for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it)
{
LLFoundData &data = *it;
if (data.mWearableType < LLWearableType::WT_COUNT)
requested_by_type[data.mWearableType]++;
if (data.mWearable)
found_by_type[data.mWearableType]++;
}
for (S32 type = 0; type < LLWearableType::WT_COUNT; ++type)
{
if (requested_by_type[type] > found_by_type[type])
{
llwarns << self_av_string() << "got fewer wearables than requested, type " << type << ": requested " << requested_by_type[type] << ", found " << found_by_type[type] << llendl;
}
if (found_by_type[type] > 0)
continue;
if (
// If at least one wearable of certain types (pants/shirt/skirt)
// was requested but none was found, create a default asset as a replacement.
// In all other cases, don't do anything.
// For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud
// due to logic in LLVOAvatarSelf::getIsCloud().
// For non-critical types (tatoo, socks, etc.) the wearable will just be missing.
(requested_by_type[type] > 0) &&
((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT)))
{
mTypesToRecover.insert(type);
mTypesToLink.insert(type);
recoverMissingWearable((LLWearableType::EType)type);
llwarns << self_av_string() << "need to replace " << type << llendl;
}
}
resetTime(60.0F);
selfStartPhase("get_missing_wearables");
if (!pollMissingWearables())
{
doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollMissingWearables,this));
}
}
void LLWearableHoldingPattern::onAllComplete()
{
if (isAgentAvatarValid())
{
gAgentAvatarp->outputRezTiming("Agent wearables fetch complete");
}
if (!isMostRecent())
{
// runway need to skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
}
// Activate all gestures in this folder
if (mGestItems.count() > 0)
{
LL_DEBUGS("Avatar") << self_av_string() << "Activating " << mGestItems.count() << " gestures" << LL_ENDL;
LLGestureMgr::instance().activateGestures(mGestItems);
// Update the inventory item labels to reflect the fact
// they are active.
LLViewerInventoryCategory* catp =
gInventory.getCategory(LLAppearanceMgr::instance().getCOF());
if (catp)
{
gInventory.updateCategory(catp);
gInventory.notifyObservers();
}
}
// Update wearables.
LL_INFOS("Avatar") << self_av_string() << "Updating agent wearables with " << mResolved << " wearable items " << LL_ENDL;
LLAppearanceMgr::instance().updateAgentWearables(this, false);
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-03-22 (Catznip-3.0.0a) | Added: Catznip-2.1.2a
// // Update attachments to match those requested.
// if (isAgentAvatarValid())
// {
// llinfos << "Updating " << mObjItems.count() << " attachments" << llendl;
// LLAgentWearables::userUpdateAttachments(mObjItems);
// }
if (isFetchCompleted() && isMissingCompleted())
{
// Only safe to delete if all wearable callbacks and all missing wearables completed.
delete this;
}
else
{
mIsAllComplete = true;
handleLateArrivals();
}
}
void LLWearableHoldingPattern::onFetchCompletion()
{
selfStopPhase("get_wearables");
if (!isMostRecent())
{
// runway skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
}
checkMissingWearables();
}
// Runs as an idle callback until all wearables are fetched (or we time out).
bool LLWearableHoldingPattern::pollFetchCompletion()
{
if (!isMostRecent())
{
// runway skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
// [SL:KB] - Patch: Appearance-COFCorruption | Checked: 2010-04-14 (Catznip-3.0.0a) | Added: Catznip-2.0.0a
// If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollStopped, this));
return true;
// [/SL:KB]
}
bool completed = isFetchCompleted();
bool timed_out = isTimedOut();
bool done = completed || timed_out;
if (done)
{
LL_INFOS("Avatar") << self_av_string() << "polling, done status: " << completed << " timed out " << timed_out
<< " elapsed " << mWaitTime.getElapsedTimeF32() << LL_ENDL;
mFired = true;
if (timed_out)
{
llwarns << self_av_string() << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << llendl;
}
onFetchCompletion();
}
return done;
}
void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder)
{
if (!holder->isMostRecent())
{
llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
// runway skip here?
}
llinfos << "Recovered item link for type " << type << llendl;
holder->eraseTypeToLink(type);
// Add wearable to FoundData for actual wearing
LLViewerInventoryItem *item = gInventory.getItem(item_id);
LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
if (linked_item)
{
gInventory.addChangedMask(LLInventoryObserver::LABEL, linked_item->getUUID());
if (item)
{
LLFoundData found(linked_item->getUUID(),
linked_item->getAssetUUID(),
linked_item->getName(),
linked_item->getType(),
linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID,
true // is replacement
);
found.mWearable = wearable;
holder->getFoundList().push_front(found);
}
else
{
llwarns << self_av_string() << "inventory item not found for recovered wearable" << llendl;
}
}
else
{
llwarns << self_av_string() << "inventory link not found for recovered wearable" << llendl;
}
}
void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder)
{
if (!holder->isMostRecent())
{
// runway skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
// [SL:KB] - Patch: Appearance-COFCorruption | Checked: 2010-04-14 (Catznip-3.0.0a) | Added: Catznip-2.0.0a
// If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
return;
// [/SL:KB]
}
LL_DEBUGS("Avatar") << self_av_string() << "Recovered item for type " << type << LL_ENDL;
LLViewerInventoryItem *itemp = gInventory.getItem(item_id);
wearable->setItemID(item_id);
LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_link_cb,_1,type,wearable,holder));
holder->eraseTypeToRecover(type);
llassert(itemp);
if (itemp)
{
link_inventory_item( gAgent.getID(),
item_id,
LLAppearanceMgr::instance().getCOF(),
itemp->getName(),
itemp->getDescription(),
LLAssetType::AT_LINK,
cb);
}
}
void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type)
{
if (!isMostRecent())
{
// runway skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
}
// Try to recover by replacing missing wearable with a new one.
LLNotificationsUtil::add("ReplacedMissingWearable");
lldebugs << "Wearable " << LLWearableType::getTypeLabel(type)
<< " could not be downloaded. Replaced inventory item with default wearable." << llendl;
LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp);
// Add a new one in the lost and found folder.
const LLUUID lost_and_found_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND);
LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_cb,_1,type,wearable,this));
create_inventory_item(gAgent.getID(),
gAgent.getSessionID(),
lost_and_found_id,
wearable->getTransactionID(),
wearable->getName(),
wearable->getDescription(),
wearable->getAssetType(),
LLInventoryType::IT_WEARABLE,
wearable->getType(),
wearable->getPermissions().getMaskNextOwner(),
cb);
}
bool LLWearableHoldingPattern::isMissingCompleted()
{
return mTypesToLink.size()==0 && mTypesToRecover.size()==0;
}
void LLWearableHoldingPattern::clearCOFLinksForMissingWearables()
{
for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it)
{
LLFoundData &data = *it;
if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable))
{
// Wearable link that was never resolved; remove links to it from COF
LL_INFOS("Avatar") << self_av_string() << "removing link for unresolved item " << data.mItemID.asString() << LL_ENDL;
LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID);
}
}
}
// [SL:KB] - Patch: Appearance-COFCorruption | Checked: 2010-04-14 (Catznip-3.0.0a) | Added: Catznip-2.0.0a
bool LLWearableHoldingPattern::pollStopped()
{
// We have to keep on polling until we're sure that all callbacks have completed or they'll cause a crash
if ( (isFetchCompleted()) && (isMissingCompleted()) )
{
delete this;
return true;
}
return false;
}
// [/SL:KB]
bool LLWearableHoldingPattern::pollMissingWearables()
{
if (!isMostRecent())
{
// runway skip here?
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
// [SL:KB] - Patch: Appearance-COFCorruption | Checked: 2010-04-14 (Catznip-3.0.0a) | Added: Catznip-2.0.0a
// If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollStopped, this));
return true;
// [/SL:KB]
}
bool timed_out = isTimedOut();
bool missing_completed = isMissingCompleted();
bool done = timed_out || missing_completed;
if (!done)
{
LL_INFOS("Avatar") << self_av_string() << "polling missing wearables, waiting for items " << mTypesToRecover.size()
<< " links " << mTypesToLink.size()
<< " wearables, timed out " << timed_out
<< " elapsed " << mWaitTime.getElapsedTimeF32()
<< " done " << done << LL_ENDL;
}
if (done)
{
selfStopPhase("get_missing_wearables");
gAgentAvatarp->debugWearablesLoaded();
// BAP - if we don't call clearCOFLinksForMissingWearables()
// here, we won't have to add the link back in later if the
// wearable arrives late. This is to avoid corruption of
// wearable ordering info. Also has the effect of making
// unworn item links visible in the COF under some
// circumstances.
//clearCOFLinksForMissingWearables();
onAllComplete();
}
return done;
}
// Handle wearables that arrived after the timeout period expired.
void LLWearableHoldingPattern::handleLateArrivals()
{
// Only safe to run if we have previously finished the missing
// wearables and other processing - otherwise we could be in some
// intermediate state - but have not been superceded by a later
// outfit change request.
if (mLateArrivals.size() == 0)
{
// Nothing to process.
return;
}
if (!isMostRecent())
{
llwarns << self_av_string() << "Late arrivals not handled - outfit change no longer valid" << llendl;
}
if (!mIsAllComplete)
{
llwarns << self_av_string() << "Late arrivals not handled - in middle of missing wearables processing" << llendl;
}
LL_INFOS("Avatar") << self_av_string() << "Need to handle " << mLateArrivals.size() << " late arriving wearables" << LL_ENDL;
// Update mFoundList using late-arriving wearables.
std::set<LLWearableType::EType> replaced_types;
for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
iter != getFoundList().end(); ++iter)
{
LLFoundData& data = *iter;
for (std::set<LLViewerWearable*>::iterator wear_it = mLateArrivals.begin();
wear_it != mLateArrivals.end();
++wear_it)
{
LLViewerWearable *wearable = *wear_it;
if(wearable->getAssetID() == data.mAssetID)
{
data.mWearable = wearable;
replaced_types.insert(data.mWearableType);
// BAP - if we didn't call
// clearCOFLinksForMissingWearables() earlier, we
// don't need to restore the link here. Fixes
// wearable ordering problems.
// LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false);
// BAP failing this means inventory or asset server
// are corrupted in a way we don't handle.
llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType));
break;
}
}
}
// Remove COF links for any default wearables previously used to replace the late arrivals.
// All this pussyfooting around with a while loop and explicit
// iterator incrementing is to allow removing items from the list
// without clobbering the iterator we're using to navigate.
LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
while (iter != getFoundList().end())
{
LLFoundData& data = *iter;
// If an item of this type has recently shown up, removed the corresponding replacement wearable from COF.
if (data.mWearable && data.mIsReplacement &&
replaced_types.find(data.mWearableType) != replaced_types.end())
{
LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID);
std::list<LLFoundData>::iterator clobber_ator = iter;
++iter;
getFoundList().erase(clobber_ator);
}
else
{
++iter;
}
}
// Clear contents of late arrivals.
mLateArrivals.clear();
// Update appearance based on mFoundList
LLAppearanceMgr::instance().updateAgentWearables(this, false);
}
void LLWearableHoldingPattern::resetTime(F32 timeout)
{
mWaitTime.reset();
mWaitTime.setTimerExpirySec(timeout);
}
void LLWearableHoldingPattern::onWearableAssetFetch(LLViewerWearable *wearable)
{
if (!isMostRecent())
{
llwarns << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
}
mResolved += 1; // just counting callbacks, not successes.
LL_DEBUGS("Avatar") << self_av_string() << "resolved " << mResolved << "/" << getFoundList().size() << LL_ENDL;
if (!wearable)
{
llwarns << self_av_string() << "no wearable found" << llendl;
}
if (mFired)
{
llwarns << self_av_string() << "called after holder fired" << llendl;
if (wearable)
{
mLateArrivals.insert(wearable);
if (mIsAllComplete)
{
handleLateArrivals();
}
}
return;
}
if (!wearable)
{
return;
}
for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
iter != getFoundList().end(); ++iter)
{
LLFoundData& data = *iter;
if(wearable->getAssetID() == data.mAssetID)
{
// Failing this means inventory or asset server are corrupted in a way we don't handle.
if ((data.mWearableType >= LLWearableType::WT_COUNT) || (wearable->getType() != data.mWearableType))
{
llwarns << self_av_string() << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << llendl;
break;
}
data.mWearable = wearable;
}
}
}
static void onWearableAssetFetch(LLViewerWearable* wearable, void* data)
{
LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data;
holder->onWearableAssetFetch(wearable);
}
static void removeDuplicateItems(LLInventoryModel::item_array_t& items)
{
LLInventoryModel::item_array_t new_items;
std::set<LLUUID> items_seen;
std::deque<LLViewerInventoryItem*> tmp_list;
// Traverse from the front and keep the first of each item
// encountered, so we actually keep the *last* of each duplicate
// item. This is needed to give the right priority when adding
// duplicate items to an existing outfit.
for (S32 i=items.count()-1; i>=0; i--)
{
LLViewerInventoryItem *item = items.get(i);
LLUUID item_id = item->getLinkedUUID();
if (items_seen.find(item_id)!=items_seen.end())
continue;
items_seen.insert(item_id);
tmp_list.push_front(item);
}
for (std::deque<LLViewerInventoryItem*>::iterator it = tmp_list.begin();
it != tmp_list.end();
++it)
{
new_items.put(*it);
}
items = new_items;
}
// [SL:KB] - Patch: Appearance-WearableDuplicateAssets | Checked: 2011-07-24 (Catznip-2.6.0e) | Added: Catznip-2.6.0e
static void removeDuplicateWearableItemsByAssetID(LLInventoryModel::item_array_t& items)
{
std::set<LLUUID> idsAsset;
for (S32 idxItem = items.count() - 1; idxItem >= 0; idxItem--)
{
const LLViewerInventoryItem* pItem = items.get(idxItem);
if (!pItem->isWearableType())
continue;
if (idsAsset.end() == idsAsset.find(pItem->getAssetUUID()))
idsAsset.insert(pItem->getAssetUUID());
else
items.remove(idxItem);
}
}
// [/SL:KB]
const LLUUID LLAppearanceMgr::getCOF() const
{
return gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
}
S32 LLAppearanceMgr::getCOFVersion() const
{
LLViewerInventoryCategory *cof = gInventory.getCategory(getCOF());
if (cof)
{
return cof->getVersion();
}
else
{
return LLViewerInventoryCategory::VERSION_UNKNOWN;
}
}
S32 LLAppearanceMgr::getLastUpdateRequestCOFVersion() const
{
return mLastUpdateRequestCOFVersion;
}
S32 LLAppearanceMgr::getLastAppearanceUpdateCOFVersion() const
{
return mLastAppearanceUpdateCOFVersion;
}
void LLAppearanceMgr::setLastAppearanceUpdateCOFVersion(S32 new_val)
{
mLastAppearanceUpdateCOFVersion = new_val;
}
const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink()
{
const LLUUID& current_outfit_cat = getCOF();
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
// Can't search on FT_OUTFIT since links to categories return FT_CATEGORY for type since they don't
// return preferred type.
LLIsType is_category( LLAssetType::AT_CATEGORY );
gInventory.collectDescendentsIf(current_outfit_cat,
cat_array,
item_array,
false,
is_category,
false);
for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin();
iter != item_array.end();
iter++)
{
const LLViewerInventoryItem *item = (*iter);
const LLViewerInventoryCategory *cat = item->getLinkedCategory();
if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT)
{
const LLUUID parent_id = cat->getParentUUID();
LLViewerInventoryCategory* parent_cat = gInventory.getCategory(parent_id);
// if base outfit moved to trash it means that we don't have base outfit
if (parent_cat != NULL && parent_cat->getPreferredType() == LLFolderType::FT_TRASH)
{
return NULL;
}
return item;
}
}
return NULL;
}
bool LLAppearanceMgr::getBaseOutfitName(std::string& name)
{
const LLViewerInventoryItem* outfit_link = getBaseOutfitLink();
if(outfit_link)
{
const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory();
if (cat)
{
name = cat->getName();
return true;
}
}
return false;
}
const LLUUID LLAppearanceMgr::getBaseOutfitUUID()
{
const LLViewerInventoryItem* outfit_link = getBaseOutfitLink();
if (!outfit_link || !outfit_link->getIsLinkType()) return LLUUID::null;
const LLViewerInventoryCategory* outfit_cat = outfit_link->getLinkedCategory();
if (!outfit_cat) return LLUUID::null;
if (outfit_cat->getPreferredType() != LLFolderType::FT_OUTFIT)
{
llwarns << "Expected outfit type:" << LLFolderType::FT_OUTFIT << " but got type:" << outfit_cat->getType() << " for folder name:" << outfit_cat->getName() << llendl;
return LLUUID::null;
}
return outfit_cat->getUUID();
}
void wear_on_avatar_cb(const LLUUID& inv_item, bool do_replace/*= false*/)
{
if (inv_item.isNull())
return;
LLViewerInventoryItem *item = gInventory.getItem(inv_item);
if (item)
{
LLAppearanceMgr::instance().wearItemOnAvatar(inv_item, true, do_replace);
}
}
bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, bool do_update, bool replace, LLPointer<LLInventoryCallback> cb)
{
if (item_id_to_wear.isNull()) return false;
// *TODO: issue with multi-wearable should be fixed:
// in this case this method will be called N times - loading started for each item
// and than N times will be called - loading completed for each item.
// That means subscribers will be notified that loading is done after first item in a batch is worn.
// (loading indicator disappears for example before all selected items are worn)
// Have not fix this issue for 2.1 because of stability reason. EXT-7777.
// Disabled for now because it is *not* acceptable to call updateAppearanceFromCOF() multiple times
// gAgentWearables.notifyLoadingStarted();
LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear);
if (!item_to_wear) return false;
if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getLibraryRootFolderID()))
{
LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(&wear_on_avatar_cb,_1,replace));
copy_inventory_item(gAgent.getID(), item_to_wear->getPermissions().getOwner(), item_to_wear->getUUID(), LLUUID::null, std::string(), cb);
return false;
}
else if (!gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getRootFolderID()))
{
return false; // not in library and not in agent's inventory
}
else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH)))
{
LLNotificationsUtil::add("CannotWearTrash");
return false;
}
else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), LLAppearanceMgr::instance().getCOF())) // EXT-84911
{
return false;
}
// [RLVa:KB] - Checked: 2013-02-12 (RLVa-1.4.8)
replace |= (LLAssetType::AT_BODYPART == item_to_wear->getType()); // Body parts should always replace
if ( (rlv_handler_t::isEnabled()) && (!rlvPredCanWearItem(item_to_wear, (replace) ? RLV_WEAR_REPLACE : RLV_WEAR_ADD)) )
{
return false;
}
// [/RLVa:KB]
switch (item_to_wear->getType())
{
case LLAssetType::AT_CLOTHING:
if (gAgentWearables.areWearablesLoaded())
{
S32 wearable_count = gAgentWearables.getWearableCount(item_to_wear->getWearableType());
if ((replace && wearable_count != 0) ||
(wearable_count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE) )
{
removeCOFItemLinks(gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), wearable_count-1));
}
addCOFItemLink(item_to_wear, do_update, cb);
}
break;
case LLAssetType::AT_BODYPART:
// TODO: investigate wearables may not be loaded at this point EXT-8231
// Remove the existing wearables of the same type.
// Remove existing body parts anyway because we must not be able to wear e.g. two skins.
removeCOFLinksOfType(item_to_wear->getWearableType());
addCOFItemLink(item_to_wear, do_update, cb);
break;
case LLAssetType::AT_OBJECT:
rez_attachment(item_to_wear, NULL, replace);
break;
default: return false;;
}
return true;
}
// Update appearance from outfit folder.
void LLAppearanceMgr::changeOutfit(bool proceed, const LLUUID& category, bool append)
{
if (!proceed)
return;
LLAppearanceMgr::instance().updateCOF(category,append);
}
void LLAppearanceMgr::replaceCurrentOutfit(const LLUUID& new_outfit)
{
LLViewerInventoryCategory* cat = gInventory.getCategory(new_outfit);
wearInventoryCategory(cat, false, false);
}
// Open outfit renaming dialog.
void LLAppearanceMgr::renameOutfit(const LLUUID& outfit_id)
{
LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id);
if (!cat)
{
return;
}
LLSD args;
args["NAME"] = cat->getName();
LLSD payload;
payload["cat_id"] = outfit_id;
LLNotificationsUtil::add("RenameOutfit", args, payload, boost::bind(onOutfitRename, _1, _2));
}
// User typed new outfit name.
// static
void LLAppearanceMgr::onOutfitRename(const LLSD& notification, const LLSD& response)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (option != 0) return; // canceled
std::string outfit_name = response["new_name"].asString();
LLStringUtil::trim(outfit_name);
if (!outfit_name.empty())
{
LLUUID cat_id = notification["payload"]["cat_id"].asUUID();
rename_category(&gInventory, cat_id, outfit_name);
}
}
void LLAppearanceMgr::setOutfitLocked(bool locked)
{
if (mOutfitLocked == locked)
{
return;
}
mOutfitLocked = locked;
if (locked)
{
mUnlockOutfitTimer->reset();
mUnlockOutfitTimer->start();
}
else
{
mUnlockOutfitTimer->stop();
}
LLOutfitObserver::instance().notifyOutfitLockChanged();
}
void LLAppearanceMgr::addCategoryToCurrentOutfit(const LLUUID& cat_id)
{
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
wearInventoryCategory(cat, false, true);
}
void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLFindWearablesEx collector(/*is_worn=*/ true, /*include_body_parts=*/ false);
gInventory.collectDescendentsIf(cat_id, cats, items, FALSE, collector);
LLInventoryModel::item_array_t::const_iterator it = items.begin();
const LLInventoryModel::item_array_t::const_iterator it_end = items.end();
uuid_vec_t uuids_to_remove;
for( ; it_end != it; ++it)
{
LLViewerInventoryItem* item = *it;
uuids_to_remove.push_back(item->getUUID());
}
removeItemsFromAvatar(uuids_to_remove);
}
// Create a copy of src_id + contents as a subfolder of dst_id.
void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id,
LLPointer<LLInventoryCallback> cb)
{
LLInventoryCategory *src_cat = gInventory.getCategory(src_id);
if (!src_cat)
{
llwarns << "folder not found for src " << src_id.asString() << llendl;
return;
}
llinfos << "starting, src_id " << src_id << " name " << src_cat->getName() << " dst_id " << dst_id << llendl;
LLUUID parent_id = dst_id;
if(parent_id.isNull())
{
parent_id = gInventory.getRootFolderID();
}
LLUUID subfolder_id = gInventory.createNewCategory( parent_id,
LLFolderType::FT_NONE,
src_cat->getName());
shallowCopyCategoryContents(src_id, subfolder_id, cb);
gInventory.notifyObservers();
}
// Copy contents of src_id to dst_id.
void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id,
LLPointer<LLInventoryCallback> cb)
{
LLInventoryModel::cat_array_t* cats;
LLInventoryModel::item_array_t* items;
gInventory.getDirectDescendentsOf(src_id, cats, items);
llinfos << "copying " << items->count() << " items" << llendl;
copyItems(dst_id, items, cb);
}
void LLAppearanceMgr::copyItems(const LLUUID& dst_id, LLInventoryModel::item_array_t* items, LLPointer<LLInventoryCallback> cb)
{
for (LLInventoryModel::item_array_t::const_iterator iter = items->begin();
iter != items->end();
++iter)
{
const LLViewerInventoryItem* item = (*iter);
switch (item->getActualType())
{
case LLAssetType::AT_LINK:
{
//LLInventoryItem::getDescription() is used for a new description
//to propagate ordering information saved in descriptions of links
link_inventory_item(gAgent.getID(),
item->getLinkedUUID(),
dst_id,
item->getName(),
item->getActualDescription(),
LLAssetType::AT_LINK, cb);
break;
}
case LLAssetType::AT_LINK_FOLDER:
{
LLViewerInventoryCategory *catp = item->getLinkedCategory();
// Skip copying outfit links.
if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT)
{
link_inventory_item(gAgent.getID(),
item->getLinkedUUID(),
dst_id,
item->getName(),
item->getDescription(),
LLAssetType::AT_LINK_FOLDER, cb);
}
break;
}
case LLAssetType::AT_CLOTHING:
case LLAssetType::AT_OBJECT:
case LLAssetType::AT_BODYPART:
case LLAssetType::AT_GESTURE:
{
if(!item->getPermissions().allowCopyBy(gAgent.getID()))
{
link_inventory_item(gAgent.getID(),
item->getUUID(),
dst_id,
item->getName(),
item->getDescription(),
LLAssetType::AT_LINK, cb);
}
else
{
llinfos << "copying inventory item " << item->getName() << llendl;
copy_inventory_item(gAgent.getID(),
item->getPermissions().getOwner(),
item->getUUID(),
dst_id,
item->getName(),
cb);
}
break;
}
default:
// Ignore non-outfit asset types
break;
}
}
}
BOOL LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id)
{
// These are the wearable items that are required for considering this
// folder as containing a complete outfit.
U32 required_wearables = 0;
required_wearables |= 1LL << LLWearableType::WT_SHAPE;
required_wearables |= 1LL << LLWearableType::WT_SKIN;
required_wearables |= 1LL << LLWearableType::WT_HAIR;
required_wearables |= 1LL << LLWearableType::WT_EYES;
// These are the wearables that the folder actually contains.
U32 folder_wearables = 0;
LLInventoryModel::cat_array_t* cats;
LLInventoryModel::item_array_t* items;
gInventory.getDirectDescendentsOf(folder_id, cats, items);
for (LLInventoryModel::item_array_t::const_iterator iter = items->begin();
iter != items->end();
++iter)
{
const LLViewerInventoryItem* item = (*iter);
if (item->isWearableType())
{
const LLWearableType::EType wearable_type = item->getWearableType();
folder_wearables |= 1LL << wearable_type;
}
}
// If the folder contains the required wearables, return TRUE.
return ((required_wearables & folder_wearables) == required_wearables);
}
bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id)
{
// Disallow removing the base outfit.
if (outfit_cat_id == getBaseOutfitUUID())
{
return false;
}
// Check if the outfit folder itself is removable.
if (!get_is_category_removable(&gInventory, outfit_cat_id))
{
return false;
}
// Check for the folder's non-removable descendants.
LLFindNonRemovableObjects filter_non_removable;
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLInventoryModel::item_array_t::const_iterator it;
gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable);
if (!cats.empty() || !items.empty())
{
return false;
}
return true;
}
// static
bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false);
gInventory.collectDescendentsIf(outfit_cat_id,
cats,
items,
LLInventoryModel::EXCLUDE_TRASH,
is_worn);
return items.size() > 0;
}
// static
bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id)
{
if (gAgentWearables.isCOFChangeInProgress())
{
return false;
}
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false);
gInventory.collectDescendentsIf(outfit_cat_id,
cats,
items,
LLInventoryModel::EXCLUDE_TRASH,
not_worn);
return items.size() > 0;
}
bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id)
{
// Don't allow wearing anything while we're changing appearance.
if (gAgentWearables.isCOFChangeInProgress())
{
return false;
}
// Check whether it's the base outfit.
// if (outfit_cat_id.isNull() || outfit_cat_id == getBaseOutfitUUID())
// [SL:KB] - Patch: Appearance-Misc | Checked: 2010-09-21 (Catznip-3.0.0a) | Added: Catznip-2.1.2d
if ( (outfit_cat_id.isNull()) || ((outfit_cat_id == getBaseOutfitUUID()) && (!isOutfitDirty())) )
// [/SL:KB]
{
return false;
}
// Check whether the outfit contains any wearables we aren't wearing already (STORM-702).
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLFindWearablesEx is_worn(/*is_worn=*/ false, /*include_body_parts=*/ true);
gInventory.collectDescendentsIf(outfit_cat_id,
cats,
items,
LLInventoryModel::EXCLUDE_TRASH,
is_worn);
return items.size() > 0;
}
void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
gInventory.collectDescendents(category, cats, items,
LLInventoryModel::EXCLUDE_TRASH);
for (S32 i = 0; i < items.count(); ++i)
{
LLViewerInventoryItem *item = items.get(i);
if (item->getActualType() != LLAssetType::AT_LINK_FOLDER)
continue;
if (item->getIsLinkType())
{
LLViewerInventoryCategory* catp = item->getLinkedCategory();
if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
{
gInventory.purgeObject(item->getUUID());
}
}
}
}
void LLAppearanceMgr::purgeCategory(const LLUUID& category, bool keep_outfit_links)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
gInventory.collectDescendents(category, cats, items,
LLInventoryModel::EXCLUDE_TRASH);
for (S32 i = 0; i < items.count(); ++i)
{
LLViewerInventoryItem *item = items.get(i);
if (keep_outfit_links && (item->getActualType() == LLAssetType::AT_LINK_FOLDER))
continue;
if (item->getIsLinkType())
{
gInventory.purgeObject(item->getUUID());
}
}
}
// [SL:KB] - Checked: 2010-04-24 (RLVa-1.2.0f) | Added: RLVa-1.2.0f
void LLAppearanceMgr::purgeItems(const LLInventoryModel::item_array_t& items)
{
for (LLInventoryModel::item_array_t::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
{
const LLViewerInventoryItem* pItem = *itItem;
if (pItem->getIsLinkType())
{
gInventory.purgeObject(pItem->getUUID());
}
}
}
void LLAppearanceMgr::purgeItemsOfType(LLAssetType::EType asset_type)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
gInventory.collectDescendents(getCOF(), cats, items, LLInventoryModel::EXCLUDE_TRASH);
for (LLInventoryModel::item_array_t::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
{
const LLInventoryItem* pItem = *itItem;
if ( (pItem->getIsLinkType()) && (asset_type == pItem->getType()) )
{
gInventory.purgeObject(pItem->getUUID());
}
}
}
void LLAppearanceMgr::syncCOF(const LLInventoryModel::item_array_t& items,
LLInventoryModel::item_array_t& items_to_add, LLInventoryModel::item_array_t& items_to_remove)
{
const LLUUID idCOF = getCOF();
LLInventoryModel::item_array_t cur_cof_items, new_cof_items = items;
// Grab the current COF contents
LLInventoryModel::cat_array_t cats;
gInventory.collectDescendents(getCOF(), cats, cur_cof_items, LLInventoryModel::EXCLUDE_TRASH);
// Purge everything in cur_cof_items that isn't part of new_cof_items
for (S32 idxCurItem = 0, cntCurItem = cur_cof_items.count(); idxCurItem < cntCurItem; idxCurItem++)
{
LLViewerInventoryItem* pItem = cur_cof_items.get(idxCurItem);
if (std::find_if(new_cof_items.begin(), new_cof_items.end(), RlvPredIsEqualOrLinkedItem(pItem)) == new_cof_items.end())
{
// Item doesn't exist in new_cof_items => purge (if it's a link)
if ( (pItem->getIsLinkType()) &&
(LLAssetType::AT_LINK_FOLDER != pItem->getActualType()) &&
(items_to_remove.end() == std::find(items_to_remove.begin(), items_to_remove.end(), pItem)) )
{
items_to_remove.push_back(pItem);
}
}
else
{
// Item exists in new_cof_items => remove *all* occurances in new_cof_items (removes duplicate COF links to this item as well)
new_cof_items.erase(
std::remove_if(new_cof_items.begin(), new_cof_items.end(), RlvPredIsEqualOrLinkedItem(pItem)), new_cof_items.end());
}
}
// Whatever remains in new_cof_items will need to have a link created
for (S32 idxNewItem = 0, cntNewItem = new_cof_items.count(); idxNewItem < cntNewItem; idxNewItem++)
{
LLViewerInventoryItem* pItem = new_cof_items.get(idxNewItem);
if (items_to_add.end() == std::find(items_to_add.begin(), items_to_add.end(), pItem))
{
items_to_add.push_back(pItem);
}
}
}
// [/SL:KB]
// Keep the last N wearables of each type. For viewer 2.0, N is 1 for
// both body parts and clothing items.
void LLAppearanceMgr::filterWearableItems(
LLInventoryModel::item_array_t& items, S32 max_per_type)
{
// Divvy items into arrays by wearable type.
std::vector<LLInventoryModel::item_array_t> items_by_type(LLWearableType::WT_COUNT);
divvyWearablesByType(items, items_by_type);
// rebuild items list, retaining the last max_per_type of each array
items.clear();
for (S32 i=0; i<LLWearableType::WT_COUNT; i++)
{
S32 size = items_by_type[i].size();
if (size <= 0)
continue;
// S32 start_index = llmax(0,size-max_per_type);
// [SL:KB] - Patch: Appearance-Misc | Checked: 2010-05-11 (Catznip-3.0.0a) | Added: Catznip-2.0.0h
S32 start_index =
llmax(0, size - ((LLAssetType::AT_BODYPART == LLWearableType::getAssetType((LLWearableType::EType)i)) ? 1 : max_per_type));
// [/SL:KB[
for (S32 j = start_index; j<size; j++)
{
items.push_back(items_by_type[i][j]);
}
}
}
// Create links to all listed items.
void LLAppearanceMgr::linkAll(const LLUUID& cat_uuid,
LLInventoryModel::item_array_t& items,
LLPointer<LLInventoryCallback> cb)
{
for (S32 i=0; i<items.count(); i++)
{
const LLInventoryItem* item = items.get(i).get();
link_inventory_item(gAgent.getID(),
item->getLinkedUUID(),
cat_uuid,
item->getName(),
item->getActualDescription(),
LLAssetType::AT_LINK,
cb);
const LLViewerInventoryCategory *cat = gInventory.getCategory(cat_uuid);
const std::string cat_name = cat ? cat->getName() : "CAT NOT FOUND";
#ifndef LL_RELEASE_FOR_DOWNLOAD
LL_DEBUGS("Avatar") << self_av_string() << "Linking Item [ name:" << item->getName() << " UUID:" << item->getUUID() << " ] to Category [ name:" << cat_name << " UUID:" << cat_uuid << " ] " << LL_ENDL;
#endif
}
}
//void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append)
// [RLVa:KB] - Checked: 2010-03-05 (RLVa-1.2.0b) | Added: RLVa-1.2.0b
void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append)
{
LLInventoryModel::item_array_t body_items_new, wear_items_new, obj_items_new, gest_items_new;
getDescendentsOfAssetType(category, body_items_new, LLAssetType::AT_BODYPART, false);
getDescendentsOfAssetType(category, wear_items_new, LLAssetType::AT_CLOTHING, false);
getDescendentsOfAssetType(category, obj_items_new, LLAssetType::AT_OBJECT, false);
getDescendentsOfAssetType(category, gest_items_new, LLAssetType::AT_GESTURE, false);
updateCOF(body_items_new, wear_items_new, obj_items_new, gest_items_new, append, category);
}
void LLAppearanceMgr::updateCOF(LLInventoryModel::item_array_t& body_items_new,
LLInventoryModel::item_array_t& wear_items_new,
LLInventoryModel::item_array_t& obj_items_new,
LLInventoryModel::item_array_t& gest_items_new,
bool append /*=false*/, const LLUUID& idOutfit /*=LLUUID::null*/)
// [/RLVa:KB]
{
// LLViewerInventoryCategory *pcat = gInventory.getCategory(category);
// llinfos << "starting, cat " << (pcat ? pcat->getName() : "[UNKNOWN]") << llendl;
// [RLVa:KB] - Checked: 2010-03-26 (RLVa-1.2.0b) | Added: RLVa-1.2.0b
// RELEASE-RLVa: [SL-2.0.0] If pcat ever gets used for anything further down the beta we'll know about it
llinfos << "starting" << llendl;
// [/RLVa:KB]
const LLUUID cof = getCOF();
// Deactivate currently active gestures in the COF, if replacing outfit
if (!append)
{
LLInventoryModel::item_array_t gest_items;
getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false);
for(S32 i = 0; i < gest_items.count(); ++i)
{
LLViewerInventoryItem *gest_item = gest_items.get(i);
if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) )
{
LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() );
}
}
}
// Collect and filter descendents to determine new COF contents.
// - Body parts: always include COF contents as a fallback in case any
// required parts are missing.
// Preserve body parts from COF if appending.
LLInventoryModel::item_array_t body_items;
getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART, false);
// getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART, false);
// [RLVa:KB] - Checked: 2010-03-19 (RLVa-1.2.0c) | Modified: RLVa-1.2.0b
// Filter out any new body parts that can't be worn before adding them
if ( (rlv_handler_t::isEnabled()) && (gRlvWearableLocks.hasLockedWearableType(RLV_LOCK_ANY)) )
body_items_new.erase(std::remove_if(body_items_new.begin(), body_items_new.end(), RlvPredCanNotWearItem(RLV_WEAR_REPLACE)), body_items_new.end());
body_items.insert(body_items.end(), body_items_new.begin(), body_items_new.end());
// [/RLVa:KB]
// NOTE-RLVa: we don't actually want to favour COF body parts over the folder's body parts (if only because it breaks force wear)
// if (append)
// reverse(body_items.begin(), body_items.end());
// Reduce body items to max of one per type.
removeDuplicateItems(body_items);
filterWearableItems(body_items, 1);
// - Wearables: include COF contents only if appending.
LLInventoryModel::item_array_t wear_items;
if (append)
getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING, false);
// [RLVa:KB] - Checked: 2010-03-19 (RLVa-1.2.0c) | Modified: RLVa-1.2.0b
else if ( (rlv_handler_t::isEnabled()) && (gRlvWearableLocks.hasLockedWearableType(RLV_LOCK_ANY)) )
{
// Make sure that all currently locked clothing layers remain in COF when replacing
getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING, false);
wear_items.erase(std::remove_if(wear_items.begin(), wear_items.end(), rlvPredCanRemoveItem), wear_items.end());
}
// [/RLVa:KB]
// getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING, false);
// [RLVa:KB] - Checked: 2010-03-19 (RLVa-1.2.0c) | Modified: RLVa-1.2.0b
// Filter out any new wearables that can't be worn before adding them
if ( (rlv_handler_t::isEnabled()) && (gRlvWearableLocks.hasLockedWearableType(RLV_LOCK_ANY)) )
wear_items_new.erase(std::remove_if(wear_items_new.begin(), wear_items_new.end(), RlvPredCanNotWearItem(RLV_WEAR)), wear_items_new.end());
wear_items.insert(wear_items.end(), wear_items_new.begin(), wear_items_new.end());
// [/RLVa:KB]
// Reduce wearables to max of one per type.
removeDuplicateItems(wear_items);
// [SL:KB] - Patch: Appearance-WearableDuplicateAssets | Checked: 2011-07-24 (Catznip-2.6.0e) | Added: Catznip-2.6.0e
removeDuplicateWearableItemsByAssetID(wear_items);
// [/SL:KB]
filterWearableItems(wear_items, LLAgentWearables::MAX_CLOTHING_PER_TYPE);
//
// - Attachments: include COF contents only if appending.
//
LLInventoryModel::item_array_t obj_items;
if (append)
getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT, false);
// [RLVa:KB] - Checked: 2010-03-05 (RLVa-1.2.0z) | Modified: RLVa-1.2.0b
else if ( (rlv_handler_t::isEnabled()) && (gRlvAttachmentLocks.hasLockedAttachmentPoint(RLV_LOCK_ANY)) )
{
// Make sure that all currently locked attachments remain in COF when replacing
getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT, false);
obj_items.erase(std::remove_if(obj_items.begin(), obj_items.end(), rlvPredCanRemoveItem), obj_items.end());
}
// [/RLVa:KB]
// getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT, false);
// [RLVa:KB] - Checked: 2010-03-05 (RLVa-1.2.0z) | Modified: RLVa-1.2.0b
// Filter out any new attachments that can't be worn before adding them
if ( (rlv_handler_t::isEnabled()) && (gRlvAttachmentLocks.hasLockedAttachmentPoint(RLV_LOCK_ANY)) )
obj_items_new.erase(std::remove_if(obj_items_new.begin(), obj_items_new.end(), RlvPredCanNotWearItem(RLV_WEAR)), obj_items_new.end());
obj_items.insert(obj_items.end(), obj_items_new.begin(), obj_items_new.end());
// [/RLVa:KB]
removeDuplicateItems(obj_items);
//
// - Gestures: include COF contents only if appending.
//
LLInventoryModel::item_array_t gest_items;
if (append)
getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false);
// getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE, false);
// [RLVa:KB] - Checked: 2010-03-05 (RLVa-1.2.0z) | Added: RLVa-1.2.0b
gest_items.insert(gest_items.end(), gest_items_new.begin(), gest_items_new.end());
// [/RLVa:KB]
removeDuplicateItems(gest_items);
// Create links to new COF contents.
LLInventoryModel::item_array_t all_items;
all_items += body_items;
all_items += wear_items;
all_items += obj_items;
all_items += gest_items;
// [SL:KB]
// Synchronize COF
// -> it's possible that we don't link to any new items in which case 'link_waiter' fires when it goes out of scope below
LLInventoryModel::item_array_t items_add, items_remove;
syncCOF(all_items, items_add, items_remove);
// [/SL:KB]
// Will link all the above items.
LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy;
// [SL:KB] - Checked: 2013-03-05 (RLVa-1.4.8)
linkAll(cof, items_add, link_waiter);
// [/SL:KB]
// linkAll(cof,all_items,link_waiter);
// Add link to outfit if category is an outfit.
// [SL:KB] - Checked: 2010-04-24 (RLVa-1.2.0f) | Added: RLVa-1.2.0f
if ( (!append) && (idOutfit.notNull()) )
{
createBaseOutfitLink(idOutfit, link_waiter);
}
// [/SL:KB]
// if (!append)
// {
// createBaseOutfitLink(category, link_waiter);
// }
//
// Remove current COF contents. Have to do this after creating
// the link_waiter so links can be followed for any items that get
// carried over (e.g. keeping old shape if the new outfit does not
// contain one)
// [SL:KB]
purgeItems(items_remove);
bool keep_outfit_links = append;
if (!keep_outfit_links)
{
purgeItemsOfType(LLAssetType::AT_LINK_FOLDER);
}
gInventory.notifyObservers();
// [/SL:KB]
// bool keep_outfit_links = append;
// purgeCategory(cof, keep_outfit_links, &all_items);
// gInventory.notifyObservers();
LL_DEBUGS("Avatar") << self_av_string() << "waiting for LLUpdateAppearanceOnDestroy" << LL_ENDL;
}
void LLAppearanceMgr::updatePanelOutfitName(const std::string& name)
{
// MULTI-WEARABLE TODO
/*LLSidepanelAppearance* panel_appearance =
dynamic_cast<LLSidepanelAppearance *>(LLFloaterSidePanelContainer::getPanel("appearance"));
if (panel_appearance)
{
panel_appearance->refreshCurrentOutfitName(name);
}*/
}
void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer<LLInventoryCallback> link_waiter)
{
const LLUUID cof = getCOF();
LLViewerInventoryCategory* catp = gInventory.getCategory(category);
std::string new_outfit_name = "";
purgeBaseOutfitLink(cof);
if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
{
link_inventory_item(gAgent.getID(), category, cof, catp->getName(), "",
LLAssetType::AT_LINK_FOLDER, link_waiter);
new_outfit_name = catp->getName();
}
updatePanelOutfitName(new_outfit_name);
}
void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder, bool append)
{
lldebugs << "updateAgentWearables()" << llendl;
LLInventoryItem::item_array_t items;
LLDynamicArray< LLViewerWearable* > wearables;
// [RLVa:KB] - Checked: 2011-03-31 (RLVa-1.3.0f) | Added: RLVa-1.3.0f
uuid_vec_t idsCurrent; LLInventoryModel::item_array_t itemsNew;
if (rlv_handler_t::isEnabled())
{
// Collect the item UUIDs of all currently worn wearables
gAgentWearables.getWearableItemIDs(idsCurrent);
}
// [/RLVa:KB]
// For each wearable type, find the wearables of that type.
for( S32 i = 0; i < LLWearableType::WT_COUNT; i++ )
{
for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->getFoundList().begin();
iter != holder->getFoundList().end(); ++iter)
{
LLFoundData& data = *iter;
LLViewerWearable* wearable = data.mWearable;
if( wearable && ((S32)wearable->getType() == i) )
{
LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID);
if( item && (item->getAssetUUID() == wearable->getAssetID()) )
{
// [RLVa:KB] - Checked: 2010-03-19 (RLVa-1.2.0g) | Modified: RLVa-1.2.0g
// TODO-RLVa: [RLVa-1.2.1] This is fall-back code so if we don't ever trigger this code it can just be removed
// -> one way to trigger the assertion:
// 1) "Replace Outfit" on a folder with clothing and an attachment that goes @addoutfit=n
// 2) updateCOF will add/link the items into COF => no @addoutfit=n present yet => allowed
// 3) llOwnerSay("@addoutfit=n") executes
// 4) code below runs => @addoutfit=n conflicts with adding new wearables
// => if it's left as-is then the wearables won't get worn (but remain in COF which causes issues of its own)
// => if it's changed to debug-only then we make tge assumption that anything that makes it into COF is always OK
#ifdef RLV_DEBUG
// NOTE: make sure we don't accidentally block setting the initial wearables
if ( (rlv_handler_t::isEnabled()) && (RLV_WEAR_LOCKED == gRlvWearableLocks.canWear(wearable->getType())) &&
(!gAgentWearables.getWearableFromItemID(item->getUUID())) && (gAgentWearables.areWearablesLoaded()) )
{
RLV_VERIFY(RLV_WEAR_LOCKED == gRlvWearableLocks.canWear(wearable->getType()));
continue;
}
#endif // RLV_DEBUG
// [/RLVa:KB]
items.put(item);
wearables.put(wearable);
// [RLVa:KB] - Checked: 2011-03-31 (RLVa-1.3.0f) | Added: RLVa-1.3.0f
if ( (rlv_handler_t::isEnabled()) && (gAgentWearables.areInitalWearablesLoaded()) )
{
// Remove the wearable from current item UUIDs if currently worn and requested, otherwise mark it as a new item
uuid_vec_t::iterator itItemID = std::find(idsCurrent.begin(), idsCurrent.end(), item->getUUID());
if (idsCurrent.end() != itItemID)
idsCurrent.erase(itItemID);
else
itemsNew.push_back(item);
}
// [/RLVa:KB]
}
}
}
}
// [RLVa:KB] - Checked: 2011-03-31 (RLVa-1.3.0f) | Added: RLVa-1.3.0f
if ( (rlv_handler_t::isEnabled()) && (gAgentWearables.areInitalWearablesLoaded()) )
{
// We need to report removals before additions or scripts will get confused
for (uuid_vec_t::const_iterator itItemID = idsCurrent.begin(); itItemID != idsCurrent.end(); ++itItemID)
{
const LLWearable* pWearable = gAgentWearables.getWearableFromItemID(*itItemID);
if (pWearable)
RlvBehaviourNotifyHandler::onTakeOff(pWearable->getType(), true);
}
for (S32 idxItem = 0, cntItem = itemsNew.count(); idxItem < cntItem; idxItem++)
{
RlvBehaviourNotifyHandler::onWear(itemsNew.get(idxItem)->getWearableType(), true);
}
}
// [/RLVa:KB]
if(wearables.count() > 0)
{
gAgentWearables.setWearableOutfit(items, wearables, !append);
}
// dec_busy_count();
}
static void remove_non_link_items(LLInventoryModel::item_array_t &items)
{
LLInventoryModel::item_array_t pruned_items;
for (LLInventoryModel::item_array_t::const_iterator iter = items.begin();
iter != items.end();
++iter)
{
const LLViewerInventoryItem *item = (*iter);
if (item && item->getIsLinkType())
{
pruned_items.push_back((*iter));
}
}
items = pruned_items;
}
//a predicate for sorting inventory items by actual descriptions
bool sort_by_actual_description(const LLInventoryItem* item1, const LLInventoryItem* item2)
{
if (!item1 || !item2)
{
llwarning("either item1 or item2 is NULL", 0);
return true;
}
return item1->getActualDescription() < item2->getActualDescription();
}
void item_array_diff(LLInventoryModel::item_array_t& full_list,
LLInventoryModel::item_array_t& keep_list,
LLInventoryModel::item_array_t& kill_list)
{
for (LLInventoryModel::item_array_t::iterator it = full_list.begin();
it != full_list.end();
++it)
{
LLViewerInventoryItem *item = *it;
if (keep_list.find(item) < 0) // Why on earth does LLDynamicArray need to redefine find()?
{
kill_list.push_back(item);
}
}
}
S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id,
LLAssetType::EType type,
S32 max_items,
LLInventoryModel::item_array_t& items_to_kill)
{
S32 to_kill_count = 0;
LLInventoryModel::item_array_t items;
getDescendentsOfAssetType(cat_id, items, type, false);
LLInventoryModel::item_array_t curr_items = items;
removeDuplicateItems(items);
if (max_items > 0)
{
filterWearableItems(items, max_items);
}
LLInventoryModel::item_array_t kill_items;
item_array_diff(curr_items,items,kill_items);
for (LLInventoryModel::item_array_t::iterator it = kill_items.begin();
it != kill_items.end();
++it)
{
items_to_kill.push_back(*it);
to_kill_count++;
}
return to_kill_count;
}
void LLAppearanceMgr::enforceItemRestrictions()
{
S32 purge_count = 0;
LLInventoryModel::item_array_t items_to_kill;
purge_count += findExcessOrDuplicateItems(getCOF(),LLAssetType::AT_BODYPART,
1, items_to_kill);
purge_count += findExcessOrDuplicateItems(getCOF(),LLAssetType::AT_CLOTHING,
LLAgentWearables::MAX_CLOTHING_PER_TYPE, items_to_kill);
purge_count += findExcessOrDuplicateItems(getCOF(),LLAssetType::AT_OBJECT,
-1, items_to_kill);
if (items_to_kill.size()>0)
{
for (LLInventoryModel::item_array_t::iterator it = items_to_kill.begin();
it != items_to_kill.end();
++it)
{
LLViewerInventoryItem *item = *it;
LL_DEBUGS("Avatar") << self_av_string() << "purging duplicate or excess item " << item->getName() << LL_ENDL;
gInventory.purgeObject(item->getUUID());
}
gInventory.notifyObservers();
}
}
void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering)
{
if (mIsInUpdateAppearanceFromCOF)
{
llwarns << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << llendl;
return;
}
BoolSetter setIsInUpdateAppearanceFromCOF(mIsInUpdateAppearanceFromCOF);
selfStartPhase("update_appearance_from_cof");
LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL;
//checking integrity of the COF in terms of ordering of wearables,
//checking and updating links' descriptions of wearables in the COF (before analyzed for "dirty" state)
updateClothingOrderingInfo(LLUUID::null, update_base_outfit_ordering);
// Remove duplicate or excess wearables. Should normally be enforced at the UI level, but
// this should catch anything that gets through.
enforceItemRestrictions();
// update dirty flag to see if the state of the COF matches
// the saved outfit stored as a folder link
updateIsDirty();
// Send server request for appearance update
if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion())
{
requestServerAppearanceUpdate();
}
// DRANO really should wait for the appearance message to set this.
// verify that deleting this line doesn't break anything.
//gAgentAvatarp->setIsUsingServerBakes(gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion());
//dumpCat(getCOF(),"COF, start");
bool follow_folder_links = false;
LLUUID current_outfit_id = getCOF();
// Find all the wearables that are in the COF's subtree.
lldebugs << "LLAppearanceMgr::updateFromCOF()" << llendl;
LLInventoryModel::item_array_t wear_items;
LLInventoryModel::item_array_t obj_items;
LLInventoryModel::item_array_t gest_items;
getUserDescendents(current_outfit_id, wear_items, obj_items, gest_items, follow_folder_links);
// Get rid of non-links in case somehow the COF was corrupted.
remove_non_link_items(wear_items);
remove_non_link_items(obj_items);
remove_non_link_items(gest_items);
// [SL:KB] - Patch: Apperance-Misc | Checked: 2010-11-24 (Catznip-3.0.0a) | Added: Catzip-2.4.0f
// Since we're following folder links we might have picked up new duplicates, or exceeded MAX_CLOTHING_PER_TYPE
removeDuplicateItems(wear_items);
removeDuplicateItems(obj_items);
removeDuplicateItems(gest_items);
filterWearableItems(wear_items, LLAgentWearables::MAX_CLOTHING_PER_TYPE);
// [/SL:KB]
// [SL:KB] - Patch: Appearance-WearableDuplicateAssets | Checked: 2011-07-24 (Catznip-2.6.0e) | Added: Catznip-2.6.0e
// Wearing two wearables that share the same asset causes some issues
removeDuplicateWearableItemsByAssetID(wear_items);
// [/SL:KB]
dumpItemArray(wear_items,"asset_dump: wear_item");
dumpItemArray(obj_items,"asset_dump: obj_item");
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-09-22 (Catznip-3.0.0a) | Added: Catznip-2.2.0a
// Update attachments to match those requested.
if (isAgentAvatarValid())
{
// Include attachments which should be in COF but don't have their link created yet
uuid_vec_t::iterator itPendingAttachLink = mPendingAttachLinks.begin();
while (itPendingAttachLink != mPendingAttachLinks.end())
{
const LLUUID& idItem = *itPendingAttachLink;
if ( (!gAgentAvatarp->isWearingAttachment(idItem)) || (isLinkInCOF(idItem)) )
{
itPendingAttachLink = mPendingAttachLinks.erase(itPendingAttachLink);
continue;
}
LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
if (pItem)
{
obj_items.push_back(pItem);
}
++itPendingAttachLink;
}
// Don't remove attachments until avatar is fully loaded (should reduce random attaching/detaching/reattaching at log-on)
LL_DEBUGS("Avatar") << self_av_string() << "Updating " << obj_items.count() << " attachments" << LL_ENDL;
LLAgentWearables::userUpdateAttachments(obj_items, !gAgentAvatarp->isFullyLoaded());
}
// [/SL:KB]
if(!wear_items.count())
{
LLNotificationsUtil::add("CouldNotPutOnOutfit");
return;
}
//preparing the list of wearables in the correct order for LLAgentWearables
sortItemsByActualDescription(wear_items);
LLWearableHoldingPattern* holder = new LLWearableHoldingPattern;
// holder->setObjItems(obj_items);
holder->setGestItems(gest_items);
// Note: can't do normal iteration, because if all the
// wearables can be resolved immediately, then the
// callback will be called (and this object deleted)
// before the final getNextData().
for(S32 i = 0; i < wear_items.count(); ++i)
{
LLViewerInventoryItem *item = wear_items.get(i);
LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
// Fault injection: use debug setting to test asset
// fetch failures (should be replaced by new defaults in
// lost&found).
U32 skip_type = gSavedSettings.getU32("ForceAssetFail");
// [RLVa:KB] - Checked: 2010-12-11 (RLVa-1.2.2c) | Added: RLVa-1.2.2c
U32 missing_type = gSavedSettings.getU32("ForceMissingType");
// [/RLVa:KB]
if (item && item->getIsLinkType() && linked_item)
{
LLFoundData found(linked_item->getUUID(),
linked_item->getAssetUUID(),
linked_item->getName(),
linked_item->getType(),
linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID
);
// [RLVa:KB] - Checked: 2010-12-15 (RLVa-1.2.2c) | Modified: RLVa-1.2.2c
#ifdef LL_RELEASE_FOR_DOWNLOAD
// Don't allow forcing an invalid wearable if the initial wearables aren't set yet, or if any wearable type is currently locked
if ( (!rlv_handler_t::isEnabled()) ||
((gAgentWearables.areInitalWearablesLoaded()) && (!gRlvWearableLocks.hasLockedWearableType(RLV_LOCK_REMOVE))) )
#endif // LL_RELEASE_FOR_DOWNLOAD
{
if (missing_type != LLWearableType::WT_INVALID && missing_type == found.mWearableType)
{
continue;
}
// [/RLVa:KB]
if (skip_type != LLWearableType::WT_INVALID && skip_type == found.mWearableType)
{
found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB
}
// [RLVa:KB] - Checked: 2010-12-15 (RLVa-1.2.2c) | Modified: RLVa-1.2.2c
}
// [/RLVa:KB]
//pushing back, not front, to preserve order of wearables for LLAgentWearables
holder->getFoundList().push_back(found);
}
else
{
if (!item)
{
llwarns << "Attempt to wear a null item " << llendl;
}
else if (!linked_item)
{
llwarns << "Attempt to wear a broken link [ name:" << item->getName() << " ] " << llendl;
}
}
}
selfStartPhase("get_wearables");
for (LLWearableHoldingPattern::found_list_t::iterator it = holder->getFoundList().begin();
it != holder->getFoundList().end(); ++it)
{
LLFoundData& found = *it;
lldebugs << self_av_string() << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << llendl;
// Fetch the wearables about to be worn.
LLWearableList::instance().getAsset(found.mAssetID,
found.mName,
gAgentAvatarp,
found.mAssetType,
onWearableAssetFetch,
(void*)holder);
}
holder->resetTime(gSavedSettings.getF32("MaxWearableWaitTime"));
if (!holder->pollFetchCompletion())
{
doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollFetchCompletion,holder));
}
}
// [SL:KB] - Patch: Appearance-MixedViewers | Checked: 2010-04-02 (Catznip-3.0.0a) | Added: Catznip-2.0.0a
void LLAppearanceMgr::updateAppearanceFromInitialWearables(LLInventoryModel::item_array_t& initial_items)
{
const LLUUID& idCOF = getCOF();
// Remove current COF contents
purgeCategory(idCOF, false);
gInventory.notifyObservers();
// Create links to new COF contents
LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy();
linkAll(idCOF, initial_items, link_waiter);
}
// [/SL:KB]
void LLAppearanceMgr::getDescendentsOfAssetType(const LLUUID& category,
LLInventoryModel::item_array_t& items,
LLAssetType::EType type,
bool follow_folder_links)
{
LLInventoryModel::cat_array_t cats;
LLIsType is_of_type(type);
gInventory.collectDescendentsIf(category,
cats,
items,
LLInventoryModel::EXCLUDE_TRASH,
is_of_type,
follow_folder_links);
}
void LLAppearanceMgr::getUserDescendents(const LLUUID& category,
LLInventoryModel::item_array_t& wear_items,
LLInventoryModel::item_array_t& obj_items,
LLInventoryModel::item_array_t& gest_items,
bool follow_folder_links)
{
LLInventoryModel::cat_array_t wear_cats;
LLFindWearables is_wearable;
gInventory.collectDescendentsIf(category,
wear_cats,
wear_items,
LLInventoryModel::EXCLUDE_TRASH,
is_wearable,
follow_folder_links);
LLInventoryModel::cat_array_t obj_cats;
LLIsType is_object( LLAssetType::AT_OBJECT );
gInventory.collectDescendentsIf(category,
obj_cats,
obj_items,
LLInventoryModel::EXCLUDE_TRASH,
is_object,
follow_folder_links);
// Find all gestures in this folder
LLInventoryModel::cat_array_t gest_cats;
LLIsType is_gesture( LLAssetType::AT_GESTURE );
gInventory.collectDescendentsIf(category,
gest_cats,
gest_items,
LLInventoryModel::EXCLUDE_TRASH,
is_gesture,
follow_folder_links);
}
void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append)
{
if(!category) return;
selfClearPhases();
selfStartPhase("wear_inventory_category");
gAgentWearables.notifyLoadingStarted();
LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName()
<< " )" << LL_ENDL;
selfStartPhase("wear_inventory_category_fetch");
callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal,
&LLAppearanceMgr::instance(),
category->getUUID(), copy, append));
}
void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append)
{
LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL;
selfStopPhase("wear_inventory_category_fetch");
// We now have an outfit ready to be copied to agent inventory. Do
// it, and wear that outfit normally.
LLInventoryCategory* cat = gInventory.getCategory(cat_id);
if(copy_items)
{
LLInventoryModel::cat_array_t* cats;
LLInventoryModel::item_array_t* items;
gInventory.getDirectDescendentsOf(cat_id, cats, items);
std::string name;
if(!cat)
{
// should never happen.
name = "New Outfit";
}
else
{
name = cat->getName();
}
LLViewerInventoryItem* item = NULL;
LLInventoryModel::item_array_t::const_iterator it = items->begin();
LLInventoryModel::item_array_t::const_iterator end = items->end();
LLUUID pid;
for(; it < end; ++it)
{
item = *it;
if(item)
{
if(LLInventoryType::IT_GESTURE == item->getInventoryType())
{
pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE);
}
else
{
pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING);
}
break;
}
}
if(pid.isNull())
{
pid = gInventory.getRootFolderID();
}
LLUUID new_cat_id = gInventory.createNewCategory(
pid,
LLFolderType::FT_NONE,
name);
// Create a CopyMgr that will copy items, manage its own destruction
new LLCallAfterInventoryCopyMgr(
*items, new_cat_id, std::string("wear_inventory_category_callback"),
boost::bind(&LLAppearanceMgr::wearInventoryCategoryOnAvatar,
LLAppearanceMgr::getInstance(),
gInventory.getCategory(new_cat_id),
append));
// BAP fixes a lag in display of created dir.
gInventory.notifyObservers();
}
else
{
// Wear the inventory category.
LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, append);
}
}
// *NOTE: hack to get from avatar inventory to avatar
void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append )
{
// Avoid unintentionally overwriting old wearables. We have to do
// this up front to avoid having to deal with the case of multiple
// wearables being dirty.
if (!category) return;
if ( !LLInventoryCallbackManager::is_instantiated() )
{
// shutting down, ignore.
return;
}
LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategoryOnAvatar '" << category->getName()
<< "'" << LL_ENDL;
if (isAgentAvatarValid() && gAgentAvatarp->isEditingAppearance())
{
// switching to outfit editor should automagically save any currently edited wearable
//LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); // MULTI-WEARABLES TODO
}
LLAppearanceMgr::changeOutfit(TRUE, category->getUUID(), append);
}
void LLAppearanceMgr::wearOutfitByName(const std::string& name)
{
LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL;
//inc_busy_count();
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
LLNameCategoryCollector has_name(name);
gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH,
has_name);
bool copy_items = false;
LLInventoryCategory* cat = NULL;
if (cat_array.count() > 0)
{
// Just wear the first one that matches
cat = cat_array.get(0);
}
else
{
gInventory.collectDescendentsIf(LLUUID::null,
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH,
has_name);
if(cat_array.count() > 0)
{
cat = cat_array.get(0);
copy_items = true;
}
}
if(cat)
{
LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false);
}
else
{
llwarns << "Couldn't find outfit " <<name<< " in wearOutfitByName()"
<< llendl;
}
//dec_busy_count();
}
bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b)
{
return (a->isWearableType() && b->isWearableType() &&
(a->getWearableType() == b->getWearableType()));
}
class LLDeferredCOFLinkObserver: public LLInventoryObserver
{
public:
LLDeferredCOFLinkObserver(const LLUUID& item_id, bool do_update, LLPointer<LLInventoryCallback> cb = NULL, std::string description = ""):
mItemID(item_id),
mDoUpdate(do_update),
mCallback(cb),
mDescription(description)
{
}
~LLDeferredCOFLinkObserver()
{
}
/* virtual */ void changed(U32 mask)
{
const LLInventoryItem *item = gInventory.getItem(mItemID);
if (item)
{
gInventory.removeObserver(this);
LLAppearanceMgr::instance().addCOFItemLink(item,mDoUpdate,mCallback);
delete this;
}
}
private:
const LLUUID mItemID;
bool mDoUpdate;
std::string mDescription;
LLPointer<LLInventoryCallback> mCallback;
};
// BAP - note that this runs asynchronously if the item is not already loaded from inventory.
// Dangerous if caller assumes link will exist after calling the function.
void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, bool do_update, LLPointer<LLInventoryCallback> cb, const std::string description)
{
const LLInventoryItem *item = gInventory.getItem(item_id);
if (!item)
{
LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, do_update, cb, description);
gInventory.addObserver(observer);
}
else
{
addCOFItemLink(item, do_update, cb, description);
}
}
void modified_cof_cb(const LLUUID& inv_item)
{
LLAppearanceMgr::instance().updateAppearanceFromCOF();
// Start editing the item if previously requested.
gAgentWearables.editWearableIfRequested(inv_item);
// TODO: camera mode may not be changed if a debug setting is tweaked
if( LLFloaterCustomize::instanceExists() )
{
// If we're in appearance editing mode, the current tab may need to be refreshed
/*LLSidepanelAppearance *panel = dynamic_cast<LLSidepanelAppearance*>(LLFloaterSidePanelContainer::getPanel("appearance"));
if (panel)
{
panel->showDefaultSubpart();
}*/
LLFloaterCustomize::getInstance()->switchToDefaultSubpart();
}
}
void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update, LLPointer<LLInventoryCallback> cb, const std::string description)
{
std::string link_description = description;
const LLViewerInventoryItem *vitem = dynamic_cast<const LLViewerInventoryItem*>(item);
if (!vitem)
{
llwarns << "not an llviewerinventoryitem, failed" << llendl;
return;
}
gInventory.addChangedMask(LLInventoryObserver::LABEL, vitem->getLinkedUUID());
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
gInventory.collectDescendents(LLAppearanceMgr::getCOF(),
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH);
bool linked_already = false;
U32 count = 0;
for (S32 i=0; i<item_array.count(); i++)
{
// Are these links to the same object?
const LLViewerInventoryItem* inv_item = item_array.get(i).get();
const LLWearableType::EType wearable_type = inv_item->getWearableType();
const bool is_body_part = (wearable_type == LLWearableType::WT_SHAPE)
|| (wearable_type == LLWearableType::WT_HAIR)
|| (wearable_type == LLWearableType::WT_EYES)
|| (wearable_type == LLWearableType::WT_SKIN);
if (inv_item->getLinkedUUID() == vitem->getLinkedUUID())
{
linked_already = true;
}
// Are these links to different items of the same body part
// type? If so, new item will replace old.
else if ((vitem->isWearableType()) && (vitem->getWearableType() == wearable_type))
{
++count;
if (is_body_part && inv_item->getIsLinkType() && (vitem->getWearableType() == wearable_type))
{
gInventory.purgeObject(inv_item->getUUID());
}
else if (count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE)
{
// MULTI-WEARABLES: make sure we don't go over MAX_CLOTHING_PER_TYPE
gInventory.purgeObject(inv_item->getUUID());
}
// [SL:KB] - Patch: Appearance-WearableDuplicateAssets | Checked: 2011-07-24 (Catznip-2.6.0e) | Added: Catznip-2.6.0e
else if ( (vitem->getWearableType() == wearable_type) && (vitem->getAssetUUID() == inv_item->getAssetUUID()) )
{
// Only allow one wearable per unique asset
linked_already = true;
}
// [/SL:KB]
}
}
if (linked_already)
{
if (do_update)
{
LLAppearanceMgr::updateAppearanceFromCOF();
}
return;
}
else
{
if(do_update && cb.isNull())
{
cb = new LLBoostFuncInventoryCallback(modified_cof_cb);
}
if (vitem->getIsLinkType())
{
link_description = vitem->getActualDescription();
}
link_inventory_item( gAgent.getID(),
vitem->getLinkedUUID(),
getCOF(),
vitem->getName(),
link_description,
LLAssetType::AT_LINK,
cb);
}
return;
}
LLInventoryModel::item_array_t LLAppearanceMgr::findCOFItemLinks(const LLUUID& item_id)
{
LLInventoryModel::item_array_t result;
const LLViewerInventoryItem *vitem =
dynamic_cast<const LLViewerInventoryItem*>(gInventory.getItem(item_id));
if (vitem)
{
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
gInventory.collectDescendents(LLAppearanceMgr::getCOF(),
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH);
for (S32 i=0; i<item_array.count(); i++)
{
const LLViewerInventoryItem* inv_item = item_array.get(i).get();
if (inv_item->getLinkedUUID() == vitem->getLinkedUUID())
{
result.put(item_array.get(i));
}
}
}
return result;
}
void LLAppearanceMgr::removeAllClothesFromAvatar()
{
// Fetch worn clothes (i.e. the ones in COF).
LLInventoryModel::item_array_t clothing_items;
LLInventoryModel::cat_array_t dummy;
LLIsType is_clothing(LLAssetType::AT_CLOTHING);
gInventory.collectDescendentsIf(getCOF(),
dummy,
clothing_items,
LLInventoryModel::EXCLUDE_TRASH,
is_clothing,
false);
uuid_vec_t item_ids;
for (LLInventoryModel::item_array_t::iterator it = clothing_items.begin();
it != clothing_items.end(); ++it)
{
item_ids.push_back((*it).get()->getLinkedUUID());
}
// Take them off by removing from COF.
removeItemsFromAvatar(item_ids);
}
void LLAppearanceMgr::removeAllAttachmentsFromAvatar()
{
if (!isAgentAvatarValid()) return;
LLAgentWearables::llvo_vec_t objects_to_remove;
for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin();
iter != gAgentAvatarp->mAttachmentPoints.end();)
{
LLVOAvatar::attachment_map_t::iterator curiter = iter++;
LLViewerJointAttachment* attachment = curiter->second;
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
attachment_iter != attachment->mAttachedObjects.end();
++attachment_iter)
{
LLViewerObject *attached_object = (*attachment_iter);
if (attached_object)
{
objects_to_remove.push_back(attached_object);
}
}
}
uuid_vec_t ids_to_remove;
for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_remove.begin();
it != objects_to_remove.end();
++it)
{
ids_to_remove.push_back((*it)->getAttachmentItemID());
}
removeItemsFromAvatar(ids_to_remove);
}
void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id)
{
gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
gInventory.collectDescendents(LLAppearanceMgr::getCOF(),
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH);
for (S32 i=0; i<item_array.count(); i++)
{
const LLInventoryItem* item = item_array.get(i).get();
if (item->getIsLinkType() && item->getLinkedUUID() == item_id)
{
// [RLVa:KB] - Checked: 2013-02-12 (RLVa-1.4.8)
#if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
// NOTE-RLVa: debug-only, can be removed down the line
if (rlv_handler_t::isEnabled())
{
RLV_ASSERT(rlvPredCanRemoveItem(item));
}
#endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
// [/RLVa:KB]
gInventory.purgeObject(item->getUUID());
}
}
}
void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type)
{
LLFindWearablesOfType filter_wearables_of_type(type);
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLInventoryModel::item_array_t::const_iterator it;
gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type);
for (it = items.begin(); it != items.end(); ++it)
{
const LLViewerInventoryItem* item = *it;
if (item->getIsLinkType()) // we must operate on links only
{
// [RLVa:KB] - Checked: 2013-02-12 (RLVa-1.4.8)
#if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
// NOTE-RLVa: debug-only, can be removed down the line
if (rlv_handler_t::isEnabled())
{
RLV_ASSERT(rlvPredCanRemoveItem(item));
}
#endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
// [/RLVa:KB]
gInventory.purgeObject(item->getUUID());
}
}
}
bool sort_by_linked_uuid(const LLViewerInventoryItem* item1, const LLViewerInventoryItem* item2)
{
if (!item1 || !item2)
{
llwarning("item1, item2 cannot be null, something is very wrong", 0);
return true;
}
return item1->getLinkedUUID() < item2->getLinkedUUID();
}
void LLAppearanceMgr::updateIsDirty()
{
LLUUID cof = getCOF();
LLUUID base_outfit;
// find base outfit link
const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink();
LLViewerInventoryCategory* catp = NULL;
if (base_outfit_item && base_outfit_item->getIsLinkType())
{
catp = base_outfit_item->getLinkedCategory();
}
if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
{
base_outfit = catp->getUUID();
}
// Set dirty to "false" if no base outfit found to disable "Save"
// and leave only "Save As" enabled in My Outfits.
mOutfitIsDirty = false;
if (base_outfit.notNull())
{
LLIsOfAssetType collector = LLIsOfAssetType(LLAssetType::AT_LINK);
LLInventoryModel::cat_array_t cof_cats;
LLInventoryModel::item_array_t cof_items;
gInventory.collectDescendentsIf(cof, cof_cats, cof_items,
LLInventoryModel::EXCLUDE_TRASH, collector);
LLInventoryModel::cat_array_t outfit_cats;
LLInventoryModel::item_array_t outfit_items;
gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items,
LLInventoryModel::EXCLUDE_TRASH, collector);
if(outfit_items.count() != cof_items.count())
{
// Current outfit folder should have one more item than the outfit folder.
// this one item is the link back to the outfit folder itself.
mOutfitIsDirty = true;
return;
}
//"dirty" - also means a difference in linked UUIDs and/or a difference in wearables order (links' descriptions)
std::sort(cof_items.begin(), cof_items.end(), sort_by_linked_uuid);
std::sort(outfit_items.begin(), outfit_items.end(), sort_by_linked_uuid);
for (U32 i = 0; i < cof_items.size(); ++i)
{
LLViewerInventoryItem *item1 = cof_items.get(i);
LLViewerInventoryItem *item2 = outfit_items.get(i);
if (item1->getLinkedUUID() != item2->getLinkedUUID() ||
item1->getName() != item2->getName() ||
item1->getActualDescription() != item2->getActualDescription())
{
mOutfitIsDirty = true;
return;
}
}
}
}
// *HACK: Must match name in Library or agent inventory
const std::string ROOT_GESTURES_FOLDER = "Gestures";
const std::string COMMON_GESTURES_FOLDER = "Common Gestures";
const std::string MALE_GESTURES_FOLDER = "Male Gestures";
const std::string FEMALE_GESTURES_FOLDER = "Female Gestures";
const std::string SPEECH_GESTURES_FOLDER = "Speech Gestures";
const std::string OTHER_GESTURES_FOLDER = "Other Gestures";
void LLAppearanceMgr::copyLibraryGestures()
{
LL_INFOS("Avatar") << self_av_string() << "Copying library gestures" << LL_ENDL;
// Copy gestures
LLUUID lib_gesture_cat_id =
gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE,false,true);
if (lib_gesture_cat_id.isNull())
{
llwarns << "Unable to copy gestures, source category not found" << llendl;
}
LLUUID dst_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE);
std::vector<std::string> gesture_folders_to_copy;
gesture_folders_to_copy.push_back(MALE_GESTURES_FOLDER);
gesture_folders_to_copy.push_back(FEMALE_GESTURES_FOLDER);
gesture_folders_to_copy.push_back(COMMON_GESTURES_FOLDER);
gesture_folders_to_copy.push_back(SPEECH_GESTURES_FOLDER);
gesture_folders_to_copy.push_back(OTHER_GESTURES_FOLDER);
for(std::vector<std::string>::iterator it = gesture_folders_to_copy.begin();
it != gesture_folders_to_copy.end();
++it)
{
std::string& folder_name = *it;
LLPointer<LLInventoryCallback> cb(NULL);
// After copying gestures, activate Common, Other, plus
// Male and/or Female, depending upon the initial outfit gender.
ESex gender = gAgentAvatarp->getSex();
std::string activate_male_gestures;
std::string activate_female_gestures;
switch (gender) {
case SEX_MALE:
activate_male_gestures = MALE_GESTURES_FOLDER;
break;
case SEX_FEMALE:
activate_female_gestures = FEMALE_GESTURES_FOLDER;
break;
case SEX_BOTH:
activate_male_gestures = MALE_GESTURES_FOLDER;
activate_female_gestures = FEMALE_GESTURES_FOLDER;
break;
}
if (folder_name == activate_male_gestures ||
folder_name == activate_female_gestures ||
folder_name == COMMON_GESTURES_FOLDER ||
folder_name == OTHER_GESTURES_FOLDER)
{
cb = new LLBoostFuncInventoryCallback(activate_gesture_cb);
}
LLUUID cat_id = findDescendentCategoryIDByName(lib_gesture_cat_id,folder_name);
if (cat_id.isNull())
{
llwarns << self_av_string() << "failed to find gesture folder for " << folder_name << llendl;
}
else
{
LL_DEBUGS("Avatar") << self_av_string() << "initiating fetch and copy for " << folder_name << " cat_id " << cat_id << LL_ENDL;
callAfterCategoryFetch(cat_id,
boost::bind(&LLAppearanceMgr::shallowCopyCategory,
&LLAppearanceMgr::instance(),
cat_id, dst_id, cb));
}
}
}
void LLAppearanceMgr::autopopulateOutfits()
{
// If this is the very first time the user has logged into viewer2+ (from a legacy viewer, or new account)
// then auto-populate outfits from the library into the My Outfits folder.
LL_INFOS("Avatar") << self_av_string() << "avatar fully visible" << LL_ENDL;
static bool check_populate_my_outfits = true;
if (check_populate_my_outfits &&
(LLInventoryModel::getIsFirstTimeInViewer2()
|| gSavedSettings.getBOOL("MyOutfitsAutofill")))
{
gAgentWearables.populateMyOutfitsFolder();
}
check_populate_my_outfits = false;
}
// Handler for anything that's deferred until avatar de-clouds.
void LLAppearanceMgr::onFirstFullyVisible()
{
gAgentAvatarp->outputRezTiming("Avatar fully loaded");
gAgentAvatarp->reportAvatarRezTime();
gAgentAvatarp->debugAvatarVisible();
// The auto-populate is failing at the point of generating outfits
// folders, so don't do the library copy until that is resolved.
// autopopulateOutfits();
// If this is the first time we've ever logged in,
// then copy default gestures from the library.
if (gAgent.isFirstLogin()) {
copyLibraryGestures();
}
}
// update "dirty" state - defined outside class to allow for calling
// after appearance mgr instance has been destroyed.
void appearance_mgr_update_dirty_state()
{
if (LLAppearanceMgr::instanceExists())
{
LLAppearanceMgr::getInstance()->updateIsDirty();
}
}
bool LLAppearanceMgr::updateBaseOutfit()
{
if (isOutfitLocked())
{
// don't allow modify locked outfit
llassert(!isOutfitLocked());
return false;
}
setOutfitLocked(true);
gAgentWearables.notifyLoadingStarted();
const LLUUID base_outfit_id = getBaseOutfitUUID();
if (base_outfit_id.isNull()) return false;
updateClothingOrderingInfo();
// in a Base Outfit we do not remove items, only links
purgeCategory(base_outfit_id, false);
LLPointer<LLInventoryCallback> dirty_state_updater =
new LLBoostFuncInventoryCallback(no_op_inventory_func, appearance_mgr_update_dirty_state);
//COF contains only links so we copy to the Base Outfit only links
shallowCopyCategoryContents(getCOF(), base_outfit_id, dirty_state_updater);
return true;
}
void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type)
{
items_by_type.resize(LLWearableType::WT_COUNT);
if (items.empty()) return;
for (S32 i=0; i<items.count(); i++)
{
LLViewerInventoryItem *item = items.get(i);
if (!item)
{
LL_WARNS("Appearance") << "NULL item found" << llendl;
continue;
}
// Ignore non-wearables.
if (!item->isWearableType())
continue;
LLWearableType::EType type = item->getWearableType();
if(type < 0 || type >= LLWearableType::WT_COUNT)
{
LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL;
continue;
}
items_by_type[type].push_back(item);
}
}
std::string build_order_string(LLWearableType::EType type, U32 i)
{
std::ostringstream order_num;
order_num << ORDER_NUMBER_SEPARATOR << type * 100 + i;
return order_num.str();
}
struct WearablesOrderComparator
{
LOG_CLASS(WearablesOrderComparator);
WearablesOrderComparator(const LLWearableType::EType type)
{
mControlSize = build_order_string(type, 0).size();
};
bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2)
{
if (!item1 || !item2)
{
llwarning("either item1 or item2 is NULL", 0);
return true;
}
const std::string& desc1 = item1->getActualDescription();
const std::string& desc2 = item2->getActualDescription();
bool item1_valid = (desc1.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc1[0]);
bool item2_valid = (desc2.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc2[0]);
if (item1_valid && item2_valid)
return desc1 < desc2;
//we need to sink down invalid items: items with empty descriptions, items with "Broken link" descriptions,
//items with ordering information but not for the associated wearables type
if (!item1_valid && item2_valid)
return false;
return true;
}
U32 mControlSize;
};
void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id, bool update_base_outfit_ordering)
{
if (cat_id.isNull())
{
cat_id = getCOF();
if (update_base_outfit_ordering)
{
const LLUUID base_outfit_id = getBaseOutfitUUID();
if (base_outfit_id.notNull())
{
updateClothingOrderingInfo(base_outfit_id,false);
}
}
}
// COF is processed if cat_id is not specified
LLInventoryModel::item_array_t wear_items;
getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING, false);
wearables_by_type_t items_by_type(LLWearableType::WT_COUNT);
divvyWearablesByType(wear_items, items_by_type);
bool inventory_changed = false;
for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; type++)
{
U32 size = items_by_type[type].size();
if (!size) continue;
//sinking down invalid items which need reordering
std::sort(items_by_type[type].begin(), items_by_type[type].end(), WearablesOrderComparator((LLWearableType::EType) type));
//requesting updates only for those links which don't have "valid" descriptions
for (U32 i = 0; i < size; i++)
{
LLViewerInventoryItem* item = items_by_type[type][i];
if (!item) continue;
std::string new_order_str = build_order_string((LLWearableType::EType)type, i);
if (new_order_str == item->getActualDescription()) continue;
item->setDescription(new_order_str);
item->setComplete(TRUE);
item->updateServer(FALSE);
gInventory.updateItem(item);
inventory_changed = true;
}
}
//*TODO do we really need to notify observers?
if (inventory_changed) gInventory.notifyObservers();
}
// This is intended for use with HTTP Clients/Responders, but is not
// specifically coupled with those classes.
class LLHTTPRetryPolicy: public LLThreadSafeRefCount
{
public:
LLHTTPRetryPolicy() {}
virtual ~LLHTTPRetryPolicy() {}
virtual bool shouldRetry(U32 status, F32& seconds_to_wait) = 0;
};
// Example of simplest possible policy, not necessarily recommended.
class LLAlwaysRetryImmediatelyPolicy: public LLHTTPRetryPolicy
{
public:
LLAlwaysRetryImmediatelyPolicy() {}
bool shouldRetry(U32 status, F32& seconds_to_wait)
{
seconds_to_wait = 0.0;
return true;
}
};
// Very general policy with geometric back-off after failures,
// up to a maximum delay, and maximum number of retries.
class LLAdaptiveRetryPolicy: public LLHTTPRetryPolicy
{
public:
LLAdaptiveRetryPolicy(F32 min_delay, F32 max_delay, F32 backoff_factor, U32 max_retries):
mMinDelay(min_delay),
mMaxDelay(max_delay),
mBackoffFactor(backoff_factor),
mMaxRetries(max_retries),
mDelay(min_delay),
mRetryCount(0)
{
}
bool shouldRetry(U32 status, F32& seconds_to_wait)
{
seconds_to_wait = mDelay;
mDelay = llclamp(mDelay*mBackoffFactor,mMinDelay,mMaxDelay);
mRetryCount++;
return (mRetryCount<=mMaxRetries);
}
private:
F32 mMinDelay; // delay never less than this value
F32 mMaxDelay; // delay never exceeds this value
F32 mBackoffFactor; // delay increases by this factor after each retry, up to mMaxDelay.
U32 mMaxRetries; // maximum number of times shouldRetry will return true.
F32 mDelay; // current delay.
U32 mRetryCount; // number of times shouldRetry has been called.
};
extern AIHTTPTimeoutPolicy requestAgentUpdateAppearance_timeout;
class RequestAgentUpdateAppearanceResponder: public LLHTTPClient::ResponderWithResult
{
public:
virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return requestAgentUpdateAppearance_timeout; }
RequestAgentUpdateAppearanceResponder()
{
mRetryPolicy = new LLAdaptiveRetryPolicy(1.0, 32.0, 2.0, 10);
}
virtual ~RequestAgentUpdateAppearanceResponder()
{
}
// Successful completion.
/* virtual */ void result(const LLSD& content)
{
LL_DEBUGS("Avatar") << "content: " << ll_pretty_print_sd(content) << LL_ENDL;
if (content["success"].asBoolean())
{
LL_DEBUGS("Avatar") << "OK" << LL_ENDL;
}
else
{
onFailure(200);
}
}
// Error
/*virtual*/ void errorWithContent(U32 status, const std::string& reason, const LLSD& content)
{
llwarns << "appearance update request failed, status: " << status << " reason: " << reason << llendl;
LL_DEBUGS("Avatar") << "content: " << ll_pretty_print_sd(content) << LL_ENDL;
onFailure(status);
}
void onFailure(U32 status)
{
F32 seconds_to_wait;
if (mRetryPolicy->shouldRetry(status,seconds_to_wait))
{
llinfos << "retrying" << llendl;
doAfterInterval(boost::bind(&LLAppearanceMgr::requestServerAppearanceUpdate,
LLAppearanceMgr::getInstance(),
LLHTTPClient::ResponderPtr(this)),
seconds_to_wait);
}
else
{
llwarns << "giving up after too many retries" << llendl;
}
}
LLPointer<LLHTTPRetryPolicy> mRetryPolicy;
/*virtual*/ char const* getName(void) const { return "RequestAgentUpdateAppearanceResponder"; }
};
void LLAppearanceMgr::requestServerAppearanceUpdate(LLHTTPClient::ResponderPtr responder_ptr)
{
if (gAgentAvatarp->isEditingAppearance())
{
// don't send out appearance updates if in appearance editing mode
return;
}
if (!gAgent.getRegion())
{
llwarns << "Region not set, cannot request server appearance update" << llendl;
return; //Avoid a crash here. RequestAgentUpdateAppearanceResponder crams requestServerAppearanceUpdate into an event...
//which results in gAgent.getRegion() legitimately returning NULL under some scenarios.
}
if (gAgent.getRegion()->getCentralBakeVersion()==0)
{
llwarns << "Region does not support baking" << llendl;
}
std::string url = gAgent.getRegion()->getCapability("UpdateAvatarAppearance");
if (url.empty())
{
llwarns << "No cap for UpdateAvatarAppearance." << llendl;
return;
}
LLSD body;
S32 cof_version = getCOFVersion();
body["cof_version"] = cof_version;
LL_DEBUGS("Avatar") << "my_cof_version " << cof_version << llendl;
//LLHTTPClient::ResponderPtr responder_ptr;
if (!responder_ptr.get())
{
responder_ptr = new RequestAgentUpdateAppearanceResponder;
}
LLHTTPClient::post(url, body, responder_ptr);
llassert(cof_version >= mLastUpdateRequestCOFVersion);
mLastUpdateRequestCOFVersion = cof_version;
}
void show_created_outfit(const LLUUID& folder_id, bool show_panel = true)
{
if (!LLApp::isRunning())
{
llwarns << "called during shutdown, skipping" << llendl;
return;
}
LLSD key;
//EXT-7727. For new accounts LLShowCreatedOutfit is created during login process
// add may be processed after login process is finished
// MULTI-WEARABLES TODO
/*if (mShowPanel)
{
LLFloaterSidePanelContainer::showPanel("appearance", "panel_outfits_inventory", key);
}
LLOutfitsList *outfits_list =
dynamic_cast<LLOutfitsList*>(LLFloaterSidePanelContainer::getPanel("appearance", "outfitslist_tab"));
if (outfits_list)
{
outfits_list->setSelectedOutfitByUUID(mFolderID);
}*/
LLAppearanceMgr::getInstance()->updateIsDirty();
gAgentWearables.notifyLoadingFinished(); // New outfit is saved.
LLAppearanceMgr::getInstance()->updatePanelOutfitName("");
}
void scroll_to_folder(const LLUUID& folder_id)
{
if (LLInventoryPanel::getActiveInventoryPanel())
{
if( LLFolderView* root = LLInventoryPanel::getActiveInventoryPanel()->getRootFolder())
{
LLFolderViewItem* item = root->getItemByID(folder_id);
if(!root)
return;
LLFolderViewFolder* folder = dynamic_cast<LLFolderViewFolder*>(item);
if(folder)
{
folder->openItem();
}
root->setSelection(item,true,false);
root->scrollToShowSelection();
}
}
}
class LLBoostFuncInventoryCallbackFireOnce : public LLBoostFuncInventoryCallback
{
public:
LLBoostFuncInventoryCallbackFireOnce(inventory_func_type fire_func, nullary_func_type destroy_func = no_op) :
LLBoostFuncInventoryCallback(fire_func, destroy_func), mFired(false)
{}
private:
void fire(const LLUUID& item_id)
{
if(mFired)
return;
mFired = true;
LLBoostFuncInventoryCallback::fire(item_id);
}
bool mFired;
};
LLUUID LLAppearanceMgr::makeNewOutfitCore(const std::string& new_folder_name, bool show_panel, LLInventoryModel::item_array_t* items /*=NULL*/)
{
if (!isAgentAvatarValid()) return LLUUID::null;
gAgentWearables.notifyLoadingStarted();
// First, make a folder in the My Outfits directory.
const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
LLUUID folder_id = gInventory.createNewCategory(
parent_id,
LLFolderType::FT_OUTFIT,
new_folder_name);
updateClothingOrderingInfo();
inventory_func_type fire_fn = no_op_inventory_func;
if(show_panel)
fire_fn = boost::bind(&scroll_to_folder,folder_id);
LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallbackFireOnce( fire_fn,
boost::bind(&show_created_outfit,folder_id,show_panel));
if(items)
copyItems(folder_id,items,cb);
else
shallowCopyCategoryContents(getCOF(),folder_id,cb);
createBaseOutfitLink(folder_id, cb);
dumpCat(folder_id,"COF, new outfit");
return folder_id;
}
LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, bool show_panel/*=true*/)
{
return makeNewOutfitCore(new_folder_name, show_panel);
}
//Given an array of items from COF. v3 outfit behavior.
LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, LLInventoryModel::item_array_t& items)
{
if (items.empty()) return LLUUID::null;
return makeNewOutfitCore(new_folder_name, true, &items);
}
//Creates item copies before links and ties all requests to a sole handler
//Requests are batched into subbatches, as too many requests at once causes the sim to
//stall with the inventory requests.
//This handler will also ensure all 'copy' requests are finished before 'link' requests are
//sent out. This behavior isn't really needed for nomod/nocopy items, but it is for multi-worn
//clothing.
//Note that the 'wear' process is pretty convoluted, but its a cludge to get rlva support in without
//tinkering with LLAppearanceMgr further.
//To use this:
// 1) assign an LLPointer the newly created LLCreateLegacyOutfit object.
// 2) Stuff with requests via makeLink and makeCopy
// 3) Call dispatch()
// 4) Let the LLPointer go out of scope.
class LLCreateLegacyOutfit : public LLBoostFuncInventoryCallbackFireOnce
{
public:
LLCreateLegacyOutfit(const LLUUID& folder_id, inventory_func_type fire_func, nullary_func_type destroy_func = no_op) :
LLBoostFuncInventoryCallbackFireOnce(fire_func, destroy_func),
mFolderID(folder_id), mFailed(false)
{}
virtual ~LLCreateLegacyOutfit()
{
if (!LLApp::isRunning() || mFailed)
return;
LLInventoryModel::item_array_t body_items, wear_items, obj_items, gest_items;
for(std::set<LLUUID>::const_iterator it = mWearItems.begin(); it != mWearItems.end(); ++it)
{
LLViewerInventoryItem* item = gInventory.getItem(*it);
if(item)
{
switch(item->getType())
{
case LLAssetType::AT_BODYPART:
body_items.push_back(item);
break;
case LLAssetType::AT_CLOTHING:
wear_items.push_back(item);
break;
case LLAssetType::AT_OBJECT:
obj_items.push_back(item);
break;
case LLAssetType::AT_GESTURE:
gest_items.push_back(item);
break;
default:
break;
}
}
}
if(!body_items.empty() || !wear_items.empty() || !obj_items.empty() || !gest_items.empty())
LLAppearanceMgr::instance().updateCOF(body_items, wear_items, obj_items, gest_items, false);
}
private:
class LLCreateBase : public LLInventoryCallback
{
public:
LLCreateBase(LLViewerInventoryItem* item, const LLUUID& folder_id, LLPointer<LLCreateLegacyOutfit> cb) :
mCallback(cb), mItem(item), mFolderID(folder_id)
{}
virtual ~LLCreateBase()
{
if(mCallback)
mCallback->finished(this, LLUUID::null);
}
virtual void dispatch() = 0;
virtual void fire(const LLUUID& item_id)
{
mCallback->finished(this, item_id);
mCallback = NULL;
}
const LLViewerInventoryItem* getItem() const {return mItem;}
protected:
LLPointer<LLViewerInventoryItem> mItem;
LLPointer<LLCreateLegacyOutfit> mCallback;
const LLUUID mFolderID;
};
class LLCreateCopy : public LLCreateBase
{
public:
LLCreateCopy(LLViewerInventoryItem* item, bool create_copy, const LLUUID& folder_id, LLPointer<LLCreateLegacyOutfit> cb) :
LLCreateBase(item,folder_id,cb), mCreateLink(create_copy),
mLinkDesc((mCreateLink && item->getIsLinkType()) ? item->LLInventoryItem::getDescription() : "" )
{}
virtual void dispatch()
{
const LLViewerInventoryItem* base_item = mItem->getLinkedItem() ? mItem->getLinkedItem() : mItem.get();
copy_inventory_item(gAgent.getID(),
base_item->getPermissions().getOwner(),
base_item->getUUID(),
mFolderID,
base_item->getName(),
this);
}
virtual void fire(const LLUUID& item_id)
{
if(mCreateLink)
mCallback->makeLink(gInventory.getItem(item_id), mLinkDesc);
LLCreateBase::fire(item_id);
}
private:
bool mCreateLink;
std::string mLinkDesc;
};
class LLCreateLink : public LLCreateBase
{
public:
LLCreateLink(LLViewerInventoryItem* item, const std::string& desc, const LLUUID& folder_id, LLPointer<LLCreateLegacyOutfit> cb) :
LLCreateBase(item,folder_id,cb), mDesc(desc)
{}
virtual void dispatch()
{
link_inventory_item(gAgent.getID(),
mItem->getLinkedUUID(),
mFolderID,
mItem->getName(),
mDesc,
LLAssetType::AT_LINK,
this);
}
private:
const std::string mDesc;
};
public:
void makeLink(LLViewerInventoryItem* item, const std::string desc)
{
if(!item)
return;
mPendingLinks.push_back(new LLCreateLink(item, desc, mFolderID, this));
}
void makeCopy(LLViewerInventoryItem* item, bool create_link)
{
if(!item)
return;
mPendingCopies.push_back(new LLCreateCopy(item, create_link, mFolderID, this));
}
void finished(const LLCreateBase* cb, const LLUUID item_id)
{
if(!LLApp::isRunning())
{
mPendingCopies.clear();
mPendingLinks.clear();
return;
}
if(item_id.notNull())
show_created_outfit(item_id);
std::vector<const LLCreateBase*>::iterator it = std::find(mActiveRequests.begin(), mActiveRequests.end(),cb);
if(it != mActiveRequests.end())
{
const LLViewerInventoryItem* old_item = (*it)->getItem();
if(item_id.notNull())
{
const LLViewerInventoryItem* item = gInventory.getItem(item_id);
if ((rlv_handler_t::isEnabled()) &&
//If the old item can be removed, but a new one can't take its place, then just use the original item again.
(((rlvPredCanRemoveItem(old_item) && !rlvPredCanWearItem(item,RLV_WEAR_REPLACE))) ||
//If the old item cannot be removed then just use the original item again.
!rlvPredCanRemoveItem(old_item)))
{
item = old_item;
}
if(item->getIsLinkType())
mWearItems.erase(item->getLinkedUUID());
mWearItems.insert(item->getUUID());
}
else
mWearItems.insert(old_item->getUUID());
mActiveRequests.erase(it);
if(!item_id.notNull())
mFailed = true;
if(mActiveRequests.empty())
dispatch(); //Fire off any pending requests.
}
}
void dispatch()
{
const S32 max_batch = 5;
S32 count=0;
if(!sendRequests(mPendingCopies,count,max_batch))
sendRequests(mPendingLinks,count,max_batch); //IFF there are NO copy requests pending.
gInventory.notifyObservers();
}
private:
bool sendRequests(std::vector<LLPointer<LLCreateBase> >& list, S32& count, const S32& max_batch)
{
bool handled = false;
for(std::vector<LLPointer<LLCreateBase> >::iterator it = list.begin();it!=list.end();)
{
if(count >= max_batch)
break;
LLPointer<LLCreateBase> cb = (*it);
it=list.erase(it);
if(cb)
{
cb->dispatch();
mActiveRequests.push_back(cb.get());
++count;
handled = true;
}
}
return handled;
}
LLUUID mFolderID;
bool mFailed;
std::vector<LLPointer<LLCreateBase> > mPendingCopies;
std::vector<LLPointer<LLCreateBase> > mPendingLinks;
std::set<LLUUID> mWearItems;
std::vector<const LLCreateBase*> mActiveRequests;
};
//Given an array of items from COF. Will only use links for no-copy, no-mod, or multi-worn clothing.
LLUUID LLAppearanceMgr::makeNewOutfitLegacy(const std::string& new_folder_name, LLInventoryModel::item_array_t& items, bool use_links )
{
if (!isAgentAvatarValid()) return LLUUID::null;
else if (items.empty()) return LLUUID::null;
gAgentWearables.notifyLoadingStarted();
// First, make a folder in the My Outfits directory.
const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING);
LLUUID folder_id = gInventory.createNewCategory(
parent_id,
LLFolderType::FT_NONE,
new_folder_name);
updateClothingOrderingInfo();
LLInventoryModel::item_array_t base_items;
LLInventoryModel::item_array_t remove_items;
LLPointer<LLCreateLegacyOutfit> cb = new LLCreateLegacyOutfit(folder_id,boost::bind(&scroll_to_folder,folder_id),boost::bind(&show_created_outfit,folder_id,true));
for (LLInventoryModel::item_array_t::const_iterator iter = items.begin();
iter != items.end();
++iter)
{
LLViewerInventoryItem* item = (*iter);
LLViewerInventoryItem* base_item = item->getLinkedItem() ? item->getLinkedItem() : item;
bool is_copy = base_item->getPermissions().allowCopyBy(gAgent.getID());
//Just treat 'object' type as modifiable... permission slam screws them up pretty well.
bool is_mod = base_item->getInventoryType() == LLInventoryType::IT_OBJECT || base_item->getPermissions().allowModifyBy(gAgent.getID());
//If it's multi-worn we want to create a copy of the item if possible AND create a new link to that new copy with the same desc as the old link.
bool is_multi = base_item->isWearableType() && gAgentWearables.getWearableCount(base_item->getWearableType()) > 1 ;
if( use_links && (!is_copy || !is_mod) )
{
cb->makeLink(item,item->LLInventoryItem::getDescription());
}
else if( is_copy )
{
cb->makeCopy(item,is_multi && use_links);
}
}
cb->dispatch();
return folder_id;
}
void LLAppearanceMgr::wearBaseOutfit()
{
const LLUUID& base_outfit_id = getBaseOutfitUUID();
if (base_outfit_id.isNull()) return;
updateCOF(base_outfit_id);
}
void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove)
{
// [RLVa:KB] - Checked: 2013-02-12 (RLVa-1.4.8)
bool fUpdateAppearance = false;
for (uuid_vec_t::const_iterator it = ids_to_remove.begin(); it != ids_to_remove.end(); ++it)
{
const LLInventoryItem* linked_item = gInventory.getLinkedItem(*it);
if ( linked_item && (rlv_handler_t::isEnabled()) && (!rlvPredCanRemoveItem(linked_item)) )
{
continue;
}
fUpdateAppearance = true;
const LLUUID& linked_item_id = gInventory.getLinkedItemID(*it);
removeCOFItemLinks(linked_item_id);
}
if (fUpdateAppearance)
{
updateAppearanceFromCOF();
}
// [/RLVa:KB]
// for (uuid_vec_t::const_iterator it = ids_to_remove.begin(); it != ids_to_remove.end(); ++it)
// {
// const LLUUID& id_to_remove = *it;
// const LLUUID& linked_item_id = gInventory.getLinkedItemID(id_to_remove);
// removeCOFItemLinks(linked_item_id);
// }
// updateAppearanceFromCOF();
}
void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove)
{
// [RLVa:KB] - Checked: 2013-02-12 (RLVa-1.4.8)
const LLInventoryItem* linked_item = gInventory.getLinkedItem(id_to_remove);
if ( linked_item && (rlv_handler_t::isEnabled()) && (!rlvPredCanRemoveItem(linked_item)) )
{
return;
}
// [/RLVA:KB]
LLUUID linked_item_id = gInventory.getLinkedItemID(id_to_remove);
removeCOFItemLinks(linked_item_id);
updateAppearanceFromCOF();
}
bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_body)
{
if (!item || !item->isWearableType()) return false;
if (item->getType() != LLAssetType::AT_CLOTHING) return false;
if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false;
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLFindWearablesOfType filter_wearables_of_type(item->getWearableType());
gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type);
if (items.empty()) return false;
// We assume that the items have valid descriptions.
std::sort(items.begin(), items.end(), WearablesOrderComparator(item->getWearableType()));
if (closer_to_body && items.front() == item) return false;
if (!closer_to_body && items.back() == item) return false;
LLInventoryModel::item_array_t::iterator it = std::find(items.begin(), items.end(), item);
if (items.end() == it) return false;
//swapping descriptions
closer_to_body ? --it : ++it;
LLViewerInventoryItem* swap_item = *it;
if (!swap_item) return false;
std::string tmp = swap_item->getActualDescription();
swap_item->setDescription(item->getActualDescription());
item->setDescription(tmp);
//items need to be updated on a dataserver
item->setComplete(TRUE);
item->updateServer(FALSE);
gInventory.updateItem(item);
swap_item->setComplete(TRUE);
swap_item->updateServer(FALSE);
gInventory.updateItem(swap_item);
//to cause appearance of the agent to be updated
bool result = false;
if ((result = gAgentWearables.moveWearable(item, closer_to_body)))
{
gAgentAvatarp->wearableUpdated(item->getWearableType(), FALSE);
}
setOutfitDirty(true);
//*TODO do we need to notify observers here in such a way?
gInventory.notifyObservers();
return result;
}
//static
void LLAppearanceMgr::sortItemsByActualDescription(LLInventoryModel::item_array_t& items)
{
if (items.size() < 2) return;
std::sort(items.begin(), items.end(), sort_by_actual_description);
}
//#define DUMP_CAT_VERBOSE
void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH);
#ifdef DUMP_CAT_VERBOSE
llinfos << llendl;
llinfos << str << llendl;
S32 hitcount = 0;
for(S32 i=0; i<items.count(); i++)
{
LLViewerInventoryItem *item = items.get(i);
if (item)
hitcount++;
llinfos << i <<" "<< item->getName() <<llendl;
}
#endif
llinfos << msg << " count " << items.count() << llendl;
}
void LLAppearanceMgr::dumpItemArray(const LLInventoryModel::item_array_t& items,
const std::string& msg)
{
for (S32 i=0; i<items.count(); i++)
{
LLViewerInventoryItem *item = items.get(i);
LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
LLUUID asset_id;
if (linked_item)
{
asset_id = linked_item->getAssetUUID();
}
LL_DEBUGS("Avatar") << self_av_string() << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << LL_ENDL;
}
}
LLAppearanceMgr::LLAppearanceMgr():
mAttachmentInvLinkEnabled(false),
mOutfitIsDirty(false),
mOutfitLocked(false),
mIsInUpdateAppearanceFromCOF(false),
mLastUpdateRequestCOFVersion(LLViewerInventoryCategory::VERSION_UNKNOWN),
mLastAppearanceUpdateCOFVersion(LLViewerInventoryCategory::VERSION_UNKNOWN)
{
LLOutfitObserver& outfit_observer = LLOutfitObserver::instance();
// unlock outfit on save operation completed
outfit_observer.addCOFSavedCallback(boost::bind(
&LLAppearanceMgr::setOutfitLocked, this, false));
mUnlockOutfitTimer.reset(new LLOutfitUnLockTimer(gSavedSettings.getS32(
"OutfitOperationsTimeout")));
gIdleCallbacks.addFunction(&LLAttachmentsMgr::onIdle,NULL);
}
LLAppearanceMgr::~LLAppearanceMgr()
{
}
void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val)
{
llinfos << "setAttachmentInvLinkEnable => " << (int) val << llendl;
mAttachmentInvLinkEnabled = val;
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-10-05 (Catznip-3.0.0a) | Added: Catznip-2.2.0a
if (mAttachmentInvLinkEnabled)
{
linkPendingAttachments();
}
// [/SL:KB]
}
void dumpAttachmentSet(const std::set<LLUUID>& atts, const std::string& msg)
{
llinfos << msg << llendl;
for (std::set<LLUUID>::const_iterator it = atts.begin();
it != atts.end();
++it)
{
LLUUID item_id = *it;
LLViewerInventoryItem *item = gInventory.getItem(item_id);
if (item)
llinfos << "atts " << item->getName() << llendl;
else
llinfos << "atts " << "UNKNOWN[" << item_id.asString() << "]" << llendl;
}
llinfos << llendl;
}
void LLAppearanceMgr::registerAttachment(const LLUUID& item_id)
{
gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-10-05 (Catznip-3.0.0a) | Added: Catznip-2.2.0a
if (isLinkInCOF(item_id))
{
return;
}
mPendingAttachLinks.push_back(item_id);
// [/SL:KB]
if (mAttachmentInvLinkEnabled)
{
// we have to pass do_update = true to call LLAppearanceMgr::updateAppearanceFromCOF.
// it will trigger gAgentWariables.notifyLoadingFinished()
// But it is not acceptable solution. See EXT-7777
// LLAppearanceMgr::addCOFItemLink(item_id, false); // Add COF link for item.
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-10-05 (Catznip-3.0.0a) | Modified: Catznip-2.2.0a
LLPointer<LLInventoryCallback> cb = new LLRegisterAttachmentCallback();
LLAppearanceMgr::addCOFItemLink(item_id, false, cb); // Add COF link for item.
// [/SL:KB]
}
else
{
//llinfos << "no link changes, inv link not enabled" << llendl;
}
}
void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id)
{
gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-10-05 (Catznip-3.0.0a) | Added: Catznip-2.2.0a
uuid_vec_t::iterator itPendingAttachLink = std::find(mPendingAttachLinks.begin(), mPendingAttachLinks.end(), item_id);
if (itPendingAttachLink != mPendingAttachLinks.end())
{
mPendingAttachLinks.erase(itPendingAttachLink);
}
// [/SL:KB]
if (mAttachmentInvLinkEnabled)
{
LLAppearanceMgr::removeCOFItemLinks(item_id);
}
else
{
//llinfos << "no link changes, inv link not enabled" << llendl;
}
}
// [SL:KB] - Patch: Appearance-SyncAttach | Checked: 2010-09-18 (Catznip-3.0.0a) | Modified: Catznip-2.2.0a
void LLAppearanceMgr::linkPendingAttachments()
{
LLPointer<LLInventoryCallback> cb = NULL;
for (uuid_vec_t::const_iterator itPendingAttachLink = mPendingAttachLinks.begin();
itPendingAttachLink != mPendingAttachLinks.end(); ++itPendingAttachLink)
{
const LLUUID& idAttachItem = *itPendingAttachLink;
if ( (gAgentAvatarp->isWearingAttachment(idAttachItem)) && (!isLinkInCOF(idAttachItem)) )
{
if (!cb)
cb = new LLRegisterAttachmentCallback();
LLAppearanceMgr::addCOFItemLink(idAttachItem, false, cb);
}
}
}
void LLAppearanceMgr::onRegisterAttachmentComplete(const LLUUID& idItem)
{
const LLUUID& idItemBase = gInventory.getLinkedItemID(idItem);
// Remove the attachment from the pending list
uuid_vec_t::iterator itPendingAttachLink = std::find(mPendingAttachLinks.begin(), mPendingAttachLinks.end(), idItemBase);
if (itPendingAttachLink != mPendingAttachLinks.end())
mPendingAttachLinks.erase(itPendingAttachLink);
// It may have been detached already in which case we should remove the COF link
if ( (isAgentAvatarValid()) && (!gAgentAvatarp->isWearingAttachment(idItemBase)) )
removeCOFItemLinks(idItemBase);
}
// [/SL:KB]
BOOL LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const
{
return gInventory.isObjectDescendentOf(obj_id, getCOF());
}
// static
bool LLAppearanceMgr::isLinkInCOF(const LLUUID& obj_id)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
LLLinkedItemIDMatches find_links(gInventory.getLinkedItemID(obj_id));
gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(),
cats,
items,
LLInventoryModel::EXCLUDE_TRASH,
find_links);
return !items.empty();
}
BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const
{
if (!getIsInCOF(obj_id)) return FALSE;
// If a non-link somehow ended up in COF, allow deletion.
const LLInventoryObject *obj = gInventory.getObject(obj_id);
if (obj && !obj->getIsLinkType())
{
return FALSE;
}
// For now, don't allow direct deletion from the COF. Instead, force users
// to choose "Detach" or "Take Off".
return TRUE;
/*
const LLInventoryObject *obj = gInventory.getObject(obj_id);
if (!obj) return FALSE;
// Can't delete bodyparts, since this would be equivalent to removing the item.
if (obj->getType() == LLAssetType::AT_BODYPART) return TRUE;
// Can't delete the folder link, since this is saved for bookkeeping.
if (obj->getActualType() == LLAssetType::AT_LINK_FOLDER) return TRUE;
return FALSE;
*/
}
class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver
{
public:
CallAfterCategoryFetchStage2(const uuid_vec_t& ids,
nullary_func_t callable) :
LLInventoryFetchItemsObserver(ids),
mCallable(callable)
{
}
~CallAfterCategoryFetchStage2()
{
}
virtual void done()
{
llinfos << this << " done with incomplete " << mIncomplete.size()
<< " complete " << mComplete.size() << " calling callable" << llendl;
gInventory.removeObserver(this);
doOnIdleOneTime(mCallable);
delete this;
}
protected:
nullary_func_t mCallable;
};
class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver
{
public:
CallAfterCategoryFetchStage1(const LLUUID& cat_id, nullary_func_t callable) :
LLInventoryFetchDescendentsObserver(cat_id),
mCallable(callable)
{
}
~CallAfterCategoryFetchStage1()
{
}
virtual void done()
{
// What we do here is get the complete information on the items in
// the library, and set up an observer that will wait for that to
// happen.
LLInventoryModel::cat_array_t cat_array;
LLInventoryModel::item_array_t item_array;
gInventory.collectDescendents(mComplete.front(),
cat_array,
item_array,
LLInventoryModel::EXCLUDE_TRASH);
S32 count = item_array.count();
if(!count)
{
llwarns << "Nothing fetched in category " << mComplete.front()
<< llendl;
//dec_busy_count();
gInventory.removeObserver(this);
// lets notify observers that loading is finished.
gAgentWearables.notifyLoadingFinished();
delete this;
return;
}
llinfos << "stage1 got " << item_array.count() << " items, passing to stage2 " << llendl;
uuid_vec_t ids;
for(S32 i = 0; i < count; ++i)
{
ids.push_back(item_array.get(i)->getUUID());
}
gInventory.removeObserver(this);
// do the fetch
CallAfterCategoryFetchStage2 *stage2 = new CallAfterCategoryFetchStage2(ids, mCallable);
stage2->startFetch();
if(stage2->isFinished())
{
// everything is already here - call done.
stage2->done();
}
else
{
// it's all on it's way - add an observer, and the inventory
// will call done for us when everything is here.
gInventory.addObserver(stage2);
}
delete this;
}
protected:
nullary_func_t mCallable;
};
void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb)
{
CallAfterCategoryFetchStage1 *stage1 = new CallAfterCategoryFetchStage1(cat_id, cb);
stage1->startFetch();
if (stage1->isFinished())
{
stage1->done();
}
else
{
gInventory.addObserver(stage1);
}
}
void wear_multiple(const uuid_vec_t& ids, bool replace)
{
LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy;
bool first = true;
uuid_vec_t::const_iterator it;
for (it = ids.begin(); it != ids.end(); ++it)
{
// if replace is requested, the first item worn will replace the current top
// item, and others will be added.
LLAppearanceMgr::instance().wearItemOnAvatar(*it,false,first && replace,cb);
first = false;
}
}
// SLapp for easy-wearing of a stock (library) avatar
//
/*
class LLWearFolderHandler : public LLCommandHandler
{
public:
// not allowed from outside the app
LLWearFolderHandler() : LLCommandHandler("wear_folder", UNTRUSTED_BLOCK) { }
bool handle(const LLSD& tokens, const LLSD& query_map,
LLMediaCtrl* web)
{
LLPointer<LLInventoryCategory> category = new LLInventoryCategory(query_map["folder_id"],
LLUUID::null,
LLFolderType::FT_CLOTHING,
"Quick Appearance");
LLSD::UUID folder_uuid = query_map["folder_id"].asUUID();
if ( gInventory.getCategory( folder_uuid ) != NULL )
{
LLAppearanceMgr::getInstance()->wearInventoryCategory(category, true, false);
// *TODOw: This may not be necessary if initial outfit is chosen already -- josh
gAgent.setGenderChosen(TRUE);
}
// release avatar picker keyboard focus
gFocusMgr.setKeyboardFocus( NULL );
return true;
}
};
LLWearFolderHandler gWearFolderHandler;*/