/** * * Copyright (c) 2009-2010, Kitty Barnett * * The source code in this file is provided to you under the terms of the * GNU General Public License, version 2.0, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. Terms of the GPL can be found in doc/GPL-license.txt * in this distribution, or online at http://www.gnu.org/licenses/gpl-2.0.txt * * By copying, modifying or distributing this software, you acknowledge that * you have read and understood your obligations described above, and agree to * abide by those obligations. * */ #include "llviewerprecompiledheaders.h" #include "llcallbacklist.h" #include "lldrawpoolalpha.h" #include "llfloaterbeacons.h" #include "llfloaterchat.h" #include "llfloaterdaycycle.h" #include "llfloaterenvsettings.h" #include "llfloaterland.h" #include "llfloatermap.h" #include "llfloaterregioninfo.h" #include "llfloatertools.h" #include "llfloaterwater.h" #include "llfloaterwindlight.h" #include "llfloaterworldmap.h" #include "llinventoryview.h" #include "llstartup.h" #include "llviewermenu.h" #include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" #include "llviewerwindow.h" #include "llworld.h" #include "pipeline.h" #include "rlvhandler.h" #include "rlvextensions.h" // Only defined in llinventorybridge.cpp #if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11 void confirm_replace_attachment_rez(S32 option, void* user_data); #endif // Only defined in llinventorymodel.cpp extern const char* NEW_CATEGORY_NAME; // ============================================================================ // Static variable initialization // BOOL RlvHandler::m_fEnabled = FALSE; bool RlvHandler::m_fFetchStarted = false; bool RlvHandler::m_fFetchComplete = false; RlvMultiStringSearch RlvHandler::m_AttachLookup; const std::string RlvHandler::cstrSharedRoot = RLV_ROOT_FOLDER; rlv_handler_t gRlvHandler; // ============================================================================ // Attachment group helper functions // // Has to match the order of ERlvAttachGroupType const std::string cstrAttachGroups[RLV_ATTACHGROUP_COUNT] = { "head", "torso", "arms", "legs", "hud" }; // Checked: 2009-10-19 (RLVa-1.1.0e) | Added: RLVa-1.1.0e inline ERlvAttachGroupType rlvGetAttachGroupTypeFromIndex(S32 idxGroup) { switch (idxGroup) { case 0: // Right Hand case 1: // Right Arm case 3: // Left Arm case 4: // Left Hand return RLV_ATTACHGROUP_ARMS; case 2: // Head return RLV_ATTACHGROUP_HEAD; case 5: // Left Leg case 7: // Right Leg return RLV_ATTACHGROUP_LEGS; case 6: // Torso return RLV_ATTACHGROUP_TORSO; case 8: // HUD return RLV_ATTACHGROUP_HUD; default: return RLV_ATTACHGROUP_INVALID; } } // Checked: 2009-10-19 (RLVa-1.1.0e) | Added: RLVa-1.1.0e inline ERlvAttachGroupType rlvGetAttachGroupTypeFromString(const std::string& strGroup) { for (int idx = 0; idx < RLV_ATTACHGROUP_COUNT; idx++) if (cstrAttachGroups[idx] == strGroup) return (ERlvAttachGroupType)idx; return RLV_ATTACHGROUP_INVALID; } // ============================================================================ // 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(";", "", 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_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-29 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b S32 RlvHandler::getAttachPointIndex(const LLInventoryCategory* pFolder, bool /*fStrict*/) const { if (!pFolder) return 0; // RLVa-1.0.1 added support for legacy matching (See http://rlva.catznip.com/blog/2009/07/attachment-point-naming-convention/) if (RlvSettings::getEnableLegacyNaming()) return getAttachPointIndexLegacy(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)) ) ? getAttachPointIndex(strAttachPt, true) : 0; } // Checked: 2009-07-29 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b S32 RlvHandler::getAttachPointIndex(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 0; // 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 S32 idxAttachPt = 0; if (pItem->getPermissions().allowModifyBy(gAgent.getID())) { idxAttachPt = (!strAttachPt.empty()) ? getAttachPointIndex(strAttachPt, true) : 0; if (!idxAttachPt) idxAttachPt = getAttachPointIndex(gInventory.getCategory(pItem->getParentUUID()), fStrict); } else { idxAttachPt = getAttachPointIndex(gInventory.getCategory(pItem->getParentUUID()), fStrict); if ( (!idxAttachPt) && (!strAttachPt.empty()) ) idxAttachPt = getAttachPointIndex(strAttachPt, true); } return idxAttachPt; } // 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 S32 RlvHandler::getAttachPointIndexLegacy(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 0; // 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 getAttachPointIndex(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-13 (RLVa-1.0.5b) | Added: RLVa-1.0.5b bool RlvHandler::isLockedAttachmentExcept(S32 idxAttachPt, ERlvLockMask eLock, LLViewerObject *pExceptObj) const { if (!pExceptObj) return isLockedAttachment(idxAttachPt, eLock); // 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() != pExceptObj->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() != pExceptObj->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 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(idxAttachPt, idRlvObj)); } if (eLock & RLV_LOCK_ADD) { m_AttachAdd.insert(std::pair(idxAttachPt, idRlvObj)); } } // Checked: 2010-07-18 (RLVa-1.1.2b) | Added: RLVa-1.1.2a void RlvHandler::dumpAttachmentLocks(void*) { LLVOAvatar* pAvatar = gAgent.getAvatarObject(); if (!pAvatar) { RLV_INFOS << "No avatar object to dump attachments for" << RLV_ENDL; return; } RLV_INFOS << "Dumping 'remove' locks:" << RLV_ENDL; for (rlv_attachlock_map_t::iterator itAttachPt = gRlvHandler.m_AttachRem.begin(); itAttachPt != gRlvHandler.m_AttachRem.end(); ++itAttachPt) { // Grab the attachment on the attachment point that's locked (if there is one) /*const*/ LLViewerJointAttachment* pAttachPt = get_if_there(pAvatar->mAttachmentPoints, (S32)itAttachPt->first, (LLViewerJointAttachment*)NULL); /*const*/ LLViewerObject* pAttachObj = (pAttachPt) ? pAttachPt->getObject() : NULL; const LLViewerInventoryItem* pAttachItem = (pAttachPt) ? gInventory.getItem(pAttachPt->getItemID()) : NULL; // Grab the locking attachment (if we can) /*const*/ LLViewerObject* pRlvObj = gObjectList.findObject(itAttachPt->second); /*const*/ LLViewerJointAttachment* pRlvAttachPt = (pRlvObj) ? pAvatar->getTargetAttachmentPoint(pRlvObj) : NULL; const LLViewerInventoryItem* pRlvItem = (pRlvAttachPt) ? gInventory.getItem(pRlvAttachPt->getItemID()) : NULL; std::string strMsg = llformat("'%s' on %s held by '%s' on %s", ((pAttachItem) ? pAttachItem->getName().c_str() : ((pAttachObj) ? pAttachObj->getID().asString().c_str() : "(empty)")), (pAttachPt) ? pAttachPt->getName().c_str() : "(unknown)", ((pRlvItem) ? pRlvItem->getName().c_str() : ((pRlvObj) ? pRlvObj->getID().asString().c_str() : "(empty)")), (pRlvAttachPt) ? pRlvAttachPt->getName().c_str() : "(unknown)"); RLV_INFOS << strMsg << RLV_ENDL; } } // 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-12-25 (RLVa-1.1.0k) | Modified: RLVa-1.1.0k 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()); while ( (pFolder) && gAgent.getInventoryRootID() != (pFolder->getUUID()) ) { if (std::string::npos != pFolder->getName().find(RLV_FOLDER_FLAG_NOSTRIP)) return false; // If the item's parent is a folded folder then we need to check its parent as well pFolder = (isFoldedFolder(pFolder, false, true)) ? gInventory.getCategory(pFolder->getParentUUID()) : NULL; } } } 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 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::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-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::addBehaviourObserver(RlvBehaviourObserver* pBhvrObserver) { if ( (pBhvrObserver) && (std::find(m_BhvrObservers.begin(), m_BhvrObservers.end(), pBhvrObserver) == m_BhvrObservers.end()) ) m_BhvrObservers.push_back(pBhvrObserver); } // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::addCommandHandler(RlvCommandHandler* pCmdHandler) { if ( (pCmdHandler) && (std::find(m_CommandHandlers.begin(), m_CommandHandlers.end(), pCmdHandler) == m_CommandHandlers.end()) ) m_CommandHandlers.push_back(pCmdHandler); } // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::removeBehaviourObserver(RlvBehaviourObserver* pBhvrObserver) { if (pBhvrObserver) m_BhvrObservers.remove(pBhvrObserver); } // Checked: 2009-11-27 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::removeCommandHandler(RlvCommandHandler* pCmdHandler) { if (pCmdHandler) m_CommandHandlers.remove(pCmdHandler); } // Checked: 2009-10-26 (RLVa-1.1.0a) | Modified: RLVa-1.1.0a void RlvHandler::clearCommandHandlers() { std::list::const_iterator itHandler = m_CommandHandlers.begin(); while (itHandler != m_CommandHandlers.end()) { delete *itHandler; ++itHandler; } m_CommandHandlers.clear(); } // Checked: 2009-06-03 (RLVa-0.2.0h) void RlvHandler::notifyBehaviourObservers(const RlvCommand& rlvCmd, bool fInternal) { for (std::list::const_iterator itBhvrObserver = m_BhvrObservers.begin(); itBhvrObserver != m_BhvrObservers.end(); ++itBhvrObserver) { (*itBhvrObserver)->changed(rlvCmd, fInternal); } } // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f bool RlvHandler::notifyCommandHandlers(rlvCommandHandler f, const LLUUID& idObj, const RlvCommand& rlvCmd, ERlvCmdRet& eRet, bool fNotifyAll) const { std::list::const_iterator itHandler = m_CommandHandlers.begin(); bool fContinue = true; eRet = RLV_RET_UNKNOWN; while ( (itHandler != m_CommandHandlers.end()) && ((fContinue) || (fNotifyAll)) ) { ERlvCmdRet eCmdRet = RLV_RET_UNKNOWN; if ((fContinue = !((*itHandler)->*f)(idObj, rlvCmd, eCmdRet)) == false) eRet = eCmdRet; ++itHandler; } RLV_ASSERT( (fContinue) || (eRet != RLV_RET_UNKNOWN) ); return !fContinue; } // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f ERlvCmdRet RlvHandler::processCommand(const LLUUID& idObj, const RlvCommand& rlvCmd, bool fFromObj) { #ifdef RLV_DEBUG RLV_INFOS << "[" << idObj << "]: " << rlvCmd.asString() << RLV_ENDL; #endif // RLV_DEBUG if (!rlvCmd.isValid()) { #ifdef RLV_DEBUG RLV_INFOS << "\t-> invalid syntax" << RLV_ENDL; #endif // RLV_DEBUG return RLV_RET_FAILED_SYNTAX; } // Using a stack for executing commands solves a few problems: // - if we passed RlvObject::m_UUID for idObj somewhere and process a @clear then idObj points to invalid/cleared memory at the end // - if command X triggers command Y along the way then getCurrentCommand()/getCurrentObject() still return Y even when finished m_CurCommandStack.push(&rlvCmd); m_CurObjectStack.push(idObj); const LLUUID& idCurObj = m_CurObjectStack.top(); ERlvCmdRet eRet = RLV_RET_UNKNOWN; switch (rlvCmd.getParamType()) { case RLV_TYPE_ADD: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f { 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" << RLV_ENDL; #endif // RLV_DEBUG eRet = RLV_RET_FAILED_LOCK; break; } rlv_object_map_t::iterator itObj = m_Objects.find(idCurObj); bool fAdded = false; if (itObj != m_Objects.end()) { RlvObject& rlvObj = itObj->second; fAdded = rlvObj.addCommand(rlvCmd); } else { RlvObject rlvObj(idCurObj); fAdded = rlvObj.addCommand(rlvCmd); m_Objects.insert(std::pair(idCurObj, rlvObj)); } #ifdef RLV_DEBUG RLV_INFOS << "\t- " << ( (fAdded) ? "adding behaviour" : "skipping duplicate" ) << RLV_ENDL; #endif // RLV_DEBUG if (fAdded) { // If FALSE then this was a duplicate, there's no need to handle those if (!m_pGCTimer) m_pGCTimer = new RlvGCTimer(); eRet = processAddRemCommand(idCurObj, rlvCmd); notifyBehaviourObservers(rlvCmd, !fFromObj); } else { eRet = RLV_RET_SUCCESS_DUPLICATE; } } break; case RLV_TYPE_REMOVE: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f { rlv_object_map_t::iterator itObj = m_Objects.find(idCurObj); bool fRemoved = false; if (itObj != m_Objects.end()) fRemoved = itObj->second.removeCommand(rlvCmd); #ifdef RLV_DEBUG RLV_INFOS << "\t- " << ( (fRemoved) ? "removing behaviour" : "skipping remove (unset behaviour or unknown object)") << RLV_ENDL; #endif // RLV_DEBUG if (fRemoved) { // Don't handle non-sensical removes eRet = processAddRemCommand(idCurObj, rlvCmd); notifyBehaviourObservers(rlvCmd, !fFromObj); if (0 == itObj->second.m_Commands.size()) { #ifdef RLV_DEBUG RLV_INFOS << "\t- command list empty => removing " << idCurObj << RLV_ENDL; #endif // RLV_DEBUG m_Objects.erase(itObj); } } else { eRet = RLV_RET_SUCCESS_UNSET; } } break; case RLV_TYPE_CLEAR: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processClearCommand(idCurObj, rlvCmd); notifyBehaviourObservers(rlvCmd, !fFromObj); break; case RLV_TYPE_FORCE: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processForceCommand(idCurObj, rlvCmd); break; case RLV_TYPE_REPLY: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processReplyCommand(idCurObj, rlvCmd); break; case RLV_TYPE_UNKNOWN: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f default: eRet = RLV_RET_FAILED_PARAM; break; } RLV_ASSERT(RLV_RET_UNKNOWN != eRet); #ifdef RLV_DEBUG RLV_INFOS << "\t--> command " << ((eRet & RLV_RET_SUCCESS) ? "succeeded" : "failed") << RLV_ENDL; #endif // RLV_DEBUG m_CurCommandStack.pop(); m_CurObjectStack.pop(); return eRet; } // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f void RlvHandler::processRetainedCommands(ERlvBehaviour eBhvrFilter /*=RLV_BHVR_UNKNOWN*/, ERlvParamType eTypeFilter /*=RLV_TYPE_UNKNOWN*/) { rlv_retained_list_t::iterator itCmd = m_Retained.begin(), itCurCmd; while (itCmd != m_Retained.end()) { itCurCmd = itCmd++; // Point the loop iterator ahead const RlvRetainedCommand& cmd = *itCurCmd; if ( ((RLV_BHVR_UNKNOWN == eBhvrFilter) || (cmd.rlvCmd.getBehaviourType() == eBhvrFilter)) && ((RLV_TYPE_UNKNOWN == eTypeFilter) || (cmd.rlvCmd.getParamType() == eTypeFilter)) ) { processCommand(cmd.idObject, cmd.rlvCmd, true); m_Retained.erase(itCurCmd); } } } ERlvCmdRet 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 ERlvCmdRet eRet = RLV_RET_SUCCESS; notifyCommandHandlers(&RlvCommandHandler::onClearCommand, idObj, rlvCmd, eRet, true); return RLV_RET_SUCCESS; // Don't fail clear commands even if the object didn't exist since it confuses people } // ============================================================================ // 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: 2010-07-18 (RLVa-1.1.2b) | Modified: RLVa-1.1.2a 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()) { // Only if we haven't been able to find this object (= attachment that rezzed in) or if it's a rezzed prim attached from in-world if ( (!itObj->second.m_fLookup) || (!itObj->second.m_idxAttachPt) ) { // Reset any lookup information we might have for this object itObj->second.m_idxAttachPt = idxAttachPt; 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 it isn't already (we need it for a potential 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 as needed LLViewerInventoryCategory* pRlvRoot = getSharedRoot(); if ( (!RlvSettings::getEnableSharedWear()) && (RlvSettings::getSharedInvAutoRename()) && (STATE_STARTED == LLStartUp::getStartupState()) && (pRlvRoot) && (pItem) && (pItem->isComplete()) && (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) ) { // TODO: find a not too convoluted way to rename the attachment in case it specifies a name different than the current attach point S32 idxAttachPtItem = getAttachPointIndex(pItem, true); if ( (idxAttachPt != idxAttachPtItem) && (!idxAttachPtItem) ) { std::string strAttachPt = pAttachPt->getName(); LLStringUtil::toLower(strAttachPt); // If we can modify the item then we rename it directly, otherwise we create a new folder and move it if (pItem->getPermissions().allowModifyBy(gAgent.getID())) { 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 { // Don't do anything if the item is a direct descendant of the shared root, or a folded folder LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID()); if ( (pFolder) && (pFolder->getUUID() != pRlvRoot->getUUID()) && (!isFoldedFolder(pFolder, true, false)) ) { std::string strFolderName = ".(" + strAttachPt + ")"; // Rename the item's parent folder if it's called "New Folder", isn't directly under #RLV and contains exactly 1 object if ( (NEW_CATEGORY_NAME == pFolder->getName()) && (pFolder->getParentUUID() != pRlvRoot->getUUID()) && (1 == rlvGetDirectDescendentsCount(pFolder, LLAssetType::AT_OBJECT)) ) { pFolder->rename(strFolderName); pFolder->updateServer(FALSE); gInventory.updateCategory(pFolder); //gInventory.notifyObservers(); <- done further down in LLVOAvatar::attachObject() } else { // "No modify" item with a non-renameable parent: create a new folder named and move the item into it LLUUID idAttachFolder = gInventory.createNewCategory(pFolder->getUUID(), LLAssetType::AT_NONE, strFolderName); move_inventory_item(gAgent.getID(), gAgent.getSessionID(), pItem->getUUID(), idAttachFolder, std::string(), NULL); //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 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::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 } // Checked: 2009-11-26 (RLVa-1.1.0f) | Added: RLVa-1.1.0f void RlvHandler::onIdleStartup(void* pParam) { LLTimer* pTimer = (LLTimer*)pParam; if (LLStartUp::getStartupState() < STATE_STARTED) { // We don't want to run this *too* often if ( (LLStartUp::getStartupState() >= STATE_MISC) && (pTimer->getElapsedTimeF32() >= 2.0) ) { gRlvHandler.processRetainedCommands(RLV_BHVR_VERSION, RLV_TYPE_REPLY); gRlvHandler.processRetainedCommands(RLV_BHVR_VERSIONNUM, RLV_TYPE_REPLY); pTimer->reset(); } } else { // Clean-up gIdleCallbacks.deleteFunction(onIdleStartup, pParam); delete pTimer; } } // ============================================================================ // 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(); const std::string& strHiddenRegion = RlvStrings::getString(RLV_STRING_HIDDEN_REGION); for (LLWorld::region_list_t::const_iterator itRegion = regions.begin(); itRegion != regions.end(); ++itRegion) rlvStringReplace(strUTF8Text, (*itRegion)->getName(), strHiddenRegion); // Filter any mention of the parcel name LLViewerParcelMgr* pParcelMgr = LLViewerParcelMgr::getInstance(); if (pParcelMgr) rlvStringReplace(strUTF8Text, pParcelMgr->getAgentParcelName(), RlvStrings::getString(RLV_STRING_HIDDEN_PARCEL)); } 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()->mActiveRegionList.begin(); itRegion != LLWorld::getInstance()->mActiveRegionList.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 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, RlvStrings::getAnonym(strName)); } } #endif } // Checked: 2010-02-27 (RLVa-1.1.1a) | Modified: RLVa-1.2.0a bool RlvHandler::redirectChatOrEmote(const std::string& strUTF8Text) const { // Sanity check - @redirchat only for chat and @rediremote only for emotes ERlvBehaviour eBhvr = (!rlvIsEmote(strUTF8Text)) ? RLV_BHVR_REDIRCHAT : RLV_BHVR_REDIREMOTE; if (!hasBehaviour(eBhvr)) return false; if (RLV_BHVR_REDIRCHAT == eBhvr) { std::string strText = strUTF8Text; filterChat(strText, false); if (strText != "...") return false; // @sendchat wouldn't filter it so @redirchat won't redirect it either } for (rlv_exception_map_t::const_iterator itRedir = m_Exceptions.lower_bound(eBhvr), endRedir = m_Exceptions.upper_bound(eBhvr); itRedir != endRedir; ++itRedir) { S32 nChannel = boost::get(itRedir->second.varOption); if ( (!hasBehaviour(RLV_BHVR_SENDCHANNEL)) || (isException(RLV_BHVR_SENDCHANNEL, nChannel)) ) rlvSendChatReply(nChannel, strUTF8Text); } return true; } // ============================================================================ // Public service functions (called by the outside world or by extension handlers) // // Checked: 2009-11-24 (RLVa-1.1.0e) bool RlvHandler::isAgentNearby(const LLUUID& idAgent) const { #if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11 for (LLWorld::region_list_t::const_iterator itRegion = LLWorld::getInstance()->mActiveRegionList.begin(); itRegion != LLWorld::getInstance()->mActiveRegionList.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: 1.23.4 std::vector idAgents; LLWorld::getInstance()->getAvatars(&idAgents, NULL); for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++) { if (idAgents[idxAgent] == idAgent) 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" << RLV_ENDL; fetcher->fetchDescendents(fetchFolders); m_fFetchStarted = true; 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("/", "", 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_COMPOSITEFOLDERS // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::getCompositeInfo(const LLInventoryCategory* pFolder, std::string* pstrName) const { if (pFolder) { // Composite folder naming: ^\.?[Folder] const std::string& cstrFolder = pFolder->getName(); std::string::size_type 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: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i 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 (we need its parent if it's a folded folder) LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID()); if (isFoldedFolder(pFolder, true, false)) // Don't check if the folder is a composite folder pFolder = gInventory.getCategory(pFolder->getParentUUID()); if ( (pFolder) && (getCompositeInfo(pFolder, pstrName)) ) { if (ppFolder) *ppFolder = pFolder; return true; } } return false; } #endif // RLV_EXPERIMENTAL_COMPOSITEFOLDERS #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_COMPOSITEFOLDERS // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::canTakeOffComposite(const LLInventoryCategory* pFolder) const { // Sanity check - if there's no folder or no avatar then there is nothing to take off LLVOAvatar* pAvatar = gAgent.getAvatarObject(); if ( (!pFolder) || (!pAvatar) ) return false; // Sanity check - if nothing is locked then we can definitely take it off if ( (!hasBehaviour(RLV_BHVR_REMOUTFIT)) && (!hasLockedAttachment(RLV_LOCK_REMOVE)) ) return true; LLInventoryModel::cat_array_t folders; LLInventoryModel::item_array_t items; RlvWearableItemCollector functor(pFolder->getUUID(), true, false); gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor); for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++) { const LLViewerInventoryItem* pItem = items.get(idxItem); switch (pItem->getType()) { case LLAssetType::AT_BODYPART: case LLAssetType::AT_CLOTHING: { LLWearable* pWearable = gAgent.getWearableFromWearableItem(pItem->getUUID()); if ( (pWearable) && (!isRemovable(pWearable->getType())) ) return false; // If one wearable in the folder is non-removeable then the entire folder should be } break; case LLAssetType::AT_OBJECT: { LLViewerObject* pObj = pAvatar->getWornAttachment(pItem->getUUID()); if ( (pObj != NULL) && (isLockedAttachment(pObj, RLV_LOCK_REMOVE)) ) return false; // If one attachment in the folder is non-detachable then the entire folder should be } break; default: break; } } return true; } // Checked: 2009-12-18 (RLVa-1.1.0k) | Modified: RLVa-1.1.0i bool RlvHandler::canWearComposite(const LLInventoryCategory* pFolder) const { // Sanity check - if there's no folder or no avatar then there is nothing to wear LLVOAvatar* pAvatar = gAgent.getAvatarObject(); if ( (!pFolder) || (!pAvatar) ) return false; // Sanity check - if nothing is locked then we can definitely wear it if ( (!hasBehaviour(RLV_BHVR_ADDOUTFIT)) && (!hasBehaviour(RLV_BHVR_REMOUTFIT)) && (!hasLockedAttachment(RLV_LOCK_ANY)) ) return true; LLInventoryModel::cat_array_t folders; LLInventoryModel::item_array_t items; RlvWearableItemCollector functor(pFolder->getUUID(), true, false); gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor); for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++) { LLViewerInventoryItem* pItem = items.get(idxItem); if (RlvForceWear::isWearingItem(pItem)) continue; // Don't examine any items we're already wearing // A wearable layer or attachment point: // - can't be "add locked" // - can't be worn and "remove locked" // - can't be worn and have its item belong to a *different* composite folder that we can't take off switch (pItem->getType()) { case LLAssetType::AT_BODYPART: case LLAssetType::AT_CLOTHING: { // NOTE: without its asset we don't know what type the wearable is so we need to look at the item's flags instead EWearableType wtType = (EWearableType)(pItem->getFlags() & LLInventoryItem::II_FLAGS_WEARABLES_MASK); LLViewerInventoryCategory* pFolder; if ( (!isWearable(wtType)) || ( (gAgent.getWearable(wtType)) && (!isRemovable(wtType)) ) || ( (gRlvHandler.getCompositeInfo(gAgent.getWearableItem(wtType), NULL, &pFolder)) && (pFolder->getUUID() != pItem->getParentUUID()) && (!gRlvHandler.canTakeOffComposite(pFolder)) ) ) { return false; } } break; case LLAssetType::AT_OBJECT: { // If we made it here then *something* is add/remove locked so we absolutely need to know its attachment point LLViewerJointAttachment* pAttachPt = getAttachPoint(pItem, true); LLViewerInventoryCategory* pFolder; if ( (!pAttachPt) || (isLockedAttachment(pAttachPt, RLV_LOCK_ADD)) || ( (pAttachPt->getObject()) && (isLockedAttachment(pAttachPt, RLV_LOCK_REMOVE)) ) || ( (gRlvHandler.getCompositeInfo(pAttachPt->getItemID(), NULL, &pFolder)) && (pFolder->getUUID() != pItem->getParentUUID()) && (!gRlvHandler.canTakeOffComposite(pFolder)) ) ) { return false; } } break; default: break; } } return true; } #endif // RLV_EXPERIMENTAL_COMPOSITEFOLDERS // ============================================================================ // Event handlers // // (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 RlvForceWear::isWearingItem()] 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 (RlvForceWear::isWearingItem(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 // // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f BOOL RlvHandler::setEnabled(BOOL fEnable) { if (m_fEnabled == fEnable) return fEnable; if (fEnable) { // Initialize the command lookup table RlvCommand::initLookupTable(); // Initialize static classes RlvSettings::initClass(); RlvStrings::initClass(); gRlvHandler.m_pAttachMgr = new RlvAttachmentManager(); gRlvHandler.addCommandHandler(new RlvExtGetSet()); // Fetch shared inventory if we're enabled after logon if (LLStartUp::getStartupState() >= STATE_CLEANUP) fetchSharedInventory(); m_fEnabled = TRUE; } else if (canDisable()) { gRlvHandler.clearState(); } #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(); clearCommandHandlers(); // <- calls delete on all registered command handlers // 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) // #define VERIFY_OPTION(x) { if (!(x)) { eRet = RLV_RET_FAILED_OPTION; break; } } #define VERIFY_OPTION_REF(x) { if (!(x)) { eRet = RLV_RET_FAILED_OPTION; break; } fRefCount = true; } // Checked: 2009-12-05 (RLVa-1.1.0h) | Modified: RLVa-1.1.0h ERlvCmdRet RlvHandler::processAddRemCommand(const LLUUID& idObj, const RlvCommand& rlvCmd) { // NOTE: - at this point the command has already been: // * added to the RlvObject // * removed from the RlvObject (which still exists at this point even if this is the last restriction) // - the object's UUID may or may not exist in gObjectList (see handling of @detach=n|y) ERlvBehaviour eBhvr = rlvCmd.getBehaviourType(); ERlvParamType eType = rlvCmd.getParamType(); ERlvCmdRet eRet = RLV_RET_SUCCESS; bool fRefCount = false, fRefreshHover = false; const std::string& strOption = rlvCmd.getOption(); switch (eBhvr) { case RLV_BHVR_DETACH: // @detach[: