/** * * Copyright (c) 2009-2011, 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 "llagent.h" #include "llappearancemgr.h" #include "llappviewer.h" #include "llhudtext.h" #include "llstartup.h" #include "llviewermenu.h" #include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" #include "rlvhandler.h" #include "rlvinventory.h" #include "rlvlocks.h" #include "rlvui.h" #include "rlvextensions.h" #include // ============================================================================ // Static variable initialization // BOOL RlvHandler::m_fEnabled = FALSE; rlv_handler_t gRlvHandler; // ============================================================================ // 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)) || (!RlvUtil::isValidReplyChannel(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: 2010-04-07 (RLVa-1.2.0d) | Modified: RLVa-1.0.1d RlvHandler::RlvHandler() : m_fCanCancelTp(true), m_posSitSource(), m_pGCTimer(NULL) { gAgent.addListener(this, "new group"); // Array auto-initialization to 0 is non-standard? (Compiler warning in VC-8.0) memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT); } // Checked: 2010-04-07 (RLVa-1.2.0d) | Modified: RLVa-1.0.1d RlvHandler::~RlvHandler() { gAgent.removeListener(this); //delete m_pGCTimer; // <- deletes itself } // ============================================================================ // Behaviour related functions // bool RlvHandler::hasBehaviourExcept(ERlvBehaviour eBhvr, 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.getObjectID()) && (itObj->second.hasBehaviour(eBhvr, strOption, false)) ) return true; return false; } // Checked: 2011-04-11 (RLVa-1.3.0h) | Added: RLVa-1.3.0h bool RlvHandler::hasBehaviourRoot(const LLUUID& idObjRoot, ERlvBehaviour eBhvr, const std::string& strOption) const { for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) if ( (idObjRoot == itObj->second.getRootID()) && (itObj->second.hasBehaviour(eBhvr, strOption, false)) ) return true; return false; } // ============================================================================ // Behaviour exception handling // // Checked: 2009-10-04 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a void RlvHandler::addException(const LLUUID& idObj, ERlvBehaviour eBhvr, const RlvExceptionOption& varOption) { m_Exceptions.insert(std::pair(eBhvr, RlvException(idObj, eBhvr, varOption))); } // 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; uuid_vec_t 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) uuid_vec_t::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; } // Checked: 2009-10-04 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a bool RlvHandler::isPermissive(ERlvBehaviour eBhvr) const { return (RlvCommand::hasStrictVariant(eBhvr)) ? !((hasBehaviour(RLV_BHVR_PERMISSIVE)) || (isException(RLV_BHVR_PERMISSIVE, eBhvr, RLV_CHECK_PERMISSIVE))) : true; } // Checked: 2009-10-04 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a void RlvHandler::removeException(const LLUUID& idObj, ERlvBehaviour eBhvr, const RlvExceptionOption& varOption) { for (rlv_exception_map_t::iterator itException = m_Exceptions.lower_bound(eBhvr), endException = m_Exceptions.upper_bound(eBhvr); itException != endException; ++itException) { if ( (itException->second.idObject == idObj) && (itException->second.varOption == varOption) ) { m_Exceptions.erase(itException); break; } } } // ============================================================================ // Command processing functions // // Checked: 2010-04-07 (RLVa-1.2.0d) | 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: 2010-04-07 (RLVa-1.2.0d) | 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: 2010-04-07 (RLVa-1.2.0d) | Modified: RLVa-1.1.0f bool RlvHandler::notifyCommandHandlers(rlvCommandHandler f, 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)(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 RlvCommand& rlvCmd, bool fFromObj) { #ifdef RLV_DEBUG RLV_INFOS << "[" << rlvCmd.getObjectID() << "]: " << 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_idObj 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(rlvCmd.getObjectID()); 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(rlvCmd); m_Objects.find(idCurObj)->second.setCommandRet(rlvCmd, eRet); // HACK-RLVa: find a better way of doing this // 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(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(rlvCmd); break; case RLV_TYPE_FORCE: // Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processForceCommand(rlvCmd); break; case RLV_TYPE_REPLY: // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f eRet = processReplyCommand(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); m_OnCommand(rlvCmd, eRet, !fFromObj); #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 ERlvCmdRet RlvHandler::processCommand(const LLUUID& idObj, const std::string& strCommand, bool fFromObj) { if (STATE_STARTED != LLStartUp::getStartupState()) { m_Retained.push_back(RlvCommand(idObj, strCommand)); return RLV_RET_RETAINED; } return processCommand(RlvCommand(idObj, strCommand), fFromObj); } // Checked: 2010-02-27 (RLVa-1.2.0a) | Modified: RLVa-1.1.0f void RlvHandler::processRetainedCommands(ERlvBehaviour eBhvrFilter /*=RLV_BHVR_UNKNOWN*/, ERlvParamType eTypeFilter /*=RLV_TYPE_UNKNOWN*/) { rlv_command_list_t::iterator itCmd = m_Retained.begin(), itCurCmd; while (itCmd != m_Retained.end()) { itCurCmd = itCmd++; // Point the loop iterator ahead const RlvCommand& rlvCmd = *itCurCmd; if ( ((RLV_BHVR_UNKNOWN == eBhvrFilter) || (rlvCmd.getBehaviourType() == eBhvrFilter)) && ((RLV_TYPE_UNKNOWN == eTypeFilter) || (rlvCmd.getParamType() == eTypeFilter)) ) { processCommand(rlvCmd, true); m_Retained.erase(itCurCmd); } } } ERlvCmdRet RlvHandler::processClearCommand(const RlvCommand& rlvCmd) { const std::string& strFilter = rlvCmd.getParam(); std::string strCmdRem; rlv_object_map_t::const_iterator itObj = m_Objects.find(rlvCmd.getObjectID()); 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(rlvCmd.getObjectID(), strCmdRem.append("=y"), false); } } } // Let our observers know about clear commands ERlvCmdRet eRet = RLV_RET_SUCCESS; notifyCommandHandlers(&RlvCommandHandler::onClearCommand, rlvCmd, eRet, true); return RLV_RET_SUCCESS; // Don't fail clear commands even if the object didn't exist since it confuses people } // ============================================================================ // Externally invoked event handlers // // Checked: 2011-05-22 (RLVa-1.4.1a) | Added: RLVa-1.3.1b bool RlvHandler::handleEvent(LLPointer event, const LLSD& sdUserdata) { // If the user managed to change their active group (= newly joined or created group) we need to reactivate the previous one if ( (hasBehaviour(RLV_BHVR_SETGROUP)) && ("new group" == event->desc()) && (m_idAgentGroup != gAgent.getGroupID()) ) { // [Copy/paste from LLGroupActions::activate()] LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_ActivateGroup); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_GroupID, m_idAgentGroup); gAgent.sendReliableMessage(); return true; } return false; } // Checked: 2010-08-29 (RLVa-1.2.1c) | Modified: RLVa-1.2.1c void RlvHandler::onSitOrStand(bool fSitting) { #ifdef RLV_EXTENSION_STARTLOCATION if (rlv_handler_t::isEnabled()) { RlvSettings::updateLoginLastLocation(); } #endif // RLV_EXTENSION_STARTLOCATION if ( (hasBehaviour(RLV_BHVR_STANDTP)) && (!fSitting) && (!m_posSitSource.isExactlyZero()) ) { // NOTE: we need to do this due to the way @standtp triggers a forced teleport: // - when standing we're called from LLVOAvatar::sitDown() which is called from LLVOAvatar::getOffObject() // -> at the time sitDown() is called the avatar's parent is still the linkset it was sitting on so "isRoot()" on the avatar will // return FALSE and we will crash in LLVOAvatar::getRenderPosition() when trying to teleport // -> postponing the teleport until the next idle tick will ensure that everything has all been properly cleaned up doOnIdleOneTime(boost::bind(RlvUtil::forceTp, m_posSitSource)); m_posSitSource.setZero(); } } // Checked: 2010-03-11 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a void RlvHandler::onAttach(const LLViewerObject* pAttachObj, const LLViewerJointAttachment* pAttachPt) { // Assertion - pAttachObj is never NULL always specifies the root RLV_ASSERT( (pAttachObj) && (pAttachObj == pAttachObj->getRootEdit()) ); // Sanity check - we need to be called *after* LLViewerJointAttachment::addObject() RLV_ASSERT( (pAttachPt) && (pAttachPt->isObjectAttached(pAttachObj)) ); if ( (!pAttachObj) || (!pAttachPt) || (!pAttachPt->isObjectAttached(pAttachObj)) ) return; // Check if we already have an RlvObject instance for this object or one of its child prims for (rlv_object_map_t::iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) { // 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) ) { const LLViewerObject* pObj = gObjectList.findObject(itObj->first); if ( (pObj) && (pObj->getRootEdit()->getID() == pAttachObj->getID()) ) { // Reset any lookup information we might have for this object itObj->second.m_fLookup = true; itObj->second.m_idxAttachPt = RlvAttachPtLookup::getAttachPointIndex(pAttachObj); itObj->second.m_idRoot = pAttachObj->getID(); // We need to check this object for an active "@detach=n" and actually lock it down now that it's been attached somewhere if (itObj->second.hasBehaviour(RLV_BHVR_DETACH, false)) gRlvAttachmentLocks.addAttachmentLock(pAttachObj->getID(), itObj->second.getObjectID()); } } } // Fetch the inventory item if it isn't already (we need it in case of a reattach-on-detach) and rename it if appropriate if ( (STATE_STARTED == LLStartUp::getStartupState()) && (gInventory.isInventoryUsable()) ) { RlvRenameOnWearObserver* pFetcher = new RlvRenameOnWearObserver(pAttachObj->getAttachmentItemID()); pFetcher->startFetch(); if (pFetcher->isFinished()) pFetcher->done(); else gInventory.addObserver(pFetcher); } } // Checked: 2010-03-11 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a void RlvHandler::onDetach(const LLViewerObject* pAttachObj, const LLViewerJointAttachment* pAttachPt) { // Assertion - pAttachObj is never NULL always specifies the root RLV_ASSERT( (pAttachObj) && (pAttachObj == pAttachObj->getRootEdit()) ); // Sanity check - we need to be called *before* LLViewerJointAttachment::removeObject() RLV_ASSERT( (pAttachPt) && (pAttachPt->isObjectAttached(pAttachObj)) ); if ( (!pAttachObj) || (!pAttachPt) || (!pAttachPt->isObjectAttached(pAttachObj)) ) return; // If the attachment is no longer attached then then the user "Drop"'ed this attachment somehow if (!pAttachObj->isAttachment()) { // Check if we have any RlvObject instances for this object (or any of its child prims) for (rlv_object_map_t::iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj) { if ( (itObj->second.m_fLookup) && (itObj->second.m_idRoot == pAttachObj->getID()) ) { // Clear the attachment point lookup since it's now an in-world prim itObj->second.m_idxAttachPt = false; // If this object has an active "@detach=n" then we need to release the attachment lock since it's no longer attached if (itObj->second.hasBehaviour(RLV_BHVR_DETACH, false)) gRlvAttachmentLocks.removeAttachmentLock(pAttachObj->getID(), itObj->second.getObjectID()); } } } else { // If it's still attached then we need to clean up any restrictions this object (or one of its child prims) may still have set rlv_object_map_t::iterator itObj = m_Objects.begin(), itCurObj; while (itObj != m_Objects.end()) { itCurObj = itObj++; // @clear will invalidate our iterator so point it ahead now #ifdef RLV_DEBUG bool itObj = true; RLV_ASSERT(itObj); // Little hack to push itObj out of scope and prevent it from being accidentally used below #endif // RLV_DEBUG // NOTE: ObjectKill seems to happen in reverse (child prims are killed before the root is) so we can't use gObjectList here if (itCurObj->second.m_idRoot == pAttachObj->getID()) { RLV_INFOS << "Clearing " << itCurObj->first.asString() << ":" << RLV_ENDL; processCommand(itCurObj->second.getObjectID(), "clear", true); RLV_INFOS << "\t-> done" << RLV_ENDL; } } } } // Checked: 2010-03-13 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a bool RlvHandler::onGC() { rlv_object_map_t::iterator itObj = m_Objects.begin(), itCurObj; while (itObj != m_Objects.end()) { itCurObj = itObj++; // @clear will invalidate our iterator so point it ahead now #ifdef RLV_DEBUG bool itObj = true; RLV_ASSERT(itObj); #endif // RLV_DEBUG // Temporary sanity check RLV_ASSERT(itCurObj->first == itCurObj->second.getObjectID()); const LLViewerObject* pObj = gObjectList.findObject(itCurObj->second.getObjectID()); if (!pObj) { // If the RlvObject once existed in gObjectList and now doesn't then expire it right away // If the RlvObject never existed in 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 ( (itCurObj->second.m_fLookup) || (++itCurObj->second.m_nLookupMisses > 20) ) { RLV_INFOS << "Garbage collecting " << itCurObj->first.asString() << ":" << RLV_ENDL; processCommand(itCurObj->first, "clear", true); RLV_INFOS << "\t-> done" << RLV_ENDL; } } else { // Assertion: if the GC encounters an RlvObject instance that hasn't existed in gObjectList up until now then // it has to be a rezzed prim (if it was an attachment then RlvHandler::onAttach() should have caught it) RLV_ASSERT( (itCurObj->second.m_fLookup) || (!pObj->isAttachment()) ); if (!itCurObj->second.m_fLookup) { RLV_INFOS << "Resolved missing object " << itCurObj->first.asString() << RLV_ENDL; itCurObj->second.m_fLookup = true; itCurObj->second.m_idxAttachPt = RlvAttachPtLookup::getAttachPointIndex(pObj); itCurObj->second.m_idRoot = pObj->getRootEdit()->getID(); // NOTE: the following code should NEVER run (see assertion above), but just to be double-triple safety sure // -> if it does run it likely means that there's a @detach=n in a *child* prim that we couldn't look up in onAttach() // -> since RLV doesn't currently support @detach=n from child prims it's actually not such a big deal right now but still if ( (pObj->isAttachment()) && (itCurObj->second.hasBehaviour(RLV_BHVR_DETACH, false)) ) gRlvAttachmentLocks.addAttachmentLock(pObj->getID(), itCurObj->second.getObjectID()); } } } RLV_ASSERT(gRlvAttachmentLocks.verifyAttachmentLocks()); // Verify that we haven't leaked any attachment locks somehow 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_VERSIONNEW, RLV_TYPE_REPLY); gRlvHandler.processRetainedCommands(RLV_BHVR_VERSIONNUM, RLV_TYPE_REPLY); pTimer->reset(); } } else { // Clean-up gIdleCallbacks.deleteFunction(onIdleStartup, pParam); delete pTimer; } } // Checked: 2010-03-09 (RLVa-1.2.0a) | Added: RLVa-1.2.0a void RlvHandler::onLoginComplete() { RlvInventory::instance().fetchWornItems(); RlvInventory::instance().fetchSharedInventory(); #ifdef RLV_EXTENSION_STARTLOCATION RlvSettings::updateLoginLastLocation(); #endif // RLV_EXTENSION_STARTLOCATION LLViewerParcelMgr::getInstance()->setTeleportFailedCallback(boost::bind(&RlvHandler::onTeleportFailed, this)); LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&RlvHandler::onTeleportFinished, this, _1)); processRetainedCommands(); } // Checked: 2010-04-05 (RLVa-1.2.0d) | Added: RLVa-1.2.0d void RlvHandler::onTeleportFailed() { setCanCancelTp(true); } // Checked: 2010-04-05 (RLVa-1.2.0d) | Added: RLVa-1.2.0d void RlvHandler::onTeleportFinished(const LLVector3d& posArrival) { setCanCancelTp(true); } // ============================================================================ // String/chat censoring functions // // Checked: 2010-03-06 (RLVa-1.2.0c) | Added: RLVa-1.1.0j bool RlvHandler::canSit(LLViewerObject* pObj, const LLVector3& posOffset /*= LLVector3::zero*/) const { // The user can sit on the specified object if: // - not prevented from sitting // - not prevented from standing up or not currently sitting // - not standtp restricted or not currently sitting (if the user is sitting and tried to sit elsewhere the tp would just kick in) // - [regular sit] not @sittp=n or @fartouch=n restricted or if they clicked on a point within 1.5m of the avie's current position // - [force sit] not @sittp=n restricted by a *different* object than the one that issued the command or the object is within 1.5m return ( (pObj) && (LL_PCODE_VOLUME == pObj->getPCode()) ) && (!hasBehaviour(RLV_BHVR_SIT)) && ( ((!hasBehaviour(RLV_BHVR_UNSIT)) && (!hasBehaviour(RLV_BHVR_STANDTP))) || ((isAgentAvatarValid()) && (!gAgentAvatarp->isSitting())) ) && ( ((NULL == getCurrentCommand() || (RLV_BHVR_SIT != getCurrentCommand()->getBehaviourType())) ? ((!hasBehaviour(RLV_BHVR_SITTP)) && (!hasBehaviour(RLV_BHVR_FARTOUCH))) // [regular sit] : (!hasBehaviourExcept(RLV_BHVR_SITTP, getCurrentObject()))) || // [force sit] (dist_vec_squared(gAgent.getPositionGlobal(), pObj->getPositionGlobal() + LLVector3d(posOffset)) < 1.5f * 1.5f) ); } // Checked: 2010-03-07 (RLVa-1.2.0c) | Added: RLVa-1.2.0a bool RlvHandler::canStand() const { // NOTE: return FALSE only if we're @unsit=n restricted and the avie is currently sitting on something and TRUE for everything else return (!hasBehaviour(RLV_BHVR_UNSIT)) || ((isAgentAvatarValid()) && (!gAgentAvatarp->isSitting())); } // Checked: 2010-04-11 (RLVa-1.3.0h) | Modified: RLVa-1.3.0h bool RlvHandler::canTouch(const LLViewerObject* pObj, const LLVector3& posOffset /*=LLVector3::zero*/) const { const LLUUID& idRoot = (pObj) ? pObj->getRootEdit()->getID() : LLUUID::null; bool fCanTouch = (idRoot.notNull()) && ((pObj->isHUDAttachment()) || (!hasBehaviour(RLV_BHVR_TOUCHALL))) && ((!hasBehaviour(RLV_BHVR_TOUCHTHIS)) || (!isException(RLV_BHVR_TOUCHTHIS, idRoot, RLV_CHECK_PERMISSIVE))); if (fCanTouch) { if ( (!pObj->isAttachment()) || (!pObj->permYouOwner()) ) { // Rezzed prim or attachment worn by another avie fCanTouch = ( (!hasBehaviour(RLV_BHVR_TOUCHWORLD)) || (isException(RLV_BHVR_TOUCHWORLD, idRoot, RLV_CHECK_PERMISSIVE)) ) && ( (!pObj->isAttachment()) || (!hasBehaviour(RLV_BHVR_TOUCHATTACHOTHER)) ) && ( (!hasBehaviour(RLV_BHVR_FARTOUCH)) || (dist_vec_squared(gAgent.getPositionGlobal(), pObj->getPositionGlobal() + LLVector3d(posOffset)) <= 1.5f * 1.5f) ); } else if (!pObj->isHUDAttachment()) { // Regular attachment worn by this avie fCanTouch = ((!hasBehaviour(RLV_BHVR_TOUCHATTACH)) || (isException(RLV_BHVR_TOUCHATTACH, idRoot, RLV_CHECK_PERMISSIVE))) && ((!hasBehaviour(RLV_BHVR_TOUCHATTACHSELF)) || (isException(RLV_BHVR_TOUCHATTACH, idRoot, RLV_CHECK_PERMISSIVE))); } #ifdef RLV_EXTENSION_CMD_TOUCHXXX else { // HUD attachment fCanTouch = (!hasBehaviour(RLV_BHVR_TOUCHHUD)) || (isException(RLV_BHVR_TOUCHHUD, idRoot, RLV_CHECK_PERMISSIVE)); } #endif // RLV_EXTENSION_CMD_TOUCHXXX } if ( (!fCanTouch) && (hasBehaviour(RLV_BHVR_TOUCHME)) ) fCanTouch = hasBehaviourRoot(idRoot, RLV_BHVR_TOUCHME); return fCanTouch; } 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: 2010-03-26 (RLVa-1.2.0b) | Modified: RLVa-1.0.0f bool RlvHandler::filterChat(std::string& strUTF8Text, bool fFilterEmote) const { if (strUTF8Text.empty()) return false; bool fFilter = false; if (RlvUtil::isEmote(strUTF8Text)) // Check if it's an emote { if (fFilterEmote) // Emote filtering depends on fFilterEmote { if ( (strUTF8Text.find_first_of("\"()*=^_?~") != std::string::npos) || (strUTF8Text.find(" -") != std::string::npos) || (strUTF8Text.find("- ") != std::string::npos) || (strUTF8Text.find("''") != std::string::npos) ) { fFilter = true; // 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 '/' { fFilter = (utf8str_strlen(strUTF8Text) > 7);// Allow as long if it's 6 characters or less } else if ( (!RlvSettings::getCanOOC()) || (strUTF8Text.length() < 4) || (strUTF8Text.compare(0, 2, "((")) || (strUTF8Text.compare(strUTF8Text.length() - 2, 2, "))")) ) { fFilter = true; // Regular chat (not OOC) } if (fFilter) strUTF8Text = (gSavedSettings.getBOOL("RestrainedLoveShowEllipsis")) ? "..." : ""; return fFilter; } // Checked: 2010-11-29 (RLVa-1.3.0c) | Added: RLVa-1.3.0c bool RlvHandler::hasException(ERlvBehaviour eBhvr) const { return (m_Exceptions.find(eBhvr) != m_Exceptions.end()); } // Checked: 2010-02-27 (RLVa-1.2.0b) | 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 = (!RlvUtil::isEmote(strUTF8Text)) ? RLV_BHVR_REDIRCHAT : RLV_BHVR_REDIREMOTE; if ( (strUTF8Text.empty()) || (!hasBehaviour(eBhvr)) ) return false; if (RLV_BHVR_REDIRCHAT == eBhvr) { std::string strText = strUTF8Text; if (!filterChat(strText, false)) 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)) ) RlvUtil::sendChatReply(nChannel, strUTF8Text); } return true; } // ============================================================================ // 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; LLWearableType::EType 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 LLVOAvatarSelf* pAvatar = gAgent.getAvatarObject(); if ( (!pFolder) || (!pAvatar) ) return false; // Sanity check - if nothing is locked then we can definitely take it off if ( (!gRlvAttachmentLocks.hasLockedAttachmentPoint(RLV_LOCK_REMOVE)) || (!gRlvWearableLocks.hasLockedWearableType(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 ( (!gRlvAttachmentLocks.hasLockedAttachmentPoint(RLV_LOCK_ANY)) || (!gRlvWearableLocks.hacLockedWearableType(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 LLWearableType::EType wtType = (LLWearableType::EType)(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 // ============================================================================ // Initialization helper functions // // Checked: 2010-02-27 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a BOOL RlvHandler::setEnabled(BOOL fEnable) { // TODO-RLVa: [RLVa-1.2.1] Allow toggling at runtime if we haven't seen any llOwnerSay("@...."); if (m_fEnabled == fEnable) return fEnable; if (fEnable) { RLV_INFOS << "Enabling Restrained Love API support - " << RlvStrings::getVersion() << RLV_ENDL; m_fEnabled = TRUE; // Initialize the command lookup table RlvCommand::initLookupTable(); // Initialize static classes RlvSettings::initClass(); RlvStrings::initClass(); gRlvHandler.addCommandHandler(new RlvExtGetSet()); // Make sure we get notified when login is successful if (LLStartUp::getStartupState() < STATE_STARTED) LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&RlvHandler::onLoginComplete, &gRlvHandler)); else gRlvHandler.onLoginComplete(); // Set up RlvUIEnabler RlvUIEnabler::getInstance(); } // 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); } 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; */ } // ============================================================================ // 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: 2010-03-03 (RLVa-1.1.3b) | Modified: RLVa-1.2.0a ERlvCmdRet RlvHandler::processAddRemCommand(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; const std::string& strOption = rlvCmd.getOption(); switch (eBhvr) { case RLV_BHVR_DETACH: // @detach[: