#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(";", "", 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 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: 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 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-06-03 (RLVa-0.2.0h) void RlvHandler::addBehaviourObserver(RlvBehaviourObserver* pBhvrObserver) { std::list::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::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::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(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[: