aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/rlvhandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/newview/rlvhandler.cpp')
-rw-r--r--linden/indra/newview/rlvhandler.cpp2592
1 files changed, 2592 insertions, 0 deletions
diff --git a/linden/indra/newview/rlvhandler.cpp b/linden/indra/newview/rlvhandler.cpp
new file mode 100644
index 0000000..69e2e2f
--- /dev/null
+++ b/linden/indra/newview/rlvhandler.cpp
@@ -0,0 +1,2592 @@
1#include "llviewerprecompiledheaders.h"
2#include "llagent.h"
3#include "lldrawpoolalpha.h"
4#include "llfirstuse.h"
5#include "llfloaterbeacons.h"
6#include "llfloaterchat.h"
7#include "llfloaterdaycycle.h"
8#include "llfloaterenvsettings.h"
9#include "llfloatergodtools.h"
10#include "llfloaterland.h"
11#include "llfloatermap.h"
12#include "llfloaterregioninfo.h"
13#include "llfloatertools.h"
14#include "llfloaterwater.h"
15#include "llfloaterwindlight.h"
16#include "llfloaterworldmap.h"
17#include "llgesturemgr.h"
18#include "llinventoryview.h"
19#include "llstartup.h"
20#include "llviewermenu.h"
21#include "llviewermessage.h"
22#include "llviewerparcelmgr.h"
23#include "llviewerregion.h"
24#include "llviewerwindow.h"
25#include "llvoavatar.h"
26#include "llworld.h"
27#include "pipeline.h"
28
29#include "rlvhelper.h"
30#include "rlvevent.h"
31#include "rlvextensions.h"
32#include "rlvhandler.h"
33
34// Only defined in llinventorybridge.cpp
35#if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11
36 #include "llinventorybridge.h"
37 void confirm_replace_attachment_rez(S32 option, void* user_data);
38#endif
39// Only defined in llinventorymodel.cpp
40extern const char* NEW_CATEGORY_NAME;
41
42// ============================================================================
43// Static variable initialization
44//
45
46BOOL RlvHandler::m_fEnabled = FALSE;
47BOOL RlvHandler::fNoSetEnv = FALSE;
48BOOL RlvHandler::fLegacyNaming = FALSE;
49BOOL RlvHandler::m_fFetchStarted = FALSE;
50BOOL RlvHandler::m_fFetchComplete = FALSE;
51RlvMultiStringSearch RlvHandler::m_AttachLookup;
52
53const std::string RlvHandler::cstrSharedRoot = RLV_ROOT_FOLDER;
54
55// Keep these consistent with regular RLV
56const std::string RlvHandler::cstrBlockedRecvIM = "*** IM blocked by your viewer";
57const std::string RlvHandler::cstrBlockedSendIM = "*** IM blocked by sender's viewer";
58const std::string RlvHandler::cstrHidden = "(Hidden)";
59const std::string RlvHandler::cstrHiddenParcel = "(Hidden parcel)";
60const std::string RlvHandler::cstrHiddenRegion = "(Hidden region)";
61const std::string RlvHandler::cstrMsgRecvIM =
62 "The Resident you messaged is prevented from reading your instant messages at the moment, please try again later.";
63const std::string RlvHandler::cstrMsgTpLure =
64 "The Resident you invited is prevented from accepting teleport offers. Please try again later.";
65
66const std::string RlvHandler::cstrAnonyms[] =
67{
68 "A resident", "This resident", "That resident", "An individual", "This individual", "That individual", "A person",
69 "This person", "That person", "A stranger", "This stranger", "That stranger", "A human being", "This human being",
70 "That human being", "An agent", "This agent", "That agent", "A soul", "This soul", "That soul", "Somebody",
71 "Some people", "Someone", "Mysterious one", "An unknown being", "Unidentified one", "An unknown person"
72};
73
74rlv_handler_t gRlvHandler;
75
76// ============================================================================
77// Helper functions
78//
79
80// Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.0e
81inline bool rlvIsWearingItem(const LLInventoryItem* pItem)
82{
83 return
84 ((LLAssetType::AT_OBJECT == pItem->getType()) && (gAgent.getAvatarObject()->isWearingAttachment(pItem->getUUID()))) ||
85 ((LLAssetType::AT_GESTURE == pItem->getType()) && (gGestureManager.isGestureActive(pItem->getUUID()))) ||
86 (gAgent.isWearingItem(pItem->getUUID()));
87}
88
89// ============================================================================
90// Command specific helper functions
91//
92
93// Checked: 2009-08-04 (RLVa-1.0.1d) | Added: RLVa-1.0.1d
94static bool rlvParseNotifyOption(const std::string& strOption, S32& nChannel, std::string& strFilter)
95{
96 boost_tokenizer tokens(strOption, boost::char_separator<char>(";", "", boost::keep_empty_tokens));
97 boost_tokenizer::const_iterator itTok = tokens.begin();
98
99 // Extract and sanity check the first token (required) which is the channel
100 if ( (itTok == tokens.end()) || (!LLStringUtil::convertToS32(*itTok, nChannel)) || (!rlvIsValidReplyChannel(nChannel)) )
101 return false;
102
103 // Second token (optional) is the filter
104 strFilter.clear();
105 if (++itTok != tokens.end())
106 {
107 strFilter = *itTok;
108 ++itTok;
109 }
110
111 return (itTok == tokens.end());
112}
113
114// ============================================================================
115// Constructor/destructor
116//
117
118// Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
119RlvHandler::RlvHandler()
120 : m_fCanCancelTp(false), m_idCurObject(LLUUID::null), m_pCurCommand(NULL), m_pGCTimer(NULL), m_pWLSnapshot(NULL), m_pBhvrNotify(NULL)
121{
122 // Array auto-initialization to 0 is non-standard? (Compiler warning in VC-8.0)
123 memset(m_LayersAdd, 0, sizeof(S16) * WT_COUNT);
124 memset(m_LayersRem, 0, sizeof(S16) * WT_COUNT);
125 memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT);
126}
127
128// Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
129RlvHandler::~RlvHandler()
130{
131 //delete m_pGCTimer; // <- deletes itself
132 delete m_pWLSnapshot; // <- delete on NULL is harmless
133 delete m_pBhvrNotify;
134}
135
136// ============================================================================
137// Attachment related functions
138//
139
140// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
141inline LLViewerJointAttachment* RlvHandler::getAttachPoint(const std::string& strText, bool fExact) const
142{
143 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
144 return (pAvatar) ? get_if_there(pAvatar->mAttachmentPoints, getAttachPointIndex(strText, fExact), (LLViewerJointAttachment*)NULL)
145 : NULL;
146}
147
148// Checked: 2009-07-29 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b
149LLViewerJointAttachment* RlvHandler::getAttachPoint(const LLInventoryCategory* pFolder, bool /*fStrict*/) const
150{
151 if (!pFolder)
152 return NULL;
153
154 // RLVa-1.0.1 added support for legacy matching (See http://rlva.catznip.com/blog/2009/07/attachment-point-naming-convention/)
155 if (fLegacyNaming)
156 return getAttachPointLegacy(pFolder);
157
158 // Otherwise the only valid way to specify an attachment point in a folder name is: ^\.\(\s+attachpt\s+\)
159 std::string::size_type idxMatch;
160 std::string strAttachPt = rlvGetFirstParenthesisedText(pFolder->getName(), &idxMatch);
161 LLStringUtil::trim(strAttachPt);
162
163 return ( (1 == idxMatch) && (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName().at(0)) ) ? getAttachPoint(strAttachPt, true) : NULL;
164}
165
166// Checked: 2009-07-29 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b
167LLViewerJointAttachment* RlvHandler::getAttachPoint(const LLInventoryItem* pItem, bool fStrict) const
168{
169 // Sanity check - if it's not an object then it can't have an attachment point
170 if ( (!pItem) || (LLAssetType::AT_OBJECT != pItem->getType()) )
171 return NULL;
172
173 // The attachment point should be placed at the end of the item's name, surrounded by parenthesis
174 // (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)
175 std::string strAttachPt = rlvGetLastParenthesisedText(pItem->getName());
176 LLStringUtil::trim(strAttachPt);
177
178 // If the item is modify : we look at the item's name first and only then at the containing folder
179 // If the item is no modify: we look at the containing folder's name first and only then at the item itself
180 LLViewerJointAttachment* pAttachPt;
181 if (pItem->getPermissions().allowModifyBy(gAgent.getID()))
182 {
183 pAttachPt = (!strAttachPt.empty()) ? getAttachPoint(strAttachPt, true) : NULL;
184 if (!pAttachPt)
185 pAttachPt = getAttachPoint(gInventory.getCategory(pItem->getParentUUID()), fStrict);
186 }
187 else
188 {
189 pAttachPt = getAttachPoint(gInventory.getCategory(pItem->getParentUUID()), fStrict);
190 if ( (!pAttachPt) && (!strAttachPt.empty()) )
191 pAttachPt = getAttachPoint(strAttachPt, true);
192 }
193 return pAttachPt;
194}
195
196// Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.2a
197S32 RlvHandler::getAttachPointIndex(const LLViewerJointAttachment* pAttachPt) const
198{
199 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
200 if (pAvatar)
201 {
202 for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin();
203 itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
204 {
205 if (itAttach->second == pAttachPt)
206 return itAttach->first;
207 }
208 }
209 return 0;
210}
211
212// Checked: 2009-07-29 (RLVa-1.0.1b) | Added: RLVa-1.0.1b
213LLViewerJointAttachment* RlvHandler::getAttachPointLegacy(const LLInventoryCategory* pFolder) const
214{
215 // Hopefully some day this can just be deprecated (see http://rlva.catznip.com/blog/2009/07/attachment-point-naming-convention/)
216 if ( (!pFolder) || (pFolder->getName().empty()) )
217 return NULL;
218
219 // Check for a (...) block *somewhere* in the name
220 std::string::size_type idxMatch;
221 std::string strAttachPt = rlvGetFirstParenthesisedText(pFolder->getName(), &idxMatch);
222 if (!strAttachPt.empty())
223 {
224 // Could be "(attachpt)", ".(attachpt)" or "Folder name (attachpt)"
225 if ( (0 != idxMatch) && ((1 != idxMatch) || (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName().at(0)) ) && // No '(' or '.(' start
226 (idxMatch + strAttachPt.length() + 1 != pFolder->getName().length()) ) // or there's extra text
227 {
228 // 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)
229 strAttachPt = rlvGetLastParenthesisedText(pFolder->getName());
230 }
231 }
232 else
233 {
234 // There's no paranthesised block, but it could still be "attachpt" or ".attachpt" (just strip away the '.' from the last one)
235 strAttachPt = pFolder->getName();
236 if (RLV_FOLDER_PREFIX_HIDDEN == strAttachPt[0])
237 strAttachPt.erase(0, 1);
238 }
239 return getAttachPoint(strAttachPt, true);
240}
241
242bool RlvHandler::hasLockedHUD() const
243{
244 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
245 if (!pAvatar)
246 return false;
247
248 LLViewerJointAttachment* pAttachPt;
249 for (rlv_detach_map_t::const_iterator itAttachPt = m_Attachments.begin(); itAttachPt != m_Attachments.end(); ++itAttachPt)
250 {
251 pAttachPt = get_if_there(pAvatar->mAttachmentPoints, (S32)itAttachPt->first, (LLViewerJointAttachment*)NULL);
252 if ( (pAttachPt) && (pAttachPt->getIsHUDAttachment()) )
253 return true; // At least one of our locked attachments is a HUD
254 }
255 return false; // None of our locked attachments is a HUD
256}
257
258bool RlvHandler::isDetachable(const LLInventoryItem* pItem) const
259{
260 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
261 return ( (pItem) && (pAvatar) ) ? isDetachable(pAvatar->getWornAttachment(pItem->getUUID())) : true;
262}
263
264// Checked: 2009-08-11 (RLVa-1.0.1h) | Added: RLVa-1.0.1h
265bool RlvHandler::isDetachableExcept(S32 idxAttachPt, LLViewerObject *pObj) const
266{
267 // Loop over every object that marked the specific attachment point undetachable (but ignore pObj and any of its children)
268 for (rlv_detach_map_t::const_iterator itAttach = m_Attachments.lower_bound(idxAttachPt),
269 endAttach = m_Attachments.upper_bound(idxAttachPt); itAttach != endAttach; ++itAttach)
270 {
271 LLViewerObject* pTempObj = gObjectList.findObject(itAttach->second);
272 if ( (!pTempObj) || (pTempObj->getRootEdit()->getID() != pObj->getID()) )
273 return false;
274 }
275 return true;
276}
277
278// Checked: 2009-09-06 (RLVa-1.0.2b) | Modified: RLVa-1.0.2b
279bool RlvHandler::setDetachable(S32 idxAttachPt, const LLUUID& idRlvObj, bool fDetachable)
280{
281 // Sanity check - make sure it's an object we know about
282 rlv_object_map_t::const_iterator itObj = m_Objects.find(idRlvObj);
283 if ( (itObj == m_Objects.end()) || (!idxAttachPt) )
284 return false; // If (idxAttachPt) == 0 then: (pObj == NULL) || (pObj->isAttachment() == FALSE)
285
286 if (!fDetachable)
287 {
288 #ifdef RLV_EXPERIMENTAL_FIRSTUSE
289 //LLFirstUse::useRlvDetach();
290 #endif // RLV_EXPERIMENTAL_FIRSTUSE
291
292 // NOTE: m_Attachments can contain duplicate <idxAttachPt, idRlvObj> pairs (ie @detach:spine=n,detach=n from an attachment on spine)
293 m_Attachments.insert(std::pair<S32, LLUUID>(idxAttachPt, itObj->second.m_UUID));
294 return true;
295 }
296 else
297 {
298 for (rlv_detach_map_t::iterator itAttach = m_Attachments.lower_bound(idxAttachPt),
299 endAttach = m_Attachments.upper_bound(idxAttachPt); itAttach != endAttach; ++itAttach)
300 {
301 if (itObj->second.m_UUID == itAttach->second)
302 {
303 m_Attachments.erase(itAttach);
304 return true;
305 }
306 }
307 }
308 return false; // Fall-through for (fDetachable == TRUE) - if the object wasn't undetachable then we consider it a failure
309}
310
311
312
313#ifdef RLV_EXTENSION_FLAG_NOSTRIP
314 // Checked: 2009-05-26 (RLVa-0.2.0d) | Modified: RLVa-0.2.0d
315 bool RlvHandler::isStrippable(const LLUUID& idItem) const
316 {
317 // An item is exempt from @detach or @remoutfit if:
318 // - its name contains "nostrip" (anywhere in the name)
319 // - its parent folder contains "nostrip" (anywhere in the name)
320 if (idItem.notNull())
321 {
322 LLViewerInventoryItem* pItem = gInventory.getItem(idItem);
323 if (pItem)
324 {
325 if (std::string::npos != pItem->getName().find(RLV_FOLDER_FLAG_NOSTRIP))
326 return false;
327
328 LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
329 if ( (pFolder) && (std::string::npos != pFolder->getName().find(RLV_FOLDER_FLAG_NOSTRIP)) )
330 return false;
331 }
332 }
333 return true;
334 }
335#endif // RLV_EXTENSION_FLAG_NOSTRIP
336
337// ============================================================================
338// Behaviour related functions
339//
340
341bool RlvHandler::hasBehaviourExcept(ERlvBehaviour eBehaviour, const std::string& strOption, const LLUUID& idObj) const
342{
343 for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
344 if ( (idObj != itObj->second.m_UUID) && (itObj->second.hasBehaviour(eBehaviour, strOption, false)) )
345 return true;
346 return false;
347}
348
349// Checked: 2009-10-04 (RLVa-1.0.4c) | Modified: RLVa-1.0.4c
350bool RlvHandler::isException(ERlvBehaviour eBhvr, const RlvExceptionOption& varOption, ERlvExceptionCheck typeCheck) const
351{
352 // We need to "strict check" exceptions only if: the restriction is actually in place *and* (isPermissive(eBhvr) == FALSE)
353 if (RLV_CHECK_DEFAULT == typeCheck)
354 typeCheck = ( (hasBehaviour(eBhvr)) && (!isPermissive(eBhvr)) ) ? RLV_CHECK_STRICT : RLV_CHECK_PERMISSIVE;
355
356 std::list<LLUUID> objList;
357 if (RLV_CHECK_STRICT == typeCheck)
358 {
359 // If we're "strict checking" then we need the UUID of every object that currently has 'eBhvr' restricted
360 for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
361 if (itObj->second.hasBehaviour(eBhvr, !hasBehaviour(RLV_BHVR_PERMISSIVE)))
362 objList.push_back(itObj->first);
363 }
364
365 for (rlv_exception_map_t::const_iterator itException = m_Exceptions.lower_bound(eBhvr),
366 endException = m_Exceptions.upper_bound(eBhvr); itException != endException; ++itException)
367 {
368 if (itException->second.varOption == varOption)
369 {
370 // For permissive checks we just return on the very first match
371 if (RLV_CHECK_PERMISSIVE == typeCheck)
372 return true;
373
374 // For strict checks we don't return until the list is empty (every object with 'eBhvr' restricted also contains the exception)
375 std::list<LLUUID>::iterator itList = std::find(objList.begin(), objList.end(), itException->second.idObject);
376 if (itList != objList.end())
377 objList.erase(itList);
378 if (objList.empty())
379 return true;
380 }
381 }
382 return false;
383}
384
385// ============================================================================
386// Command processing functions
387//
388
389// Checked: 2009-06-03 (RLVa-0.2.0h)
390void RlvHandler::addBehaviourObserver(RlvBehaviourObserver* pBhvrObserver)
391{
392 std::list<RlvBehaviourObserver*>::iterator itBhvrObserver = std::find(m_BhvrObservers.begin(), m_BhvrObservers.end(), pBhvrObserver);
393 if (itBhvrObserver == m_BhvrObservers.end())
394 m_BhvrObservers.push_back(pBhvrObserver);
395}
396
397// Checked: 2009-06-03 (RLVa-0.2.0h)
398void RlvHandler::removeBehaviourObserver(RlvBehaviourObserver* pBhvrObserver)
399{
400 std::list<RlvBehaviourObserver*>::iterator itBhvrObserver = std::find(m_BhvrObservers.begin(), m_BhvrObservers.end(), pBhvrObserver);
401 if (itBhvrObserver != m_BhvrObservers.end())
402 m_BhvrObservers.erase(itBhvrObserver);
403}
404
405// Checked: 2009-06-03 (RLVa-0.2.0h)
406void RlvHandler::notifyBehaviourObservers(const RlvCommand& rlvCmd, bool fInternal)
407{
408 for (std::list<RlvBehaviourObserver*>::const_iterator itBhvrObserver = m_BhvrObservers.begin();
409 itBhvrObserver != m_BhvrObservers.end(); ++itBhvrObserver)
410 {
411 (*itBhvrObserver)->changed(rlvCmd, fInternal);
412 }
413}
414
415// Checked:
416BOOL RlvHandler::processCommand(const LLUUID& uuid, const std::string& strCmd, bool fFromObj)
417{
418 #ifdef RLV_DEBUG
419 RLV_INFOS << "[" << uuid << "]: " << strCmd << LL_ENDL;
420 #endif // RLV_DEBUG
421
422 RlvCommand rlvCmd(strCmd);
423 if (!rlvCmd.isValid())
424 {
425 #ifdef RLV_DEBUG
426 RLV_INFOS << "\t-> invalid command: " << strCmd << LL_ENDL;
427 #endif // RLV_DEBUG
428 return FALSE;
429 }
430 m_pCurCommand = &rlvCmd; m_idCurObject = uuid;
431
432 BOOL fRet = FALSE;
433 switch (rlvCmd.getParamType())
434 {
435 case RLV_TYPE_ADD: // Checked: 2009-06-03 (RLVa-0.2.0h) | Modified: RLVa-0.2.0h
436 {
437 if ( (m_Behaviours[rlvCmd.getBehaviourType()]) &&
438 ( (RLV_BHVR_SETDEBUG == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETENV == rlvCmd.getBehaviourType()) ) )
439 {
440 // Some restrictions can only be held by one single object to avoid deadlocks
441 #ifdef RLV_DEBUG
442 RLV_INFOS << "\t- " << rlvCmd.getBehaviour() << " is already set by another object => discarding" << LL_ENDL;
443 #endif // RLV_DEBUG
444 break;
445 }
446
447 rlv_object_map_t::iterator itObj = m_Objects.find(uuid);
448 if (itObj != m_Objects.end())
449 {
450 RlvObject& rlvObj = itObj->second;
451 fRet = rlvObj.addCommand(rlvCmd);
452 }
453 else
454 {
455 RlvObject rlvObj(uuid);
456 fRet = rlvObj.addCommand(rlvCmd);
457 m_Objects.insert(std::pair<LLUUID, RlvObject>(uuid, rlvObj));
458 }
459
460 #ifdef RLV_DEBUG
461 RLV_INFOS << "\t- " << ( (fRet) ? "adding behaviour" : "skipping duplicate") << LL_ENDL;
462 #endif // RLV_DEBUG
463
464 if (fRet) { // If FALSE then this was a duplicate, there's no need to handle those
465 if (!m_pGCTimer)
466 m_pGCTimer = new RlvGCTimer();
467 processAddCommand(uuid, rlvCmd);
468 notifyBehaviourObservers(rlvCmd, !fFromObj);
469 }
470 }
471 break;
472 case RLV_TYPE_REMOVE: // Checked:
473 {
474 rlv_object_map_t::iterator itObj = m_Objects.find(uuid);
475 if (itObj != m_Objects.end())
476 fRet = itObj->second.removeCommand(rlvCmd);
477
478 #ifdef RLV_DEBUG
479 RLV_INFOS << "\t- " << ( (fRet) ? "removing behaviour"
480 : "skipping remove (unset behaviour or unknown object)") << LL_ENDL;
481 #endif // RLV_DEBUG
482
483 if (fRet) { // Don't handle non-sensical removes
484 processRemoveCommand(uuid, rlvCmd);
485 notifyBehaviourObservers(rlvCmd, !fFromObj);
486
487 if (0 == itObj->second.m_Commands.size())
488 {
489 #ifdef RLV_DEBUG
490 RLV_INFOS << "\t- command list empty => removing " << uuid << LL_ENDL;
491 #endif // RLV_DEBUG
492 m_Objects.erase(itObj);
493 }
494 }
495 }
496 break;
497 case RLV_TYPE_CLEAR:
498 fRet = processClearCommand(uuid, rlvCmd);
499 notifyBehaviourObservers(rlvCmd, !fFromObj);
500 break;
501 case RLV_TYPE_FORCE: // Checked:
502 fRet = processForceCommand(uuid, rlvCmd);
503 break;
504 case RLV_TYPE_REPLY: // Checked:
505 fRet = processReplyCommand(uuid, rlvCmd);
506 break;
507 case RLV_TYPE_UNKNOWN: // Checked:
508 break;
509 #ifdef LL_GNUC
510 default:
511 break;
512 #endif // LL_GNUC
513 }
514
515 #ifdef RLV_DEBUG
516 RLV_INFOS << "\t--> command " << ((fRet) ? "succeeded" : "failed") << LL_ENDL;
517 #endif // RLV_DEBUG
518
519 m_pCurCommand = NULL; m_idCurObject.setNull();
520 return fRet;
521}
522
523BOOL RlvHandler::processAddCommand(const LLUUID& uuid, const RlvCommand& rlvCmd)
524{
525 // NOTE: - at this point the command has already been added to the corresponding RlvObject instance
526 // - the object's UUID may or may not exist in gObjectList (see handling of @detach=n)
527
528 ERlvBehaviour eBehaviour = rlvCmd.getBehaviourType();
529 const std::string& strOption = rlvCmd.getOption();
530
531 if ( (RLV_BHVR_UNKNOWN != eBehaviour) && (strOption.empty()) )
532 {
533 if (rlvCmd.isStrict())
534 addException(uuid, RLV_BHVR_PERMISSIVE, eBehaviour);
535 m_Behaviours[eBehaviour]++;
536 }
537
538 switch (eBehaviour)
539 {
540 case RLV_BHVR_DETACH: // @detach[:<option>]=n - Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
541 {
542 LLViewerObject* pObj = NULL; S32 idxAttachPt = 0;
543 if (strOption.empty()) // @detach=n
544 {
545 // If the object rezzed before we received @detach=n from it then we can just do our thing here
546 // If the object hasn't rezzed yet then we need to wait until RlvHandler::onAttach()
547 // If @detach=n were possible for non-attachments another copy/paste would be needed in RlvHandler::onGC()
548 if ((pObj = gObjectList.findObject(uuid)) != NULL)
549 setDetachable(pObj, uuid, false);
550 }
551 else if ((idxAttachPt = getAttachPointIndex(strOption, true)) != 0) // @detach:<attachpt>=n
552 {
553 setDetachable(idxAttachPt, uuid, false);
554
555 // (See below)
556 LLViewerJointAttachment* pAttachPt = getAttachPoint(strOption, true);
557 if (pAttachPt)
558 pObj = pAttachPt->getObject();
559 }
560
561 // When at least one HUD attachment is locked we want to make sure they're all visible (ie prevent hiding a blindfold HUD)
562 // However, since @detach:<attachpt>=n might lock a HUD attachment point that doesn't currently have an object we
563 // have to do this here *and* in RlvHandler::onAttach()
564 if ( (pObj) && (pObj->isHUDAttachment()) )
565 LLPipeline::sShowHUDAttachments = TRUE;
566 }
567 break;
568 case RLV_BHVR_REDIRCHAT: // @redirchat:<option>=n - Checked: 2009-07-07 (RLVa-1.0.0d)
569 case RLV_BHVR_REDIREMOTE: // @rediremote:<option>=n - Checked: 2009-07-07 (RLVa-1.0.0d) | Added: RLVa-0.2.2a
570 {
571 if (!strOption.empty())
572 m_Behaviours[eBehaviour]++; // @redirchat and @rediremote don't have an optionless version so keep track of it here
573 else
574 m_Behaviours[eBehaviour]--; // @redirchat=n and @rediremote=n are undefined, don't keep track of them
575 }
576 break;
577 case RLV_BHVR_SHOWWORLDMAP: // @showworldmap=n - Checked: 2009-07-05 (RLVa-1.0.0c)
578 {
579 // Simulate clicking the Map button [see LLToolBar::onClickMap()]
580 if (gFloaterWorldMap->getVisible())
581 LLFloaterWorldMap::toggle(NULL);
582 }
583 break;
584 case RLV_BHVR_SHOWMINIMAP: // @showminimap=n - Checked: 2009-07-05 (RLVa-1.0.0c)
585 {
586 // Simulate clicking the Minimap button [see LLToolBar::onClickRadar()]
587 if (LLFloaterMap::instanceVisible())
588 LLFloaterMap::hideInstance();
589 }
590 break;
591 #ifdef RLV_EXTENSION_STARTLOCATION
592 case RLV_BHVR_TPLOC: // @tploc=n - Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
593 case RLV_BHVR_UNSIT: // @unsit=n - Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
594 {
595 if (strOption.empty())
596 RlvSettings::updateLoginLastLocation();
597 }
598 break;
599 #endif // RLV_EXTENSION_STARTLOCATION
600 case RLV_BHVR_EDIT: // @edit=n - Checked: 2009-07-04 (RLVa-1.0.0b)
601 {
602 // Turn off "View / Highlight Transparent"
603 LLDrawPoolAlpha::sShowDebugAlpha = FALSE;
604
605 // Close the Beacons floater if it's open
606 if (LLFloaterBeacons::instanceVisible())
607 LLFloaterBeacons::toggleInstance();
608
609 // Get rid of the build floater if it's open [copy/paste from toggle_build_mode()]
610 if (gFloaterTools->getVisible())
611 {
612 gAgent.resetView(FALSE);
613 gFloaterTools->close();
614 gViewerWindow->showCursor();
615 }
616 }
617 break;
618 case RLV_BHVR_ADDOUTFIT: // @addoutfit[:<layer>]=n - Checked: 2009-07-07 (RLVa-1.0.0d)
619 case RLV_BHVR_REMOUTFIT: // @remoutfit[:<layer>]=n - Checked: 2009-07-07 (RLVa-1.0.0d)
620 {
621 S16* pLayers = (eBehaviour == RLV_BHVR_ADDOUTFIT) ? m_LayersAdd : m_LayersRem;
622
623 if (strOption.empty())
624 {
625 for (int idx = 0; idx < WT_COUNT; idx++)
626 pLayers[idx]++;
627 }
628 else
629 {
630 EWearableType type = LLWearable::typeNameToType(strOption);
631 if (WT_INVALID != type)
632 {
633 pLayers[type]++;
634 m_Behaviours[eBehaviour]++;
635 }
636 }
637 }
638 break;
639 case RLV_BHVR_SHOWINV: // @showinv=n - Checked: 2009-07-10 (RLVa-1.0.0g) | Modified: RLVa-1.0.0g
640 {
641 // Close all open inventory windows
642 LLInventoryView::closeAll();
643 }
644 break;
645 case RLV_BHVR_SHOWLOC: // @showloc=n - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
646 {
647 // If we're the first @showloc=n restriction refresh all object text so we can filter it if necessary
648 if (1 == m_Behaviours[RLV_BHVR_SHOWLOC])
649 LLHUDText::refreshAllObjectText();
650
651 // Close the "About Land" floater if it's currently visible
652 if (LLFloaterLand::instanceVisible())
653 LLFloaterLand::hideInstance();
654
655 // Close the "Estate Tools" floater is it's currently visible
656 if (LLFloaterRegionInfo::instanceVisible())
657 LLFloaterRegionInfo::hideInstance();
658
659 // NOTE: we should close the "God Tools" floater as well, but since calling LLFloaterGodTools::instance() always
660 // creates a new instance of the floater and since it's very unlikely to be open it's just better not to
661 }
662 break;
663 case RLV_BHVR_SHOWNAMES: // @shownames=n - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
664 {
665 // If we're the first @shownames=n restriction refresh all object text so we can filter it if necessary
666 if (1 == m_Behaviours[RLV_BHVR_SHOWNAMES])
667 LLHUDText::refreshAllObjectText();
668
669 // Close the "Active Speakers" panel if it's currently visible
670 LLFloaterChat::getInstance()->childSetVisible("active_speakers_panel", false);
671 }
672 break;
673 case RLV_BHVR_FARTOUCH:
674 {
675 #ifdef RLV_EXPERIMENTAL_FIRSTUSE
676 //LLFirstUse::useRlvFartouch();
677 #endif // RLV_EXPERIMENTAL_FIRSTUSE
678 }
679 break;
680 case RLV_BHVR_FLY: // @fly=n - Checked: 2009-07-05 (RLVa-1.0.0c)
681 {
682 // If currently flying, simulate clicking the Fly button [see LLToolBar::onClickFly()]
683 if (gAgent.getFlying())
684 gAgent.toggleFlying();
685 }
686 break;
687 case RLV_BHVR_SETENV: // @setenv=n - Checked: 2009-07-10 (RLVa-1.0.0g) | Modified: RLVa-0.2.0a
688 {
689 if (!fNoSetEnv)
690 {
691 // Only close the floaters if their instance exists and they're actually visible
692 if ( (LLFloaterEnvSettings::isOpen()) && (LLFloaterEnvSettings::instance()->getVisible()) )
693 LLFloaterEnvSettings::instance()->close();
694 if ( (LLFloaterWindLight::isOpen()) && (LLFloaterWindLight::instance()->getVisible()) )
695 LLFloaterWindLight::instance()->close();
696 if ( (LLFloaterWater::isOpen()) && (LLFloaterWater::instance()->getVisible()) )
697 LLFloaterWater::instance()->close();
698 if ( (LLFloaterDayCycle::isOpen()) && (LLFloaterDayCycle::instance()->getVisible()) )
699 LLFloaterDayCycle::instance()->close();
700
701 // Save the current WindLight params so we can restore them on @setenv=y
702 if (m_pWLSnapshot)
703 {
704 RLV_ERRS << "m_pWLSnapshot != NULL" << LL_ENDL; // Safety net in case we set @setenv=n for more than 1 object
705 delete m_pWLSnapshot;
706 }
707 m_pWLSnapshot = RlvWLSnapshot::takeSnapshot();
708 }
709 }
710 break;
711 case RLV_BHVR_SHOWHOVERTEXTALL: // @showhovertextal=n - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
712 case RLV_BHVR_SHOWHOVERTEXTWORLD: // @showhovertextworld=n - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
713 case RLV_BHVR_SHOWHOVERTEXTHUD: // @showhovertexthud=n - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
714 {
715 // Refresh all hover text (LLHUDText::setStringUTF8() will decide what needs clearing and what doesn't)
716 LLHUDText::refreshAllObjectText();
717 }
718 break;
719 case RLV_BHVR_SHOWHOVERTEXT: // @showhovertext:<uuid>=n - Checked: 2009-07-09 (RLVa-0.2.2a) | Modified: RLVa-1.0.0f
720 {
721 LLUUID idException(strOption);
722 if (idException.notNull()) // If there's an option it should be a valid UUID
723 {
724 addException(uuid, eBehaviour, idException);
725
726 // Clear the object's hover text
727 LLViewerObject* pObj = gObjectList.findObject(idException);
728 if ( (pObj) && (pObj->mText.notNull()) && (!pObj->mText->getObjectText().empty()) )
729 pObj->mText->setStringUTF8("");
730 }
731 }
732 break;
733 case RLV_BHVR_NOTIFY: // @notify:<option>=add - Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
734 {
735 S32 nChannel; std::string strFilter;
736 if ( (!strOption.empty()) && (rlvParseNotifyOption(strOption, nChannel, strFilter)) )
737 {
738 if (!m_pBhvrNotify)
739 addBehaviourObserver(m_pBhvrNotify = new RlvBehaviourNotifyObserver());
740 m_pBhvrNotify->addNotify(uuid, nChannel, strFilter);
741 }
742 }
743 break;
744 case RLV_BHVR_SENDCHANNEL: // @sendchannel:<uuid>=add - Checked: 2009-10-05 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a
745 {
746 S32 nChannel; // If there's an option it should be a valid (=positive and non-zero) chat channel
747 if ( (!strOption.empty()) && (LLStringUtil::convertToS32(strOption, nChannel)) && (nChannel > 0) )
748 addException(uuid, eBehaviour, nChannel);
749 }
750 break;
751 case RLV_BHVR_RECVCHAT: // @recvchat:<uuid>=add - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
752 case RLV_BHVR_RECVEMOTE: // @recvemote:<uuid>=add - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
753 case RLV_BHVR_RECVIM: // @recvim:<uuid>=add - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
754 case RLV_BHVR_SENDIM: // @sendim:<uuid>=add - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
755 case RLV_BHVR_TPLURE: // @tplure:<uuid>=add - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
756 case RLV_BHVR_ACCEPTTP: // @accepttp:<uuid>=add - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
757 {
758 LLUUID idException(strOption);
759 if (idException.notNull()) // If there's an option it should be a valid UUID
760 addException(uuid, eBehaviour, LLUUID(strOption));
761 }
762 break;
763 case RLV_BHVR_UNKNOWN:
764 {
765 // Give our observers a chance to handle any command we don't
766 RlvEvent rlvEvent(uuid, rlvCmd);
767 m_Emitter.update(&RlvObserver::onAddCommand, rlvEvent);
768 }
769 break;
770 default:
771 break;
772 }
773 return TRUE; // Add command success/failure is decided by RlvObject::addCommand()
774}
775
776// Checked: 2009-08-05 (RLVa-1.0.1e) | Added: RLVa-1.0.1e
777void RlvHandler::processRetainedCommands()
778{
779 for (rlv_retained_list_t::const_iterator itCmd = m_Retained.begin(); itCmd != m_Retained.end(); ++itCmd)
780 {
781 const RlvRetainedCommand& cmd = *itCmd;
782 processCommand(cmd.idObject, cmd.strCmd, true);
783 }
784 m_Retained.clear();
785}
786
787BOOL RlvHandler::processRemoveCommand(const LLUUID& uuid, const RlvCommand& rlvCmd)
788{
789 // NOTE: - the RlvObject instance still exists at this point, but the viewer might already have removed it from its object list
790 ERlvBehaviour eBehaviour = rlvCmd.getBehaviourType();
791 const std::string& strOption = rlvCmd.getOption();
792
793 if ( (RLV_BHVR_UNKNOWN != eBehaviour) && (strOption.empty()) )
794 {
795 if (rlvCmd.isStrict())
796 removeException(uuid, RLV_BHVR_PERMISSIVE, eBehaviour);
797 m_Behaviours[eBehaviour]--;
798 }
799
800 switch (eBehaviour)
801 {
802 case RLV_BHVR_DETACH: // @detach[:<option>]=y - Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
803 {
804 S32 idxAttachPt = 0;
805 if (strOption.empty()) // @detach=y
806 {
807 // The object may or may not (if it got detached) still exist
808 rlv_object_map_t::const_iterator itObj = m_Objects.find(uuid);
809 if (itObj != m_Objects.end())
810 idxAttachPt = itObj->second.m_idxAttachPt;
811 if (idxAttachPt)
812 setDetachable(idxAttachPt, uuid, true);
813 }
814 else if ((idxAttachPt = getAttachPointIndex(strOption, true))) // @detach:<attachpt>=y
815 {
816 setDetachable(idxAttachPt, uuid, true);
817 }
818 }
819 break;
820 case RLV_BHVR_REDIRCHAT: // @redirchat:<option>=y - Checked: 2009-07-07 (RLVa-1.0.0d)
821 case RLV_BHVR_REDIREMOTE: // @rediremote:<option>=y - Checked: 2009-07-07 (RLVa-1.0.0d) | Added: RLVa-0.2.2a
822 {
823 if (!strOption.empty())
824 m_Behaviours[eBehaviour]--; // @redirchat and @rediremote don't have an optionless version so keep track of it here
825 else
826 m_Behaviours[eBehaviour]++; // @redirchat=n and @rediremote=n are undefined, don't keep track of them
827 }
828 break;
829 #ifdef RLV_EXTENSION_STARTLOCATION
830 case RLV_BHVR_TPLOC: // @tploc=y - Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
831 case RLV_BHVR_UNSIT: // @unsit=y - Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.1d
832 {
833 if (strOption.empty())
834 RlvSettings::updateLoginLastLocation();
835 }
836 break;
837 #endif // RLV_EXTENSION_STARTLOCATION
838 case RLV_BHVR_ADDOUTFIT: // @addoutfit[:<layer>]=y - Checked: 2009-07-07 (RLVa-1.0.0d)
839 case RLV_BHVR_REMOUTFIT: // @remoutfit[:<layer>]=y - Checked: 2009-07-07 (RLVa-1.0.0d)
840 {
841 S16* pLayers = (eBehaviour == RLV_BHVR_ADDOUTFIT) ? m_LayersAdd : m_LayersRem;
842
843 if (strOption.empty())
844 {
845 for (int idx = 0; idx < WT_COUNT; idx++)
846 pLayers[idx]--;
847 }
848 else
849 {
850 EWearableType type = LLWearable::typeNameToType(strOption);
851 if (WT_INVALID != type)
852 {
853 pLayers[type]--;
854 m_Behaviours[eBehaviour]--;
855 }
856 }
857 }
858 break;
859 case RLV_BHVR_SETENV: // @setenv=y - Checked: 2009-07-10 (RLVa-1.0.0g) | Added: RLVa-0.2.0h
860 {
861 if (!fNoSetEnv)
862 {
863 // Restore WindLight parameters to what they were before @setenv=n was issued
864 RlvWLSnapshot::restoreSnapshot(m_pWLSnapshot);
865 delete m_pWLSnapshot;
866 m_pWLSnapshot = NULL;
867 }
868 }
869 break;
870 case RLV_BHVR_SHOWLOC: // @showloc=y - Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
871 case RLV_BHVR_SHOWNAMES: // @shownames=y - Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
872 case RLV_BHVR_SHOWHOVERTEXTALL: // @showhovertextal=y - Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
873 case RLV_BHVR_SHOWHOVERTEXTWORLD: // @showhovertextworld=y - Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
874 case RLV_BHVR_SHOWHOVERTEXTHUD: // @showhovertexthud=y - Checked: 2009-07-09 (RLVa-1.0.0f) | Added: RLVa-1.0.0f
875 {
876 // If this was the last of any of the five restrictions we should refresh all hover text in case anything needs restoring
877 if (!m_Behaviours[eBehaviour])
878 LLHUDText::refreshAllObjectText();
879 }
880 break;
881 case RLV_BHVR_SHOWHOVERTEXT: // @showhovertext:<uuid>=y - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
882 {
883 LLUUID idException(strOption);
884 if (idException.notNull()) // If there's an option it should be a valid UUID
885 {
886 removeException(uuid, eBehaviour, idException);
887
888 // Restore the object's hover text
889 LLViewerObject* pObj = gObjectList.findObject(idException);
890 if ( (pObj) && (pObj->mText.notNull()) && (!pObj->mText->getObjectText().empty()) )
891 pObj->mText->setStringUTF8(pObj->mText->getObjectText());
892 }
893 }
894 break;
895 case RLV_BHVR_NOTIFY: // @notify:<option>=rem - Checked: 2009-08-04 (RLVa-1.0.1d) | Modified: RLVa-1.0.1d
896 {
897 S32 nChannel; std::string strFilter;
898 if ( (m_pBhvrNotify) && (!strOption.empty()) && (rlvParseNotifyOption(strOption, nChannel, strFilter)) )
899 {
900 m_pBhvrNotify->removeNotify(uuid, nChannel, strFilter);
901
902 if (!m_pBhvrNotify->hasNotify())
903 {
904 removeBehaviourObserver(m_pBhvrNotify);
905 delete m_pBhvrNotify;
906 m_pBhvrNotify = NULL;
907 }
908 }
909 }
910 break;
911 case RLV_BHVR_SENDCHANNEL: // @sendchannel:<uuid>=rem - Checked: 2009-10-05 (RLVa-1.0.4a) | Modified: RLVa-1.0.4a
912 {
913 S32 nChannel; // If there's an option it should be a valid (=positive and non-zero) chat channel
914 if ( (!strOption.empty()) && (LLStringUtil::convertToS32(strOption, nChannel)) && (nChannel > 0) )
915 removeException(uuid, eBehaviour, nChannel);
916 }
917 break;
918 case RLV_BHVR_RECVCHAT: // @recvchat:<uuid>=rem - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
919 case RLV_BHVR_RECVEMOTE: // @recvemote:<uui>=red - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
920 case RLV_BHVR_RECVIM: // @recvim:<uuid>=rem - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
921 case RLV_BHVR_SENDIM: // @sendim:<uuid>=rem - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
922 case RLV_BHVR_TPLURE: // @recvim:<uuid>=rem - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
923 case RLV_BHVR_ACCEPTTP: // @accepttp:<uuid>=rem - Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
924 {
925 LLUUID idException(strOption);
926 if (idException.notNull()) // If there's an option it should be a valid UUID
927 removeException(uuid, eBehaviour, LLUUID(strOption));
928 }
929 break;
930 case RLV_BHVR_UNKNOWN:
931 {
932 // Give our observers a chance to handle any command we don't
933 RlvEvent rlvEvent(uuid, rlvCmd);
934 m_Emitter.update(&RlvObserver::onRemoveCommand, rlvEvent);
935 }
936 break;
937 default:
938 break;
939 }
940 return TRUE; // Remove commands don't fail, doesn't matter what we return here
941}
942
943BOOL RlvHandler::processClearCommand(const LLUUID& idObj, const RlvCommand& rlvCmd)
944{
945 const std::string& strFilter = rlvCmd.getParam(); std::string strCmdRem;
946
947 rlv_object_map_t::const_iterator itObj = m_Objects.find(idObj);
948 if (itObj != m_Objects.end()) // No sense in clearing if we don't have any commands for this object
949 {
950 const RlvObject& rlvObj = itObj->second; bool fContinue = true;
951 for (rlv_command_list_t::const_iterator itCmd = rlvObj.m_Commands.begin(), itCurCmd;
952 ((fContinue) && (itCmd != rlvObj.m_Commands.end())); )
953 {
954 itCurCmd = itCmd++; // Point itCmd ahead so it won't get invalidated if/when we erase a command
955
956 const RlvCommand& rlvCmdRem = *itCurCmd; strCmdRem = rlvCmdRem.asString();
957 if ( (strFilter.empty()) || (std::string::npos != strCmdRem.find(strFilter)) )
958 {
959 fContinue = (rlvObj.m_Commands.size() > 1); // rlvObj will become invalid once we remove the last command
960 processCommand(idObj, strCmdRem.append("=y"), false);
961 }
962 }
963 }
964
965 // Let our observers know about clear commands
966 RlvEvent rlvEvent(idObj, rlvCmd);
967 m_Emitter.update(&RlvObserver::onClearCommand, rlvEvent);
968
969 return TRUE; // Don't fail clear commands even if the object didn't exist since it confuses people
970}
971
972BOOL RlvHandler::processForceCommand(const LLUUID& idObj, const RlvCommand& rlvCmd) const
973{
974 const std::string& strOption = rlvCmd.getOption();
975 BOOL fHandled = TRUE;
976
977 switch (rlvCmd.getBehaviourType())
978 {
979 case RLV_BHVR_DETACH: // @detach[:<option>]=force - Checked:
980 onForceDetach(idObj, strOption);
981 break;
982 case RLV_BHVR_REMOUTFIT: // @remoutfit:<option>=force - Checked:
983 onForceRemOutfit(idObj, strOption);
984 break;
985 case RLV_BHVR_UNSIT: // @unsit=force - Checked: 2009-06-02 (RLVa-0.2.0g)
986 {
987 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
988 if ( (pAvatar) && (pAvatar->mIsSitting) && (!hasBehaviourExcept(RLV_BHVR_UNSIT, idObj)) )
989 {
990 // See behaviour notes on why we have to force an agent update here
991 gAgent.setControlFlags(AGENT_CONTROL_STAND_UP);
992 send_agent_update(TRUE, TRUE);
993 }
994 }
995 break;
996 case RLV_BHVR_TPTO: // @tpto:<option>=force - Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-1.0.0h
997 {
998 fHandled = FALSE;
999 if ( (!strOption.empty()) && (-1 == strOption.find_first_not_of("0123456789/.")) )
1000 {
1001 LLVector3d posGlobal;
1002
1003 boost_tokenizer tokens(strOption, boost::char_separator<char>("/", "", boost::keep_empty_tokens)); int idx = 0;
1004 for (boost_tokenizer::const_iterator itToken = tokens.begin(); itToken != tokens.end(); ++itToken)
1005 {
1006 if (idx < 3)
1007 LLStringUtil::convertToF64(*itToken, posGlobal[idx++]);
1008 }
1009
1010 if (idx == 3)
1011 {
1012 gAgent.teleportViaLocation(posGlobal);
1013 fHandled = TRUE;
1014 }
1015 }
1016 }
1017 break;
1018 case RLV_BHVR_SIT: // @sit:<option>=force - Checked: 2009-06-02 (RLVa-0.2.0g)
1019 fHandled = onForceSit(idObj, rlvCmd.getOption());
1020 break;
1021 case RLV_BHVR_ADDOUTFIT: // @addoutfit:<option>=force <- synonym of @attach:<option>=force
1022 case RLV_BHVR_ATTACH: // @attach:<option>=force - Checked:
1023 onForceWear(rlvCmd.getOption(), true, false); // Force attach single folder
1024 break;
1025 case RLV_BHVR_ATTACHALL: // @attachall:<option>=force - Checked:
1026 onForceWear(rlvCmd.getOption(), true, true); // Force attach nested folders
1027 break;
1028 case RLV_BHVR_DETACHALL: // @detachall:<option>=force - Checked:
1029 onForceWear(rlvCmd.getOption(), false, true); // Force detach nested folders
1030 break;
1031 case RLV_BHVR_ATTACHTHIS:
1032 case RLV_BHVR_ATTACHALLTHIS:
1033 case RLV_BHVR_DETACHTHIS:
1034 case RLV_BHVR_DETACHALLTHIS:
1035 {
1036 ERlvBehaviour eBehaviour = rlvCmd.getBehaviourType();
1037 std::string strReply;
1038 if (onGetPath(idObj, strOption, strReply))
1039 {
1040 LLStringUtil::toLower(strReply);
1041 onForceWear(strReply,
1042 (RLV_BHVR_ATTACHTHIS == eBehaviour) || (RLV_BHVR_ATTACHALLTHIS == eBehaviour),
1043 (RLV_BHVR_ATTACHALLTHIS == eBehaviour) || (RLV_BHVR_DETACHALLTHIS == eBehaviour));
1044 }
1045 }
1046 break;
1047 case RLV_BHVR_DETACHME: // @detachme=force - Checked: 2009-06-07 (RLVa-0.2.1c)
1048 {
1049 // NOTE: @detachme=force could be seen as a @detach:<attachpt>=force but RLV implements it as a "detach by UUID"
1050 LLViewerObject* pObj; LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachPt;
1051 if ( ((pObj = gObjectList.findObject(idObj)) != NULL) && (pObj->isAttachment()) &&
1052 ((pAvatar = gAgent.getAvatarObject()) != NULL) &&
1053 ((pAttachPt = pAvatar->getTargetAttachmentPoint(pObj->getRootEdit())) != NULL) )
1054 {
1055 handle_detach_from_avatar(pAttachPt);
1056 }
1057 }
1058 break;
1059 case RLV_BHVR_UNKNOWN:
1060 {
1061 // Give our observers a chance to handle any command we don't
1062 RlvEvent rlvEvent(idObj, rlvCmd);
1063 fHandled = m_Emitter.update(&RlvObserver::onForceCommand, rlvEvent);
1064 }
1065 break;
1066 default:
1067 break;
1068 }
1069 return fHandled; // If we handled it then it'll still be TRUE; if an observer doesn't handle it'll be FALSE
1070}
1071
1072// Checked: 2009-07-12 (RLVa-1.0.0h)
1073BOOL RlvHandler::processReplyCommand(const LLUUID& uuid, const RlvCommand& rlvCmd) const
1074{
1075 const std::string& strOption = rlvCmd.getOption();
1076 const std::string& strChannel = rlvCmd.getParam();
1077 std::string strReply;
1078
1079 BOOL fHandled = TRUE;
1080 switch (rlvCmd.getBehaviourType())
1081 {
1082 case RLV_BHVR_VERSION: // @version=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h)
1083 strReply = getVersionString();
1084 break;
1085 case RLV_BHVR_VERSIONNUM: // @versionnum=<channel> - Checked: 2009-10-04 (RLVa-1.0.4b) | Added: RLVa-1.0.4b
1086 strReply = getVersionNumString();
1087 break;
1088 case RLV_BHVR_GETOUTFIT: // @getoufit[:<layer>]=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
1089 {
1090 // (Quirk: RLV 1.16.1 will execute @getoutfit=<channel> if <layer> is invalid, so we need to as well)
1091 EWearableType layerType = LLWearable::typeNameToType(strOption);
1092
1093 const EWearableType layerTypes[] =
1094 {
1095 WT_GLOVES, WT_JACKET, WT_PANTS, WT_SHIRT, WT_SHOES, WT_SKIRT, WT_SOCKS,
1096 WT_UNDERPANTS, WT_UNDERSHIRT, WT_SKIN, WT_EYES, WT_HAIR, WT_SHAPE
1097 };
1098
1099 #ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING
1100 for (int idx = 0, cnt = sizeof(layerTypes) / sizeof(EWearableType); idx < cnt; idx++)
1101 {
1102 if ( (WT_INVALID == layerType) || (layerTypes[idx] == layerType) )
1103 {
1104 // TODO-RLVa: add support for 'fHideLockedLayers'
1105 bool fWorn = (gAgent.getWearable(layerTypes[idx])) &&
1106 (!isHiddenCompositeItem(gAgent.getWearableItem(layerTypes[idx]),
1107 LLWearable::typeToTypeName(layerTypes[idx])));
1108 strReply.push_back( (fWorn) ? '1' : '0' );
1109 }
1110 }
1111 #else
1112 for (int idx = 0, cnt = sizeof(layerTypes) / sizeof(EWearableType); idx < cnt; idx++)
1113 if ( (WT_INVALID == layerType) || (layerTypes[idx] == layerType) )
1114 {
1115 // We never hide body parts, even if they're "locked" and we're hiding locked layers
1116 // (nor do we hide a layer if the issuing object is the only one that has this layer locked)
1117 bool fWorn = (gAgent.getWearable(layerTypes[idx])) &&
1118 ( (!RlvSettings::getHideLockedLayers()) ||
1119 (LLAssetType::AT_BODYPART == LLWearable::typeToAssetType(layerTypes[idx])) ||
1120 ( (isRemovableExcept(layerTypes[idx], uuid)) &&
1121 (isStrippable(gAgent.getWearableItem(layerTypes[idx]))) ) );
1122 strReply.push_back( (fWorn) ? '1' : '0' );
1123 //strReply.push_back( (gAgent.getWearable(layerTypes[idx])) ? '1' : '0' );
1124 }
1125 #endif // RLV_EXPERIMENTAL_COMPOSITE_FOLDING
1126 }
1127 break;
1128 case RLV_BHVR_GETATTACH: // @getattach[:<layer>]=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
1129 {
1130 // If we're fetching all worn attachments then the reply should start with 0
1131 if (strOption.empty())
1132 strReply.push_back('0');
1133
1134 LLVOAvatar* pAvatar = gAgent.getAvatarObject(); std::string strAttachName;
1135 for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin();
1136 itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
1137 {
1138 LLViewerJointAttachment* pAttachment = itAttach->second;
1139 if (!pAttachment)
1140 continue;
1141
1142 strAttachName = pAttachment->getName(); // Capitalized (see avatar_lad.xml)
1143 LLStringUtil::toLower(strAttachName);
1144
1145 #ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING
1146 if ( (strOption.empty()) || (strOption == strAttachName) )
1147 {
1148 // TODO-RLVa: add support for 'fHideLockedAttach'
1149 bool fWorn = (pAttachment->getItemID().notNull()) &&
1150 (!isHiddenCompositeItem(pAttachment->getItemID(), strAttachName));
1151 strReply.push_back( (fWorn) ? '1' : '0' );
1152 }
1153 #else
1154 if ( (strOption.empty()) || (strOption == strAttachName) )
1155 {
1156 bool fWorn = (pAttachment->getItemID().notNull()) &&
1157 ( (!RlvSettings::getHideLockedAttach()) ||
1158 ( (isDetachable(itAttach->first)) && (isStrippable(pAttachment->getItemID())) ) );
1159 strReply.push_back( (fWorn) ? '1' : '0' );
1160 //strReply.push_back( (pAttachment->getItemID().notNull()) ? '1' : '0' );
1161 }
1162 #endif // RLV_EXPERIMENTAL_COMPOSITE_FOLDING
1163 }
1164 }
1165 break;
1166 case RLV_BHVR_GETSTATUS: // @getstatus[:<option>]=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h)
1167 {
1168 // NOTE: specification says response should start with '/' but RLV-1.16.1 returns an empty string when no rules are set
1169 rlv_object_map_t::const_iterator itObj = m_Objects.find(uuid);
1170 if (itObj != m_Objects.end())
1171 {
1172 std::string strObjStatus = itObj->second.getStatusString(strOption);
1173 if (!strObjStatus.empty())
1174 {
1175 strReply.push_back('/');
1176 strReply += strObjStatus;
1177 }
1178 }
1179 }
1180 break;
1181 case RLV_BHVR_GETSTATUSALL: // @getstatusall[:<option>]=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h)
1182 {
1183 // NOTE: specification says response should start with '/' but RLV-1.16.1 returns an empty string when no rules are set
1184 std::string strObjStatus;
1185 for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
1186 {
1187 strObjStatus = itObj->second.getStatusString(strOption);
1188 if (!strObjStatus.empty())
1189 {
1190 strReply.push_back('/');
1191 strReply += strObjStatus;
1192 }
1193 }
1194 }
1195 break;
1196 case RLV_BHVR_GETINV: // @getinv[:<path>]=<channel> - Checked: 2009-07-28 (RLVa-1.0.1b) | Modified: RLVa-1.0.1b
1197 {
1198 LLViewerInventoryCategory* pFolder = getSharedFolder(strOption);
1199 if (pFolder)
1200 {
1201 LLInventoryModel::cat_array_t* pFolders;
1202 LLInventoryModel::item_array_t* pItems;
1203 gInventory.getDirectDescendentsOf(pFolder->getUUID(), pFolders, pItems);
1204
1205 if (pFolders)
1206 {
1207 for (S32 idxFolder = 0, cntFolder = pFolders->count(); idxFolder < cntFolder; idxFolder++)
1208 {
1209 const std::string& strFolder = pFolders->get(idxFolder)->getName();
1210 if ( (!strFolder.empty()) && (RLV_FOLDER_PREFIX_HIDDEN != strFolder[0]) &&
1211 (!isFoldedFolder(pFolders->get(idxFolder).get(), true)) )
1212 {
1213 if (!strReply.empty())
1214 strReply.push_back(',');
1215 strReply += strFolder;
1216 }
1217 }
1218 }
1219 }
1220 }
1221 break;
1222 case RLV_BHVR_GETINVWORN: // @getinvworn[:path]=<channel> - Checked:
1223 onGetInvWorn(rlvCmd.getOption(), strReply);
1224 break;
1225 case RLV_BHVR_FINDFOLDER: // @findfolder:<criteria>=<channel> - Checked: 2009-08-26 (RLVa-1.0.2a) | Modified: RLVa-1.0.2a
1226 {
1227 // COMPAT-RLV: RLV 1.16.1 returns the first random folder it finds (probably tries to match "" to a folder name?)
1228 // (just going to stick with what's there for now... no option => no folder)
1229 LLInventoryModel::cat_array_t folders;
1230 if ( (!strOption.empty()) && (findSharedFolders(strOption, folders)) )
1231 {
1232 // We need to return an "in depth" result so whoever has the most '/' is our lucky winner
1233 // (maxSlashes needs to be initialized to -1 since children of the #RLV folder won't have '/' in their shared path)
1234 int maxSlashes = -1, curSlashes; std::string strFolderName;
1235 for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
1236 {
1237 strFolderName = getSharedPath(folders.get(idxFolder));
1238
1239 curSlashes = std::count(strFolderName.begin(), strFolderName.end(), '/');
1240 if (curSlashes > maxSlashes)
1241 {
1242 maxSlashes = curSlashes;
1243 strReply = strFolderName;
1244 }
1245 }
1246 }
1247 }
1248 break;
1249 #ifdef RLV_EXTENSION_CMD_FINDFOLDERS
1250 case RLV_BHVR_FINDFOLDERS: // @findfolders:<criteria>=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.0b
1251 {
1252 LLInventoryModel::cat_array_t folders;
1253 if ( (!strOption.empty()) && (findSharedFolders(strOption, folders)) )
1254 {
1255 for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
1256 {
1257 if (!strReply.empty())
1258 strReply.push_back(',');
1259 strReply += getSharedPath(folders.get(idxFolder));
1260 }
1261 }
1262 }
1263 break;
1264 #endif // RLV_EXTENSION_CMD_FINDFOLDERS
1265 case RLV_BHVR_GETPATH: // @getpath[:<option>]=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h)
1266 onGetPath(uuid, rlvCmd.getOption(), strReply);
1267 break;
1268 case RLV_BHVR_GETSITID: // @getsitid=<channel> - Checked: 2009-07-12 (RLVa-1.0.0h)
1269 {
1270 // (Quirk: RLV 1.16.1 returns a NULL uuid if we're not sitting)
1271 LLVOAvatar* pAvatarObj = gAgent.getAvatarObject(); LLUUID uuid;
1272 if ( (pAvatarObj) && (pAvatarObj->mIsSitting) )
1273 {
1274 // LLVOAvatar inherits from 2 classes so make sure we get the right vfptr
1275 LLViewerObject* pAvatar = dynamic_cast<LLViewerObject*>(pAvatarObj), *pParent;
1276 // (If there is a parent, we need to upcast it from LLXform to LLViewerObject to get its UUID)
1277 if ( (pAvatar) && ((pParent = static_cast<LLViewerObject*>(pAvatar->getRoot())) != pAvatar) )
1278 uuid = pParent->getID();
1279 }
1280 strReply = uuid.asString();
1281 }
1282 break;
1283 case RLV_BHVR_UNKNOWN:
1284 {
1285 // Give our observers a chance to handle any command we don't
1286 RlvEvent rlvEvent(uuid, rlvCmd);
1287 return m_Emitter.update(&RlvObserver::onReplyCommand, rlvEvent);
1288 }
1289 break;
1290 default:
1291 break;
1292 }
1293
1294 if (fHandled)
1295 rlvSendChatReply(strChannel, strReply);
1296 return fHandled;
1297}
1298
1299// ============================================================================
1300// House keeping (see debug notes for test methodology, test script and last run)
1301//
1302
1303void RlvHandler::initLookupTables()
1304{
1305 static bool fInitialized = false;
1306 if (!fInitialized)
1307 {
1308 // Initialize the attachment name lookup table
1309 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
1310 if (pAvatar)
1311 {
1312 std::string strAttachPtName;
1313 for (LLVOAvatar::attachment_map_t::const_iterator itAttach = pAvatar->mAttachmentPoints.begin();
1314 itAttach != pAvatar->mAttachmentPoints.end(); ++itAttach)
1315 {
1316 LLViewerJointAttachment* pAttachPt = itAttach->second;
1317 if (pAttachPt)
1318 {
1319 strAttachPtName = pAttachPt->getName();
1320 LLStringUtil::toLower(strAttachPtName);
1321 m_AttachLookup.addKeyword(strAttachPtName, itAttach->first);
1322 }
1323 }
1324 fInitialized = true;
1325 }
1326 }
1327}
1328
1329// Checked: 2009-08-11 (RLVa-1.0.1h) | Modified: RLVa-1.0.1h
1330void RlvHandler::onAttach(LLViewerJointAttachment* pAttachPt, bool fFullyLoaded)
1331{
1332 // Sanity check - LLVOAvatar::attachObject() should call us *after* calling LLViewerJointAttachment::addObject()
1333 LLViewerObject* pObj = pAttachPt->getObject();
1334 S32 idxAttachPt = getAttachPointIndex(pObj); // getAttachPointIndex() has a NULL pointer check so this is safe
1335 if ( (!pObj) || (!idxAttachPt) )
1336 {
1337 RLV_ERRS << "pAttachPt->getObject() == NULL" << LL_ENDL;
1338 return;
1339 }
1340
1341 // Check if this attachment point has a pending "reattach-on-detach"
1342 rlv_reattach_map_t::iterator itReattach = m_AttachPending.find(idxAttachPt);
1343 if (itReattach != m_AttachPending.end())
1344 {
1345 if (itReattach->second == pAttachPt->getItemID())
1346 {
1347 RLV_INFOS << "Reattached " << pAttachPt->getItemID().asString() << " to " << idxAttachPt << LL_ENDL;
1348 m_AttachPending.erase(itReattach);
1349 }
1350 }
1351 else if ( (fFullyLoaded) && (!isDetachableExcept(idxAttachPt, pObj)) )
1352 {
1353 // We're fully loaded with no pending reattach on this attach point but it's "undetachable" -> force detach the new attachment
1354
1355 // Assertion: the only way the attachment point could be locked at this point is if some object locked it with @detach:attachpt=n
1356 // - previous attachments on this attachment point might have issued @detach=n but those were all cleaned up at detach
1357 // - the new attachment might have issued @detach=n but that won't actually lock down the attachment point until further down
1358 // NOTE 1: "some object" may no longer exist if it was not an attachment and the GC hasn't cleaned it up yet (informative)
1359 // NOTE 2: "some object" may refer to the new attachment - ie @detach:spine=n from object on spine (problematic, causes reattach)
1360 // -> solved by using isDetachableExcept(idxAttachPt, pObj) instead of isDetachable(idxAttachPt)
1361
1362 m_DetachPending.insert(std::pair<S32, LLUUID>(idxAttachPt, pObj->getID()));
1363 rlvForceDetach(pAttachPt);
1364 }
1365
1366 // Check if we already have an RlvObject instance for this object (rezzed prim attached from in-world, or an attachment that rezzed in)
1367 rlv_object_map_t::iterator itObj = m_Objects.find(pObj->getID());
1368 if (itObj != m_Objects.end())
1369 {
1370 // Save the attachment point index
1371 itObj->second.m_idxAttachPt = idxAttachPt;
1372
1373 // If it's an attachment we processed commands for but that only just rezzed in we need to mark it as existing in gObjectList
1374 if (!itObj->second.m_fLookup)
1375 itObj->second.m_fLookup = true;
1376
1377 // In both cases we should check for "@detach=n" and actually lock down the attachment point it got attached to
1378 if (itObj->second.hasBehaviour(RLV_BHVR_DETACH, false))
1379 {
1380 // (Copy/paste from processAddCommand)
1381 setDetachable(idxAttachPt, pObj->getID(), false);
1382
1383 if (pObj->isHUDAttachment())
1384 LLPipeline::sShowHUDAttachments = TRUE; // Prevents hiding of locked HUD attachments
1385 }
1386 }
1387
1388 // Fetch the inventory item if we don't currently have it since we might need it for reattach-on-detach
1389 const LLUUID& idItem = pAttachPt->getItemID();
1390 LLViewerInventoryItem* pItem = ( (idItem.notNull()) && (gInventory.isInventoryUsable()) ) ? gInventory.getItem(idItem) : NULL;
1391 if ( (STATE_STARTED == LLStartUp::getStartupState()) && (pItem != NULL) )
1392 {
1393 RlvCurrentlyWorn f;
1394 f.fetchItem(idItem);
1395 }
1396
1397 // If what we're wearing is located under the shared root then append the attachment point name (if needed)
1398 LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
1399 if ( (STATE_STARTED == LLStartUp::getStartupState()) && (pRlvRoot) && (pItem) && (pItem->isComplete()) &&
1400 (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) )
1401 {
1402 std::string strAttachPt = pAttachPt->getName();
1403 LLStringUtil::toLower(strAttachPt);
1404
1405 // If we can modify the item then it should contain the attach point name itself, otherwise its parent should
1406 if (pItem->getPermissions().allowModifyBy(gAgent.getID()))
1407 {
1408 if (!getAttachPoint(pItem, true))
1409 {
1410 // It doesn't specify an attach point and we can rename it [see LLItemBridge::renameItem()]
1411 std::string strName = pItem->getName();
1412 LLStringUtil::truncate(strName, DB_INV_ITEM_NAME_STR_LEN - strAttachPt.length() - 3);
1413
1414 strName += " (" + strAttachPt + ")";
1415
1416 pItem->rename(strName);
1417 pItem->updateServer(FALSE);
1418 gInventory.updateItem(pItem);
1419 //gInventory.notifyObservers(); <- done further down in LLVOAvatar::attachObject()
1420 }
1421 }
1422 else
1423 {
1424 // Folder can't be the shared root, or be its direct descendant (= nested at least 2 levels deep)
1425 LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
1426 if ( (pFolder) &&
1427 (pFolder->getUUID() != pRlvRoot->getUUID()) && (pFolder->getParentUUID() != pRlvRoot->getUUID()) &&
1428 (!getAttachPoint(pFolder, true)) )
1429 {
1430 // It's no mod and its parent folder doesn't contain an attach point
1431 if ( (1 == rlvGetDirectDescendentsCount(pFolder, LLAssetType::AT_OBJECT)) && (NEW_CATEGORY_NAME == pFolder->getName()) )
1432 {
1433 // Only rename if there's exactly 1 object/attachment inside of it [see LLFolderBridge::renameItem()]
1434 std::string strName = ".(" + strAttachPt + ")";
1435
1436 pFolder->rename(strName);
1437 pFolder->updateServer(FALSE);
1438 gInventory.updateCategory(pFolder);
1439 //gInventory.notifyObservers(); <- done further down in LLVOAvatar::attachObject()
1440 }
1441 }
1442 }
1443 }
1444}
1445
1446// Checked: 2009-05-31 (RLVa-0.2.0e) | Modified: RLVa-0.2.0e
1447void RlvHandler::onDetach(LLViewerJointAttachment* pAttachPt)
1448{
1449 LLViewerObject* pObj = pAttachPt->getObject();
1450 if (!pObj)
1451 {
1452 // LLVOAvatar::detachObject() should call us *before* calling LLViewerJointAttachment::removeObject()
1453 RLV_ERRS << "pAttachPt->getObject() == NULL" << LL_ENDL;
1454 return;
1455 }
1456 S32 idxAttachPt = getAttachPointIndex(pObj);
1457 if (0 == idxAttachPt)
1458 {
1459 // If we ended up here then the user "Drop"'ed this attachment (which we can't recover from)
1460 return;
1461 }
1462
1463 #ifdef RLV_DEBUG
1464 // TODO-RLV: when we're exiting (for whatever reason) app state won't always reflect it but
1465 // gAgent.getAvatarObject()->mAttachmentPoints will be NULL so anywhere we use
1466 // "get_if_there" will call through a NULL pointer. One case is "idling out" -> test the rest
1467 //LLViewerJointAttachment* pDbgAttachmentPt =
1468 // get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, (S32)idxAttachPt, (LLViewerJointAttachment*)NULL);
1469 //RLV_INFOS << "Clean up for '" << pDbgAttachmentPt->getName() << "'" << LL_ENDL;
1470 #endif // RLV_DEBUG
1471
1472 // If the attachment was locked then we should reattach it (unless we're already trying to reattach to this attachment point)
1473 // (unless we forcefully detached it else in which case we do not want to reattach it)
1474 rlv_reattach_map_t::iterator itDetach = m_DetachPending.find(idxAttachPt);
1475 if (itDetach != m_DetachPending.end())
1476 {
1477 // RLVa-TODO: we should really be comparing item UUIDs but is it even possible to end up here and not have them match?
1478 m_DetachPending.erase(itDetach);
1479 }
1480 else if ( (!isDetachable(idxAttachPt)) && (m_AttachPending.find(idxAttachPt) == m_AttachPending.end()) )
1481 {
1482 // In an ideal world we would simply set up an LLInventoryObserver but there's no specific "asset updated" changed flag *sighs*
1483 // NOTE: attachments *always* know their "inventory item UUID" so we don't have to worry about fetched vs unfetched inventory
1484 m_AttachPending.insert(std::pair<S32, LLUUID>(idxAttachPt, pAttachPt->getItemID()));
1485 }
1486
1487 // We can't - easily - clean up child prims that never issued @detach=n but the GC will get those eventually
1488 rlv_detach_map_t::iterator itAttach = m_Attachments.find(idxAttachPt);
1489 while ( (itAttach != m_Attachments.upper_bound(idxAttachPt)) && (itAttach != m_Attachments.end()) )
1490 {
1491 LLViewerObject* pTempObj = gObjectList.findObject(itAttach->second);
1492 if ( (pTempObj) && (pTempObj->getRootEdit()->getID() == pObj->getID()) )
1493 {
1494 // Iterator points to the object (or to a child prim) so issue a clear on behalf of the object (there's the
1495 // possibility of going into an eternal loop, but that's ok since it indicates a bug in @clear that needs fixing)
1496 processCommand(itAttach->second, "clear", true);
1497
1498 itAttach = m_Attachments.find(idxAttachPt); // @clear will invalidate all iterators so we have to start anew
1499 }
1500 else
1501 {
1502 itAttach++;
1503 }
1504 }
1505
1506 // Clean up in case there was never a @detach=n (only works for the root prim - see above)
1507 rlv_object_map_t::iterator itObj = m_Objects.find(pObj->getID());
1508 if (itObj != m_Objects.end())
1509 processCommand(itObj->second.m_UUID, "clear", true);
1510}
1511
1512// Checked: 2009-07-30 (RLVa-1.0.1c) | Modified: RLVa-1.0.1c
1513bool RlvHandler::onGC()
1514{
1515 // We can't issue @clear on an object while we're in the loop below since that would invalidate our iterator
1516 // (and starting over would mean that some objects might get their "lookup misses" counter updated more than once per GC run)
1517 std::list<LLUUID> ExpiredObjects;
1518
1519 for (rlv_object_map_t::iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
1520 {
1521 LLViewerObject* pObj = gObjectList.findObject(itObj->second.m_UUID);
1522 if (!pObj)
1523 {
1524 // If the RlvObject once existed in the gObjectList and now doesn't then expire it right now
1525 // If the RlvObject never existed in the gObjectList and still doesn't then increase its "lookup misses" counter
1526 // but if that reaches 20 (we run every 30 seconds so that's about 10 minutes) then we'll expire it too
1527 if ( (itObj->second.m_fLookup) || (++itObj->second.m_nLookupMisses > 20) )
1528 ExpiredObjects.push_back(itObj->first);
1529 }
1530 else
1531 {
1532 // Check if this is an RlvObject instance who's object never existed in gObjectList before (rezzed prim in-world)
1533 // (it could also be an attachment that only just rezzed in but RlvHandler::onAttach() should be handling those)
1534 if ( (!itObj->second.m_fLookup) && (!pObj->isAttachment()) )
1535 itObj->second.m_fLookup = true;
1536 }
1537 }
1538
1539 for (std::list<LLUUID>::const_iterator itExpired = ExpiredObjects.begin(); itExpired != ExpiredObjects.end(); ++itExpired)
1540 {
1541 #ifdef RLV_DEBUG
1542 RLV_INFOS << "Garbage collecting " << *itExpired << LL_ENDL;
1543 #endif // RLV_DEBUG
1544
1545 processCommand(*itExpired, "clear", true);
1546 }
1547
1548 return (0 != m_Objects.size()); // GC will kill itself if it has nothing to do
1549}
1550
1551// Checked: 2009-08-08 (RLVa-1.0.1g) | Modified: RLVa-1.0.1g
1552void RlvHandler::onSavedAssetIntoInventory(const LLUUID& idItem)
1553{
1554 for (rlv_reattach_map_t::iterator itAttach = m_AttachPending.begin(); itAttach != m_AttachPending.end(); ++itAttach)
1555 {
1556 if (idItem == itAttach->second)
1557 {
1558 RLV_INFOS << "Reattaching " << idItem.asString() << " to " << itAttach->first << LL_ENDL;
1559
1560 #if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11
1561 LLAttachmentRezAction* rez_action = new LLAttachmentRezAction;
1562 rez_action->mItemID = itAttach->second;
1563 rez_action->mAttachPt = itAttach->first;
1564
1565 confirm_replace_attachment_rez(0/*YES*/, (void*)rez_action); // (Will call delete on rez_action)
1566 #else // Version: 1.23.4
1567 LLSD payload;
1568 payload["item_id"] = itAttach->second;
1569 payload["attachment_point"] = itAttach->first;
1570
1571 LLNotifications::instance().forceResponse(LLNotification::Params("ReplaceAttachment").payload(payload), 0/*YES*/);
1572 #endif
1573 }
1574 }
1575}
1576
1577// ============================================================================
1578// String/chat censoring functions
1579//
1580
1581// LL must have included an strlen for UTF8 *somewhere* but I can't seem to find it so this one is home grown
1582size_t utf8str_strlen(const std::string& utf8)
1583{
1584 const char* pUTF8 = utf8.c_str(); size_t length = 0;
1585 for (int idx = 0, cnt = utf8.length(); idx < cnt ;idx++)
1586 {
1587 // We're looking for characters that don't start with 10 as their high bits
1588 if ((pUTF8[idx] & 0xC0) != 0x80)
1589 length++;
1590 }
1591 return length;
1592}
1593
1594// TODO-RLV: works, but more testing won't hurt
1595std::string utf8str_chtruncate(const std::string& utf8, size_t length)
1596{
1597 if (0 == length)
1598 return std::string();
1599 if (utf8.length() <= length)
1600 return utf8;
1601
1602 const char* pUTF8 = utf8.c_str(); int idx = 0;
1603 while ( (pUTF8[idx]) && (length > 0) )
1604 {
1605 // We're looking for characters that don't start with 10 as their high bits
1606 if ((pUTF8[idx] & 0xC0) != 0x80)
1607 length--;
1608 idx++;
1609 }
1610
1611 return utf8.substr(0, idx);
1612}
1613
1614// Checked: 2009-07-09 (RLVa-1.0.0f) | Modified: RLVa-1.0.0f
1615void RlvHandler::filterChat(std::string& strUTF8Text, bool fFilterEmote) const
1616{
1617 if (strUTF8Text.empty())
1618 return;
1619
1620 if (rlvIsEmote(strUTF8Text)) // Check if it's an emote
1621 {
1622 if (fFilterEmote) // Emote filtering depends on fFilterEmote
1623 {
1624 if ( (strUTF8Text.find_first_of("\"()*=^_?~") != -1) ||
1625 (strUTF8Text.find(" -") != -1) || (strUTF8Text.find("- ") != -1) || (strUTF8Text.find("''") != -1) )
1626 {
1627 strUTF8Text = "..."; // Emote contains illegal character (or character sequence)
1628 }
1629 else if (!hasBehaviour(RLV_BHVR_EMOTE))
1630 {
1631 int idx = strUTF8Text.find('.'); // Truncate at 20 characters or at the dot (whichever is shorter)
1632 strUTF8Text = utf8str_chtruncate(strUTF8Text, ( (idx > 0) && (idx < 20) ) ? idx + 1 : 20);
1633 }
1634 }
1635 }
1636 else if (strUTF8Text[0] == '/') // Not an emote, but starts with a '/'
1637 {
1638 if (utf8str_strlen(strUTF8Text) > 7) // Allow as long if it's 6 characters or less
1639 strUTF8Text = "...";
1640 }
1641 else if ((strUTF8Text.length() < 4) || (strUTF8Text.compare(0, 2, "((")) || (strUTF8Text.compare(strUTF8Text.length() - 2, 2, "))")))
1642 {
1643 strUTF8Text = "..."; // Regular chat (not OOC)
1644 }
1645}
1646
1647// Checked: 2009-07-04 (RLVa-1.0.0a) | Modified: RLVa-1.0.0a
1648void RlvHandler::filterLocation(std::string& strUTF8Text) const
1649{
1650 // TODO-RLVa: if either the region or parcel name is a simple word such as "a" or "the" then confusion will ensue?
1651 // -> not sure how you would go about preventing this though :|...
1652
1653 // Filter any mention of the surrounding region names
1654 LLWorld::region_list_t regions = LLWorld::getInstance()->getRegionList();
1655 for (LLWorld::region_list_t::const_iterator itRegion = regions.begin(); itRegion != regions.end(); ++itRegion)
1656 rlvStringReplace(strUTF8Text, (*itRegion)->getName(), rlv_handler_t::cstrHiddenRegion);
1657
1658 // Filter any mention of the parcel name
1659 LLViewerParcelMgr* pParcelMgr = LLViewerParcelMgr::getInstance();
1660 if (pParcelMgr)
1661 rlvStringReplace(strUTF8Text, pParcelMgr->getAgentParcelName(), rlv_handler_t::cstrHiddenParcel);
1662}
1663
1664void RlvHandler::filterNames(std::string& strUTF8Text) const
1665{
1666 std::string strFirstName, strLastName, strName;
1667
1668 // TODO-RLV: make this a bit more efficient (ie people with a large draw distance will have a load of active regions)
1669 // -> the cost of multi string matching them all at once seems to be about the same as calling rlvStringReplace
1670 // twice so that would be a tremendous gain (and we'd get first name and word matching for free)
1671 #if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11
1672 for (LLWorld::region_list_t::const_iterator itRegion = LLWorld::getInstance()->getRegionList().begin();
1673 itRegion != LLWorld::getInstance()->getRegionList().end(); ++itRegion)
1674 {
1675 LLViewerRegion* pRegion = *itRegion;
1676
1677 for (S32 idxAgent = 0, cntAgent = pRegion->mMapAvatars.count(); idxAgent < cntAgent; idxAgent++)
1678 {
1679 // LLCacheName::getName() will add the UUID to the lookup queue if we don't know it yet
1680 if (gCacheName->getName(pRegion->mMapAvatarIDs.get(idxAgent), strFirstName, strLastName))
1681 {
1682 strName = strFirstName + " " + strLastName;
1683
1684 rlvStringReplace(strUTF8Text, strName, getAnonym(strName));
1685 }
1686 }
1687 }
1688 #else // Version: trunk
1689 // TODO-RLV: should restrict this to a certain radius (probably 1-2 sim range?)
1690 std::vector<LLUUID> idAgents;
1691 LLWorld::getInstance()->getAvatars(&idAgents, NULL);
1692
1693 for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++)
1694 {
1695 // LLCacheName::getName() will add the UUID to the lookup queue if we don't know it yet
1696 if (gCacheName->getName(idAgents[idxAgent], strFirstName, strLastName))
1697 {
1698 strName = strFirstName + " " + strLastName;
1699
1700 rlvStringReplace(strUTF8Text, strName, getAnonym(strName));
1701 }
1702 }
1703 #endif
1704}
1705
1706const std::string& RlvHandler::getAnonym(const std::string& strName) const
1707{
1708 const char* pszName = strName.c_str();
1709 U32 nHash = 0;
1710
1711 // Test with 11,264 SL names showed a 3.33% - 3.82% occurance for each so we *should* get a very even spread
1712 for (int idx = 0, cnt = strName.length(); idx < cnt; idx++)
1713 nHash += pszName[idx];
1714
1715 return cstrAnonyms[nHash % 28];
1716}
1717
1718// Checked: 2009-07-07 (RLVa-1.0.0d) | Modified: RLVa-0.2.2a
1719bool RlvHandler::redirectChatOrEmote(const std::string& strUTF8Text) const
1720{
1721 // Sanity check - @redirchat only for chat and @rediremote only for emotes
1722 bool fIsEmote = rlvIsEmote(strUTF8Text);
1723 if ( ((!fIsEmote) && (!hasBehaviour(RLV_BHVR_REDIRCHAT))) || ((fIsEmote) && (!hasBehaviour(RLV_BHVR_REDIREMOTE))) )
1724 return false;
1725
1726 if (!fIsEmote)
1727 {
1728 std::string strText = strUTF8Text;
1729 filterChat(strText, true);
1730 if (strText != "...")
1731 return false; // @sendchat wouldn't filter it so @redirchat won't redirect it either
1732 }
1733
1734 bool fSendChannel = hasBehaviour(RLV_BHVR_SENDCHANNEL); S32 nChannel = 0;
1735 for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
1736 {
1737 for (rlv_command_list_t::const_iterator itCmd = itObj->second.m_Commands.begin(),
1738 endCmd = itObj->second.m_Commands.end(); itCmd != endCmd; ++itCmd)
1739 {
1740 if ( ( ((!fIsEmote) && (RLV_BHVR_REDIRCHAT == itCmd->getBehaviourType())) || // Redirect if: (not an emote and @redirchat
1741 ((fIsEmote) && (RLV_BHVR_REDIREMOTE == itCmd->getBehaviourType())) ) && // OR an emote and @rediremote)
1742 (LLStringUtil::convertToS32(itCmd->getOption(), nChannel)) && // AND the channel is a number
1743 ( (!fSendChannel) || (isException(RLV_BHVR_SENDCHANNEL, nChannel)) ) ) // AND we're allowed to send to that channel
1744 {
1745 rlvSendChatReply(nChannel, strUTF8Text);
1746 }
1747 }
1748 }
1749 return true;
1750}
1751
1752// ============================================================================
1753// Public service functions (called by the outside world or by extension handlers)
1754//
1755
1756BOOL RlvHandler::isAgentNearby(const LLUUID& uuid) const
1757{
1758 #if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11
1759 for (LLWorld::region_list_t::const_iterator itRegion = LLWorld::getInstance()->getRegionList().begin();
1760 itRegion != LLWorld::getInstance()->getRegionList().end(); ++itRegion)
1761 {
1762 LLViewerRegion* pRegion = *itRegion;
1763
1764 for (S32 idxAgent = 0, cntAgent = pRegion->mMapAvatars.count(); idxAgent < cntAgent; idxAgent++)
1765 if (pRegion->mMapAvatarIDs.get(idxAgent) == uuid)
1766 return TRUE;
1767 }
1768 #else // Version: trunk
1769 // TODO-RLV: rewrite this to fit trunk, but still need the radius limited to a sane range
1770 std::vector<LLUUID> idAgents;
1771 LLWorld::getInstance()->getAvatars(&idAgents, NULL);
1772
1773 for (int idxAgent = 0, cntAgent = idAgents.size(); idxAgent < cntAgent; idxAgent++)
1774 {
1775 if (idAgents[idxAgent] == uuid)
1776 return TRUE;
1777 }
1778 #endif
1779 return FALSE;
1780}
1781
1782// ============================================================================
1783// General purpose inventory functions
1784//
1785
1786// Checked: 2009-07-12 (RLVa-1.0.0h)
1787class RlvSharedRootFetcher : public LLInventoryFetchDescendentsObserver
1788{
1789public:
1790 RlvSharedRootFetcher() {}
1791
1792 virtual void done()
1793 {
1794 RLV_INFOS << "Shared folders fetch completed" << LL_ENDL;
1795 RlvHandler::m_fFetchComplete = TRUE;
1796
1797 gInventory.removeObserver(this);
1798 delete this;
1799 }
1800};
1801
1802// Checked: 2009-07-12 (RLVa-1.0.0h)
1803void RlvHandler::fetchSharedInventory()
1804{
1805 // Sanity check - don't fetch if we're already fetching, or if we don't have a shared root
1806 LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
1807 if ( (m_fFetchStarted) || (!pRlvRoot) )
1808 return;
1809
1810 // Grab all the folders under the shared root
1811 LLInventoryModel::cat_array_t folders;
1812 LLInventoryModel::item_array_t items;
1813 gInventory.collectDescendents(pRlvRoot->getUUID(), folders, items, FALSE);
1814
1815 /*
1816 * Add them to the "to fetch" list
1817 */
1818 LLInventoryFetchDescendentsObserver::folder_ref_t fetchFolders;
1819
1820 fetchFolders.push_back(pRlvRoot->getUUID());
1821 for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
1822 fetchFolders.push_back(folders.get(idxFolder)->getUUID());
1823
1824 /*
1825 * Now fetch them all in one go
1826 */
1827 RlvSharedRootFetcher* fetcher = new RlvSharedRootFetcher;
1828
1829 RLV_INFOS << "Starting fetch of " << fetchFolders.size() << " shared folders" << LL_ENDL;
1830 fetcher->fetchDescendents(fetchFolders);
1831
1832 if (fetcher->isEverythingComplete())
1833 fetcher->done();
1834 else
1835 gInventory.addObserver(fetcher);
1836}
1837
1838bool RlvHandler::findSharedFolders(const std::string& strCriteria, LLInventoryModel::cat_array_t& folders) const
1839{
1840 // Sanity check - can't do anything without a shared root
1841 LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
1842 if (!pRlvRoot)
1843 return false;
1844
1845 folders.clear();
1846 LLInventoryModel::item_array_t items;
1847 RlvCriteriaCategoryCollector functor(strCriteria);
1848 gInventory.collectDescendentsIf(pRlvRoot->getUUID(), folders, items, FALSE, functor);
1849
1850 return (folders.count() != 0);
1851}
1852
1853// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0e
1854LLViewerInventoryCategory* RlvHandler::getSharedRoot()
1855{
1856 if (gInventory.isInventoryUsable())
1857 {
1858 LLInventoryModel::cat_array_t* pFolders;
1859 LLInventoryModel::item_array_t* pItems;
1860 gInventory.getDirectDescendentsOf(gAgent.getInventoryRootID(), pFolders, pItems);
1861 if (pFolders)
1862 {
1863 // NOTE: we might have multiple #RLV folders so we'll just go with the first one we come across
1864 LLViewerInventoryCategory* pFolder;
1865 for (S32 idxFolder = 0, cntFolder = pFolders->count(); idxFolder < cntFolder; idxFolder++)
1866 {
1867 if ( ((pFolder = pFolders->get(idxFolder)) != NULL) && (RlvHandler::cstrSharedRoot == pFolder->getName()) )
1868 return pFolder;
1869 }
1870 }
1871 }
1872 return NULL;
1873}
1874
1875// Checked: 2009-07-28 (RLVa-1.0.1a) | Modified: RLVa-1.0.1a
1876LLViewerInventoryCategory* RlvHandler::getSharedFolder(const LLUUID& idParent, const std::string& strFolderName) const
1877{
1878 LLInventoryModel::cat_array_t* pFolders;
1879 LLInventoryModel::item_array_t* pItems;
1880 gInventory.getDirectDescendentsOf(idParent, pFolders, pItems);
1881 if ( (!pFolders) || (strFolderName.empty()) )
1882 return NULL;
1883
1884 // If we can't find an exact match then we'll settle for a "contains" match
1885 LLViewerInventoryCategory* pPartial = NULL;
1886
1887 //LLStringUtil::toLower(strFolderName); <- everything was already converted to lower case before
1888
1889 std::string strName;
1890 for (S32 idxFolder = 0, cntFolder = pFolders->count(); idxFolder < cntFolder; idxFolder++)
1891 {
1892 LLViewerInventoryCategory* pFolder = pFolders->get(idxFolder);
1893
1894 strName = pFolder->getName();
1895 if (strName.empty())
1896 continue;
1897 LLStringUtil::toLower(strName);
1898
1899 if (strFolderName == strName)
1900 return pFolder; // Found an exact match, no need to keep on going
1901 else if ( (!pPartial) && (RLV_FOLDER_PREFIX_HIDDEN != strName[0]) && (strName.find(strFolderName) != std::string::npos) )
1902 pPartial = pFolder; // Found a partial (non-hidden) match, but we might still find an exact one (first partial match wins)
1903 }
1904
1905 return pPartial;
1906}
1907
1908// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0e
1909LLViewerInventoryCategory* RlvHandler::getSharedFolder(const std::string& strPath) const
1910{
1911 // Sanity check - no shared root => no shared folder
1912 LLViewerInventoryCategory* pRlvRoot = getSharedRoot(), *pFolder = pRlvRoot;
1913 if (!pRlvRoot)
1914 return NULL;
1915
1916 // Walk the path (starting at the root)
1917 boost_tokenizer tokens(strPath, boost::char_separator<char>("/", "", boost::drop_empty_tokens));
1918 for (boost_tokenizer::const_iterator itToken = tokens.begin(); itToken != tokens.end(); ++itToken)
1919 {
1920 pFolder = getSharedFolder(pFolder->getUUID(), *itToken);
1921 if (!pFolder)
1922 return NULL; // No such folder
1923 }
1924
1925 return pFolder; // If strPath was empty or just a bunch of //// then: pFolder == pRlvRoot
1926}
1927
1928// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0g
1929std::string RlvHandler::getSharedPath(const LLViewerInventoryCategory* pFolder) const
1930{
1931 LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
1932 // Sanity check - no shared root or no folder => no path
1933 if ( (!pRlvRoot) || (!pFolder) || (pRlvRoot->getUUID() == pFolder->getUUID()) )
1934 return std::string();
1935
1936 const LLUUID& idRLV = pRlvRoot->getUUID();
1937 const LLUUID& idRoot = gAgent.getInventoryRootID();
1938 std::string strPath;
1939
1940 // Walk up the tree until we reach the top
1941 while (pFolder)
1942 {
1943 strPath = "/" + pFolder->getName() + strPath;
1944
1945 const LLUUID& idParent = pFolder->getParentUUID();
1946 if (idRLV == idParent) // Reached the shared root, we're done
1947 break;
1948 else if (idRoot == idParent) // We reached the agent's inventory root (indicative of a logic error elsewhere)
1949 {
1950 RLV_ERRS << "Reached agent's inventory root while building path for shared folder" << LL_ENDL;
1951 return std::string();
1952 }
1953 else
1954 pFolder = gInventory.getCategory(idParent);
1955 }
1956
1957 return strPath.erase(0, 1);
1958}
1959
1960// ============================================================================
1961// Composite folders
1962//
1963
1964#ifdef RLV_EXPERIMENTAL_COMPOSITES
1965 // Checked:
1966 bool RlvHandler::getCompositeInfo(const LLInventoryCategory* pFolder, std::string* pstrName) const
1967 {
1968 if (pFolder)
1969 {
1970 // Composite folder naming: ^\.?[Folder]
1971 const std::string& cstrFolder = pFolder->getName();
1972 int idxStart = cstrFolder.find('['), idxEnd = cstrFolder.find(']', idxStart);
1973 if ( ((0 == idxStart) || (1 == idxStart)) && (idxEnd - idxStart > 1) )
1974 {
1975 if (pstrName)
1976 pstrName->assign(cstrFolder.substr(idxStart + 1, idxEnd - idxStart - 1));
1977 return true;
1978 }
1979 }
1980 return false;
1981 }
1982
1983 // Checked:
1984 bool RlvHandler::getCompositeInfo(const LLUUID& idItem, std::string* pstrName, LLViewerInventoryCategory** ppFolder) const
1985 {
1986 LLViewerInventoryCategory* pRlvRoot; LLViewerInventoryItem* pItem;
1987
1988 if ( (idItem.notNull()) && ((pRlvRoot = getSharedRoot()) != NULL) &&
1989 (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) && ((pItem = gInventory.getItem(idItem)) != NULL) )
1990 {
1991 // We know it's an item in a folder under the shared root...
1992 LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
1993 if (getAttachPoint(pFolder, true))
1994 {
1995 // ... but it could be named ".(attachpt)" in which case we need its parent
1996 pFolder = gInventory.getCategory(pFolder->getParentUUID());
1997 }
1998
1999 if ( (pFolder) && (getCompositeInfo(pFolder, pstrName)) )
2000 {
2001 if (ppFolder)
2002 *ppFolder = pFolder;
2003 return true;
2004 }
2005 }
2006 return false;
2007 }
2008#endif // RLV_EXPERIMENTAL_COMPOSITES
2009
2010#ifdef RLV_EXPERIMENTAL_COMPOSITE_FOLDING
2011 // Checked:
2012 inline bool RlvHandler::isHiddenCompositeItem(const LLUUID& idItem, const std::string& cstrItemType) const
2013 {
2014 // An item that's part of a composite folder will be hidden from @getoutfit and @getattach if:
2015 // (1) the composite name specifies either a wearable layer or an attachment point
2016 // (2) the specified wearable layer or attachment point is worn and resides in the folder
2017 // (3) cstrItemType isn't the specified wearable layer or attach point
2018 //
2019 // Example: #RLV/Separates/Shoes/ChiChi Pumps/.[shoes] with items: "Shoe Base", "Shoe (left foot)" and "Shoe (right foot)"
2020 // -> as long as "Shoe Base" is worn, @getattach should not reflect "left foot", nor "right foot"
2021 std::string strComposite; LLViewerInventoryCategory* pFolder;
2022 EWearableType type; S32 idxAttachPt;
2023 if ( (getCompositeInfo(idItem, &strComposite, &pFolder)) && (cstrItemType != strComposite) )
2024 {
2025 LLUUID idCompositeItem;
2026 if ((type = LLWearable::typeNameToType(strComposite)) != WT_INVALID)
2027 {
2028 idCompositeItem = gAgent.getWearableItem(type);
2029 }
2030 else if ((idxAttachPt = getAttachPointIndex(strComposite, true)) != 0)
2031 {
2032 LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachmentPt;
2033 if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) &&
2034 ((pAttachmentPt = get_if_there(pAvatar->mAttachmentPoints, idxAttachPt, (LLViewerJointAttachment*)NULL)) != NULL) )
2035 {
2036 idCompositeItem = pAttachmentPt->getItemID();
2037 }
2038 }
2039
2040 if ( (idCompositeItem.notNull()) && (gInventory.isObjectDescendentOf(idCompositeItem, pFolder->getUUID())) )
2041 return true;
2042 }
2043 return false;
2044 }
2045#endif // RLV_EXPERIMENTAL_COMPOSITEFOLDING
2046
2047#ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2048 // Checked:
2049 bool RlvHandler::canTakeOffComposite(const LLInventoryCategory* pFolder) const
2050 {
2051 if (!pFolder) // If there's no folder then there is nothing to take off
2052 return false;
2053
2054 LLInventoryModel::cat_array_t folders;
2055 LLInventoryModel::item_array_t items;
2056 RlvWearableItemCollector functor(pFolder->getUUID(), true, false);
2057
2058 // Grab a list of all the items @detachthis would be detaching/unwearing
2059 gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);
2060 if (!items.count())
2061 return false; // There are no wearable items in the folder so there is nothing to take off
2062
2063 LLViewerInventoryItem* pItem;
2064 for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
2065 {
2066 pItem = items.get(idxItem);
2067
2068 switch (pItem->getType())
2069 {
2070 case LLAssetType::AT_CLOTHING:
2071 {
2072 LLWearable* pWearable = gAgent.getWearableFromWearableItem(pItem->getUUID());
2073 if ( (pWearable) && (!isRemovable(pWearable->getType())) )
2074 return false; // If one clothing layer in the composite folder is unremoveable then the entire folder is
2075 }
2076 break;
2077 case LLAssetType::AT_OBJECT:
2078 {
2079 LLVOAvatar* pAvatar; LLViewerObject* pObj;
2080 if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) &&
2081 ((pObj = pAvatar->getWornAttachment(pItem->getUUID())) != NULL) && (!isDetachable(pObj)) )
2082 {
2083 return false; // If one attachment in the composite folder is undetachable then the entire folder is
2084 }
2085 }
2086 break;
2087 #ifdef LL_GNUC
2088 default:
2089 break;
2090 #endif // LL_GNUC
2091 }
2092 }
2093 return true;
2094 }
2095#endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2096
2097// ============================================================================
2098// Event handlers
2099//
2100
2101// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
2102void RlvHandler::onForceDetach(const LLUUID& idObj, const std::string& strOption) const
2103{
2104 U16 nParam;
2105 if (strOption.empty())
2106 {
2107 // Simulate right-click / Take Off > Detach All
2108 LLAgent::userRemoveAllAttachments(NULL);
2109 }
2110 else if (m_AttachLookup.getExactMatchParam(strOption, nParam))
2111 {
2112 // Simulate right-click / Take Off > Detach > ...
2113 LLVOAvatar* pAvatar; LLViewerJointAttachment* pAttachmentPt;
2114 if ( ((pAvatar = gAgent.getAvatarObject()) != NULL) && // Make sure we're actually wearing something on the attachment point
2115 ((pAttachmentPt = get_if_there(pAvatar->mAttachmentPoints, (S32)nParam, (LLViewerJointAttachment*)NULL)) != NULL) &&
2116 (isStrippable(pAttachmentPt->getItemID())) ) // ... and that it's not marked as "nostrip"
2117 {
2118 #ifdef RLV_EXPERIMENTAL_COMPOSITES
2119 // If we're stripping something that's part of a composite folder then we should @detachthis instead
2120 if (isCompositeDescendent(pAttachmentPt->getItemID()))
2121 {
2122 std::string strCmd = "detachthis:" + strOption + "=force";
2123 #ifdef RLV_DEBUG
2124 RLV_INFOS << "\t- '" << strOption << "' belongs to composite folder: @" << strCmd << LL_ENDL;
2125 #endif // RLV_DEBUG
2126 processForceCommand(idObj, RlvCommand(strCmd));
2127 }
2128 else
2129 #endif // RLV_EXPERIMENTAL_COMPOSITES
2130 {
2131 handle_detach_from_avatar(pAttachmentPt);
2132 }
2133 }
2134 }
2135 else
2136 {
2137 // Force detach single folder
2138 onForceWear(strOption, false, false);
2139 }
2140}
2141
2142// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0d
2143void RlvHandler::onForceRemOutfit(const LLUUID& idObj, const std::string& strOption) const
2144{
2145 EWearableType typeOption = LLWearable::typeNameToType(strOption), type;
2146 if ( (WT_INVALID == typeOption) && (!strOption.empty()) )
2147 return;
2148
2149 // Before we had an option and optionless branch, but with the addition of composites and nostrip there's less duplication this way
2150 for (int idxType = 0; idxType < WT_COUNT; idxType++)
2151 {
2152 type = (EWearableType)idxType;
2153 if (LLAssetType::AT_CLOTHING != LLWearable::typeToAssetType(type))
2154 continue; // Only strip clothing, not bodyparts
2155
2156 if ( ((typeOption == type) || (strOption.empty())) && (gAgent.getWearable(type)) && (isStrippable(gAgent.getWearableItem(type))) )
2157 {
2158 #ifdef RLV_EXPERIMENTAL_COMPOSITES
2159 // If we're stripping something that's part of a composite folder then we should @detachthis instead
2160 if (isCompositeDescendent(gAgent.getWearableItem(type)))
2161 {
2162 std::string strCmd = "detachthis:" + LLWearable::typeToTypeName(type) + "=force";
2163 #ifdef RLV_DEBUG
2164 RLV_INFOS << "\t- '" << LLWearable::typeToTypeName(type) << "' is composite descendent: @" << strCmd << LL_ENDL;
2165 #endif // RLV_DEBUG
2166 processForceCommand(idObj, RlvCommand(strCmd));
2167 }
2168 else
2169 #endif // RLV_EXPERIMENTAL_COMPOSITES
2170 {
2171 gAgent.removeWearable(type);
2172 }
2173 }
2174 }
2175}
2176
2177// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0g
2178bool RlvHandler::onForceSit(const LLUUID& idObj, const std::string& strOption) const
2179{
2180 LLViewerObject* pObject = NULL; LLUUID idTarget(strOption);
2181 // Sanity checking - we need to know about the object and it should identify a prim/linkset
2182 if ( (idTarget.isNull()) || ((pObject = gObjectList.findObject(idTarget)) == NULL) || (LL_PCODE_VOLUME != pObject->getPCode()) )
2183 return false;
2184
2185 // Don't force sit if:
2186 // 1) currently sitting and prevented from standing up
2187 // 2) prevented from sitting
2188 // 3) @sittp=n restricted (except if @sittp=n was issued by the same prim that's currently force sitting the avie)
2189 if ( ( (hasBehaviour(RLV_BHVR_UNSIT)) && (gAgent.getAvatarObject()) && (gAgent.getAvatarObject()->mIsSitting) ) ||
2190 ( (hasBehaviour(RLV_BHVR_SIT)) ) ||
2191 ( (hasBehaviourExcept(RLV_BHVR_SITTP, idObj)) &&
2192 (dist_vec_squared(gAgent.getPositionGlobal(), pObject->getPositionGlobal()) > 1.5f * 1.5f) ))
2193 {
2194 return false;
2195 }
2196
2197 // Copy/paste from handle_sit_or_stand() [see http://wiki.secondlife.com/wiki/AgentRequestSit]
2198 gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit);
2199 gMessageSystem->nextBlockFast(_PREHASH_AgentData);
2200 gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
2201 gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
2202 gMessageSystem->nextBlockFast(_PREHASH_TargetObject);
2203 gMessageSystem->addUUIDFast(_PREHASH_TargetID, pObject->mID);
2204 // Offset: "a rough position in local coordinates for the edge to sit on"
2205 // (we might not even be looking at the object so I don't think we can supply the offset to an edge)
2206 gMessageSystem->addVector3Fast(_PREHASH_Offset, LLVector3::zero);
2207 pObject->getRegion()->sendReliableMessage();
2208
2209 return true;
2210}
2211
2212void RlvHandler::onForceWear(const std::string& strPath, bool fAttach, bool fMatchAll) const
2213{
2214 // See LLWearableBridge::wearOnAvatar(): don't wear anything until initial wearables are loaded, can destroy clothing items
2215 if (!gAgent.areWearablesLoaded())
2216 return;
2217
2218 LLViewerInventoryCategory* pFolder = getSharedFolder(strPath);
2219 if (!pFolder) // Folder not found = nothing to attach
2220 return;
2221
2222 LLInventoryModel::cat_array_t folders;
2223 LLInventoryModel::item_array_t items;
2224 RlvWearableItemCollector functor(pFolder->getUUID(), fAttach, fMatchAll);
2225
2226 // Grab a list of all the items we'll be wearing/attaching
2227 gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);
2228
2229 LLViewerInventoryItem* pItem;
2230 for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
2231 {
2232 pItem = items.get(idxItem);
2233
2234 switch (pItem->getType())
2235 {
2236 case LLAssetType::AT_CLOTHING:
2237 case LLAssetType::AT_BODYPART:
2238 {
2239 LLWearable* pWearable = gAgent.getWearableFromWearableItem(pItem->getUUID());
2240
2241 #ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2242 // If we're already wearing something on this layer then we have to check if it isn't part of a composite
2243 // folder that has at least one unremovable item (in which case we can't wear or remove this item)
2244 LLViewerInventoryCategory* pCompositeFolder;
2245 if ( (!pWearable) || (!getCompositeInfo(pItem->getUUID(), NULL, &pCompositeFolder)) ||
2246 (canTakeOffComposite(pFolder)))
2247 {
2248 #endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2249 if (fAttach)
2250 {
2251 // Simulate wearing a clothing item from inventory (right click / "Wear")
2252 // LLWearableBridge::performAction() => LLWearableBridge::wearOnAvatar() => wear_inventory_item_on_avatar()
2253 wear_inventory_item_on_avatar(pItem);
2254 }
2255 else
2256 {
2257 if ( (pWearable) && (LLAssetType::AT_CLOTHING == pItem->getType()) )
2258 gAgent.removeWearable(pWearable->getType());
2259 }
2260 #ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2261 }
2262 #endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2263 }
2264 break;
2265 case LLAssetType::AT_OBJECT:
2266 {
2267 LLVOAvatar* pAvatar = gAgent.getAvatarObject();
2268 LLViewerObject* pObj;
2269
2270 #ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2271 // If we're already wearing something on this attach point then we have to check if it isn't part of a composite
2272 // folder that has at least one unremovable item (in which case we can't attach or detach this item)
2273 LLViewerInventoryCategory* pCompositeFolder;
2274 if ( (pAvatar) &&
2275 ( ((pObj = pAvatar->getWornAttachment(pItem->getUUID())) == NULL) ||
2276 (!getCompositeInfo(pItem->getUUID(), NULL, &pCompositeFolder)) || (canTakeOffComposite(pFolder)) ) )
2277 {
2278 #endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2279 if (fAttach)
2280 {
2281 // Simulate wearing an object to a specific attachment point (copy/paste to suppress replacement dialog)
2282 // LLAttachObject::handleEvent() => rez_attachment()
2283 LLViewerJointAttachment* pAttachPt = getAttachPoint(pItem, true);
2284 if ( (pAttachPt) && (isDetachable(pAttachPt)) )
2285 {
2286 #if RLV_TARGET < RLV_MAKE_TARGET(1, 23, 0) // Version: 1.22.11
2287 LLAttachmentRezAction* rez_action = new LLAttachmentRezAction;
2288 rez_action->mItemID = pItem->getUUID();
2289 rez_action->mAttachPt = getAttachPointIndex(pAttachPt->getName(), true);
2290
2291 confirm_replace_attachment_rez(0/*YES*/, (void*)rez_action); // (Will call delete on rez_action)
2292 #else // Version: 1.23.4
2293 LLSD payload;
2294 payload["item_id"] = pItem->getUUID();
2295 payload["attachment_point"] = getAttachPointIndex(pAttachPt->getName(), true);
2296
2297 LLNotifications::instance().forceResponse(
2298 LLNotification::Params("ReplaceAttachment").payload(payload), 0/*YES*/);
2299 #endif
2300 }
2301 }
2302 else
2303 {
2304 if ( (pAvatar) && ((pObj = pAvatar->getWornAttachment(pItem->getUUID())) != NULL) )
2305 {
2306 LLViewerJointAttachment* pAttachment = pAvatar->getTargetAttachmentPoint(pObj);
2307 if (pAttachment)
2308 handle_detach_from_avatar(pAttachment);
2309 }
2310 }
2311 #ifdef RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2312 }
2313 #endif // RLV_EXPERIMENTAL_COMPOSITE_LOCKING
2314 }
2315 break;
2316 #ifdef LL_GNUC
2317 default:
2318 break;
2319 #endif // LL_GNUC
2320 }
2321 }
2322}
2323
2324// Checked: 2009-07-12 (RLVa-1.0.0h) | Modified: RLVa-0.2.0g
2325bool RlvHandler::onGetPath(const LLUUID &uuid, const std::string& strOption, std::string& strReply) const
2326{
2327 // Sanity check - no need to go through all this trouble if we don't have a shared root
2328 LLViewerInventoryCategory* pRlvRoot = getSharedRoot();
2329 if (!pRlvRoot)
2330 return false;
2331
2332 LLUUID idItem;
2333
2334 // <option> can be a clothing layer
2335 EWearableType layerType = LLWearable::typeNameToType(strOption);
2336 if (WT_INVALID != layerType)
2337 {
2338 idItem = gAgent.getWearableItem(layerType);
2339 }
2340 else
2341 {
2342 LLViewerJointAttachment* pAttachPt = NULL;
2343
2344 // ... or it can be empty
2345 if (strOption.empty())
2346 {
2347 // (in which case we act on the object that issued the command)
2348 LLViewerObject* pObj = gObjectList.findObject(uuid);
2349 if ( (pObj) && (pObj->isAttachment()) && (gAgent.getAvatarObject()) )
2350 pAttachPt = gAgent.getAvatarObject()->getTargetAttachmentPoint(pObj);
2351 }
2352 else
2353 {
2354 // ... or it can specify an attach point
2355 pAttachPt = getAttachPoint(strOption, true);
2356 }
2357
2358 // If we found something, get its inventory item UUID
2359 if (pAttachPt)
2360 idItem = pAttachPt->getItemID();
2361 }
2362
2363 // If we found something and it's under the shared root, then get its path
2364 if ( (!idItem.isNull()) && (gInventory.isObjectDescendentOf(idItem, pRlvRoot->getUUID())) )
2365 {
2366 LLInventoryItem* pItem = gInventory.getItem(idItem);
2367 if (pItem)
2368 {
2369 // ... unless the containing folder's name specifies an attach point (or nostrip) in which case we need its parent
2370 LLViewerInventoryCategory* pFolder = gInventory.getCategory(pItem->getParentUUID());
2371 #ifdef RLV_EXTENSION_FLAG_NOSTRIP
2372 if ( (getAttachPoint(pFolder, true)) || (pFolder->getName() == ".("RLV_FOLDER_FLAG_NOSTRIP")") )
2373 #else
2374 if (getAttachPoint(pFolder, true))
2375 #endif // RLV_EXTENSION_FLAG_NOSTRIP
2376 strReply = getSharedPath(pFolder->getParentUUID());
2377 else
2378 strReply = getSharedPath(pFolder);
2379 }
2380 }
2381 return !strReply.empty();
2382}
2383
2384struct rlv_wear_info { U32 cntWorn, cntTotal, cntChildWorn, cntChildTotal; };
2385
2386// Checked: 2009-05-30 (RLVa-0.2.0e) | Modified: RLVa-0.2.0e
2387void RlvHandler::onGetInvWorn(const std::string& strPath, std::string& strReply) const
2388{
2389 // Sanity check - getAvatarObject() can't be NULL [see rlvIsWearingItem()] and the folder should exist and not be hidden
2390 LLViewerInventoryCategory* pFolder = getSharedFolder(strPath);
2391 if ((!gAgent.getAvatarObject()) || (!pFolder) || (pFolder->getName().empty()) || (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName()[0]))
2392 return;
2393
2394 // Collect everything @attachall would be attaching
2395 LLInventoryModel::cat_array_t folders;
2396 LLInventoryModel::item_array_t items;
2397 RlvWearableItemCollector functor(pFolder->getUUID(), true, true);
2398 gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);
2399
2400 rlv_wear_info wi = {0};
2401
2402 // Add all the folders to a lookup map
2403 std::map<LLUUID, rlv_wear_info> mapFolders;
2404 mapFolders.insert(std::pair<LLUUID, rlv_wear_info>(pFolder->getUUID(), wi));
2405 for (S32 idxFolder = 0, cntFolder = folders.count(); idxFolder < cntFolder; idxFolder++)
2406 mapFolders.insert(std::pair<LLUUID, rlv_wear_info>(folders.get(idxFolder)->getUUID(), wi));
2407
2408 // Iterate over all the found items
2409 LLViewerInventoryItem* pItem; std::map<LLUUID, rlv_wear_info>::iterator itFolder;
2410 for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
2411 {
2412 pItem = items.get(idxItem);
2413
2414 // The "folded parent" is the folder this item should be considered a direct descendent of (may or may not match actual parent)
2415 const LLUUID& idParent = functor.getFoldedParent(pItem->getParentUUID());
2416
2417 // Walk up the tree: sooner or later one of the parents will be a folder in the map
2418 LLViewerInventoryCategory* pParent = gInventory.getCategory(idParent);
2419 while ( (itFolder = mapFolders.find(pParent->getUUID())) == mapFolders.end() )
2420 pParent = gInventory.getCategory(pParent->getParentUUID());
2421
2422 U32 &cntWorn = (idParent == pParent->getUUID()) ? itFolder->second.cntWorn : itFolder->second.cntChildWorn,
2423 &cntTotal = (idParent == pParent->getUUID()) ? itFolder->second.cntTotal : itFolder->second.cntChildTotal;
2424
2425 if (rlvIsWearingItem(pItem))
2426 cntWorn++;
2427 cntTotal++;
2428 }
2429
2430 // Extract the result for the main folder
2431 itFolder = mapFolders.find(pFolder->getUUID());
2432 wi.cntWorn = itFolder->second.cntWorn;
2433 wi.cntTotal = itFolder->second.cntTotal;
2434 mapFolders.erase(itFolder);
2435
2436 // Build the result for each child folder
2437 for (itFolder = mapFolders.begin(); itFolder != mapFolders.end(); ++itFolder)
2438 {
2439 rlv_wear_info& wiFolder = itFolder->second;
2440
2441 wi.cntChildWorn += wiFolder.cntWorn + wiFolder.cntChildWorn;
2442 wi.cntChildTotal += wiFolder.cntTotal + wiFolder.cntChildTotal;
2443
2444 strReply += llformat(",%s|%d%d", gInventory.getCategory(itFolder->first)->getName().c_str(),
2445 (0 == wiFolder.cntTotal) ? 0 : (0 == wiFolder.cntWorn) ? 1 : (wiFolder.cntWorn != wiFolder.cntTotal) ? 2 : 3,
2446 (0 == wiFolder.cntChildTotal) ? 0 : (0 == wiFolder.cntChildWorn) ? 1 : (wiFolder.cntChildWorn != wiFolder.cntChildTotal) ? 2 : 3
2447 );
2448 }
2449
2450 // Now just prepend the root and done
2451 strReply = llformat("|%d%d", (0 == wi.cntTotal) ? 0 : (0 == wi.cntWorn) ? 1 : (wi.cntWorn != wi.cntTotal) ? 2 : 3,
2452 (0 == wi.cntChildTotal) ? 0 : (0 == wi.cntChildWorn) ? 1 : (wi.cntChildWorn != wi.cntChildTotal) ? 2: 3) + strReply;
2453}
2454
2455// (In case anyone cares: this isn't used in public builds)
2456bool RlvHandler::getWornInfo(const LLInventoryCategory* pFolder, U8& wiFolder, U8& wiChildren) const
2457{
2458 // Sanity check - getAvatarObject() can't be NULL [see rlvIsWearingItem()] and the folder should exist and not be hidden
2459 if ((!gAgent.getAvatarObject()) || (!pFolder) || (pFolder->getName().empty()) || (RLV_FOLDER_PREFIX_HIDDEN == pFolder->getName()[0]))
2460 return false;
2461
2462 LLInventoryModel::cat_array_t folders;
2463 LLInventoryModel::item_array_t items;
2464 RlvWearableItemCollector functor(pFolder->getUUID(), true, true);
2465 gInventory.collectDescendentsIf(pFolder->getUUID(), folders, items, FALSE, functor);
2466
2467 LLViewerInventoryItem* pItem;
2468 U32 cntWorn = 0, cntTotal = 0, cntChildWorn = 0, cntChildTotal = 0;
2469 for (S32 idxItem = 0, cntItem = items.count(); idxItem < cntItem; idxItem++)
2470 {
2471 pItem = items.get(idxItem);
2472
2473 bool fDirectDescendent = (pFolder->getUUID() == functor.getFoldedParent(pItem->getParentUUID()));
2474 U32 &refWorn = (fDirectDescendent) ? cntWorn : cntChildWorn, &refTotal = (fDirectDescendent) ? cntTotal : cntChildTotal;
2475
2476 if (rlvIsWearingItem(pItem))
2477 refWorn++;
2478 refTotal++;
2479 }
2480
2481 wiFolder = (0 == cntTotal + cntChildTotal) ? 0 : (0 == cntWorn + cntChildWorn) ? 1 :
2482 (cntWorn + cntChildWorn != cntTotal + cntChildTotal) ? 2 : 3;
2483 wiChildren = (0 == cntChildTotal) ? 0 : (0 == cntChildWorn) ? 1 : (cntChildWorn != cntChildTotal) ? 2 : 3;
2484
2485 return true;
2486}
2487
2488// ============================================================================
2489// Initialization helper functions
2490//
2491
2492BOOL RlvHandler::setEnabled(BOOL fEnable)
2493{
2494 if (m_fEnabled == fEnable)
2495 return fEnable;
2496
2497 if (fEnable)
2498 {
2499 if (gSavedSettings.controlExists(RLV_SETTING_NOSETENV))
2500 fNoSetEnv = gSavedSettings.getBOOL(RLV_SETTING_NOSETENV);
2501 if (gSavedSettings.controlExists(RLV_SETTING_ENABLELEGACYNAMING))
2502 fLegacyNaming = gSavedSettings.getBOOL(RLV_SETTING_ENABLELEGACYNAMING);
2503 if (gSavedSettings.controlExists(RLV_SETTING_SHOWNAMETAGS))
2504 RlvSettings::fShowNameTags = gSavedSettings.getBOOL(RLV_SETTING_SHOWNAMETAGS);
2505
2506 RlvCommand::initLookupTable();
2507 gRlvHandler.addObserver(new RlvExtGetSet());
2508
2509 if (LLStartUp::getStartupState() >= STATE_CLEANUP)
2510 fetchSharedInventory();
2511
2512 m_fEnabled = TRUE;
2513 }
2514 else if (canDisable())
2515 {
2516 #ifdef RLV_DEBUG
2517 RLV_INFOS << "Disabling RLV:" << LL_ENDL;
2518 #endif // RLV_DEBUG
2519
2520 gRlvHandler.clearState();
2521
2522 #ifdef RLV_DEBUG
2523 RLV_INFOS << "\t--> RLV disabled" << LL_ENDL;
2524 #endif // RLV_DEBUG
2525
2526 m_fEnabled = FALSE;
2527 }
2528
2529 #ifdef RLV_ADVANCED_MENU
2530 // 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
2531 LLMenuGL* pClientMenu = NULL;
2532 if ( (gMenuBarView) && ((pClientMenu = gMenuBarView->getChildMenuByName("Advanced", FALSE)) != NULL) )
2533 {
2534 pClientMenu->setItemVisible("RLVa", m_fEnabled);
2535 pClientMenu->setItemEnabled("RLVa", m_fEnabled);
2536 }
2537 #endif // RLV_ADVANCED_MENU
2538
2539 return m_fEnabled; // Return enabled/disabled state
2540}
2541
2542BOOL RlvHandler::canDisable()
2543{
2544 return FALSE;
2545}
2546
2547void RlvHandler::clearState()
2548{
2549 // TODO-RLVa: should restore all RLV controlled debug variables to their defaults
2550
2551 // Issue @clear on behalf of every object that has a currently active RLV restriction (even if it's just an exception)
2552 LLUUID idObj; LLViewerObject* pObj; bool fDetachable;
2553 while (m_Objects.size())
2554 {
2555 idObj = m_Objects.begin()->first; // Need a copy since after @clear the data it points to will no longer exist
2556 fDetachable = ((pObj = gObjectList.findObject(idObj)) != NULL) ? isDetachable(pObj) : true;
2557
2558 processCommand(idObj, "clear", false);
2559 if (!fDetachable)
2560 processCommand(idObj, "detachme=force", false);
2561 }
2562
2563 // Sanity check - these should all be empty after we issue @clear on the last object
2564 if ( (!m_Objects.empty()) || !(m_Exceptions.empty()) || (!m_Attachments.empty()) )
2565 {
2566 RLV_ERRS << "Object, exception or attachment map not empty after clearing state!" << LL_ENDL;
2567 m_Objects.clear();
2568 m_Exceptions.clear();
2569 m_Attachments.clear();
2570 }
2571
2572 // These all need manual clearing
2573 memset(m_LayersAdd, 0, sizeof(S16) * WT_COUNT);
2574 memset(m_LayersRem, 0, sizeof(S16) * WT_COUNT);
2575 memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT);
2576 m_AttachPending.clear();
2577 m_Emitter.clearObservers(); // <- calls delete on all active observers
2578
2579 // Clear dynamically allocated memory
2580 if (m_pGCTimer)
2581 {
2582 delete m_pGCTimer;
2583 m_pGCTimer = NULL;
2584 }
2585 if (m_pWLSnapshot)
2586 {
2587 delete m_pWLSnapshot;
2588 m_pWLSnapshot = NULL;
2589 }
2590}
2591
2592// ============================================================================