#include "llviewerprecompiledheaders.h"
#include "llagent.h"
#include "lldrawpoolalpha.h"
#include "llfirstuse.h"
#include "llfloaterbeacons.h"
#include "llfloaterchat.h"
#include "llfloaterdaycycle.h"
#include "llfloaterenvsettings.h"
#include "llfloatergodtools.h"
#include "llfloaterland.h"
#include "llfloatermap.h"
#include "llfloaterregioninfo.h"
#include "llfloatertools.h"
#include "llfloaterwater.h"
#include "llfloaterwindlight.h"
#include "llfloaterworldmap.h"
#include "llgesturemgr.h"
#include "llinventoryview.h"
#include "llstartup.h"
#include "llviewermenu.h"
#include "llviewermessage.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
#include "llviewerwindow.h"
#include "llvoavatar.h"
#include "llworld.h"
#include "pipeline.h"

#include "rlvhelper.h"
#include "rlvevent.h"
#include "rlvextensions.h"
#include "rlvhandler.h"

// Only defined in llinventorymodel.cpp
extern const char* NEW_CATEGORY_NAME;

// ============================================================================
// Static variable initialization
//

BOOL RlvHandler::m_fEnabled = FALSE;
BOOL RlvHandler::fNoSetEnv = FALSE;
BOOL RlvHandler::fLegacyNaming = FALSE;
BOOL RlvHandler::m_fFetchStarted = FALSE;
BOOL RlvHandler::m_fFetchComplete = FALSE;
RlvMultiStringSearch RlvHandler::m_AttachLookup;

const std::string RlvHandler::cstrSharedRoot = RLV_ROOT_FOLDER;

// Keep these consistent with regular RLV
const std::string RlvHandler::cstrBlockedRecvIM = "*** IM blocked by your viewer";
const std::string RlvHandler::cstrBlockedSendIM = "*** IM blocked by sender's viewer";
const std::string RlvHandler::cstrHidden = "(Hidden)";
const std::string RlvHandler::cstrHiddenParcel = "(Hidden parcel)";
const std::string RlvHandler::cstrHiddenRegion = "(Hidden region)";
const std::string RlvHandler::cstrMsgRecvIM = 
	"The Resident you messaged is prevented from reading your instant messages at the moment, please try again later.";
const std::string RlvHandler::cstrMsgTpLure = 
	"The Resident you invited is prevented from accepting teleport offers. Please try again later.";

const std::string RlvHandler::cstrAnonyms[] =
{
	"A resident", "This resident", "That resident", "An individual", "This individual", "That individual", "A person",
	"This person", "That person", "A stranger", "This stranger", "That stranger", "A being", "This being", 
	"That being", "An agent", "This agent", "That agent", "A soul", "This soul", "That soul", "Somebody", 
	"Some people", "Someone", "Mysterious one", "An unknown being", "Unidentified one", "An unknown person"
};

rlv_handler_t gRlvHandler;

// ============================================================================
// Helper functions
//

// Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.0e
inline bool rlvIsWearingItem(const LLInventoryItem* pItem)
{
	return 
		((LLAssetType::AT_OBJECT == pItem->getType()) && (gAgent.getAvatarObject()->isWearingAttachment(pItem->getUUID()))) ||
		((LLAssetType::AT_GESTURE == pItem->getType()) && (gGestureManager.isGestureActive(pItem->getUUID()))) ||
		(gAgent.isWearingItem(pItem->getUUID()));
}

// ============================================================================
// Command specific helper functions
//

// Checked: 2009-08-04 (RLVa-1.0.1d) | Added: RLVa-1.0.1d
static bool rlvParseNotifyOption(const std::string& strOption, S32& nChannel, std::string& strFilter)
{
	boost_tokenizer tokens(strOption, boost::char_separator<char>(";", "", boost::keep_empty_tokens));
	boost_tokenizer::const_iterator itTok = tokens.begin();

	// Extract and sanity check the first token (required) which is the channel
	if ( (itTok == tokens.end()) || (!LLStringUtil::convertToS32(*itTok, nChannel)) || (!rlvIsValidReplyChannel(nChannel)) )
		return false;

	// Second token (optional) is the filter
	strFilter.clear();
	if (++itTok != tokens.end())
	{
		strFilter = *itTok;
		++itTok;
	}

	return (itTok == tokens.end());
}

// ============================================================================
// Constructor/destructor
//

// Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
RlvHandler::RlvHandler() 
	: m_fCanCancelTp(true), m_idCurObject(LLUUID::null), m_pCurCommand(NULL), m_pGCTimer(NULL), m_pWLSnapshot(NULL), m_pBhvrNotify(NULL)
{
	// Array auto-initialization to 0 is non-standard? (Compiler warning in VC-8.0)
	memset(m_LayersAdd, 0, sizeof(S16) * WT_COUNT);
	memset(m_LayersRem, 0, sizeof(S16) * WT_COUNT);
	memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT);
}

// Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
RlvHandler::~RlvHandler()
{
	//delete m_pGCTimer;	// <- deletes itself
	delete m_pWLSnapshot;	// <- delete on NULL is harmless
	delete m_pBhvrNotify;
	delete m_pAttachMgr;
}

// ============================================================================
// Attachment related functions 
//

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
inline LLViewerJointAttachment* RlvHandler::getAttachPoint(const std::string& strText, bool fExact) const
{
	LLVOAvatar* pAvatar = gAgent.getAvatarObject();
	return (pAvatar) ? get_if_there(pAvatar->mAttachmentPoints, getAttachPointIndex(strText, fExact), (LLViewerJointAttachment*)NULL)
	                 : NULL;
}

// Checked: 2009-07-29 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b
LLViewerJointAttachment* RlvHandler::getAttachPoint(const LLInventoryCategory* pFolder, bool /*fStrict*/) const
{
	if (!pFolder)
		return NULL;

	// RLVa-1.0.1 added support for legacy matching (See http://rlva.catznip.com/blog/2009/07/attachment-point-naming-convention/)
	if (fLegacyNaming)
		return getAttachPointLegacy(pFolder);

	// Otherwise the only valid way to specify an attachment point in a folder name is: ^\.\(\s+attachpt\s+\)
	std::string::size_type idxMatch;
	std::string strAttachPt = rlvGetFirstParenthesisedText(pFolder->getName(), &idxMatch);
	LLStringUtil::trim(strAttachPt);

	return ( (1 == idxMatch) && (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName().at(0)) ) ? getAttachPoint(strAttachPt, true) : NULL;
}

// Checked: 2009-07-29 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b
LLViewerJointAttachment* RlvHandler::getAttachPoint(const LLInventoryItem* pItem, bool fStrict) const
{
	// Sanity check - if it's not an object then it can't have an attachment point
	if ( (!pItem) || (LLAssetType::AT_OBJECT != pItem->getType()) )
		return NULL;

	// The attachment point should be placed at the end of the item's name, surrounded by parenthesis
	// (if there is no such text then strAttachPt will be an empty string which is fine since it means we'll look at the item's parent)
	std::string strAttachPt = rlvGetLastParenthesisedText(pItem->getName());
	LLStringUtil::trim(strAttachPt);

	// If the item is modify   : we look at the item's name first and only then at the containing folder
	// If the item is no modify: we look at the containing folder's name first and only then at the item itself
	LLViewerJointAttachment* pAttachPt;
	if (pItem->getPermissions().allowModifyBy(gAgent.getID()))
	{
		pAttachPt = (!strAttachPt.empty()) ? getAttachPoint(strAttachPt, true) : NULL;
		if (!pAttachPt)
			pAttachPt = getAttachPoint(gInventory.getCategory(pItem->getParentUUID()), fStrict);
	}
	else
	{
		pAttachPt = getAttachPoint(gInventory.getCategory(pItem->getParentUUID()), fStrict);
		if ( (!pAttachPt) && (!strAttachPt.empty()) )
			pAttachPt = getAttachPoint(strAttachPt, true);
	}
	return pAttachPt;
}

// Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.2a
S32 RlvHandler::getAttachPointIndex(const LLViewerJointAttachment* pAttachPt) const
{
	LLVOAvatar* pAvatar = gAgent.getAvatarObject();
	if (pAvatar)
	{
		for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin(); 
				itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
		{
			if (itAttach->second == pAttachPt)
				return itAttach->first;
		}
	}
	return 0;
}

// Checked: 2009-07-29 (RLVa-1.0.1b) | Added: RLVa-1.0.1b
LLViewerJointAttachment* RlvHandler::getAttachPointLegacy(const LLInventoryCategory* pFolder) const
{
	// Hopefully some day this can just be deprecated (see http://rlva.catznip.com/blog/2009/07/attachment-point-naming-convention/)
	if ( (!pFolder) || (pFolder->getName().empty()) )
		return NULL;

	// Check for a (...) block *somewhere* in the name
	std::string::size_type idxMatch;
	std::string strAttachPt = rlvGetFirstParenthesisedText(pFolder->getName(), &idxMatch);
	if (!strAttachPt.empty())
	{
		// Could be "(attachpt)", ".(attachpt)" or "Folder name (attachpt)"
		if ( (0 != idxMatch) && ((1 != idxMatch) || (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName().at(0)) ) &&	// No '(' or '.(' start
			 (idxMatch + strAttachPt.length() + 1 != pFolder->getName().length()) )								// or there's extra text
		{
			// It's definitely not one of the first two so assume it's the last form (in which case we need the last paranthesised block)
			strAttachPt = rlvGetLastParenthesisedText(pFolder->getName());
		}
	}
	else
	{
		// There's no paranthesised block, but it could still be "attachpt" or ".attachpt" (just strip away the '.' from the last one)
		strAttachPt = pFolder->getName();
		if (RLV_FOLDER_PREFIX_HIDDEN == strAttachPt[0])
			strAttachPt.erase(0, 1);
	}
	return getAttachPoint(strAttachPt, true);
}

bool RlvHandler::hasLockedHUD() const
{
	LLVOAvatar* pAvatar = gAgent.getAvatarObject();
	if (!pAvatar)
		return false;
	
	LLViewerJointAttachment* pAttachPt;
	for (rlv_attachlock_map_t::const_iterator itAttachPt = m_AttachRem.begin(); itAttachPt != m_AttachRem.end(); ++itAttachPt)
	{
		pAttachPt = get_if_there(pAvatar->mAttachmentPoints, (S32)itAttachPt->first, (LLViewerJointAttachment*)NULL);
		if ( (pAttachPt) && (pAttachPt->getIsHUDAttachment()) )
			return true;	// At least one of our locked attachments is a HUD
	}
	return false;			// None of our locked attachments is a HUD
}

// Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
bool RlvHandler::isLockedAttachment(const LLInventoryItem* pItem, ERlvLockMask eLock) const
{
	LLVOAvatar* pAvatar = gAgent.getAvatarObject();
	return (pItem) && (pAvatar) && (isLockedAttachment(pAvatar->getWornAttachment(pItem->getUUID()), eLock));
}

// Checked: 2009-10-13 (RLVa-1.0.5b) | Added: RLVa-1.0.5b
bool RlvHandler::isLockedAttachmentExcept(S32 idxAttachPt, ERlvLockMask eLock, LLViewerObject *pObj) const
{
	// Loop over every object that marked the specific attachment point eLock type locked (but ignore pObj and any of its children)
	LLViewerObject* pTempObj;
	if (eLock & RLV_LOCK_REMOVE)
	{
		for (rlv_attachlock_map_t::const_iterator itAttach = m_AttachRem.lower_bound(idxAttachPt), 
			endAttach = m_AttachRem.upper_bound(idxAttachPt); itAttach != endAttach; ++itAttach)
		{
			if ( ((pTempObj = gObjectList.findObject(itAttach->second)) == NULL) || (pTempObj->getRootEdit()->getID() != pObj->getID()) )
				return true;
		}
	}
	if (eLock & RLV_LOCK_ADD)
	{
		for (rlv_attachlock_map_t::const_iterator itAttach = m_AttachAdd.lower_bound(idxAttachPt), 
			endAttach = m_AttachAdd.upper_bound(idxAttachPt); itAttach != endAttach; ++itAttach)
		{
			if ( ((pTempObj = gObjectList.findObject(itAttach->second)) == NULL) || (pTempObj->getRootEdit()->getID() != pObj->getID()) )
				return true;
		}
	}
	return false;
}

// Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
void RlvHandler::addAttachmentLock(S32 idxAttachPt, const LLUUID &idRlvObj, ERlvLockMask eLock)
{
	// Sanity check - make sure it's an object we know about
	if ( (m_Objects.find(idRlvObj) == m_Objects.end()) || (!idxAttachPt) )
		return;	// If (idxAttachPt) == 0 then: (pObj == NULL) || (pObj->isAttachment() == FALSE)

	// NOTE: m_AttachXXX can contain duplicate <idxAttachPt, idRlvObj> pairs (ie @detach:spine=n,detach=n from an attachment on spine)
	if (eLock & RLV_LOCK_REMOVE)
	{
		#ifdef RLV_EXPERIMENTAL_FIRSTUSE
			//LLFirstUse::useRlvDetach();
		#endif // RLV_EXPERIMENTAL_FIRSTUSE

		m_AttachRem.insert(std::pair<S32, LLUUID>(idxAttachPt, idRlvObj));
	}
	if (eLock & RLV_LOCK_ADD)
	{
		m_AttachAdd.insert(std::pair<S32, LLUUID>(idxAttachPt, idRlvObj));
	}
}

// Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
void RlvHandler::removeAttachmentLock(S32 idxAttachPt, const LLUUID &idRlvObj, ERlvLockMask eLock)
{
	// Sanity check - make sure it's an object we know about
	if ( (m_Objects.find(idRlvObj) == m_Objects.end()) || (!idxAttachPt) )
		return;	// If (idxAttachPt) == 0 then: (pObj == NULL) || (pObj->isAttachment() == FALSE)

	if (eLock & RLV_LOCK_REMOVE)
	{
		for (rlv_attachlock_map_t::iterator itAttach = m_AttachRem.lower_bound(idxAttachPt), 
				endAttach = m_AttachRem.upper_bound(idxAttachPt); itAttach != endAttach; ++itAttach)
		{
			if (idRlvObj == itAttach->second)
			{
				m_AttachRem.erase(itAttach);
				break;
			}
		}
	}
	if (eLock & RLV_LOCK_ADD)
	{
		for (rlv_attachlock_map_t::iterator itAttach = m_AttachAdd.lower_bound(idxAttachPt), 
				endAttach = m_AttachAdd.upper_bound(idxAttachPt); itAttach != endAttach; ++itAttach)
		{
			if (idRlvObj == itAttach->second)
			{
				m_AttachAdd.erase(itAttach);
				break;
			}
		}
	}
}

#ifdef RLV_EXTENSION_FLAG_NOSTRIP
	// Checked: 2009-05-26 (RLVa-0.2.0d) | Modified: RLVa-0.2.0d
	bool RlvHandler::isStrippable(const LLUUID& idItem) const
	{
		// An item is exempt from @detach or @remoutfit if:
		//   - its name contains "nostrip" (anywhere in the name)
		//   - its parent folder contains "nostrip" (anywhere in the name)
		if (idItem.notNull())
		{
			LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
			if (pItem)
			{
				if (std::string::npos != pItem->getName().find(RLV_FOLDER_FLAG_NOSTRIP))
					return false;

				LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
				if ( (pFolder) && (std::string::npos != pFolder->getName().find(RLV_FOLDER_FLAG_NOSTRIP)) )
					return false;
			}
		}
		return true;
	}
#endif // RLV_EXTENSION_FLAG_NOSTRIP

// ============================================================================
// Behaviour related functions
//

bool RlvHandler::hasBehaviourExcept(ERlvBehaviour eBehaviour, const std::string& strOption, const LLUUID& idObj) const
{
	for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
		if ( (idObj != itObj->second.m_UUID) && (itObj->second.hasBehaviour(eBehaviour, strOption, false)) )
			return true;
	return false;
}

// Checked: 2009-10-04 (RLVa-1.0.4c) | Modified: RLVa-1.0.4c
bool RlvHandler::isException(ERlvBehaviour eBhvr, const RlvExceptionOption& varOption, ERlvExceptionCheck typeCheck) const
{
	// We need to "strict check" exceptions only if: the restriction is actually in place *and* (isPermissive(eBhvr) == FALSE)
	if (RLV_CHECK_DEFAULT == typeCheck)
		typeCheck = ( (hasBehaviour(eBhvr)) && (!isPermissive(eBhvr)) ) ? RLV_CHECK_STRICT : RLV_CHECK_PERMISSIVE;

	std::list<LLUUID> objList;
	if (RLV_CHECK_STRICT == typeCheck)
	{
		// If we're "strict checking" then we need the UUID of every object that currently has 'eBhvr' restricted
		for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
			if (itObj->second.hasBehaviour(eBhvr, !hasBehaviour(RLV_BHVR_PERMISSIVE)))
				objList.push_back(itObj->first);
	}

	for (rlv_exception_map_t::const_iterator itException = m_Exceptions.lower_bound(eBhvr), 
			endException = m_Exceptions.upper_bound(eBhvr); itException != endException; ++itException)
	{
		if (itException->second.varOption == varOption)
		{
			// For permissive checks we just return on the very first match
			if (RLV_CHECK_PERMISSIVE == typeCheck)
				return true;

			// For strict checks we don't return until the list is empty (every object with 'eBhvr' restricted also contains the exception)
			std::list<LLUUID>::iterator itList = std::find(objList.begin(), objList.end(), itException->second.idObject);
			if (itList != objList.end())
				objList.erase(itList);
			if (objList.empty())
				return true;
		}
	}
	return false;
}

// ============================================================================
// Command processing functions
//

// Checked: 2009-06-03 (RLVa-0.2.0h)
void RlvHandler::addBehaviourObserver(RlvBehaviourObserver* pBhvrObserver)
{
	std::list<RlvBehaviourObserver*>::iterator itBhvrObserver = std::find(m_BhvrObservers.begin(), m_BhvrObservers.end(), pBhvrObserver);
	if (itBhvrObserver == m_BhvrObservers.end())
		m_BhvrObservers.push_back(pBhvrObserver);
}

// Checked: 2009-06-03 (RLVa-0.2.0h)
void RlvHandler::removeBehaviourObserver(RlvBehaviourObserver* pBhvrObserver)
{
	std::list<RlvBehaviourObserver*>::iterator itBhvrObserver = std::find(m_BhvrObservers.begin(), m_BhvrObservers.end(), pBhvrObserver);
	if (itBhvrObserver != m_BhvrObservers.end())
		m_BhvrObservers.erase(itBhvrObserver);
}

// Checked: 2009-06-03 (RLVa-0.2.0h)
void RlvHandler::notifyBehaviourObservers(const RlvCommand& rlvCmd, bool fInternal)
{
	for (std::list<RlvBehaviourObserver*>::const_iterator itBhvrObserver = m_BhvrObservers.begin();
		itBhvrObserver != m_BhvrObservers.end(); ++itBhvrObserver)
	{
		(*itBhvrObserver)->changed(rlvCmd, fInternal);
	}
}

// Checked:
BOOL RlvHandler::processCommand(const LLUUID& idObj, const std::string& strCmd, bool fFromObj)
{
	#ifdef RLV_DEBUG
		RLV_INFOS << "[" << idObj << "]: " << strCmd << LL_ENDL;
	#endif // RLV_DEBUG

	RlvCommand rlvCmd(strCmd);
	if (!rlvCmd.isValid())
	{
		#ifdef RLV_DEBUG
			RLV_INFOS << "\t-> invalid command: " << strCmd << LL_ENDL;
		#endif // RLV_DEBUG
		return FALSE;
	}

	// NOTE: if we pass RlvObject::m_UUID for idObj somewhere and process a @clear then it will point to invalid/cleared memory at the end
	//       so make sure to *always* pass our private copy to other functions
	m_pCurCommand = &rlvCmd; m_idCurObject = idObj;

	BOOL fRet = FALSE;
	switch (rlvCmd.getParamType())
	{
		case RLV_TYPE_ADD:		// Checked: 2009-06-03 (RLVa-0.2.0h) | Modified: RLVa-0.2.0h
			{
				if ( (m_Behaviours[rlvCmd.getBehaviourType()]) && 
					 ( (RLV_BHVR_SETDEBUG == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETENV == rlvCmd.getBehaviourType()) ) )
				{
					// Some restrictions can only be held by one single object to avoid deadlocks
					#ifdef RLV_DEBUG
						RLV_INFOS << "\t- " << rlvCmd.getBehaviour() << " is already set by another object => discarding" << LL_ENDL;
					#endif // RLV_DEBUG
					break;
				}

				rlv_object_map_t::iterator itObj = m_Objects.find(m_idCurObject);
				if (itObj != m_Objects.end())
				{
					RlvObject& rlvObj = itObj->second;
					fRet = rlvObj.addCommand(rlvCmd);
				}
				else
				{
					RlvObject rlvObj(m_idCurObject);
					fRet = rlvObj.addCommand(rlvCmd);
					m_Objects.insert(std::pair<LLUUID, RlvObject>(m_idCurObject, rlvObj));
				}

				#ifdef RLV_DEBUG
					RLV_INFOS << "\t- " << ( (fRet) ? "adding behaviour" : "skipping duplicate") << LL_ENDL;
				#endif // RLV_DEBUG

				if (fRet) {	// If FALSE then this was a duplicate, there's no need to handle those
					if (!m_pGCTimer)
						m_pGCTimer = new RlvGCTimer();
					processAddCommand(m_idCurObject, rlvCmd);
					notifyBehaviourObservers(rlvCmd, !fFromObj);
				}
			}
			break;
		case RLV_TYPE_REMOVE:		// Checked:
			{
				rlv_object_map_t::iterator itObj = m_Objects.find(m_idCurObject);
				if (itObj != m_Objects.end())
					fRet = itObj->second.removeCommand(rlvCmd);

				#ifdef RLV_DEBUG
					RLV_INFOS << "\t- " << ( (fRet) ? "removing behaviour"
						                            : "skipping remove (unset behaviour or unknown object)") << LL_ENDL;
				#endif // RLV_DEBUG

				if (fRet) {	// Don't handle non-sensical removes
					processRemoveCommand(m_idCurObject, rlvCmd);
					notifyBehaviourObservers(rlvCmd, !fFromObj);

					if (0 == itObj->second.m_Commands.size())
					{
						#ifdef RLV_DEBUG
							RLV_INFOS << "\t- command list empty => removing " << m_idCurObject << LL_ENDL;
						#endif // RLV_DEBUG
						m_Objects.erase(itObj);
					}
				}
			}
			break;
		case RLV_TYPE_CLEAR:
			fRet = processClearCommand(m_idCurObject, rlvCmd);
			notifyBehaviourObservers(rlvCmd, !fFromObj);
			break;
		case RLV_TYPE_FORCE:		// Checked:
			fRet = processForceCommand(m_idCurObject, rlvCmd);
			break;
		case RLV_TYPE_REPLY:		// Checked:
			fRet = processReplyCommand(m_idCurObject, rlvCmd);
			break;
		case RLV_TYPE_UNKNOWN:		// Checked:
			break;
		#ifdef LL_GNUC
		default:
			break;
		#endif // LL_GNUC
	}

	#ifdef RLV_DEBUG
		RLV_INFOS << "\t--> command " << ((fRet) ? "succeeded" : "failed") << LL_ENDL;
	#endif // RLV_DEBUG

	m_pCurCommand = NULL; m_idCurObject.setNull();
	return fRet;
}

BOOL RlvHandler::processAddCommand(const LLUUID& uuid, const RlvCommand& rlvCmd)
{
	// NOTE: - at this point the command has already been added to the corresponding RlvObject instance
	//       - the object's UUID may or may not exist in gObjectList (see handling of @detach=n)

	ERlvBehaviour eBehaviour = rlvCmd.getBehaviourType();
	const std::string& strOption = rlvCmd.getOption();

	if ( (RLV_BHVR_UNKNOWN != eBehaviour) && (strOption.empty()) )
	{
		if (rlvCmd.isStrict())
			addException(uuid, RLV_BHVR_PERMISSIVE, eBehaviour);
		m_Behaviours[eBehaviour]++;
	}

	bool fRefCount = false; // Unused for the moment
	switch (eBehaviour)
	{
		case RLV_BHVR_DETACH:				// @detach[:<option>]=n		- Checked: 2009-10-10 (RLVa-1.0.5a) | Modified: RLVa-1.0.5a
			onAddRemDetach(uuid, rlvCmd, fRefCount);
			break;
		case RLV_BHVR_ADDATTACH:			// @addattach[:<option>]=n	- Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
		case RLV_BHVR_REMATTACH:			// @addattach[:<option>]=n	- Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
			onAddRemAttach(uuid, rlvCmd, fRefCount);
			break;
		case RLV_BHVR_REDIRCHAT:			// @redirchat:<option>=n	- Checked: 2009-07-07 (RLVa-1.0.0d)
		case RLV_BHVR_REDIREMOTE:			// @rediremote:<option>=n	- Checked: 2009-07-07 (RLVa-1.0.0d) | Added: RLVa-0.2.2a
			{
				if (!strOption.empty())
					m_Behaviours[eBehaviour]++;	// @redirchat and @rediremote don't have an optionless version so keep track of it here
				else
					m_Behaviours[eBehaviour]--;	// @redirchat=n and @rediremote=n are undefined, don't keep track of them
			}
			break;
		case RLV_BHVR_SHOWWORLDMAP:			// @showworldmap=n			- Checked: 2009-07-05 (RLVa-1.0.0c)
			{
				// Simulate clicking the Map button [see LLToolBar::onClickMap()]
				if (gFloaterWorldMap->getVisible())
					LLFloaterWorldMap::toggle(NULL);
			}
			break;
		case RLV_BHVR_SHOWMINIMAP:			// @showminimap=n			- Checked: 2009-07-05 (RLVa-1.0.0c)
			{
				// Simulate clicking the Minimap button [see LLToolBar::onClickRadar()]
				if (LLFloaterMap::instanceVisible())
						LLFloaterMap::hideInstance();
			}
			break;
		#ifdef RLV_EXTENSION_STARTLOCATION
		case RLV_BHVR_TPLOC:				// @tploc=n					- Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
		case RLV_BHVR_UNSIT:				// @unsit=n					- Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
			{
				if (strOption.empty())
					RlvSettings::updateLoginLastLocation();
			}
			break;
		#endif // RLV_EXTENSION_STARTLOCATION
		case RLV_BHVR_EDIT:					// @edit=n					- Checked: 2009-07-04 (RLVa-1.0.0b)
			{
				// Turn off "View / Highlight Transparent"
				LLDrawPoolAlpha::sShowDebugAlpha = FALSE;

				// Close the Beacons floater if it's open
				if (LLFloaterBeacons::instanceVisible())
					LLFloaterBeacons::toggleInstance();

				// Get rid of the build floater if it's open [copy/paste from toggle_build_mode()]
				if (gFloaterTools->getVisible())
				{
					gAgent.resetView(FALSE);
					gFloaterTools->close();
					gViewerWindow->showCursor();
				}
			}
			break;
		case RLV_BHVR_ADDOUTFIT:			// @addoutfit[:<layer>]=n	- Checked: 2009-07-07 (RLVa-1.0.0d)
		case RLV_BHVR_REMOUTFIT:			// @remoutfit[:<layer>]=n	- Checked: 2009-07-07 (RLVa-1.0.0d)
			{
				S16* pLayers = (eBehaviour == RLV_BHVR_ADDOUTFIT) ? m_LayersAdd : m_LayersRem;

				if (strOption.empty())
				{
					for (int idx = 0; idx < WT_COUNT; idx++)
						pLayers[idx]++;
				}
				else
				{
					EWearableType type = LLWearable::typeNameToType(strOption);
					if (WT_INVALID != type)
					{
						pLayers[type]++;
						m_Behaviours[eBehaviour]++;
					}
				}
			}
			break;
		case RLV_BHVR_SHOWINV:				// @showinv=n				- Checked: 2009-07-10 (RLVa-1.0.0g) | Modified: RLVa-1.0.0g
			{
				// Close all open inventory windows
				LLInventoryView::closeAll();
			}
			break;
		case RLV_BHVR_SHOWLOC:				// @showloc=n				- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
			{
				// If we're the first @showloc=n restriction refresh all object text so we can filter it if necessary
				if (1 == m_Behaviours[RLV_BHVR_SHOWLOC])
					LLHUDText::refreshAllObjectText();

				// Close the "About Land" floater if it's currently visible
				if (LLFloaterLand::instanceVisible())
					LLFloaterLand::hideInstance();

				// Close the "Estate Tools" floater is it's currently visible
				if (LLFloaterRegionInfo::instanceVisible())
					LLFloaterRegionInfo::hideInstance();

				// NOTE: we should close the "God Tools" floater as well, but since calling LLFloaterGodTools::instance() always
				//       creates a new instance of the floater and since it's very unlikely to be open it's just better not to
			}
			break;
		case RLV_BHVR_SHOWNAMES:			// @shownames=n				- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
			{
				// If we're the first @shownames=n restriction refresh all object text so we can filter it if necessary
				if (1 == m_Behaviours[RLV_BHVR_SHOWNAMES])
					LLHUDText::refreshAllObjectText();

				// Close the "Active Speakers" panel if it's currently visible
				LLFloaterChat::getInstance()->childSetVisible("active_speakers_panel", false);
			}
			break;
		case RLV_BHVR_FARTOUCH:
			{
				#ifdef RLV_EXPERIMENTAL_FIRSTUSE
					//LLFirstUse::useRlvFartouch();
				#endif // RLV_EXPERIMENTAL_FIRSTUSE
			}
			break;
		case RLV_BHVR_FLY:					// @fly=n					- Checked: 2009-07-05 (RLVa-1.0.0c)
			{
				// If currently flying, simulate clicking the Fly button [see LLToolBar::onClickFly()]
				if (gAgent.getFlying())
					gAgent.toggleFlying();
			}
			break;
		case RLV_BHVR_SETENV:				// @setenv=n				- Checked: 2009-07-10 (RLVa-1.0.0g) | Modified: RLVa-0.2.0a
			{
				if (!fNoSetEnv)
				{
					// Only close the floaters if their instance exists and they're actually visible
					if ( (LLFloaterEnvSettings::isOpen()) && (LLFloaterEnvSettings::instance()->getVisible()) )
						LLFloaterEnvSettings::instance()->close();
					if ( (LLFloaterWindLight::isOpen()) && (LLFloaterWindLight::instance()->getVisible()) )
						LLFloaterWindLight::instance()->close();
					if ( (LLFloaterWater::isOpen()) && (LLFloaterWater::instance()->getVisible()) )
						LLFloaterWater::instance()->close();
					if ( (LLFloaterDayCycle::isOpen()) && (LLFloaterDayCycle::instance()->getVisible()) )
						LLFloaterDayCycle::instance()->close();

					// Save the current WindLight params so we can restore them on @setenv=y
					if (m_pWLSnapshot)
					{
						RLV_ERRS << "m_pWLSnapshot != NULL" << LL_ENDL; // Safety net in case we set @setenv=n for more than 1 object
						delete m_pWLSnapshot;
					}
					m_pWLSnapshot = RlvWLSnapshot::takeSnapshot();
				}
			}
			break;
		case RLV_BHVR_SHOWHOVERTEXTALL:		// @showhovertextal=n		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_SHOWHOVERTEXTWORLD:	// @showhovertextworld=n	- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_SHOWHOVERTEXTHUD:		// @showhovertexthud=n		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
			{
				// Refresh all hover text (LLHUDText::setStringUTF8() will decide what needs clearing and what doesn't)
				LLHUDText::refreshAllObjectText();
			}
			break;
		case RLV_BHVR_SHOWHOVERTEXT:		// @showhovertext:<uuid>=n	- Checked: 2009-07-09 (RLVa-0.2.2a) | Modified: RLVa-1.0.0f
			{
				LLUUID idException(strOption);
				if (idException.notNull())	// If there's an option it should be a valid UUID
				{
					addException(uuid, eBehaviour, idException);

					// Clear the object's hover text
					LLViewerObject* pObj = gObjectList.findObject(idException);
					if ( (pObj) && (pObj->mText.notNull()) && (!pObj->mText->getObjectText().empty()) )
						pObj->mText->setStringUTF8("");
				}
			}
			break;
		case RLV_BHVR_NOTIFY:				// @notify:<option>=add		- Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
			{
				S32 nChannel; std::string strFilter;
				if ( (!strOption.empty()) && (rlvParseNotifyOption(strOption, nChannel, strFilter)) )
				{
					if (!m_pBhvrNotify)
						addBehaviourObserver(m_pBhvrNotify = new RlvBehaviourNotifyObserver());
					m_pBhvrNotify->addNotify(uuid, nChannel, strFilter);
				}
			}
			break;
		case RLV_BHVR_SENDCHANNEL:			// @sendchannel:<uuid>=add	- Checked: 2009-10-05 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a
			{
				S32 nChannel;	// If there's an option it should be a valid (=positive and non-zero) chat channel
				if ( (!strOption.empty()) && (LLStringUtil::convertToS32(strOption, nChannel)) && (nChannel > 0) )
					addException(uuid, eBehaviour, nChannel);
			}
			break;
		case RLV_BHVR_RECVCHAT:				// @recvchat:<uuid>=add		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_RECVEMOTE:			// @recvemote:<uuid>=add	- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_RECVIM:				// @recvim:<uuid>=add		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_SENDIM:				// @sendim:<uuid>=add		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_TPLURE:				// @tplure:<uuid>=add		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_ACCEPTTP:				// @accepttp:<uuid>=add		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
			{
				LLUUID idException(strOption);
				if (idException.notNull())	// If there's an option it should be a valid UUID
					addException(uuid, eBehaviour, LLUUID(strOption));
			}
			break;
		case RLV_BHVR_UNKNOWN:
			{
				// Give our observers a chance to handle any command we don't
				RlvEvent rlvEvent(uuid, rlvCmd);
				m_Emitter.update(&RlvObserver::onAddCommand, rlvEvent);
			}
			break;
		default:
			break;
	}
	return TRUE; // Add command success/failure is decided by RlvObject::addCommand()
}

// Checked: 2009-08-05 (RLVa-1.0.1e) | Added: RLVa-1.0.1e
void RlvHandler::processRetainedCommands()
{
	for (rlv_retained_list_t::const_iterator itCmd = m_Retained.begin(); itCmd != m_Retained.end(); ++itCmd)
	{
		const RlvRetainedCommand& cmd = *itCmd;
		processCommand(cmd.idObject, cmd.strCmd, true);
	}
	m_Retained.clear();
}

BOOL RlvHandler::processRemoveCommand(const LLUUID& uuid, const RlvCommand& rlvCmd)
{
	// NOTE: - the RlvObject instance still exists at this point, but the viewer might already have removed it from its object list
	ERlvBehaviour eBehaviour = rlvCmd.getBehaviourType();
	const std::string& strOption = rlvCmd.getOption();

	if ( (RLV_BHVR_UNKNOWN != eBehaviour) && (strOption.empty()) )
	{
		if (rlvCmd.isStrict())
			removeException(uuid, RLV_BHVR_PERMISSIVE, eBehaviour);
		m_Behaviours[eBehaviour]--;
	}

	bool fRefCount = false; // Unused for the moment
	switch (eBehaviour)
	{
		case RLV_BHVR_DETACH:				// @detach[:<option>]=y		- Checked: 2009-10-10 (RLVa-1.0.5a) | Modified: RLVa-1.0.5a
			onAddRemDetach(uuid, rlvCmd, fRefCount);
			break;
		case RLV_BHVR_ADDATTACH:			// @addattach[:<option>]=y	- Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
		case RLV_BHVR_REMATTACH:			// @addattach[:<option>]=y	- Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
			onAddRemAttach(uuid, rlvCmd, fRefCount);
			break;
		case RLV_BHVR_REDIRCHAT:			// @redirchat:<option>=y	- Checked: 2009-07-07 (RLVa-1.0.0d)
		case RLV_BHVR_REDIREMOTE:			// @rediremote:<option>=y	- Checked: 2009-07-07 (RLVa-1.0.0d) | Added: RLVa-0.2.2a
			{
				if (!strOption.empty())
					m_Behaviours[eBehaviour]--;	// @redirchat and @rediremote don't have an optionless version so keep track of it here
				else
					m_Behaviours[eBehaviour]++;	// @redirchat=n and @rediremote=n are undefined, don't keep track of them
			}
			break;
		#ifdef RLV_EXTENSION_STARTLOCATION
		case RLV_BHVR_TPLOC:				// @tploc=y					- Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
		case RLV_BHVR_UNSIT:				// @unsit=y					- Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
			{
				if (strOption.empty())
					RlvSettings::updateLoginLastLocation();
			}
			break;
		#endif // RLV_EXTENSION_STARTLOCATION
		case RLV_BHVR_ADDOUTFIT:			// @addoutfit[:<layer>]=y	- Checked: 2009-07-07 (RLVa-1.0.0d)
		case RLV_BHVR_REMOUTFIT:			// @remoutfit[:<layer>]=y	- Checked: 2009-07-07 (RLVa-1.0.0d)
			{
				S16* pLayers = (eBehaviour == RLV_BHVR_ADDOUTFIT) ? m_LayersAdd : m_LayersRem;

				if (strOption.empty())
				{
					for (int idx = 0; idx < WT_COUNT; idx++)
						pLayers[idx]--;
				}
				else
				{
					EWearableType type = LLWearable::typeNameToType(strOption);
					if (WT_INVALID != type)
					{
						pLayers[type]--;
						m_Behaviours[eBehaviour]--;
					}
				}
			}
			break;
		case RLV_BHVR_SETENV:				// @setenv=y				- Checked: 2009-07-10 (RLVa-1.0.0g) | Added: RLVa-0.2.0h
			{
				if (!fNoSetEnv)
				{
					// Restore WindLight parameters to what they were before @setenv=n was issued
					RlvWLSnapshot::restoreSnapshot(m_pWLSnapshot);
					delete m_pWLSnapshot;
					m_pWLSnapshot = NULL;
				}
			}
			break;
		case RLV_BHVR_SHOWLOC:				// @showloc=y				- Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
		case RLV_BHVR_SHOWNAMES:			// @shownames=y				- Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
		case RLV_BHVR_SHOWHOVERTEXTALL:		// @showhovertextal=y		- Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
		case RLV_BHVR_SHOWHOVERTEXTWORLD:	// @showhovertextworld=y	- Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
		case RLV_BHVR_SHOWHOVERTEXTHUD:		// @showhovertexthud=y		- Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
			{
				// If this was the last of any of the five restrictions we should refresh all hover text in case anything needs restoring
				if (!m_Behaviours[eBehaviour])
					LLHUDText::refreshAllObjectText();
			}
			break;
		case RLV_BHVR_SHOWHOVERTEXT:		// @showhovertext:<uuid>=y	- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
			{
				LLUUID idException(strOption);
				if (idException.notNull())	// If there's an option it should be a valid UUID
				{
					removeException(uuid, eBehaviour, idException);

					// Restore the object's hover text
					LLViewerObject* pObj = gObjectList.findObject(idException);
					if ( (pObj) && (pObj->mText.notNull()) && (!pObj->mText->getObjectText().empty()) )
						pObj->mText->setStringUTF8(pObj->mText->getObjectText());
				}
			}
			break;
		case RLV_BHVR_NOTIFY:				// @notify:<option>=rem		- Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
			{
				S32 nChannel; std::string strFilter;
				if ( (m_pBhvrNotify) && (!strOption.empty()) && (rlvParseNotifyOption(strOption, nChannel, strFilter)) )
				{
					m_pBhvrNotify->removeNotify(uuid, nChannel, strFilter);

					if (!m_pBhvrNotify->hasNotify())
					{
						removeBehaviourObserver(m_pBhvrNotify);
						delete m_pBhvrNotify;
						m_pBhvrNotify = NULL;
					}
				}
			}
			break;
		case RLV_BHVR_SENDCHANNEL:			// @sendchannel:<uuid>=rem	- Checked: 2009-10-05 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a
			{
				S32 nChannel;	// If there's an option it should be a valid (=positive and non-zero) chat channel
				if ( (!strOption.empty()) && (LLStringUtil::convertToS32(strOption, nChannel)) && (nChannel > 0) )
					removeException(uuid, eBehaviour, nChannel);
			}
			break;
		case RLV_BHVR_RECVCHAT:				// @recvchat:<uuid>=rem		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_RECVEMOTE:			// @recvemote:<uui>=red		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_RECVIM:				// @recvim:<uuid>=rem		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_SENDIM:				// @sendim:<uuid>=rem		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_TPLURE:				// @recvim:<uuid>=rem		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
		case RLV_BHVR_ACCEPTTP:				// @accepttp:<uuid>=rem		- Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
			{
				LLUUID idException(strOption);
				if (idException.notNull())	// If there's an option it should be a valid UUID
					removeException(uuid, eBehaviour, LLUUID(strOption));
			}
			break;
		case RLV_BHVR_UNKNOWN:
			{
				// Give our observers a chance to handle any command we don't
				RlvEvent rlvEvent(uuid, rlvCmd);
				m_Emitter.update(&RlvObserver::onRemoveCommand, rlvEvent);
			}
			break;
		default:
			break;
	}
	return TRUE; // Remove commands don't fail, doesn't matter what we return here
}

