opensim-development – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Collections.Generic;
30 using System.Reflection;
31 using System.IO;
32 using System.Threading;
33 using System.Xml;
34 using log4net;
35 using Mono.Addins;
36 using Nini.Config;
37 using OpenMetaverse;
38 using OpenMetaverse.Packets;
39 using OpenSim.Framework;
40 using OpenSim.Region.Framework;
41 using OpenSim.Region.Framework.Interfaces;
42 using OpenSim.Region.Framework.Scenes;
43 using OpenSim.Region.Framework.Scenes.Serialization;
44  
45 namespace OpenSim.Region.CoreModules.Avatar.Attachments
46 {
47 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsModule")]
48 public class AttachmentsModule : IAttachmentsModule, INonSharedRegionModule
49 {
50 #region INonSharedRegionModule
51 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
52  
53 public int DebugLevel { get; set; }
54  
55 /// <summary>
56 /// Period to sleep per 100 prims in order to avoid CPU spikes when an avatar with many attachments logs in/changes
57 /// outfit or many avatars with a medium levels of attachments login/change outfit simultaneously.
58 /// </summary>
59 /// <remarks>
60 /// A value of 0 will apply no pause. The pause is specified in milliseconds.
61 /// </remarks>
62 public int ThrottlePer100PrimsRezzed { get; set; }
63  
64 private Scene m_scene;
65 private IInventoryAccessModule m_invAccessModule;
66  
67 /// <summary>
68 /// Are attachments enabled?
69 /// </summary>
70 public bool Enabled { get; private set; }
71  
72 public string Name { get { return "Attachments Module"; } }
73 public Type ReplaceableInterface { get { return null; } }
74  
75 public void Initialise(IConfigSource source)
76 {
77 IConfig config = source.Configs["Attachments"];
78 if (config != null)
79 {
80 Enabled = config.GetBoolean("Enabled", true);
81  
82 ThrottlePer100PrimsRezzed = config.GetInt("ThrottlePer100PrimsRezzed", 0);
83 }
84 else
85 {
86 Enabled = true;
87 }
88 }
89  
90 public void AddRegion(Scene scene)
91 {
92 m_scene = scene;
93 if (Enabled)
94 {
95 // Only register module with scene if it is enabled. All callers check for a null attachments module.
96 // Ideally, there should be a null attachments module for when this core attachments module has been
97 // disabled. Registering only when enabled allows for other attachments module implementations.
98 m_scene.RegisterModuleInterface<IAttachmentsModule>(this);
99 m_scene.EventManager.OnNewClient += SubscribeToClientEvents;
100 m_scene.EventManager.OnStartScript += (localID, itemID) => HandleScriptStateChange(localID, true);
101 m_scene.EventManager.OnStopScript += (localID, itemID) => HandleScriptStateChange(localID, false);
102  
103 MainConsole.Instance.Commands.AddCommand(
104 "Debug",
105 false,
106 "debug attachments log",
107 "debug attachments log [0|1]",
108 "Turn on attachments debug logging",
109 " <= 0 - turns off debug logging\n"
110 + " >= 1 - turns on attachment message debug logging",
111 HandleDebugAttachmentsLog);
112  
113 MainConsole.Instance.Commands.AddCommand(
114 "Debug",
115 false,
116 "debug attachments throttle",
117 "debug attachments throttle <ms>",
118 "Turn on attachments throttling.",
119 "This requires a millisecond value. " +
120 " == 0 - disable throttling.\n"
121 + " > 0 - sleeps for this number of milliseconds per 100 prims rezzed.",
122 HandleDebugAttachmentsThrottle);
123  
124 MainConsole.Instance.Commands.AddCommand(
125 "Debug",
126 false,
127 "debug attachments status",
128 "debug attachments status",
129 "Show current attachments debug status",
130 HandleDebugAttachmentsStatus);
131 }
132  
133 // TODO: Should probably be subscribing to CloseClient too, but this doesn't yet give us IClientAPI
134 }
135  
136 private void HandleDebugAttachmentsLog(string module, string[] args)
137 {
138 int debugLevel;
139  
140 if (!(args.Length == 4 && int.TryParse(args[3], out debugLevel)))
141 {
142 MainConsole.Instance.OutputFormat("Usage: debug attachments log [0|1]");
143 }
144 else
145 {
146 DebugLevel = debugLevel;
147 MainConsole.Instance.OutputFormat(
148 "Set event queue debug level to {0} in {1}", DebugLevel, m_scene.Name);
149 }
150 }
151  
152 private void HandleDebugAttachmentsThrottle(string module, string[] args)
153 {
154 int ms;
155  
156 if (args.Length == 4 && int.TryParse(args[3], out ms))
157 {
158 ThrottlePer100PrimsRezzed = ms;
159 MainConsole.Instance.OutputFormat(
160 "Attachments rez throttle per 100 prims is now {0} in {1}", ThrottlePer100PrimsRezzed, m_scene.Name);
161  
162 return;
163 }
164  
165 MainConsole.Instance.OutputFormat("Usage: debug attachments throttle <ms>");
166 }
167  
168 private void HandleDebugAttachmentsStatus(string module, string[] args)
169 {
170 MainConsole.Instance.OutputFormat("Settings for {0}", m_scene.Name);
171 MainConsole.Instance.OutputFormat("Debug logging level: {0}", DebugLevel);
172 MainConsole.Instance.OutputFormat("Throttle per 100 prims: {0}ms", ThrottlePer100PrimsRezzed);
173 }
174  
175 /// <summary>
176 /// Listen for client triggered running state changes so that we can persist the script's object if necessary.
177 /// </summary>
178 /// <param name='localID'></param>
179 /// <param name='itemID'></param>
180 private void HandleScriptStateChange(uint localID, bool started)
181 {
182 SceneObjectGroup sog = m_scene.GetGroupByPrim(localID);
183 if (sog != null && sog.IsAttachment)
184 {
185 if (!started)
186 {
187 // FIXME: This is a convoluted way for working out whether the script state has changed to stop
188 // because it has been manually stopped or because the stop was called in UpdateDetachedObject() below
189 // This needs to be handled in a less tangled way.
190 ScenePresence sp = m_scene.GetScenePresence(sog.AttachedAvatar);
191 if (sp.ControllingClient.IsActive)
192 sog.HasGroupChanged = true;
193 }
194 else
195 {
196 sog.HasGroupChanged = true;
197 }
198 }
199 }
200  
201 public void RemoveRegion(Scene scene)
202 {
203 m_scene.UnregisterModuleInterface<IAttachmentsModule>(this);
204  
205 if (Enabled)
206 m_scene.EventManager.OnNewClient -= SubscribeToClientEvents;
207 }
208  
209 public void RegionLoaded(Scene scene)
210 {
211 m_invAccessModule = m_scene.RequestModuleInterface<IInventoryAccessModule>();
212 }
213  
214 public void Close()
215 {
216 RemoveRegion(m_scene);
217 }
218  
219 #endregion
220  
221 #region IAttachmentsModule
222  
223 public void CopyAttachments(IScenePresence sp, AgentData ad)
224 {
225 lock (sp.AttachmentsSyncLock)
226 {
227 // Attachment objects
228 List<SceneObjectGroup> attachments = sp.GetAttachments();
229 if (attachments.Count > 0)
230 {
231 ad.AttachmentObjects = new List<ISceneObject>();
232 ad.AttachmentObjectStates = new List<string>();
233 // IScriptModule se = m_scene.RequestModuleInterface<IScriptModule>();
234 sp.InTransitScriptStates.Clear();
235  
236 foreach (SceneObjectGroup sog in attachments)
237 {
238 // We need to make a copy and pass that copy
239 // because of transfers withn the same sim
240 ISceneObject clone = sog.CloneForNewScene();
241 // Attachment module assumes that GroupPosition holds the offsets...!
242 ((SceneObjectGroup)clone).RootPart.GroupPosition = sog.RootPart.AttachedPos;
243 ((SceneObjectGroup)clone).IsAttachment = false;
244 ad.AttachmentObjects.Add(clone);
245 string state = sog.GetStateSnapshot();
246 ad.AttachmentObjectStates.Add(state);
247 sp.InTransitScriptStates.Add(state);
248 // Let's remove the scripts of the original object here
249 sog.RemoveScriptInstances(true);
250 }
251 }
252 }
253 }
254  
255 public void CopyAttachments(AgentData ad, IScenePresence sp)
256 {
257 if (ad.AttachmentObjects != null && ad.AttachmentObjects.Count > 0)
258 {
259 lock (sp.AttachmentsSyncLock)
260 sp.ClearAttachments();
261  
262 int i = 0;
263 foreach (ISceneObject so in ad.AttachmentObjects)
264 {
265 ((SceneObjectGroup)so).LocalId = 0;
266 ((SceneObjectGroup)so).RootPart.ClearUpdateSchedule();
267 so.SetState(ad.AttachmentObjectStates[i++], m_scene);
268 m_scene.IncomingCreateObject(Vector3.Zero, so);
269 }
270 }
271 }
272  
273 public void RezAttachments(IScenePresence sp)
274 {
275 if (!Enabled)
276 return;
277  
278 if (null == sp.Appearance)
279 {
280 m_log.WarnFormat("[ATTACHMENTS MODULE]: Appearance has not been initialized for agent {0}", sp.UUID);
281  
282 return;
283 }
284  
285 if (sp.GetAttachments().Count > 0)
286 {
287 if (DebugLevel > 0)
288 m_log.DebugFormat(
289 "[ATTACHMENTS MODULE]: Not doing simulator-side attachment rez for {0} in {1} as their viewer has already rezzed attachments",
290 m_scene.Name, sp.Name);
291  
292 return;
293 }
294  
295 if (DebugLevel > 0)
296 m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing any attachments for {0} from simulator-side", sp.Name);
297  
298 List<AvatarAttachment> attachments = sp.Appearance.GetAttachments();
299 foreach (AvatarAttachment attach in attachments)
300 {
301 uint attachmentPt = (uint)attach.AttachPoint;
302  
303 // m_log.DebugFormat(
304 // "[ATTACHMENTS MODULE]: Doing initial rez of attachment with itemID {0}, assetID {1}, point {2} for {3} in {4}",
305 // attach.ItemID, attach.AssetID, p, sp.Name, m_scene.RegionInfo.RegionName);
306  
307 // For some reason assetIDs are being written as Zero's in the DB -- need to track tat down
308 // But they're not used anyway, the item is being looked up for now, so let's proceed.
309 //if (UUID.Zero == assetID)
310 //{
311 // m_log.DebugFormat("[ATTACHMENT]: Cannot rez attachment in point {0} with itemID {1}", p, itemID);
312 // continue;
313 //}
314  
315 try
316 {
317 // If we're an NPC then skip all the item checks and manipulations since we don't have an
318 // inventory right now.
319 RezSingleAttachmentFromInventoryInternal(
320 sp, sp.PresenceType == PresenceType.Npc ? UUID.Zero : attach.ItemID, attach.AssetID, attachmentPt, true);
321 }
322 catch (Exception e)
323 {
324 UUID agentId = (sp.ControllingClient == null) ? default(UUID) : sp.ControllingClient.AgentId;
325 m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}",
326 attach.ItemID, attach.AssetID, attachmentPt, agentId, e.Message, e.StackTrace);
327 }
328 }
329 }
330  
331 public void DeRezAttachments(IScenePresence sp)
332 {
333 if (!Enabled)
334 return;
335  
336 if (DebugLevel > 0)
337 m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name);
338  
339 List<SceneObjectGroup> attachments = sp.GetAttachments();
340  
341 if (attachments.Count <= 0)
342 return;
343  
344 Dictionary<SceneObjectGroup, string> scriptStates = new Dictionary<SceneObjectGroup, string>();
345  
346 foreach (SceneObjectGroup so in attachments)
347 {
348 // Scripts MUST be snapshotted before the object is
349 // removed from the scene because doing otherwise will
350 // clobber the run flag
351 // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
352 // scripts performing attachment operations at the same time. Getting object states stops the scripts.
353 scriptStates[so] = PrepareScriptInstanceForSave(so, false);
354 }
355  
356 lock (sp.AttachmentsSyncLock)
357 {
358 foreach (SceneObjectGroup so in attachments)
359 UpdateDetachedObject(sp, so, scriptStates[so]);
360  
361 sp.ClearAttachments();
362 }
363 }
364  
365 public void DeleteAttachmentsFromScene(IScenePresence sp, bool silent)
366 {
367 if (!Enabled)
368 return;
369  
370 if (DebugLevel > 0)
371 m_log.DebugFormat(
372 "[ATTACHMENTS MODULE]: Deleting attachments from scene {0} for {1}, silent = {2}",
373 m_scene.RegionInfo.RegionName, sp.Name, silent);
374  
375 foreach (SceneObjectGroup sop in sp.GetAttachments())
376 {
377 sop.Scene.DeleteSceneObject(sop, silent);
378 }
379  
380 sp.ClearAttachments();
381 }
382  
383 public bool AttachObject(
384 IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool addToInventory, bool append)
385 {
386 if (!Enabled)
387 return false;
388  
389 return AttachObjectInternal(sp, group, attachmentPt, silent, addToInventory, false, append);
390 }
391  
392 /// <summary>
393 /// Internal method which actually does all the work for attaching an object.
394 /// </summary>
395 /// <returns>The object attached.</returns>
396 /// <param name='sp'></param>
397 /// <param name='group'>The object to attach.</param>
398 /// <param name='attachmentPt'></param>
399 /// <param name='silent'></param>
400 /// <param name='addToInventory'>If true then add object to user inventory.</param>
401 /// <param name='resumeScripts'>If true then scripts are resumed on the attached object.</param>
402 /// <param name='append'>Append to attachment point rather than replace.</param>
403 private bool AttachObjectInternal(
404 IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool addToInventory, bool resumeScripts, bool append)
405 {
406 if (group.GetSittingAvatarsCount() != 0)
407 {
408 if (DebugLevel > 0)
409 m_log.WarnFormat(
410 "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it",
411 group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount());
412  
413 return false;
414 }
415  
416 Vector3 attachPos = group.AbsolutePosition;
417 // If the attachment point isn't the same as the one previously used
418 // set it's offset position = 0 so that it appears on the attachment point
419 // and not in a weird location somewhere unknown.
420 if (attachmentPt != (uint)AttachmentPoint.Default && attachmentPt != group.AttachmentPoint)
421 {
422 attachPos = Vector3.Zero;
423 }
424  
425 // if the attachment point is the same as previous, make sure we get the saved
426 // position info.
427 if (attachmentPt != 0 && attachmentPt == group.RootPart.Shape.LastAttachPoint)
428 {
429 attachPos = group.RootPart.AttachedPos;
430 }
431  
432 // AttachmentPt 0 means the client chose to 'wear' the attachment.
433 if (attachmentPt == (uint)AttachmentPoint.Default)
434 {
435 // Check object for stored attachment point
436 attachmentPt = group.AttachmentPoint;
437 }
438  
439 // if we didn't find an attach point, look for where it was last attached
440 if (attachmentPt == 0)
441 {
442 attachmentPt = (uint)group.RootPart.Shape.LastAttachPoint;
443 attachPos = group.RootPart.AttachedPos;
444 group.HasGroupChanged = true;
445 }
446  
447 // if we still didn't find a suitable attachment point.......
448 if (attachmentPt == 0)
449 {
450 // Stick it on left hand with Zero Offset from the attachment point.
451 attachmentPt = (uint)AttachmentPoint.LeftHand;
452 attachPos = Vector3.Zero;
453 }
454  
455 group.AttachmentPoint = attachmentPt;
456 group.AbsolutePosition = attachPos;
457  
458 List<SceneObjectGroup> attachments = sp.GetAttachments(attachmentPt);
459  
460 if (attachments.Contains(group))
461 {
462 if (DebugLevel > 0)
463 m_log.WarnFormat(
464 "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached",
465 group.Name, group.LocalId, sp.Name, attachmentPt);
466  
467 return false;
468 }
469  
470 // If we already have 5, remove the oldest until only 4 are left. Skip over temp ones
471 while (attachments.Count >= 5)
472 {
473 if (attachments[0].FromItemID != UUID.Zero)
474 DetachSingleAttachmentToInv(sp, attachments[0]);
475 attachments.RemoveAt(0);
476 }
477  
478 // If we're not appending, remove the rest as well
479 if (attachments.Count != 0 && !append)
480 {
481 foreach (SceneObjectGroup g in attachments)
482 {
483 if (g.FromItemID != UUID.Zero)
484 DetachSingleAttachmentToInv(sp, g);
485 }
486 }
487  
488 lock (sp.AttachmentsSyncLock)
489 {
490 if (addToInventory && sp.PresenceType != PresenceType.Npc)
491 UpdateUserInventoryWithAttachment(sp, group, attachmentPt, append);
492  
493 AttachToAgent(sp, group, attachmentPt, attachPos, silent);
494  
495 if (resumeScripts)
496 {
497 // Fire after attach, so we don't get messy perms dialogs
498 // 4 == AttachedRez
499 group.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4);
500 group.ResumeScripts();
501 }
502  
503 // Do this last so that event listeners have access to all the effects of the attachment
504 m_scene.EventManager.TriggerOnAttach(group.LocalId, group.FromItemID, sp.UUID);
505 }
506  
507 return true;
508 }
509  
510 private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool append)
511 {
512 // Add the new attachment to inventory if we don't already have it.
513 UUID newAttachmentItemID = group.FromItemID;
514 if (newAttachmentItemID == UUID.Zero)
515 newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID;
516  
517 ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group, append);
518 }
519  
520 public SceneObjectGroup RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt)
521 {
522 if (!Enabled)
523 return null;
524  
525 if (DebugLevel > 0)
526 m_log.DebugFormat(
527 "[ATTACHMENTS MODULE]: RezSingleAttachmentFromInventory to point {0} from item {1} for {2} in {3}",
528 (AttachmentPoint)AttachmentPt, itemID, sp.Name, m_scene.Name);
529  
530 // We check the attachments in the avatar appearance here rather than the objects attached to the
531 // ScenePresence itself so that we can ignore calls by viewer 2/3 to attach objects on startup. We are
532 // already doing this in ScenePresence.MakeRootAgent(). Simulator-side attaching needs to be done
533 // because pre-outfit folder viewers (most version 1 viewers) require it.
534 bool alreadyOn = false;
535 List<AvatarAttachment> existingAttachments = sp.Appearance.GetAttachments();
536 foreach (AvatarAttachment existingAttachment in existingAttachments)
537 {
538 if (existingAttachment.ItemID == itemID)
539 {
540 alreadyOn = true;
541 break;
542 }
543 }
544  
545 if (alreadyOn)
546 {
547 if (DebugLevel > 0)
548 m_log.DebugFormat(
549 "[ATTACHMENTS MODULE]: Ignoring request by {0} to wear item {1} at {2} since it is already worn",
550 sp.Name, itemID, AttachmentPt);
551  
552 return null;
553 }
554  
555 bool append = (AttachmentPt & 0x80) != 0;
556 AttachmentPt &= 0x7f;
557  
558 return RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt, append);
559 }
560  
561 public void RezMultipleAttachmentsFromInventory(IScenePresence sp, List<KeyValuePair<UUID, uint>> rezlist)
562 {
563 if (!Enabled)
564 return;
565  
566 if (DebugLevel > 0)
567 m_log.DebugFormat(
568 "[ATTACHMENTS MODULE]: Rezzing {0} attachments from inventory for {1} in {2}",
569 rezlist.Count, sp.Name, m_scene.Name);
570  
571 foreach (KeyValuePair<UUID, uint> rez in rezlist)
572 {
573 RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value);
574 }
575 }
576  
577 public void DetachSingleAttachmentToGround(IScenePresence sp, uint soLocalId)
578 {
579 DetachSingleAttachmentToGround(sp, soLocalId, sp.AbsolutePosition, Quaternion.Identity);
580 }
581  
582 public void DetachSingleAttachmentToGround(IScenePresence sp, uint soLocalId, Vector3 absolutePos, Quaternion absoluteRot)
583 {
584 if (!Enabled)
585 return;
586  
587 if (DebugLevel > 0)
588 m_log.DebugFormat(
589 "[ATTACHMENTS MODULE]: DetachSingleAttachmentToGround() for {0}, object {1}",
590 sp.UUID, soLocalId);
591  
592 SceneObjectGroup so = m_scene.GetGroupByPrim(soLocalId);
593  
594 if (so == null)
595 return;
596  
597 if (so.AttachedAvatar != sp.UUID)
598 return;
599  
600 UUID inventoryID = so.FromItemID;
601  
602 // As per Linden spec, drop is disabled for temp attachs
603 if (inventoryID == UUID.Zero)
604 return;
605  
606 if (DebugLevel > 0)
607 m_log.DebugFormat(
608 "[ATTACHMENTS MODULE]: In DetachSingleAttachmentToGround(), object is {0} {1}, associated item is {2}",
609 so.Name, so.LocalId, inventoryID);
610  
611 lock (sp.AttachmentsSyncLock)
612 {
613 if (!m_scene.Permissions.CanRezObject(
614 so.PrimCount, sp.UUID, sp.AbsolutePosition))
615 return;
616  
617 bool changed = false;
618 if (inventoryID != UUID.Zero)
619 changed = sp.Appearance.DetachAttachment(inventoryID);
620 if (changed && m_scene.AvatarFactory != null)
621 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
622  
623 sp.RemoveAttachment(so);
624 so.FromItemID = UUID.Zero;
625  
626 SceneObjectPart rootPart = so.RootPart;
627 so.AbsolutePosition = absolutePos;
628 if (absoluteRot != Quaternion.Identity)
629 {
630 so.UpdateGroupRotationR(absoluteRot);
631 }
632 so.AttachedAvatar = UUID.Zero;
633 rootPart.SetParentLocalId(0);
634 so.ClearPartAttachmentData();
635 rootPart.ApplyPhysics(rootPart.GetEffectiveObjectFlags(), rootPart.VolumeDetectActive);
636 so.HasGroupChanged = true;
637 so.RootPart.Shape.LastAttachPoint = (byte)so.AttachmentPoint;
638 rootPart.Rezzed = DateTime.Now;
639 rootPart.RemFlag(PrimFlags.TemporaryOnRez);
640 so.AttachToBackup();
641 m_scene.EventManager.TriggerParcelPrimCountTainted();
642 rootPart.ScheduleFullUpdate();
643 rootPart.ClearUndoState();
644  
645 List<UUID> uuids = new List<UUID>();
646 uuids.Add(inventoryID);
647 m_scene.InventoryService.DeleteItems(sp.UUID, uuids);
648 sp.ControllingClient.SendRemoveInventoryItem(inventoryID);
649 }
650  
651 m_scene.EventManager.TriggerOnAttach(so.LocalId, so.UUID, UUID.Zero);
652 }
653  
654 public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so)
655 {
656 if (so.AttachedAvatar != sp.UUID)
657 {
658 m_log.WarnFormat(
659 "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}",
660 so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName);
661  
662 return;
663 }
664  
665 if (DebugLevel > 0)
666 m_log.DebugFormat(
667 "[ATTACHMENTS MODULE]: Detaching object {0} {1} (FromItemID {2}) for {3} in {4}",
668 so.Name, so.LocalId, so.FromItemID, sp.Name, m_scene.Name);
669  
670 // Scripts MUST be snapshotted before the object is
671 // removed from the scene because doing otherwise will
672 // clobber the run flag
673 // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
674 // scripts performing attachment operations at the same time. Getting object states stops the scripts.
675 string scriptedState = PrepareScriptInstanceForSave(so, true);
676  
677 lock (sp.AttachmentsSyncLock)
678 {
679 // Save avatar attachment information
680 // m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID);
681  
682 bool changed = sp.Appearance.DetachAttachment(so.FromItemID);
683 if (changed && m_scene.AvatarFactory != null)
684 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
685  
686 sp.RemoveAttachment(so);
687 UpdateDetachedObject(sp, so, scriptedState);
688 }
689 }
690  
691 public void UpdateAttachmentPosition(SceneObjectGroup sog, Vector3 pos)
692 {
693 if (!Enabled)
694 return;
695  
696 sog.UpdateGroupPosition(pos);
697 sog.HasGroupChanged = true;
698 }
699  
700 #endregion
701  
702 #region AttachmentModule private methods
703  
704 // This is public but is not part of the IAttachmentsModule interface.
705 // RegionCombiner module needs to poke at it to deliver client events.
706 // This breaks the encapsulation of the module and should get fixed somehow.
707 public void SubscribeToClientEvents(IClientAPI client)
708 {
709 client.OnRezSingleAttachmentFromInv += Client_OnRezSingleAttachmentFromInv;
710 client.OnRezMultipleAttachmentsFromInv += Client_OnRezMultipleAttachmentsFromInv;
711 client.OnObjectAttach += Client_OnObjectAttach;
712 client.OnObjectDetach += Client_OnObjectDetach;
713 client.OnDetachAttachmentIntoInv += Client_OnDetachAttachmentIntoInv;
714 client.OnObjectDrop += Client_OnObjectDrop;
715 }
716  
717 // This is public but is not part of the IAttachmentsModule interface.
718 // RegionCombiner module needs to poke at it to deliver client events.
719 // This breaks the encapsulation of the module and should get fixed somehow.
720 public void UnsubscribeFromClientEvents(IClientAPI client)
721 {
722 client.OnRezSingleAttachmentFromInv -= Client_OnRezSingleAttachmentFromInv;
723 client.OnRezMultipleAttachmentsFromInv -= Client_OnRezMultipleAttachmentsFromInv;
724 client.OnObjectAttach -= Client_OnObjectAttach;
725 client.OnObjectDetach -= Client_OnObjectDetach;
726 client.OnDetachAttachmentIntoInv -= Client_OnDetachAttachmentIntoInv;
727 client.OnObjectDrop -= Client_OnObjectDrop;
728 }
729  
730 /// <summary>
731 /// Update the attachment asset for the new sog details if they have changed.
732 /// </summary>
733 /// <remarks>
734 /// This is essential for preserving attachment attributes such as permission. Unlike normal scene objects,
735 /// these details are not stored on the region.
736 /// </remarks>
737 /// <param name="sp"></param>
738 /// <param name="grp"></param>
739 /// <param name="saveAllScripted"></param>
740 private void UpdateKnownItem(IScenePresence sp, SceneObjectGroup grp, string scriptedState)
741 {
742 if (grp.FromItemID == UUID.Zero)
743 {
744 // We can't save temp attachments
745 grp.HasGroupChanged = false;
746 return;
747 }
748  
749 // Saving attachments for NPCs messes them up for the real owner!
750 INPCModule module = m_scene.RequestModuleInterface<INPCModule>();
751 if (module != null)
752 {
753 if (module.IsNPC(sp.UUID, m_scene))
754 return;
755 }
756  
757 if (grp.HasGroupChanged)
758 {
759 m_log.DebugFormat(
760 "[ATTACHMENTS MODULE]: Updating asset for attachment {0}, attachpoint {1}",
761 grp.UUID, grp.AttachmentPoint);
762  
763 string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp, scriptedState);
764  
765 InventoryItemBase item = new InventoryItemBase(grp.FromItemID, sp.UUID);
766 item = m_scene.InventoryService.GetItem(item);
767  
768 if (item != null)
769 {
770 AssetBase asset = m_scene.CreateAsset(
771 grp.GetPartName(grp.LocalId),
772 grp.GetPartDescription(grp.LocalId),
773 (sbyte)AssetType.Object,
774 Utils.StringToBytes(sceneObjectXml),
775 sp.UUID);
776 m_scene.AssetService.Store(asset);
777  
778 item.AssetID = asset.FullID;
779 item.Description = asset.Description;
780 item.Name = asset.Name;
781 item.AssetType = asset.Type;
782 item.InvType = (int)InventoryType.Object;
783  
784 m_scene.InventoryService.UpdateItem(item);
785  
786 // If the name of the object has been changed whilst attached then we want to update the inventory
787 // item in the viewer.
788 if (sp.ControllingClient != null)
789 sp.ControllingClient.SendInventoryItemCreateUpdate(item, 0);
790 }
791  
792 grp.HasGroupChanged = false; // Prevent it being saved over and over
793 }
794 else if (DebugLevel > 0)
795 {
796 m_log.DebugFormat(
797 "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}",
798 grp.UUID, grp.AttachmentPoint);
799 }
800 }
801  
802 /// <summary>
803 /// Attach this scene object to the given avatar.
804 /// </summary>
805 /// <remarks>
806 /// This isn't publicly available since attachments should always perform the corresponding inventory
807 /// operation (to show the attach in user inventory and update the asset with positional information).
808 /// </remarks>
809 /// <param name="sp"></param>
810 /// <param name="so"></param>
811 /// <param name="attachmentpoint"></param>
812 /// <param name="attachOffset"></param>
813 /// <param name="silent"></param>
814 private void AttachToAgent(
815 IScenePresence sp, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent)
816 {
817 if (DebugLevel > 0)
818 m_log.DebugFormat(
819 "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} in pt {2} pos {3} {4}",
820 so.Name, sp.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos);
821  
822 so.DetachFromBackup();
823  
824 // Remove from database and parcel prim count
825 m_scene.DeleteFromStorage(so.UUID);
826 m_scene.EventManager.TriggerParcelPrimCountTainted();
827  
828 so.AttachedAvatar = sp.UUID;
829  
830 if (so.RootPart.PhysActor != null)
831 so.RootPart.RemoveFromPhysics();
832  
833 so.AbsolutePosition = attachOffset;
834 so.RootPart.AttachedPos = attachOffset;
835 so.IsAttachment = true;
836 so.RootPart.SetParentLocalId(sp.LocalId);
837 so.AttachmentPoint = attachmentpoint;
838  
839 sp.AddAttachment(so);
840  
841 if (!silent)
842 {
843 if (so.HasPrivateAttachmentPoint)
844 {
845 if (DebugLevel > 0)
846 m_log.DebugFormat(
847 "[ATTACHMENTS MODULE]: Killing private HUD {0} for avatars other than {1} at attachment point {2}",
848 so.Name, sp.Name, so.AttachmentPoint);
849  
850 // As this scene object can now only be seen by the attaching avatar, tell everybody else in the
851 // scene that it's no longer in their awareness.
852 m_scene.ForEachClient(
853 client =>
854 { if (client.AgentId != so.AttachedAvatar)
855 client.SendKillObject(new List<uint>() { so.LocalId });
856 });
857 }
858  
859 // Fudge below is an extremely unhelpful comment. It's probably here so that the scheduled full update
860 // will succeed, as that will not update if an attachment is selected.
861 so.IsSelected = false; // fudge....
862  
863 so.ScheduleGroupForFullUpdate();
864 }
865  
866 // In case it is later dropped again, don't let
867 // it get cleaned up
868 so.RootPart.RemFlag(PrimFlags.TemporaryOnRez);
869 }
870  
871 /// <summary>
872 /// Add a scene object as a new attachment in the user inventory.
873 /// </summary>
874 /// <param name="remoteClient"></param>
875 /// <param name="grp"></param>
876 /// <returns>The user inventory item created that holds the attachment.</returns>
877 private InventoryItemBase AddSceneObjectAsNewAttachmentInInv(IScenePresence sp, SceneObjectGroup grp)
878 {
879 if (m_invAccessModule == null)
880 return null;
881  
882 if (DebugLevel > 0)
883 m_log.DebugFormat(
884 "[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {0} {1} for {2}",
885 grp.Name, grp.LocalId, sp.Name);
886  
887 InventoryItemBase newItem
888 = m_invAccessModule.CopyToInventory(
889 DeRezAction.TakeCopy,
890 m_scene.InventoryService.GetFolderForType(sp.UUID, AssetType.Object).ID,
891 new List<SceneObjectGroup> { grp },
892 sp.ControllingClient, true)[0];
893  
894 // sets itemID so client can show item as 'attached' in inventory
895 grp.FromItemID = newItem.ID;
896  
897 return newItem;
898 }
899  
900 /// <summary>
901 /// Prepares the script instance for save.
902 /// </summary>
903 /// <remarks>
904 /// This involves triggering the detach event and getting the script state (which also stops the script)
905 /// This MUST be done outside sp.AttachmentsSyncLock, since otherwise there is a chance of deadlock if a
906 /// running script is performing attachment operations.
907 /// </remarks>
908 /// <returns>
909 /// The script state ready for persistence.
910 /// </returns>
911 /// <param name='grp'>
912 /// </param>
913 /// <param name='fireDetachEvent'>
914 /// If true, then fire the script event before we save its state.
915 /// </param>
916 private string PrepareScriptInstanceForSave(SceneObjectGroup grp, bool fireDetachEvent)
917 {
918 if (fireDetachEvent)
919 m_scene.EventManager.TriggerOnAttach(grp.LocalId, grp.FromItemID, UUID.Zero);
920  
921 using (StringWriter sw = new StringWriter())
922 {
923 using (XmlTextWriter writer = new XmlTextWriter(sw))
924 {
925 grp.SaveScriptedState(writer);
926 }
927  
928 return sw.ToString();
929 }
930 }
931  
932 private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so, string scriptedState)
933 {
934 // Don't save attachments for HG visitors, it
935 // messes up their inventory. When a HG visitor logs
936 // out on a foreign grid, their attachments will be
937 // reloaded in the state they were in when they left
938 // the home grid. This is best anyway as the visited
939 // grid may use an incompatible script engine.
940 bool saveChanged
941 = sp.PresenceType != PresenceType.Npc
942 && (m_scene.UserManagementModule == null
943 || m_scene.UserManagementModule.IsLocalGridUser(sp.UUID));
944  
945 // Remove the object from the scene so no more updates
946 // are sent. Doing this before the below changes will ensure
947 // updates can't cause "HUD artefacts"
948 m_scene.DeleteSceneObject(so, false, false);
949  
950 // Prepare sog for storage
951 so.AttachedAvatar = UUID.Zero;
952 so.RootPart.SetParentLocalId(0);
953 so.IsAttachment = false;
954  
955 if (saveChanged)
956 {
957 // We cannot use AbsolutePosition here because that would
958 // attempt to cross the prim as it is detached
959 so.ForEachPart(x => { x.GroupPosition = so.RootPart.AttachedPos; });
960  
961 UpdateKnownItem(sp, so, scriptedState);
962 }
963  
964 // Now, remove the scripts
965 so.RemoveScriptInstances(true);
966 }
967  
968 protected SceneObjectGroup RezSingleAttachmentFromInventoryInternal(
969 IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt, bool append)
970 {
971 if (m_invAccessModule == null)
972 return null;
973  
974 SceneObjectGroup objatt;
975  
976 if (itemID != UUID.Zero)
977 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
978 itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
979 false, false, sp.UUID, true);
980 else
981 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
982 null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
983 false, false, sp.UUID, true);
984  
985 if (objatt == null)
986 {
987 m_log.WarnFormat(
988 "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}",
989 itemID, sp.Name, attachmentPt);
990  
991 return null;
992 }
993  
994 if (DebugLevel > 0)
995 m_log.DebugFormat(
996 "[ATTACHMENTS MODULE]: Rezzed single object {0} with {1} prims for attachment to {2} on point {3} in {4}",
997 objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name);
998  
999 // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller.
1000 objatt.HasGroupChanged = false;
1001 bool tainted = false;
1002 if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint)
1003 tainted = true;
1004  
1005 // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal
1006 // course of events. If not, then it's probably not worth trying to recover the situation
1007 // since this is more likely to trigger further exceptions and confuse later debugging. If
1008 // exceptions can be thrown in expected error conditions (not NREs) then make this consistent
1009 // since other normal error conditions will simply return false instead.
1010 // This will throw if the attachment fails
1011 try
1012 {
1013 AttachObjectInternal(sp, objatt, attachmentPt, false, true, true, append);
1014 }
1015 catch (Exception e)
1016 {
1017 m_log.ErrorFormat(
1018 "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}",
1019 objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace);
1020  
1021 // Make sure the object doesn't stick around and bail
1022 sp.RemoveAttachment(objatt);
1023 m_scene.DeleteSceneObject(objatt, false);
1024 return null;
1025 }
1026  
1027 if (tainted)
1028 objatt.HasGroupChanged = true;
1029  
1030 if (ThrottlePer100PrimsRezzed > 0)
1031 {
1032 int throttleMs = (int)Math.Round((float)objatt.PrimCount / 100 * ThrottlePer100PrimsRezzed);
1033  
1034 if (DebugLevel > 0)
1035 m_log.DebugFormat(
1036 "[ATTACHMENTS MODULE]: Throttling by {0}ms after rez of {1} with {2} prims for attachment to {3} on point {4} in {5}",
1037 throttleMs, objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name);
1038  
1039 Thread.Sleep(throttleMs);
1040 }
1041  
1042 return objatt;
1043 }
1044  
1045 /// <summary>
1046 /// Update the user inventory to reflect an attachment
1047 /// </summary>
1048 /// <param name="sp"></param>
1049 /// <param name="AttachmentPt"></param>
1050 /// <param name="itemID"></param>
1051 /// <param name="att"></param>
1052 private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att, bool append)
1053 {
1054 // m_log.DebugFormat(
1055 // "[USER INVENTORY]: Updating attachment {0} for {1} at {2} using item ID {3}",
1056 // att.Name, sp.Name, AttachmentPt, itemID);
1057  
1058 if (UUID.Zero == itemID)
1059 {
1060 m_log.Error("[ATTACHMENTS MODULE]: Unable to save attachment. Error inventory item ID.");
1061 return;
1062 }
1063  
1064 if (0 == AttachmentPt)
1065 {
1066 m_log.Error("[ATTACHMENTS MODULE]: Unable to save attachment. Error attachment point.");
1067 return;
1068 }
1069  
1070 InventoryItemBase item = new InventoryItemBase(itemID, sp.UUID);
1071 item = m_scene.InventoryService.GetItem(item);
1072 if (item == null)
1073 return;
1074  
1075 int attFlag = append ? 0x80 : 0;
1076 bool changed = sp.Appearance.SetAttachment((int)AttachmentPt | attFlag, itemID, item.AssetID);
1077 if (changed && m_scene.AvatarFactory != null)
1078 {
1079 if (DebugLevel > 0)
1080 m_log.DebugFormat(
1081 "[ATTACHMENTS MODULE]: Queueing appearance save for {0}, attachment {1} point {2} in ShowAttachInUserInventory()",
1082 sp.Name, att.Name, AttachmentPt);
1083  
1084 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
1085 }
1086 }
1087  
1088 #endregion
1089  
1090 #region Client Event Handlers
1091  
1092 private ISceneEntity Client_OnRezSingleAttachmentFromInv(IClientAPI remoteClient, UUID itemID, uint AttachmentPt)
1093 {
1094 if (!Enabled)
1095 return null;
1096  
1097 if (DebugLevel > 0)
1098 m_log.DebugFormat(
1099 "[ATTACHMENTS MODULE]: Rezzing attachment to point {0} from item {1} for {2}",
1100 (AttachmentPoint)AttachmentPt, itemID, remoteClient.Name);
1101  
1102 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1103  
1104 if (sp == null)
1105 {
1106 m_log.ErrorFormat(
1107 "[ATTACHMENTS MODULE]: Could not find presence for client {0} {1} in RezSingleAttachmentFromInventory()",
1108 remoteClient.Name, remoteClient.AgentId);
1109 return null;
1110 }
1111  
1112 return RezSingleAttachmentFromInventory(sp, itemID, AttachmentPt);
1113 }
1114  
1115 private void Client_OnRezMultipleAttachmentsFromInv(IClientAPI remoteClient, List<KeyValuePair<UUID, uint>> rezlist)
1116 {
1117 if (!Enabled)
1118 return;
1119  
1120 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1121 if (sp != null)
1122 RezMultipleAttachmentsFromInventory(sp, rezlist);
1123 else
1124 m_log.ErrorFormat(
1125 "[ATTACHMENTS MODULE]: Could not find presence for client {0} {1} in RezMultipleAttachmentsFromInventory()",
1126 remoteClient.Name, remoteClient.AgentId);
1127 }
1128  
1129 private void Client_OnObjectAttach(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, bool silent)
1130 {
1131 if (DebugLevel > 0)
1132 m_log.DebugFormat(
1133 "[ATTACHMENTS MODULE]: Attaching object local id {0} to {1} point {2} from ground (silent = {3})",
1134 objectLocalID, remoteClient.Name, AttachmentPt, silent);
1135  
1136 if (!Enabled)
1137 return;
1138  
1139 try
1140 {
1141 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1142  
1143 if (sp == null)
1144 {
1145 m_log.ErrorFormat(
1146 "[ATTACHMENTS MODULE]: Could not find presence for client {0} {1}", remoteClient.Name, remoteClient.AgentId);
1147 return;
1148 }
1149  
1150 // If we can't take it, we can't attach it!
1151 SceneObjectPart part = m_scene.GetSceneObjectPart(objectLocalID);
1152 if (part == null)
1153 return;
1154  
1155 if (!m_scene.Permissions.CanTakeObject(part.UUID, remoteClient.AgentId))
1156 {
1157 remoteClient.SendAgentAlertMessage(
1158 "You don't have sufficient permissions to attach this object", false);
1159  
1160 return;
1161 }
1162  
1163 bool append = (AttachmentPt & 0x80) != 0;
1164 AttachmentPt &= 0x7f;
1165  
1166 // Calls attach with a Zero position
1167 if (AttachObject(sp, part.ParentGroup, AttachmentPt, false, true, append))
1168 {
1169 if (DebugLevel > 0)
1170 m_log.Debug(
1171 "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId
1172 + ", AttachmentPoint: " + AttachmentPt);
1173  
1174 // Save avatar attachment information
1175 m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId);
1176 }
1177 }
1178 catch (Exception e)
1179 {
1180 m_log.ErrorFormat("[ATTACHMENTS MODULE]: exception upon Attach Object {0}{1}", e.Message, e.StackTrace);
1181 }
1182 }
1183  
1184 private void Client_OnObjectDetach(uint objectLocalID, IClientAPI remoteClient)
1185 {
1186 if (!Enabled)
1187 return;
1188  
1189 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1190 SceneObjectGroup group = m_scene.GetGroupByPrim(objectLocalID);
1191  
1192 if (sp != null && group != null && group.FromItemID != UUID.Zero)
1193 DetachSingleAttachmentToInv(sp, group);
1194 }
1195  
1196 private void Client_OnDetachAttachmentIntoInv(UUID itemID, IClientAPI remoteClient)
1197 {
1198 if (!Enabled)
1199 return;
1200  
1201 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1202 if (sp != null)
1203 {
1204 List<SceneObjectGroup> attachments = sp.GetAttachments();
1205  
1206 foreach (SceneObjectGroup group in attachments)
1207 {
1208 if (group.FromItemID == itemID && group.FromItemID != UUID.Zero)
1209 {
1210 DetachSingleAttachmentToInv(sp, group);
1211 return;
1212 }
1213 }
1214 }
1215 }
1216  
1217 private void Client_OnObjectDrop(uint soLocalId, IClientAPI remoteClient)
1218 {
1219 if (!Enabled)
1220 return;
1221  
1222 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1223 if (sp != null)
1224 DetachSingleAttachmentToGround(sp, soLocalId);
1225 }
1226  
1227 #endregion
1228 }
1229 }