diff options
Diffstat (limited to 'linden/indra/newview/rlvhandler.cpp')
-rw-r--r-- | linden/indra/newview/rlvhandler.cpp | 2592 |
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 | ||
40 | extern const char* NEW_CATEGORY_NAME; | ||
41 | |||
42 | // ============================================================================ | ||
43 | // Static variable initialization | ||
44 | // | ||
45 | |||
46 | BOOL RlvHandler::m_fEnabled = FALSE; | ||
47 | BOOL RlvHandler::fNoSetEnv = FALSE; | ||
48 | BOOL RlvHandler::fLegacyNaming = FALSE; | ||
49 | BOOL RlvHandler::m_fFetchStarted = FALSE; | ||
50 | BOOL RlvHandler::m_fFetchComplete = FALSE; | ||
51 | RlvMultiStringSearch RlvHandler::m_AttachLookup; | ||
52 | |||
53 | const std::string RlvHandler::cstrSharedRoot = RLV_ROOT_FOLDER; | ||
54 | |||
55 | // Keep these consistent with regular RLV | ||
56 | const std::string RlvHandler::cstrBlockedRecvIM = "*** IM blocked by your viewer"; | ||
57 | const std::string RlvHandler::cstrBlockedSendIM = "*** IM blocked by sender's viewer"; | ||
58 | const std::string RlvHandler::cstrHidden = "(Hidden)"; | ||
59 | const std::string RlvHandler::cstrHiddenParcel = "(Hidden parcel)"; | ||
60 | const std::string RlvHandler::cstrHiddenRegion = "(Hidden region)"; | ||
61 | const std::string RlvHandler::cstrMsgRecvIM = | ||
62 | "The Resident you messaged is prevented from reading your instant messages at the moment, please try again later."; | ||
63 | const std::string RlvHandler::cstrMsgTpLure = | ||
64 | "The Resident you invited is prevented from accepting teleport offers. Please try again later."; | ||
65 | |||
66 | const 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 | |||
74 | rlv_handler_t gRlvHandler; | ||
75 | |||
76 | // ============================================================================ | ||
77 | // Helper functions | ||
78 | // | ||
79 | |||
80 | // Checked: 2009-07-12 (RLVa-1.0.0h) | Added: RLVa-0.2.0e | ||
81 | inline 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 | ||
94 | static 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 | ||
119 | RlvHandler::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 | ||
129 | RlvHandler::~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 | ||
141 | inline 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 | ||
149 | LLViewerJointAttachment* 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 | ||
167 | LLViewerJointAttachment* 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 | ||
197 | S32 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 | ||
213 | LLViewerJointAttachment* 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 | |||
242 | bool 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 | |||
258 | bool 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 | ||
265 | bool 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 | ||
279 | bool 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 | |||
341 | bool 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 | ||
350 | bool 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) | ||
390 | void 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) | ||
398 | void 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) | ||
406 | void 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: | ||
416 | BOOL 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 | |||
523 | BOOL 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 | ||
777 | void 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 | |||
787 | BOOL 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 | |||
943 | BOOL 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 | |||
972 | BOOL 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) | ||
1073 | BOOL 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 | |||
1303 | void 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 | ||
1330 | void 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 | ||
1447 | void 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 | ||
1513 | bool 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 | ||
1552 | void 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 | ||
1582 | size_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 | ||
1595 | std::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 | ||
1615 | void 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 | ||
1648 | void 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 | |||
1664 | void 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 | |||
1706 | const 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 | ||
1719 | bool 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 | |||
1756 | BOOL 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) | ||
1787 | class RlvSharedRootFetcher : public LLInventoryFetchDescendentsObserver | ||
1788 | { | ||
1789 | public: | ||
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) | ||
1803 | void 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 | |||
1838 | bool 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 | ||
1854 | LLViewerInventoryCategory* 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 | ||
1876 | LLViewerInventoryCategory* 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 | ||
1909 | LLViewerInventoryCategory* 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 | ||
1929 | std::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 | ||
2102 | void 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 | ||
2143 | void 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 | ||
2178 | bool 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 | |||
2212 | void 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 | ||
2325 | bool 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 | |||
2384 | struct rlv_wear_info { U32 cntWorn, cntTotal, cntChildWorn, cntChildTotal; }; | ||
2385 | |||
2386 | // Checked: 2009-05-30 (RLVa-0.2.0e) | Modified: RLVa-0.2.0e | ||
2387 | void 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) | ||
2456 | bool 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 | |||
2492 | BOOL 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 | |||
2542 | BOOL RlvHandler::canDisable() | ||
2543 | { | ||
2544 | return FALSE; | ||
2545 | } | ||
2546 | |||
2547 | void 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 | // ============================================================================ | ||