BOOL RlvHandler::processClearCommand(const LLUUID idObj, const RlvCommand& rlvCmd)
{
	const std::string& strFilter = rlvCmd.getParam(); std::string strCmdRem;

	rlv_object_map_t::const_iterator itObj = m_Objects.find(idObj);
	if (itObj != m_Objects.end())	// No sense in clearing if we don't have any commands for this object
	{
		const RlvObject& rlvObj = itObj->second; bool fContinue = true;
		for (rlv_command_list_t::const_iterator itCmd = rlvObj.m_Commands.begin(), itCurCmd;
				((fContinue) && (itCmd != rlvObj.m_Commands.end())); )
		{
			itCurCmd = itCmd++;		// Point itCmd ahead so it won't get invalidated if/when we erase a command

			const RlvCommand& rlvCmdRem = *itCurCmd; strCmdRem = rlvCmdRem.asString();
			if ( (strFilter.empty()) || (std::string::npos != strCmdRem.find(strFilter)) )
			{
				fContinue = (rlvObj.m_Commands.size() > 1); // rlvObj will become invalid once we remove the last command
				processCommand(idObj, strCmdRem.append("=y"), false);
			}
		}
	}

	// Let our observers know about clear commands
	RlvEvent rlvEvent(idObj, rlvCmd);
	m_Emitter.update(&RlvObserver::onClearCommand, rlvEvent);

	return TRUE; // Don't fail clear commands even if the object didn't exist since it confuses people
}

BOOL RlvHandler::processForceCommand(const LLUUID& idObj, const RlvCommand& rlvCmd) const
{
	const std::string& strOption = rlvCmd.getOption();
	BOOL fHandled = TRUE;

	switch (rlvCmd.getBehaviourType())
	{
		case RLV_BHVR_DETACH:		// @detach[:<option>]=force		- Checked: 2009-10-12 (RLVa-1.0.5a) | Modified: RLVa-1.0.5a
			onForceDetach(idObj, rlvCmd);
			break;
		case RLV_BHVR_REMATTACH:	// @remattach[:<option>]=force  - Checked: 2009-10-12 (RLVa-1.0.5a) | Modified: RLVa-1.0.5a
			onForceRemAttach(idObj, rlvCmd);
			break;
		case RLV_BHVR_REMOUTFIT:	// @remoutfit:<option>=force	- Checked:
			onForceRemOutfit(idObj, strOption);
			break;
		case RLV_BHVR_UNSIT:		// @unsit=force					- Checked: 2009-06-02 (RLVa-0.2.0g)
			{
				LLVOAvatar* pAvatar = gAgent.getAvatarObject();
				if ( (pAvatar) && (pAvatar->mIsSitting) && (!hasBehaviourExcept(RLV_BHVR_UNSIT, idObj)) )
				{
					// See behaviour notes on why we have to force an agent update here
					gAgent.setControlFlags(AGENT_CONTROL_STAND_UP);
					send_agent_update(TRUE, TRUE);
				}
			}
			break;
		case RLV_BHVR_TPTO:			// @tpto:<option>=force			- Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-1.0.0h
			{
				fHandled = FALSE;
				if ( (!strOption.empty()) && (-1 == strOption.find_first_not_of("0123456789/.")) )
				{
					LLVector3d posGlobal;

					boost_tokenizer tokens(strOption, boost::char_separator<char>("/", "", boost::keep_empty_tokens)); int idx = 0;
					for (boost_tokenizer::const_iterator itToken = tokens.begin(); itToken != tokens.end(); ++itToken)
					{
						if (idx < 3)
							LLStringUtil::convertToF64(*itToken, posGlobal[idx++]);
					}

					if (idx == 3)
					{
						gAgent.teleportViaLocation(posGlobal);
						fHandled = TRUE;
					}
				} 
			}
			break;
		case RLV_BHVR_SIT:			// @sit:<option>=force			- Checked: 2009-06-02 (RLVa-0.2.0g)
			fHandled = onForceSit(idObj, rlvCmd.getOption());
			break;
		case RLV_BHVR_ADDOUTFIT:	// @addoutfit:<option>=force <- synonym of @attach:<option>=force
		case RLV_BHVR_ATTACH:		// @attach:<option>=force		- Checked:
			onForceWear(rlvCmd.getOption(), true, false);	// Force attach single folder
			break;
		case RLV_BHVR_ATTACHALL:	// @attachall:<option>=force	- Checked:
			onForceWear(rlvCmd.getOption(), true, true);	// Force attach nested folders
			break;
		case RLV_BHVR_DETACHALL:	// @detachall:<option>=force	- Checked:
			onForceWear(rlvCmd.getOption(), false, true);	// Force detach nested folders
			break;
		case RLV_BHVR_ATTACHTHIS:
		case RLV_BHVR_ATTACHALLTHIS:
		case RLV_BHVR_DETACHTHIS:
		case RLV_BHVR_DETACHALLTHIS:
			{
				ERlvBehaviour eBehaviour = rlvCmd.getBehaviourType();
				std::string strReply;
				if (onGetPath(idObj, strOption, strReply))
				{
					LLStringUtil::toLower(strReply);
					onForceWear(strReply, 
						(RLV_BHVR_ATTACHTHIS == eBehaviour) || (RLV_BHVR_ATTACHALLTHIS == eBehaviour), 
						(RLV_BHVR_ATTACHALLTHIS == eBehaviour) || (RLV_BHVR_DETACHALLTHIS == eBehaviour));
				}
			}
			break;
		case RLV_BHVR_DETACHME:		// @detachme=force				- Checked: 2009-06-07 (RLVa-0.2.1c)
			{
				// NOTE: @detachme=force could be seen as a @detach:<attachpt>=force but RLV implements it as a "detach by UUID"
				LLViewerObject* pObj; LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachPt;
				if ( ((pObj = gObjectList.findObject(idObj)) != NULL) && (pObj->isAttachment()) && 
					 ((pAvatar = gAgent.getAvatarObject()) != NULL) && 
					 ((pAttachPt = pAvatar->getTargetAttachmentPoint(pObj->getRootEdit())) != NULL) )
				{
					handle_detach_from_avatar(pAttachPt);
				}
			}
			break;
		case RLV_BHVR_UNKNOWN:
			{
				// Give our observers a chance to handle any command we don't
				RlvEvent rlvEvent(idObj, rlvCmd);
				fHandled = m_Emitter.update(&RlvObserver::onForceCommand, rlvEvent);
			}
			break;
		default:
			break;
	}
	return fHandled; // If we handled it then it'll still be TRUE; if an observer doesn't handle it'll be FALSE
}

// Checked: 2009-07-12 (RLVa-1.0.0h)
BOOL RlvHandler::processReplyCommand(const LLUUID& uuid, const RlvCommand& rlvCmd) const
{
	const std::string& strOption = rlvCmd.getOption();
	const std::string& strChannel = rlvCmd.getParam();
	std::string strReply;

	BOOL fHandled = TRUE;
	switch (rlvCmd.getBehaviourType())
	{
		case RLV_BHVR_VERSION:			// @version=<channel>				  - Checked: 2009-07-12 (RLVa-1.0.0h)
			strReply = getVersionString();
			break;
		case RLV_BHVR_VERSIONNUM:		// @versionnum=<channel>			  - Checked: 2009-10-04 (RLVa-1.0.4b) | Added: RLVa-1.0.4b
			strReply = getVersionNumString();
			break;
		case RLV_BHVR_GETOUTFIT:		// @getoufit[:<layer>]=<channel>	  - Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
			{
				// (Quirk: RLV 1.16.1 will execute @getoutfit=<channel> if <layer> is invalid, so we need to as well)
				EWearableType layerType = LLWearable::typeNameToType(strOption);

				const EWearableType layerTypes[] =
					{ 
						WT_GLOVES, WT_JACKET, WT_PANTS, WT_SHIRT, WT_SHOES, WT_SKIRT, WT_ALPHA, WT_TATTOO, WT_SOCKS, 
						WT_UNDERPANTS, WT_UNDERSHIRT, WT_SKIN, WT_EYES, WT_HAIR, WT_SHAPE
					};

				#ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING
					for (int idx = 0, cnt = sizeof(layerTypes) / sizeof(EWearableType); idx < cnt; idx++)
					{
						if ( (WT_INVALID == layerType) || (layerTypes[idx] == layerType) )
						{
							// TODO-RLVa: add support for 'fHideLockedLayers'
							bool fWorn = (gAgent.getWearable(layerTypes[idx])) && 
										 (!isHiddenCompositeItem(gAgent.getWearableItem(layerTypes[idx]), 
																 LLWearable::typeToTypeName(layerTypes[idx])));
							strReply.push_back( (fWorn) ? '1' : '0' );
						}
					}
				#else
					for (int idx = 0, cnt = sizeof(layerTypes) / sizeof(EWearableType); idx < cnt; idx++)
						if ( (WT_INVALID == layerType) || (layerTypes[idx] == layerType) )
						{
							// We never hide body parts, even if they're "locked" and we're hiding locked layers
							// (nor do we hide a layer if the issuing object is the only one that has this layer locked)
							bool fWorn = (gAgent.getWearable(layerTypes[idx])) && 
								( (!RlvSettings::getHideLockedLayers()) || 
								  (LLAssetType::AT_BODYPART == LLWearable::typeToAssetType(layerTypes[idx])) ||
								  ( (isRemovableExcept(layerTypes[idx], uuid)) && 
								    (isStrippable(gAgent.getWearableItem(layerTypes[idx]))) ) );
							strReply.push_back( (fWorn) ? '1' : '0' );
							//strReply.push_back( (gAgent.getWearable(layerTypes[idx])) ? '1' : '0' );
						}
				#endif // RLV_EXPERIMENTAL_COMPOSITE_FOLDING
			}
			break;
		case RLV_BHVR_GETATTACH:		// @getattach[:<layer>]=<channel>	  - Checked: 2009-10-10 (RLVa-1.0.5a) | Modified: RLVa-1.0.5a
			{
				// If we're fetching all worn attachments then the reply should start with 0
				if (strOption.empty())
					strReply.push_back('0');

				LLVOAvatar* pAvatar = gAgent.getAvatarObject(); std::string strAttachName;
				for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin(); 
					 itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
				{
					LLViewerJointAttachment* pAttachment = itAttach->second;
					if (!pAttachment)
						continue;

					strAttachName = pAttachment->getName(); // Capitalized (see avatar_lad.xml)
					LLStringUtil::toLower(strAttachName);

					#ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING
						if ( (strOption.empty()) || (strOption == strAttachName) )
						{
							// TODO-RLVa: add support for 'fHideLockedAttach'
							bool fWorn = (pAttachment->getItemID().notNull()) && 
										 (!isHiddenCompositeItem(pAttachment->getItemID(), strAttachName));
							strReply.push_back( (fWorn) ? '1' : '0' );
						}
					#else
						if ( (strOption.empty()) || (strOption == strAttachName) )
						{
							bool fWorn = (pAttachment->getItemID().notNull()) && 
								( (!RlvSettings::getHideLockedAttach()) || 
								  ( (!isLockedAttachmentExcept(itAttach->first, RLV_LOCK_REMOVE, gObjectList.findObject(uuid))) && 
								    (isStrippable(pAttachment->getItemID())) ) );
							strReply.push_back( (fWorn) ? '1' : '0' );
						}
					#endif // RLV_EXPERIMENTAL_COMPOSITE_FOLDING
				}
			}
			break;
		case RLV_BHVR_GETSTATUS:		// @getstatus[:<option>]=<channel>	  - Checked: 2009-07-12 (RLVa-1.0.0h)
			{
				// NOTE: specification says response should start with '/' but RLV-1.16.1 returns an empty string when no rules are set
				rlv_object_map_t::const_iterator itObj = m_Objects.find(uuid);
				if (itObj != m_Objects.end())
				{
					std::string strObjStatus = itObj->second.getStatusString(strOption);
					if (!strObjStatus.empty())
					{
						strReply.push_back('/');
						strReply += strObjStatus;
					}
				}
			}
			break;
		case RLV_BHVR_GETSTATUSALL:		// @getstatusall[:<option>]=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h)
			{
				// NOTE: specification says response should start with '/' but RLV-1.16.1 returns an empty string when no rules are set
				std::string strObjStatus;
				for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
				{
					strObjStatus = itObj->second.getStatusString(strOption);
					if (!strObjStatus.empty())
					{
						strReply.push_back('/');
						strReply += strObjStatus;
					}
				}
			}
			break;
		case RLV_BHVR_GETINV:			// @getinv[:<path>]=<channel>		  - Checked: 2009-07-28 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b
			{
				LLViewerInventoryCategory* pFolder = getSharedFolder(strOption);
				if (pFolder)
				{
					LLInventoryModel::cat_array_t*  pFolders;
					LLInventoryModel::item_array_t* pItems;
					gInventory.getDirectDescendentsOf(pFolder->getUUID(), pFolders, pItems);

					if (pFolders)
					{
						for (S32 idxFolder = 0, cntFolder = pFolders->count(); idxFolder < cntFolder; idxFolder++)
						{
							const std::string& strFolder = pFolders->get(idxFolder)->getName();
							if ( (!strFolder.empty()) && (RLV_FOLDER_PREFIX_HIDDEN != strFolder[0]) &&
								 (!isFoldedFolder(pFolders->get(idxFolder).get(), true)) )
							{
								if (!strReply.empty())
									strReply.push_back(',');
								strReply += strFolder;
							}
						}
					}
				}
			}
			break;
		case RLV_BHVR_GETINVWORN:		// @getinvworn[:path]=<channel>		  - Checked:
			onGetInvWorn(rlvCmd.getOption(), strReply);
			break;
		case RLV_BHVR_FINDFOLDER:		// @findfolder:<criteria>=<channel>   - Checked: 2009-08-26 (RLVa-1.0.2a) | Modified: RLVa-1.0.2a
			{
				// COMPAT-RLV: RLV 1.16.1 returns the first random folder it finds (probably tries to match "" to a folder name?)
				//             (just going to stick with what's there for now... no option => no folder)
				LLInventoryModel::cat_array_t folders;
				if ( (!strOption.empty()) && (findSharedFolders(strOption, folders)) )
				{
					// We need to return an "in depth" result so whoever has the most '/' is our lucky winner
					// (maxSlashes needs to be initialized to -1 since children of the #RLV folder won't have '/' in their shared path)
					int maxSlashes = -1, curSlashes; std::string strFolderName;
					for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
					{
						strFolderName = getSharedPath(folders.get(idxFolder));

						curSlashes = std::count(strFolderName.begin(), strFolderName.end(), '/');
						if (curSlashes > maxSlashes)
						{
							maxSlashes = curSlashes;
							strReply = strFolderName;
						}
					}
				}
			}
			break;
		#ifdef RLV_EXTENSION_CMD_FINDFOLDERS
			case RLV_BHVR_FINDFOLDERS:	// @findfolders:<criteria>=<channel>  - Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.0b
				{
					LLInventoryModel::cat_array_t folders;
					if ( (!strOption.empty()) && (findSharedFolders(strOption, folders)) )
					{
						for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
						{
							if (!strReply.empty())
								strReply.push_back(',');
							strReply += getSharedPath(folders.get(idxFolder));
						}
					}
				}
				break;
		#endif // RLV_EXTENSION_CMD_FINDFOLDERS
		case RLV_BHVR_GETPATH:			// @getpath[:<option>]=<channel>	  - Checked: 2009-07-12 (RLVa-1.0.0h)
			onGetPath(uuid, rlvCmd.getOption(), strReply);
			break;
		case RLV_BHVR_GETSITID:			// @getsitid=<channel>				  - Checked: 2009-07-12 (RLVa-1.0.0h)
			{
				// (Quirk: RLV 1.16.1 returns a NULL uuid if we're not sitting)
				LLVOAvatar* pAvatarObj = gAgent.getAvatarObject(); LLUUID uuid;
				if ( (pAvatarObj) && (pAvatarObj->mIsSitting) )
				{
					// LLVOAvatar inherits from 2 classes so make sure we get the right vfptr
					LLViewerObject* pAvatar = dynamic_cast<LLViewerObject*>(pAvatarObj), *pParent;
					// (If there is a parent, we need to upcast it from LLXform to LLViewerObject to get its UUID)
					if ( (pAvatar) && ((pParent = static_cast<LLViewerObject*>(pAvatar->getRoot())) != pAvatar) )
						uuid = pParent->getID();
				}
				strReply = uuid.asString();
			}
			break;
		case RLV_BHVR_UNKNOWN:
			{
				// Give our observers a chance to handle any command we don't
				RlvEvent rlvEvent(uuid, rlvCmd);
				return m_Emitter.update(&RlvObserver::onReplyCommand, rlvEvent);
			}
			break;
		default:
			break;
	}

	if (fHandled)
		rlvSendChatReply(strChannel, strReply);
	return fHandled;
}

// ============================================================================
// House keeping (see debug notes for test methodology, test script and last run)
//

void RlvHandler::initLookupTables()
{
	static bool fInitialized = false;
	if (!fInitialized)
	{
		// Initialize the attachment name lookup table
		LLVOAvatar* pAvatar = gAgent.getAvatarObject();
		if (pAvatar)
		{
			std::string strAttachPtName;
			for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin(); 
					 itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
			{
				LLViewerJointAttachment* pAttachPt = itAttach->second;
				if (pAttachPt)
				{
					strAttachPtName = pAttachPt->getName();
					LLStringUtil::toLower(strAttachPtName);
					m_AttachLookup.addKeyword(strAttachPtName, itAttach->first);
				}
			}
			fInitialized = true;
		}
	}
}

// Checked: 2009-08-11 (RLVa-1.0.1h) | Modified: RLVa-1.0.1h
void RlvHandler::onAttach(LLViewerJointAttachment* pAttachPt)
{
	// Sanity check - LLVOAvatar::attachObject() should call us *after* calling LLViewerJointAttachment::addObject()
	LLViewerObject* pObj = pAttachPt->getObject();
	S32 idxAttachPt = getAttachPointIndex(pObj);	 // getAttachPointIndex() has a NULL pointer check so this is safe
	if ( (!pObj) || (!idxAttachPt) )
	{
		RLV_ERRS << "pAttachPt->getObject() == NULL" << LL_ENDL;
		return;
	}

	// Let the attachment manager know
	m_pAttachMgr->onAttach(pAttachPt);

	// Check if we already have an RlvObject instance for this object (rezzed prim attached from in-world, or an attachment that rezzed in)
	rlv_object_map_t::iterator itObj = m_Objects.find(pObj->getID());
	if (itObj != m_Objects.end())
	{
		// Save the attachment point index
		itObj->second.m_idxAttachPt = idxAttachPt;

		// If it's an attachment we processed commands for but that only just rezzed in we need to mark it as existing in gObjectList
		if (!itObj->second.m_fLookup)
			itObj->second.m_fLookup = true;

		// In both cases we should check for "@detach=n" and actually lock down the attachment point it got attached to
		if (itObj->second.hasBehaviour(RLV_BHVR_DETACH, false))
		{
			// (Copy/paste from processAddCommand)
			addAttachmentLock(idxAttachPt, itObj->second.m_UUID, RLV_LOCK_REMOVE);

			if (pObj->isHUDAttachment())
				LLPipeline::sShowHUDAttachments = TRUE;	// Prevents hiding of locked HUD attachments
		}
	}

	// Fetch the inventory item if we don't currently have it since we might need it for reattach-on-detach
	const LLUUID& idItem = pAttachPt->getItemID();
	LLViewerInventoryItem* pItem = ( (idItem.notNull()) && (gInventory.isInventoryUsable()) ) ? gInventory.getItem(idItem) : NULL;
	if ( (STATE_STARTED == LLStartUp::getStartupState()) && (pItem != NULL) )
	{
		RlvCurrentlyWorn f;
		f.fetchItem(idItem);
	}

	// If what we're wearing is located under the shared root then append the attachment point name (if needed)
	LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
	if ( (STATE_STARTED == LLStartUp::getStartupState()) && (pRlvRoot) && (pItem) && (pItem->isComplete()) &&
		 (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) )
	{
		std::string strAttachPt = pAttachPt->getName();
		LLStringUtil::toLower(strAttachPt);

		// If we can modify the item then it should contain the attach point name itself, otherwise its parent should
		if (pItem->getPermissions().allowModifyBy(gAgent.getID()))
		{
			if (!getAttachPoint(pItem, true))
			{
				// It doesn't specify an attach point and we can rename it [see LLItemBridge::renameItem()]
				std::string strName = pItem->getName();
				LLStringUtil::truncate(strName, DB_INV_ITEM_NAME_STR_LEN - strAttachPt.length() - 3);

				strName += " (" + strAttachPt + ")";

				pItem->rename(strName);
				pItem->updateServer(FALSE);
				gInventory.updateItem(pItem);
				//gInventory.notifyObservers(); <- done further down in LLVOAvatar::attachObject()
			}
		}
		else
		{
			// Folder can't be the shared root, or be its direct descendant (= nested at least 2 levels deep)
			LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
			if ( (pFolder) && 
				 (pFolder->getUUID() != pRlvRoot->getUUID()) && (pFolder->getParentUUID() != pRlvRoot->getUUID()) && 
				 (!getAttachPoint(pFolder, true)) )
			{
				// It's no mod and its parent folder doesn't contain an attach point
				if ( (1 == rlvGetDirectDescendentsCount(pFolder, LLAssetType::AT_OBJECT)) && (NEW_CATEGORY_NAME == pFolder->getName()) )
				{
					// Only rename if there's exactly 1 object/attachment inside of it [see LLFolderBridge::renameItem()]
					std::string strName = ".(" + strAttachPt + ")";

					pFolder->rename(strName);
					pFolder->updateServer(FALSE);
					gInventory.updateCategory(pFolder);
					//gInventory.notifyObservers(); <- done further down in LLVOAvatar::attachObject()
				}
			}
		}
	}
}

// Checked: 2009-05-31 (RLVa-0.2.0e) | Modified: RLVa-0.2.0e
void RlvHandler::onDetach(LLViewerJointAttachment* pAttachPt)
{
	LLViewerObject* pObj = pAttachPt->getObject();
	if (!pObj)
	{
		// LLVOAvatar::detachObject() should call us *before* calling LLViewerJointAttachment::removeObject()
		RLV_ERRS << "pAttachPt->getObject() == NULL" << LL_ENDL;
		return;
	}
	S32 idxAttachPt = getAttachPointIndex(pObj);
	if (0 == idxAttachPt)
	{
		// If we ended up here then the user "Drop"'ed this attachment (which we can't recover from)
		return;
	}

	#ifdef RLV_DEBUG
		// TODO-RLV: when we're exiting (for whatever reason) app state won't always reflect it but 
		//           gAgent.getAvatarObject()->mAttachmentPoints will be NULL so anywhere we use
		//			 "get_if_there" will call through a NULL pointer. One case is "idling out" -> test the rest
		//LLViewerJointAttachment* pDbgAttachmentPt = 
		//	get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, (S32)idxAttachPt, (LLViewerJointAttachment*)NULL);
		//RLV_INFOS << "Clean up for '" << pDbgAttachmentPt->getName() << "'" << LL_ENDL;
	#endif // RLV_DEBUG

	// Let the attachment manager know
	m_pAttachMgr->onDetach(pAttachPt);

	// Clean up any restriction this object (or one of its child prims) may have
	rlv_object_map_t::iterator itObj = m_Objects.begin(), itCurrent;
	while (itObj != m_Objects.end())
	{
		itCurrent = itObj++; // @clear will invalidate our iterator so point it ahead now

		// NOTE: ObjectKill seems to happen in reverse (child prims are killed before the root is) so we can't use gObjectList here
		if (itCurrent->second.m_idxAttachPt == idxAttachPt)
			processCommand(itCurrent->second.m_UUID, "clear", true);
	}
}

// Checked: 2009-07-30 (RLVa-1.0.1c) | Modified: RLVa-1.0.1c
bool RlvHandler::onGC()
{
	// We can't issue @clear on an object while we're in the loop below since that would invalidate our iterator
	// (and starting over would mean that some objects might get their "lookup misses" counter updated more than once per GC run)
	std::list<LLUUID> ExpiredObjects;

	for (rlv_object_map_t::iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
	{
		LLViewerObject* pObj = gObjectList.findObject(itObj->second.m_UUID);
		if (!pObj)
		{
			// If the RlvObject once existed in the gObjectList and now doesn't then expire it right now
			// If the RlvObject never existed in the gObjectList and still doesn't then increase its "lookup misses" counter
			// but if that reaches 20 (we run every 30 seconds so that's about 10 minutes) then we'll expire it too
			if ( (itObj->second.m_fLookup) || (++itObj->second.m_nLookupMisses > 20) )
				ExpiredObjects.push_back(itObj->first);
		}
		else
		{
			// Check if this is an RlvObject instance who's object never existed in gObjectList before (rezzed prim in-world)
			// (it could also be an attachment that only just rezzed in but RlvHandler::onAttach() should be handling those)
			if ( (!itObj->second.m_fLookup) && (!pObj->isAttachment()) )
				itObj->second.m_fLookup = true;
		}
	}

	for (std::list<LLUUID>::const_iterator itExpired = ExpiredObjects.begin(); itExpired != ExpiredObjects.end(); ++itExpired)
	{
		#ifdef RLV_DEBUG
			RLV_INFOS << "Garbage collecting " << *itExpired << LL_ENDL;
		#endif // RLV_DEBUG

		processCommand(*itExpired, "clear", true);
	}

	return (0 != m_Objects.size());	// GC will kill itself if it has nothing to do
}

// ============================================================================
// String/chat censoring functions
//

// LL must have included an strlen for UTF8 *somewhere* but I can't seem to find it so this one is home grown
size_t utf8str_strlen(const std::string& utf8)
{
	const char* pUTF8 = utf8.c_str(); size_t length = 0;
	for (int idx = 0, cnt = utf8.length(); idx < cnt ;idx++)
	{
		// We're looking for characters that don't start with 10 as their high bits
		if ((pUTF8[idx] & 0xC0) != 0x80)
			length++;
	}
	return length;
}

// TODO-RLV: works, but more testing won't hurt
std::string utf8str_chtruncate(const std::string& utf8, size_t length)
{
	if (0 == length)
		return std::string();
	if (utf8.length() <= length)
		return utf8;

	const char* pUTF8 = utf8.c_str(); int idx = 0;
	while ( (pUTF8[idx]) && (length > 0) )
	{
		// We're looking for characters that don't start with 10 as their high bits
		if ((pUTF8[idx] & 0xC0) != 0x80)
			length--;
		idx++;
	}

	return utf8.substr(0, idx);
}

// Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
void RlvHandler::filterChat(std::string& strUTF8Text, bool fFilterEmote) const
{
	if (strUTF8Text.empty())
		return;

	if (rlvIsEmote(strUTF8Text))					// Check if it's an emote
	{
		if (fFilterEmote)							// Emote filtering depends on fFilterEmote
		{
			if ( (strUTF8Text.find_first_of("\"()*=^_?~") != -1) || 
				 (strUTF8Text.find(" -") != -1) || (strUTF8Text.find("- ") != -1) || (strUTF8Text.find("''") != -1) )
			{
				strUTF8Text = "...";				// Emote contains illegal character (or character sequence)
			}
			else if (!hasBehaviour(RLV_BHVR_EMOTE))
			{
				int idx = strUTF8Text.find('.');	// Truncate at 20 characters or at the dot (whichever is shorter)
				strUTF8Text = utf8str_chtruncate(strUTF8Text, ( (idx > 0) && (idx < 20) ) ? idx + 1 : 20);
			}
		}
	} 
	else if (strUTF8Text[0] == '/')					// Not an emote, but starts with a '/'
	{
		if (utf8str_strlen(strUTF8Text) > 7)		// Allow as long if it's 6 characters or less
			strUTF8Text = "...";
	}
	else if ((strUTF8Text.length() < 4) || (strUTF8Text.compare(0, 2, "((")) || (strUTF8Text.compare(strUTF8Text.length() - 2, 2, "))")))
	{
		strUTF8Text = "...";						// Regular chat (not OOC)
	}
}

// Checked: 2009-07-04 (RLVa-1.0.0a) | Modified: RLVa-1.0.0a
void RlvHandler::filterLocation(std::string& strUTF8Text) const
{
	// TODO-RLVa: if either the region or parcel name is a simple word such as "a" or "the" then confusion will ensue?
	//            -> not sure how you would go about preventing this though :|...

	// Filter any mention of the surrounding region names
	LLWorld::region_list_t regions = LLWorld::getInstance()->getRegionList();
	for (LLWorld::region_list_t::const_iterator itRegion = regions.begin(); itRegion != regions.end(); ++itRegion)
		rlvStringReplace(strUTF8Text, (*itRegion)->getName(), rlv_handler_t::cstrHiddenRegion);

	// Filter any mention of the parcel name
	LLViewerParcelMgr* pParcelMgr = LLViewerParcelMgr::getInstance();
	if (pParcelMgr)
		rlvStringReplace(strUTF8Text, pParcelMgr->getAgentParcelName(), rlv_handler_t::cstrHiddenParcel);
}

void RlvHandler::filterNames(std::string& strUTF8Text) const
{
	std::string strFirstName, strLastName, strName;

	// TODO-RLV: make this a bit more efficient (ie people with a large draw distance will have a load of active regions)
	//   -> the cost of multi string matching them all at once seems to be about the same as calling rlvStringReplace 
	//      twice so that would be a tremendous gain (and we'd get first name and word matching for free)
	#if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0)			// Version: 1.22.11
		for (LLWorld::region_list_t::const_iterator itRegion = LLWorld::getInstance()->getRegionList().begin();
			 itRegion != LLWorld::getInstance()->getRegionList().end(); ++itRegion)
		{
			LLViewerRegion* pRegion = *itRegion;
			
			for (S32 idxAgent = 0, cntAgent = pRegion->mMapAvatars.count(); idxAgent < cntAgent; idxAgent++)
			{
				// LLCacheName::getName() will add the UUID to the lookup queue if we don't know it yet
				if (gCacheName->getName(pRegion->mMapAvatarIDs.get(idxAgent), strFirstName, strLastName))
				{
					strName = strFirstName + " " + strLastName;

					rlvStringReplace(strUTF8Text, strName, getAnonym(strName));
				}
			}
		}
	#else												// Version: trunk
		// TODO-RLV: should restrict this to a certain radius (probably 1-2 sim range?)
		std::vector<LLUUID> idAgents;
		LLWorld::getInstance()->getAvatars(&idAgents, NULL);

		for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++)
		{
			// LLCacheName::getName() will add the UUID to the lookup queue if we don't know it yet
			if (gCacheName->getName(idAgents[idxAgent], strFirstName, strLastName))
			{
				strName = strFirstName + " " + strLastName;

				rlvStringReplace(strUTF8Text, strName, getAnonym(strName));
			}
		}
	#endif
}

const std::string& RlvHandler::getAnonym(const std::string& strName) const
{
	const char* pszName = strName.c_str();
	U32 nHash = 0;
	
	// Test with 11,264 SL names showed a 3.33% - 3.82% occurance for each so we *should* get a very even spread
	for (int idx = 0, cnt = strName.length(); idx < cnt; idx++)
		nHash += pszName[idx];

	return cstrAnonyms[nHash % 28];
}

// Checked: 2009-07-07 (RLVa-1.0.0d) | Modified: RLVa-0.2.2a
bool RlvHandler::redirectChatOrEmote(const std::string& strUTF8Text) const
{
	// Sanity check - @redirchat only for chat and @rediremote only for emotes
	bool fIsEmote = rlvIsEmote(strUTF8Text);
	if ( ((!fIsEmote) && (!hasBehaviour(RLV_BHVR_REDIRCHAT))) || ((fIsEmote) && (!hasBehaviour(RLV_BHVR_REDIREMOTE))) )
		return false;

	if (!fIsEmote)
	{
		std::string strText = strUTF8Text;
		filterChat(strText, true);
		if (strText != "...")
			return false;	// @sendchat wouldn't filter it so @redirchat won't redirect it either
	}

	bool fSendChannel = hasBehaviour(RLV_BHVR_SENDCHANNEL); S32 nChannel = 0;
	for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
	{
		for (rlv_command_list_t::const_iterator itCmd = itObj->second.m_Commands.begin(), 
				endCmd = itObj->second.m_Commands.end(); itCmd != endCmd; ++itCmd)
		{
			if ( ( ((!fIsEmote) && (RLV_BHVR_REDIRCHAT == itCmd->getBehaviourType())) ||	// Redirect if: (not an emote and @redirchat
				   ((fIsEmote) && (RLV_BHVR_REDIREMOTE == itCmd->getBehaviourType())) ) &&	// OR an emote and @rediremote)
				 (LLStringUtil::convertToS32(itCmd->getOption(), nChannel)) &&				// AND the channel is a number
				 ( (!fSendChannel) || (isException(RLV_BHVR_SENDCHANNEL, nChannel)) ) )		// AND we're allowed to send to that channel
			{
				rlvSendChatReply(nChannel, strUTF8Text);
			}
		}
	}
	return true;
}

// ============================================================================
// Public service functions (called by the outside world or by extension handlers)
//

BOOL RlvHandler::isAgentNearby(const LLUUID& uuid) const
{
	#if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0)			// Version: 1.22.11
		for (LLWorld::region_list_t::const_iterator itRegion = LLWorld::getInstance()->getRegionList().begin();
			 itRegion != LLWorld::getInstance()->getRegionList().end(); ++itRegion)
		{
			LLViewerRegion* pRegion = *itRegion;
			
			for (S32 idxAgent = 0, cntAgent = pRegion->mMapAvatars.count(); idxAgent < cntAgent; idxAgent++)
				if (pRegion->mMapAvatarIDs.get(idxAgent) == uuid)
					return TRUE;
		}
	#else												// Version: trunk
		// TODO-RLV: rewrite this to fit trunk, but still need the radius limited to a sane range
		std::vector<LLUUID> idAgents;
		LLWorld::getInstance()->getAvatars(&idAgents, NULL);

		for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++)
		{
			if (idAgents[idxAgent] == uuid)
				return TRUE;
		}
	#endif
	return FALSE;
}

// ============================================================================
// General purpose inventory functions
//

// Checked: 2009-07-12 (RLVa-1.0.0h)
class RlvSharedRootFetcher : public LLInventoryFetchDescendentsObserver
{
public:
	RlvSharedRootFetcher() {}

	virtual void done()
	{
		RLV_INFOS << "Shared folders fetch completed" << LL_ENDL;
		RlvHandler::m_fFetchComplete = TRUE;

		gInventory.removeObserver(this);
		delete this;
	}
};

// Checked: 2009-07-12 (RLVa-1.0.0h)
void RlvHandler::fetchSharedInventory()
{
	// Sanity check - don't fetch if we're already fetching, or if we don't have a shared root
	LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
	if ( (m_fFetchStarted) || (!pRlvRoot) )
		return;

	// Grab all the folders under the shared root
	LLInventoryModel::cat_array_t  folders;
	LLInventoryModel::item_array_t items;
	gInventory.collectDescendents(pRlvRoot->getUUID(), folders, items, FALSE);

	/*
	 * Add them to the "to fetch" list
	 */ 
	LLInventoryFetchDescendentsObserver::folder_ref_t fetchFolders;

	fetchFolders.push_back(pRlvRoot->getUUID());
	for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
		fetchFolders.push_back(folders.get(idxFolder)->getUUID());

	/*
	 * Now fetch them all in one go
	 */
	RlvSharedRootFetcher* fetcher = new RlvSharedRootFetcher;

	RLV_INFOS << "Starting fetch of " << fetchFolders.size() << " shared folders" << LL_ENDL;
	fetcher->fetchDescendents(fetchFolders);

	if (fetcher->isEverythingComplete())
		fetcher->done();
	else
		gInventory.addObserver(fetcher);
}

bool RlvHandler::findSharedFolders(const std::string& strCriteria, LLInventoryModel::cat_array_t& folders) const
{
	// Sanity check - can't do anything without a shared root
	LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
	if (!pRlvRoot)
		return false;

	folders.clear();
	LLInventoryModel::item_array_t items;
	RlvCriteriaCategoryCollector functor(strCriteria);
	gInventory.collectDescendentsIf(pRlvRoot->getUUID(), folders, items, FALSE, functor);

	return (folders.count() != 0);
}

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0e
LLViewerInventoryCategory* RlvHandler::getSharedRoot()
{
	if (gInventory.isInventoryUsable())
	{
		LLInventoryModel::cat_array_t*  pFolders;
		LLInventoryModel::item_array_t* pItems;
		gInventory.getDirectDescendentsOf(gAgent.getInventoryRootID(), pFolders, pItems);
		if (pFolders)
		{
			// NOTE: we might have multiple #RLV folders so we'll just go with the first one we come across
			LLViewerInventoryCategory* pFolder;
			for (S32 idxFolder = 0, cntFolder = pFolders->count(); idxFolder < cntFolder; idxFolder++)
			{
				if ( ((pFolder = pFolders->get(idxFolder)) != NULL) && (RlvHandler::cstrSharedRoot == pFolder->getName()) )
					return pFolder;
			}
		}
	}
	return NULL;
}

// Checked: 2009-07-28 (RLVa-1.0.1a) | Modified: RLVa-1.0.1a
LLViewerInventoryCategory* RlvHandler::getSharedFolder(const LLUUID& idParent, const std::string& strFolderName) const
{
	LLInventoryModel::cat_array_t*  pFolders;
	LLInventoryModel::item_array_t* pItems;
	gInventory.getDirectDescendentsOf(idParent, pFolders, pItems);
	if ( (!pFolders) || (strFolderName.empty()) )
		return NULL;

	// If we can't find an exact match then we'll settle for a "contains" match
	LLViewerInventoryCategory* pPartial = NULL;

	//LLStringUtil::toLower(strFolderName); <- everything was already converted to lower case before

	std::string strName;
	for (S32 idxFolder = 0, cntFolder = pFolders->count(); idxFolder < cntFolder; idxFolder++)
	{
		LLViewerInventoryCategory* pFolder = pFolders->get(idxFolder);

		strName = pFolder->getName();
		if (strName.empty())
			continue;
		LLStringUtil::toLower(strName);

		if (strFolderName == strName)
			return pFolder;		// Found an exact match, no need to keep on going
		else if ( (!pPartial) && (RLV_FOLDER_PREFIX_HIDDEN != strName[0]) && (strName.find(strFolderName) != std::string::npos) )
			pPartial = pFolder;	// Found a partial (non-hidden) match, but we might still find an exact one (first partial match wins)
	}

	return pPartial;
}

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0e
LLViewerInventoryCategory* RlvHandler::getSharedFolder(const std::string& strPath) const
{
	// Sanity check - no shared root => no shared folder
	LLViewerInventoryCategory* pRlvRoot = getSharedRoot(), *pFolder = pRlvRoot;
	if (!pRlvRoot)
		return NULL;

	// Walk the path (starting at the root)
	boost_tokenizer tokens(strPath, boost::char_separator<char>("/", "", boost::drop_empty_tokens));
	for (boost_tokenizer::const_iterator itToken = tokens.begin(); itToken != tokens.end(); ++itToken)
	{
		pFolder = getSharedFolder(pFolder->getUUID(), *itToken);
		if (!pFolder)
			return NULL;	// No such folder
	}

	return pFolder;			// If strPath was empty or just a bunch of //// then: pFolder == pRlvRoot
}

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0g
std::string RlvHandler::getSharedPath(const LLViewerInventoryCategory* pFolder) const
{
	LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
	// Sanity check - no shared root or no folder => no path
	if ( (!pRlvRoot) || (!pFolder) || (pRlvRoot->getUUID() == pFolder->getUUID()) )
		return std::string();

	const LLUUID& idRLV  = pRlvRoot->getUUID();
	const LLUUID& idRoot = gAgent.getInventoryRootID();
	std::string strPath;

	// Walk up the tree until we reach the top
	while (pFolder)
	{
		strPath = "/" + pFolder->getName() + strPath;

		const LLUUID& idParent = pFolder->getParentUUID();
		if (idRLV == idParent)			// Reached the shared root, we're done
			break;
		else if (idRoot == idParent)	// We reached the agent's inventory root (indicative of a logic error elsewhere)
		{
			RLV_ERRS << "Reached agent's inventory root while building path for shared folder" << LL_ENDL;
			return std::string();
		}
		else
			pFolder = gInventory.getCategory(idParent);
	}

	return strPath.erase(0, 1);
}

// ============================================================================
// Composite folders
//

#ifdef RLV_EXPERIMENTAL_COMPOSITES
	// Checked:
	bool RlvHandler::getCompositeInfo(const LLInventoryCategory* pFolder, std::string* pstrName) const
	{
		if (pFolder)
		{
			// Composite folder naming: ^\.?[Folder]
			const std::string& cstrFolder = pFolder->getName();
			int idxStart = cstrFolder.find('['), idxEnd = cstrFolder.find(']', idxStart);
			if ( ((0 == idxStart) || (1 == idxStart)) && (idxEnd - idxStart > 1) )
			{
				if (pstrName)
					pstrName->assign(cstrFolder.substr(idxStart + 1, idxEnd - idxStart - 1));
				return true;
			}
		}
		return false;
	}

	// Checked:
	bool RlvHandler::getCompositeInfo(const LLUUID& idItem, std::string* pstrName, LLViewerInventoryCategory** ppFolder) const
	{
		LLViewerInventoryCategory* pRlvRoot; LLViewerInventoryItem* pItem;

		if ( (idItem.notNull()) && ((pRlvRoot = getSharedRoot()) != NULL) && 
			 (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) && ((pItem = gInventory.getItem(idItem)) != NULL) )
		{
			// We know it's an item in a folder under the shared root...
			LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
			if (getAttachPoint(pFolder, true))
			{
				// ... but it could be named ".(attachpt)" in which case we need its parent
				pFolder = gInventory.getCategory(pFolder->getParentUUID());
			}

			if ( (pFolder) && (getCompositeInfo(pFolder, pstrName)) )
			{
				if (ppFolder)
					*ppFolder = pFolder;
				return true;
			}
		}
		return false;
	}
#endif // RLV_EXPERIMENTAL_COMPOSITES

#ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING
	// Checked:
	inline bool RlvHandler::isHiddenCompositeItem(const LLUUID& idItem, const std::string& cstrItemType) const
	{
		// An item that's part of a composite folder will be hidden from @getoutfit and @getattach if:
		//   (1) the composite name specifies either a wearable layer or an attachment point
		//   (2) the specified wearable layer or attachment point is worn and resides in the folder
		//   (3) cstrItemType isn't the specified wearable layer or attach point
		//
		// Example: #RLV/Separates/Shoes/ChiChi Pumps/.[shoes] with items: "Shoe Base", "Shoe (left foot)" and "Shoe (right foot)"
		//   -> as long as "Shoe Base" is worn, @getattach should not reflect "left foot", nor "right foot"
		std::string strComposite; LLViewerInventoryCategory* pFolder;
		EWearableType type; S32 idxAttachPt;
		if ( (getCompositeInfo(idItem, &strComposite, &pFolder)) && (cstrItemType != strComposite) )
		{
			LLUUID idCompositeItem;
			if ((type = LLWearable::typeNameToType(strComposite)) != WT_INVALID)
			{
				idCompositeItem = gAgent.getWearableItem(type);
			}
			else if ((idxAttachPt = getAttachPointIndex(strComposite, true)) != 0)
			{
				LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachmentPt;
				if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) && 
					 ((pAttachmentPt = get_if_there(pAvatar->mAttachmentPoints, idxAttachPt, (LLViewerJointAttachment*)NULL)) != NULL) )
				{
					idCompositeItem = pAttachmentPt->getItemID();
				}
			}

			if ( (idCompositeItem.notNull()) && (gInventory.isObjectDescendentOf(idCompositeItem, pFolder->getUUID())) )
				return true;
		}
		return false;
	}
#endif // RLV_EXPERIMENTAL_COMPOSITEFOLDING

#ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
	// Checked:
	bool RlvHandler::canTakeOffComposite(const LLInventoryCategory* pFolder) const
	{
		if (!pFolder)		// If there's no folder then there is nothing to take off
			return false;

		LLInventoryModel::cat_array_t  folders;
		LLInventoryModel::item_array_t items;
		RlvWearableItemCollector functor(pFolder->getUUID(), true, false);

		// Grab a list of all the items @detachthis would be detaching/unwearing
		gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);
		if (!items.count())
			return false;	// There are no wearable items in the folder so there is nothing to take off

		LLViewerInventoryItem* pItem;
		for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
		{
			pItem = items.get(idxItem);

			switch (pItem->getType())
			{
				case LLAssetType::AT_CLOTHING:
					{
						LLWearable* pWearable = gAgent.getWearableFromWearableItem(pItem->getUUID());
						if ( (pWearable) && (!isRemovable(pWearable->getType())) )
							return false;	// If one clothing layer in the composite folder is unremoveable then the entire folder is
					}
					break;
				case LLAssetType::AT_OBJECT:
					{
						LLVOAvatar* pAvatar; LLViewerObject* pObj;
						if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) && 
							 ((pObj = pAvatar->getWornAttachment(pItem->getUUID())) != NULL) && (!isDetachable(pObj)) )
						{
							return false;	// If one attachment in the composite folder is undetachable then the entire folder is
						}
					}
					break;
				#ifdef LL_GNUC
				default:
					break;
				#endif // LL_GNUC
			}
		}
		return true;
	}
#endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING

// ============================================================================
// Event handlers
//

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
void RlvHandler::onForceRemOutfit(const LLUUID& idObj, const std::string& strOption) const
{
	EWearableType typeOption = LLWearable::typeNameToType(strOption), type;
	if ( (WT_INVALID == typeOption) && (!strOption.empty()) )
		return;

	// Before we had an option and optionless branch, but with the addition of composites and nostrip there's less duplication this way
	for (int idxType = 0; idxType < WT_COUNT; idxType++)
	{
		type = (EWearableType)idxType;
		if (LLAssetType::AT_CLOTHING != LLWearable::typeToAssetType(type))
			continue; // Only strip clothing, not bodyparts

		if ( ((typeOption == type) || (strOption.empty())) && (gAgent.getWearable(type)) && (isStrippable(gAgent.getWearableItem(type))) )
		{
			#ifdef RLV_EXPERIMENTAL_COMPOSITES
				// If we're stripping something that's part of a composite folder then we should @detachthis instead
				if (isCompositeDescendent(gAgent.getWearableItem(type)))
				{
					std::string strCmd = "detachthis:" + LLWearable::typeToTypeName(type) + "=force";
					#ifdef RLV_DEBUG
						RLV_INFOS << "\t- '" << LLWearable::typeToTypeName(type) << "' is composite descendent: @" << strCmd << LL_ENDL;
					#endif // RLV_DEBUG
					processForceCommand(idObj, RlvCommand(strCmd));
				}
				else
			#endif // RLV_EXPERIMENTAL_COMPOSITES
				{
					gAgent.removeWearable(type);
				}
		}
	}
}

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0g
bool RlvHandler::onForceSit(const LLUUID& idObj, const std::string& strOption) const
{
	LLViewerObject* pObject = NULL; LLUUID idTarget(strOption);
	// Sanity checking - we need to know about the object and it should identify a prim/linkset
	if ( (idTarget.isNull()) || ((pObject = gObjectList.findObject(idTarget)) == NULL) || (LL_PCODE_VOLUME != pObject->getPCode()) )
		return false;

	// Don't force sit if:
	//   1) currently sitting and prevented from standing up
	//   2) prevented from sitting
	//   3) @sittp=n restricted (except if @sittp=n was issued by the same prim that's currently force sitting the avie)
	if ( ( (hasBehaviour(RLV_BHVR_UNSIT)) && (gAgent.getAvatarObject()) && (gAgent.getAvatarObject()->mIsSitting) ) || 
		 ( (hasBehaviour(RLV_BHVR_SIT)) ) ||
		 ( (hasBehaviourExcept(RLV_BHVR_SITTP, idObj)) && 
		   (dist_vec_squared(gAgent.getPositionGlobal(), pObject->getPositionGlobal()) > 1.5f * 1.5f) ))
	{
		return false;
	}

	// Copy/paste from handle_sit_or_stand() [see http://wiki.secondlife.com/wiki/AgentRequestSit]
	gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit);
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->nextBlockFast(_PREHASH_TargetObject);
	gMessageSystem->addUUIDFast(_PREHASH_TargetID, pObject->mID);
	// Offset: "a rough position in local coordinates for the edge to sit on"
	// (we might not even be looking at the object so I don't think we can supply the offset to an edge)
	gMessageSystem->addVector3Fast(_PREHASH_Offset, LLVector3::zero);
	pObject->getRegion()->sendReliableMessage();

	return true;
}

// Checked: 2009-10-10 (RLVa-1.0.5a) | Modified: RLVa-1.0.5a
void RlvHandler::onForceWear(const std::string& strPath, bool fAttach, bool fMatchAll) const
{
	// See LLWearableBridge::wearOnAvatar(): don't wear anything until initial wearables are loaded, can destroy clothing items
	if (!gAgent.areWearablesLoaded()) 
		return;

	LLViewerInventoryCategory* pFolder = getSharedFolder(strPath);
	if (!pFolder)	// Folder not found = nothing to attach
		return;

	LLInventoryModel::cat_array_t  folders;
	LLInventoryModel::item_array_t items;
	RlvWearableItemCollector functor(pFolder->getUUID(), fAttach, fMatchAll);

	// Grab a list of all the items we'll be wearing/attaching
	gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);

	LLViewerInventoryItem* pItem;
	for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
	{
		pItem = items.get(idxItem);

		switch (pItem->getType())
		{
			case LLAssetType::AT_CLOTHING:
			case LLAssetType::AT_BODYPART:
				{
					LLWearable* pWearable = gAgent.getWearableFromWearableItem(pItem->getUUID());

					#ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
						// If we're already wearing something on this layer then we have to check if it isn't part of a composite
						// folder that has at least one unremovable item (in which case we can't wear or remove this item)
						LLViewerInventoryCategory* pCompositeFolder;
						if ( (!pWearable) || (!getCompositeInfo(pItem->getUUID(), NULL, &pCompositeFolder)) || 
							 (canTakeOffComposite(pFolder)))
						{
					#endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
							if (fAttach)
							{
								// Simulate wearing a clothing item from inventory (right click / "Wear")
								// LLWearableBridge::performAction() => LLWearableBridge::wearOnAvatar() => wear_inventory_item_on_avatar()
								wear_inventory_item_on_avatar(pItem);
							}
							else
							{
								if ( (pWearable) && (LLAssetType::AT_CLOTHING == pItem->getType()) )
									gAgent.removeWearable(pWearable->getType());
							}
					#ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
						}
					#endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
				}
				break;
			case LLAssetType::AT_OBJECT:
				{
					LLVOAvatar* pAvatar = gAgent.getAvatarObject();
					LLViewerObject* pObj;

					#ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
						// If we're already wearing something on this attach point then we have to check if it isn't part of a composite
						// folder that has at least one unremovable item (in which case we can't attach or detach this item)
						LLViewerInventoryCategory* pCompositeFolder;
						if ( (pAvatar) && 
							 ( ((pObj = pAvatar->getWornAttachment(pItem->getUUID())) == NULL) || 
							   (!getCompositeInfo(pItem->getUUID(), NULL, &pCompositeFolder)) || (canTakeOffComposite(pFolder)) ) )
						{
					#endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
							if (fAttach)
							{
								// Simulate wearing an object to a specific attachment point (copy/paste to suppress replacement dialog)
								// LLAttachObject::handleEvent() => rez_attachment()
								LLViewerJointAttachment* pAttachPt = getAttachPoint(pItem, true);
								if ( (pAttachPt) &&														 // Need a specific attach pt that
									 ( (!isLockedAttachment(pAttachPt->getObject(), RLV_LOCK_REMOVE)) && // doesn't have locked object
									   (!isLockedAttachment(pAttachPt, RLV_LOCK_ADD)) ) )				 // and that can be attached to
								{
									RlvAttachmentManager::forceAttach(pItem->getUUID(), getAttachPointIndex(pAttachPt->getName(), true));
								}
							}
							else
							{
								if ( (pAvatar) && ((pObj = pAvatar->getWornAttachment(pItem->getUUID())) != NULL) )
								{
									LLViewerJointAttachment* pAttachment = pAvatar->getTargetAttachmentPoint(pObj);
									if (pAttachment)
										handle_detach_from_avatar(pAttachment);
								}
							}
					#ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
						}
					#endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
				}
				break;
			#ifdef LL_GNUC
			default:
				break;
			#endif // LL_GNUC
		}
	}
}

// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0g
bool RlvHandler::onGetPath(const LLUUID &uuid, const std::string& strOption, std::string& strReply) const
{
	// Sanity check - no need to go through all this trouble if we don't have a shared root
	LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
	if (!pRlvRoot)
		return false;

	LLUUID idItem;

	// <option> can be a clothing layer
	EWearableType layerType = LLWearable::typeNameToType(strOption);
	if (WT_INVALID != layerType)
	{
		idItem = gAgent.getWearableItem(layerType);
	}
	else
	{
		LLViewerJointAttachment* pAttachPt = NULL;

		// ... or it can be empty
		if (strOption.empty())
		{
			// (in which case we act on the object that issued the command)
			LLViewerObject* pObj = gObjectList.findObject(uuid);
			if ( (pObj) && (pObj->isAttachment()) && (gAgent.getAvatarObject()) )
				pAttachPt = gAgent.getAvatarObject()->getTargetAttachmentPoint(pObj);
		}
		else
		{
			// ... or it can specify an attach point
			pAttachPt = getAttachPoint(strOption, true);
		}

		// If we found something, get its inventory item UUID
		if (pAttachPt)
			idItem = pAttachPt->getItemID();
	}

	// If we found something and it's under the shared root, then get its path
	if ( (!idItem.isNull()) && (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) )
	{
		LLInventoryItem* pItem = gInventory.getItem(idItem);
		if (pItem)
		{
			// ... unless the containing folder's name specifies an attach point (or nostrip) in which case we need its parent
			LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
			#ifdef RLV_EXTENSION_FLAG_NOSTRIP
				if ( (getAttachPoint(pFolder, true)) || (pFolder->getName() == ".("RLV_FOLDER_FLAG_NOSTRIP")") )
			#else
				if (getAttachPoint(pFolder, true))
			#endif // RLV_EXTENSION_FLAG_NOSTRIP
					strReply = getSharedPath(pFolder->getParentUUID());
				else
					strReply = getSharedPath(pFolder);
		}
	}
	return !strReply.empty();
}

struct rlv_wear_info { U32 cntWorn, cntTotal, cntChildWorn, cntChildTotal; };

// Checked: 2009-05-30 (RLVa-0.2.0e) | Modified: RLVa-0.2.0e
void RlvHandler::onGetInvWorn(const std::string& strPath, std::string& strReply) const
{
	// Sanity check - getAvatarObject() can't be NULL [see rlvIsWearingItem()] and the folder should exist and not be hidden
	LLViewerInventoryCategory* pFolder = getSharedFolder(strPath);
	if ((!gAgent.getAvatarObject()) || (!pFolder) || (pFolder->getName().empty()) || (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName()[0]))
		return;

	// Collect everything @attachall would be attaching
	LLInventoryModel::cat_array_t  folders;
	LLInventoryModel::item_array_t items;
	RlvWearableItemCollector functor(pFolder->getUUID(), true, true);
	gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);

	rlv_wear_info wi = {0};

	// Add all the folders to a lookup map 
	std::map<LLUUID, rlv_wear_info> mapFolders;
	mapFolders.insert(std::pair<LLUUID, rlv_wear_info>(pFolder->getUUID(), wi));
	for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
		mapFolders.insert(std::pair<LLUUID, rlv_wear_info>(folders.get(idxFolder)->getUUID(), wi));

	// Iterate over all the found items
	LLViewerInventoryItem* pItem; std::map<LLUUID, rlv_wear_info>::iterator itFolder;
	for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
	{
		pItem = items.get(idxItem);

		// The "folded parent" is the folder this item should be considered a direct descendent of (may or may not match actual parent)
		const LLUUID& idParent = functor.getFoldedParent(pItem->getParentUUID());

		// Walk up the tree: sooner or later one of the parents will be a folder in the map
		LLViewerInventoryCategory* pParent = gInventory.getCategory(idParent);
		while ( (itFolder = mapFolders.find(pParent->getUUID())) == mapFolders.end() )
			pParent = gInventory.getCategory(pParent->getParentUUID());

		U32 &cntWorn  = (idParent == pParent->getUUID()) ? itFolder->second.cntWorn : itFolder->second.cntChildWorn, 
			&cntTotal = (idParent == pParent->getUUID()) ? itFolder->second.cntTotal : itFolder->second.cntChildTotal;

		if (rlvIsWearingItem(pItem))
			cntWorn++;
		cntTotal++;
	}

	// Extract the result for the main folder
	itFolder = mapFolders.find(pFolder->getUUID());
	wi.cntWorn = itFolder->second.cntWorn;
	wi.cntTotal = itFolder->second.cntTotal;
	mapFolders.erase(itFolder);

	// Build the result for each child folder
	for (itFolder = mapFolders.begin(); itFolder != mapFolders.end(); ++itFolder)
	{
		rlv_wear_info& wiFolder = itFolder->second;

		wi.cntChildWorn += wiFolder.cntWorn + wiFolder.cntChildWorn;
		wi.cntChildTotal += wiFolder.cntTotal + wiFolder.cntChildTotal;

		strReply += llformat(",%s|%d%d", gInventory.getCategory(itFolder->first)->getName().c_str(),
		 (0 == wiFolder.cntTotal) ? 0 : (0 == wiFolder.cntWorn) ? 1 : (wiFolder.cntWorn != wiFolder.cntTotal) ? 2 : 3,
		 (0 == wiFolder.cntChildTotal) ? 0 : (0 == wiFolder.cntChildWorn) ? 1 : (wiFolder.cntChildWorn != wiFolder.cntChildTotal) ? 2 : 3
		);
	}

	// Now just prepend the root and done
	strReply = llformat("|%d%d", (0 == wi.cntTotal) ? 0 : (0 == wi.cntWorn) ? 1 : (wi.cntWorn != wi.cntTotal) ? 2 : 3,
		(0 == wi.cntChildTotal) ? 0 : (0 == wi.cntChildWorn) ? 1 : (wi.cntChildWorn != wi.cntChildTotal) ? 2: 3) + strReply;
}

// (In case anyone cares: this isn't used in public builds)
bool RlvHandler::getWornInfo(const LLInventoryCategory* pFolder, U8& wiFolder, U8& wiChildren) const
{
	// Sanity check - getAvatarObject() can't be NULL [see rlvIsWearingItem()] and the folder should exist and not be hidden
	if ((!gAgent.getAvatarObject()) || (!pFolder) || (pFolder->getName().empty()) || (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName()[0]))
		return false;

	LLInventoryModel::cat_array_t  folders;
	LLInventoryModel::item_array_t items;
	RlvWearableItemCollector functor(pFolder->getUUID(), true, true);
	gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);

	LLViewerInventoryItem* pItem;
	U32 cntWorn = 0, cntTotal = 0, cntChildWorn = 0, cntChildTotal = 0;
	for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
	{
		pItem = items.get(idxItem);

		bool fDirectDescendent = (pFolder->getUUID() == functor.getFoldedParent(pItem->getParentUUID()));
		U32 &refWorn = (fDirectDescendent) ? cntWorn : cntChildWorn, &refTotal = (fDirectDescendent) ? cntTotal : cntChildTotal;

		if (rlvIsWearingItem(pItem))
			refWorn++;
		refTotal++;
	}

	wiFolder = (0 == cntTotal + cntChildTotal) ? 0 : (0 == cntWorn + cntChildWorn) ? 1 : 
		(cntWorn + cntChildWorn != cntTotal + cntChildTotal) ? 2 : 3;
	wiChildren = (0 == cntChildTotal) ? 0 : (0 == cntChildWorn) ? 1 : (cntChildWorn != cntChildTotal) ? 2 : 3;

	return true;
}

// ============================================================================
// Initialization helper functions
//

BOOL RlvHandler::setEnabled(BOOL fEnable)
{
	if (m_fEnabled == fEnable)
		return fEnable;

	if (fEnable)
	{
		if (gSavedSettings.controlExists(RLV_SETTING_NOSETENV))
			fNoSetEnv = gSavedSettings.getBOOL(RLV_SETTING_NOSETENV);
		if (gSavedSettings.controlExists(RLV_SETTING_ENABLELEGACYNAMING))
			fLegacyNaming = gSavedSettings.getBOOL(RLV_SETTING_ENABLELEGACYNAMING);
		if (gSavedSettings.controlExists(RLV_SETTING_SHOWNAMETAGS))
			RlvSettings::fShowNameTags = gSavedSettings.getBOOL(RLV_SETTING_SHOWNAMETAGS);

		RlvCommand::initLookupTable();
		gRlvHandler.m_pAttachMgr = new RlvAttachmentManager();
		gRlvHandler.addObserver(new RlvExtGetSet());

		if (LLStartUp::getStartupState() >= STATE_CLEANUP)
			fetchSharedInventory();

		m_fEnabled = TRUE;
	}
	else if (canDisable())
	{
		#ifdef RLV_DEBUG
			RLV_INFOS << "Disabling RLV:" << LL_ENDL;
		#endif // RLV_DEBUG

		gRlvHandler.clearState();

		#ifdef RLV_DEBUG
			RLV_INFOS << "\t--> RLV disabled" << LL_ENDL;
		#endif // RLV_DEBUG

		m_fEnabled = FALSE;
	}

	#ifdef RLV_ADVANCED_MENU
		// RELEASE-RLVa: LL defines CLIENT_MENU_NAME but we can't get to it from here so we need to keep those two in sync manually
		LLMenuGL* pClientMenu = NULL;
		if ( (gMenuBarView) && ((pClientMenu = gMenuBarView->getChildMenuByName("Advanced", FALSE)) != NULL) )
		{
			pClientMenu->setItemVisible("RLVa", m_fEnabled);
			pClientMenu->setItemEnabled("RLVa", m_fEnabled);
		}
	#endif // RLV_ADVANCED_MENU

	return m_fEnabled;		// Return enabled/disabled state
}

BOOL RlvHandler::canDisable()
{
	return FALSE;
}

void RlvHandler::clearState()
{
	// TODO-RLVa: should restore all RLV controlled debug variables to their defaults

	// Issue @clear on behalf of every object that has a currently active RLV restriction (even if it's just an exception)
	LLUUID idObj; LLViewerObject* pObj; bool fDetachable;
	while (m_Objects.size())
	{
		idObj = m_Objects.begin()->first; // Need a copy since after @clear the data it points to will no longer exist
		fDetachable = ((pObj = gObjectList.findObject(idObj)) != NULL) ? isLockedAttachment(pObj, RLV_LOCK_REMOVE) : true;

		processCommand(idObj, "clear", false);
		if (!fDetachable)
			processCommand(idObj, "detachme=force", false);
	}

	// Sanity check - these should all be empty after we issue @clear on the last object
	if ( (!m_Objects.empty()) || !(m_Exceptions.empty()) || (!m_AttachAdd.empty()) || (!m_AttachRem.empty()) )
	{
		RLV_ERRS << "Object, exception or attachment map not empty after clearing state!" << LL_ENDL;
		m_Objects.clear();
		m_Exceptions.clear();
		m_AttachAdd.clear();
		m_AttachRem.clear();
	}

	// These all need manual clearing
	memset(m_LayersAdd, 0, sizeof(S16) * WT_COUNT);
	memset(m_LayersRem, 0, sizeof(S16) * WT_COUNT);
	memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT);
	m_Retained.clear();
	m_Emitter.clearObservers(); // <- calls delete on all active observers

	// Clear dynamically allocated memory
	delete m_pGCTimer;
	m_pGCTimer = NULL;
	delete m_pWLSnapshot;
	m_pWLSnapshot = NULL;
	delete m_pAttachMgr;
	m_pAttachMgr = NULL;
}

// ============================================================================
// Command handlers (RLV_TYPE_ADD and RLV_TYPE_REMOVE)
//

// Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
ERlvCmdRet RlvHandler::onAddRemAttach(const LLUUID& idObj, const RlvCommand& rlvCmd, bool& fRefCount)
{
	// Sanity check - if there's an option it should specify a valid attachment point name
	S32 idxAttachPt = getAttachPointIndex(rlvCmd.getOption(), true);
	if ( (!idxAttachPt) && (!rlvCmd.getOption().empty())  )
		return RLV_RET_FAILED_OPTION;

	LLVOAvatar* pAvatar = gAgent.getAvatarObject();
	if (!pAvatar)
		return RLV_RET_FAILED;

	ERlvLockMask eLock = (RLV_BHVR_REMATTACH == rlvCmd.getBehaviourType()) ? RLV_LOCK_REMOVE : RLV_LOCK_ADD;
	for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin(); 
			itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
	{
		if ( (0 == idxAttachPt) || (itAttach->first == idxAttachPt) )
		{
			if (RLV_TYPE_ADD == rlvCmd.getParamType())
				addAttachmentLock(itAttach->first, idObj, eLock);
			else
				removeAttachmentLock(itAttach->first, idObj, eLock);
		}
	}

	// Refresh HUD visibility if needed
	if ( (RLV_BHVR_REMATTACH == rlvCmd.getBehaviourType()) && (hasLockedHUD()) )
		LLPipeline::sShowHUDAttachments = TRUE;

	fRefCount = rlvCmd.getOption().empty();	// Only reference count global locks
	return RLV_RET_NOERROR;
}

// Checked: 2009-10-10 (RLVa-1.0.5a) | Added: RLVa-1.0.5a
ERlvCmdRet RlvHandler::onAddRemDetach(const LLUUID& idObj, const RlvCommand& rlvCmd, bool& fRefCount)
{
	S32 idxAttachPt = 0;
	if (rlvCmd.getOption().empty())	// @detach=n|y
	{
		// The object may or may not exist (it may not have rezzed yet, or it may have already been killed):
		//   * @detach=n: - if it has rezzed then we'll already have its attachment point 
		//                - if it hasn't rezzed yet then it's a @detach=n from a non-attachment and RlvHandler::onAttach() takes care of it
		//   * @detach=y: - if it ever rezzed as an attachment we'll have cached its attach point 
		//                - if it never rezzed as an attachment there won't be a lock to remove
		rlv_object_map_t::const_iterator itObj = m_Objects.find(idObj);
		if (itObj != m_Objects.end())
			idxAttachPt = itObj->second.m_idxAttachPt;
	}
	else							// @detach:<attachpt>=n|y
	{
		idxAttachPt = getAttachPointIndex(rlvCmd.getOption(), true);
	}

	// The attach point can be zero for @detach=n|y (i.e. non-attachment) but should always be non-zero for @detach:<attachpt>=n|y
	if (0 == idxAttachPt)
		return (rlvCmd.getOption().empty()) ? RLV_RET_NOERROR : RLV_RET_FAILED_OPTION;

	// Actually lock the attachment point (@detach=n locks remove only; @detach:<attachpt>=n locks both remove and add)
	ERlvLockMask eLock = (rlvCmd.getOption().empty()) ? RLV_LOCK_REMOVE : (ERlvLockMask)(RLV_LOCK_ADD | RLV_LOCK_REMOVE);
	if (RLV_TYPE_ADD == rlvCmd.getParamType())
		addAttachmentLock(idxAttachPt, idObj, eLock);
	else
		removeAttachmentLock(idxAttachPt, idObj, eLock);

	// Refresh HUD visibility if needed
	if ( (RLV_TYPE_ADD == rlvCmd.getParamType()) && (hasLockedHUD()) )
		LLPipeline::sShowHUDAttachments = TRUE;

	fRefCount = false;	// Don't reference count @detach[:<option>]=n
	return RLV_RET_NOERROR;
}

// ============================================================================
// Command handlers (RLV_TYPE_FORCE)
//

// Checked: 2009-10-12 (RLVa-1.0.5b) | Modified: RLVa-1.0.5b
ERlvCmdRet RlvHandler::onForceDetach(const LLUUID& idObj, const RlvCommand& rlvCmd) const
{
	// TODO-RLVA: this still needs a rewrite to conform to the new event handler system
	if ( (rlvCmd.getOption().empty()) || (getAttachPointIndex(rlvCmd.getOption(), true)) )
	{
		onForceRemAttach(idObj, rlvCmd);
	}
	else
	{
		// Force detach single folder
		onForceWear(rlvCmd.getOption(), false, false);
	}

	return RLV_RET_NOERROR;
}

// Checked: 2009-10-12 (RLVa-1.0.5b) | Added: RLVa-1.0.5b
ERlvCmdRet RlvHandler::onForceRemAttach(const LLUUID& idObj, const RlvCommand& rlvCmd) const
{
	S32 idxAttachPt = 0;
	if (rlvCmd.getOption().empty())
	{
		// Simulate right-click / Take Off > Detach All
		LLAgent::userRemoveAllAttachments(NULL);
		return RLV_RET_NOERROR;
	}
	else if ((idxAttachPt = getAttachPointIndex(rlvCmd.getOption(), true)) != 0)
	{
		// Simulate right-click / Take Off > Detach > ...
		LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachmentPt;
		if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) &&	// Make sure we're actually wearing something on the attachment point
			 ((pAttachmentPt = get_if_there(pAvatar->mAttachmentPoints, (S32)idxAttachPt, (LLViewerJointAttachment*)NULL)) != NULL) &&
			 (isStrippable(pAttachmentPt->getItemID())) )		// ... and that it's not marked as "nostrip"
		{
			#ifdef RLV_EXPERIMENTAL_COMPOSITES
				// If we're stripping something that's part of a composite folder then we should @detachthis instead
				if (isCompositeDescendent(pAttachmentPt->getItemID()))
				{
					std::string strCmd = "detachthis:" + strOption + "=force";
					#ifdef RLV_DEBUG
						RLV_INFOS << "\t- '" << strOption << "' belongs to composite folder: @" << strCmd << LL_ENDL;
					#endif // RLV_DEBUG
					processForceCommand(idObj, RlvCommand(strCmd));
				}
				else
			#endif // RLV_EXPERIMENTAL_COMPOSITES
				{
					handle_detach_from_avatar(pAttachmentPt);
				}
		}
		return RLV_RET_NOERROR;
	}
	return RLV_RET_FAILED_OPTION;
}

// ============================================================================