clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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  
249 // Scripts of the originals will be removed when the Agent is successfully removed.
250 // sog.RemoveScriptInstances(true);
251 }
252 }
253 }
254 }
255  
256 public void CopyAttachments(AgentData ad, IScenePresence sp)
257 {
258 if (ad.AttachmentObjects != null && ad.AttachmentObjects.Count > 0)
259 {
260 lock (sp.AttachmentsSyncLock)
261 sp.ClearAttachments();
262  
263 int i = 0;
264 foreach (ISceneObject so in ad.AttachmentObjects)
265 {
266 ((SceneObjectGroup)so).LocalId = 0;
267 ((SceneObjectGroup)so).RootPart.ClearUpdateSchedule();
268 so.SetState(ad.AttachmentObjectStates[i++], m_scene);
269 m_scene.IncomingCreateObject(Vector3.Zero, so);
270 }
271 }
272 }
273  
274 public void RezAttachments(IScenePresence sp)
275 {
276 if (!Enabled)
277 return;
278  
279 if (null == sp.Appearance)
280 {
281 m_log.WarnFormat("[ATTACHMENTS MODULE]: Appearance has not been initialized for agent {0}", sp.UUID);
282  
283 return;
284 }
285  
286 if (sp.GetAttachments().Count > 0)
287 {
288 if (DebugLevel > 0)
289 m_log.DebugFormat(
290 "[ATTACHMENTS MODULE]: Not doing simulator-side attachment rez for {0} in {1} as their viewer has already rezzed attachments",
291 m_scene.Name, sp.Name);
292  
293 return;
294 }
295  
296 if (DebugLevel > 0)
297 m_log.DebugFormat("[ATTACHMENTS MODULE]: Rezzing any attachments for {0} from simulator-side", sp.Name);
298  
299 List<AvatarAttachment> attachments = sp.Appearance.GetAttachments();
300 foreach (AvatarAttachment attach in attachments)
301 {
302 uint attachmentPt = (uint)attach.AttachPoint;
303  
304 // m_log.DebugFormat(
305 // "[ATTACHMENTS MODULE]: Doing initial rez of attachment with itemID {0}, assetID {1}, point {2} for {3} in {4}",
306 // attach.ItemID, attach.AssetID, p, sp.Name, m_scene.RegionInfo.RegionName);
307  
308 // For some reason assetIDs are being written as Zero's in the DB -- need to track tat down
309 // But they're not used anyway, the item is being looked up for now, so let's proceed.
310 //if (UUID.Zero == assetID)
311 //{
312 // m_log.DebugFormat("[ATTACHMENT]: Cannot rez attachment in point {0} with itemID {1}", p, itemID);
313 // continue;
314 //}
315  
316 try
317 {
318 // If we're an NPC then skip all the item checks and manipulations since we don't have an
319 // inventory right now.
320 RezSingleAttachmentFromInventoryInternal(
321 sp, sp.PresenceType == PresenceType.Npc ? UUID.Zero : attach.ItemID, attach.AssetID, attachmentPt, true);
322 }
323 catch (Exception e)
324 {
325 UUID agentId = (sp.ControllingClient == null) ? default(UUID) : sp.ControllingClient.AgentId;
326 m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}",
327 attach.ItemID, attach.AssetID, attachmentPt, agentId, e.Message, e.StackTrace);
328 }
329 }
330 }
331  
332 public void DeRezAttachments(IScenePresence sp)
333 {
334 if (!Enabled)
335 return;
336  
337 if (DebugLevel > 0)
338 m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name);
339  
340 List<SceneObjectGroup> attachments = sp.GetAttachments();
341  
342 if (attachments.Count <= 0)
343 return;
344  
345 Dictionary<SceneObjectGroup, string> scriptStates = new Dictionary<SceneObjectGroup, string>();
346  
347 foreach (SceneObjectGroup so in attachments)
348 {
349 // Scripts MUST be snapshotted before the object is
350 // removed from the scene because doing otherwise will
351 // clobber the run flag
352 // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
353 // scripts performing attachment operations at the same time. Getting object states stops the scripts.
354 scriptStates[so] = PrepareScriptInstanceForSave(so, false);
355 }
356  
357 lock (sp.AttachmentsSyncLock)
358 {
359 foreach (SceneObjectGroup so in attachments)
360 UpdateDetachedObject(sp, so, scriptStates[so]);
361  
362 sp.ClearAttachments();
363 }
364 }
365  
366 public void DeleteAttachmentsFromScene(IScenePresence sp, bool silent)
367 {
368 if (!Enabled)
369 return;
370  
371 if (DebugLevel > 0)
372 m_log.DebugFormat(
373 "[ATTACHMENTS MODULE]: Deleting attachments from scene {0} for {1}, silent = {2}",
374 m_scene.RegionInfo.RegionName, sp.Name, silent);
375  
376 foreach (SceneObjectGroup sop in sp.GetAttachments())
377 {
378 sop.Scene.DeleteSceneObject(sop, silent);
379 }
380  
381 sp.ClearAttachments();
382 }
383  
384 public bool AttachObject(
385 IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool addToInventory, bool append)
386 {
387 if (!Enabled)
388 return false;
389  
390 return AttachObjectInternal(sp, group, attachmentPt, silent, addToInventory, false, append);
391 }
392  
393 /// <summary>
394 /// Internal method which actually does all the work for attaching an object.
395 /// </summary>
396 /// <returns>The object attached.</returns>
397 /// <param name='sp'></param>
398 /// <param name='group'>The object to attach.</param>
399 /// <param name='attachmentPt'></param>
400 /// <param name='silent'></param>
401 /// <param name='addToInventory'>If true then add object to user inventory.</param>
402 /// <param name='resumeScripts'>If true then scripts are resumed on the attached object.</param>
403 /// <param name='append'>Append to attachment point rather than replace.</param>
404 private bool AttachObjectInternal(
405 IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent, bool addToInventory, bool resumeScripts, bool append)
406 {
407 if (group.GetSittingAvatarsCount() != 0)
408 {
409 if (DebugLevel > 0)
410 m_log.WarnFormat(
411 "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it",
412 group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount());
413  
414 return false;
415 }
416  
417 Vector3 attachPos = group.AbsolutePosition;
418 // If the attachment point isn't the same as the one previously used
419 // set it's offset position = 0 so that it appears on the attachment point
420 // and not in a weird location somewhere unknown.
421 if (attachmentPt != (uint)AttachmentPoint.Default && attachmentPt != group.AttachmentPoint)
422 {
423 attachPos = Vector3.Zero;
424 }
425  
426 // if the attachment point is the same as previous, make sure we get the saved
427 // position info.
428 if (attachmentPt != 0 && attachmentPt == group.RootPart.Shape.LastAttachPoint)
429 {
430 attachPos = group.RootPart.AttachedPos;
431 }
432  
433 // AttachmentPt 0 means the client chose to 'wear' the attachment.
434 if (attachmentPt == (uint)AttachmentPoint.Default)
435 {
436 // Check object for stored attachment point
437 attachmentPt = group.AttachmentPoint;
438 }
439  
440 // if we didn't find an attach point, look for where it was last attached
441 if (attachmentPt == 0)
442 {
443 attachmentPt = (uint)group.RootPart.Shape.LastAttachPoint;
444 attachPos = group.RootPart.AttachedPos;
445 group.HasGroupChanged = true;
446 }
447  
448 // if we still didn't find a suitable attachment point.......
449 if (attachmentPt == 0)
450 {
451 // Stick it on left hand with Zero Offset from the attachment point.
452 attachmentPt = (uint)AttachmentPoint.LeftHand;
453 attachPos = Vector3.Zero;
454 }
455  
456 group.AttachmentPoint = attachmentPt;
457 group.AbsolutePosition = attachPos;
458  
459 List<SceneObjectGroup> attachments = sp.GetAttachments(attachmentPt);
460  
461 if (attachments.Contains(group))
462 {
463 if (DebugLevel > 0)
464 m_log.WarnFormat(
465 "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached",
466 group.Name, group.LocalId, sp.Name, attachmentPt);
467  
468 return false;
469 }
470  
471 // If we already have 5, remove the oldest until only 4 are left. Skip over temp ones
472 while (attachments.Count >= 5)
473 {
474 if (attachments[0].FromItemID != UUID.Zero)
475 DetachSingleAttachmentToInv(sp, attachments[0]);
476 attachments.RemoveAt(0);
477 }
478  
479 // If we're not appending, remove the rest as well
480 if (attachments.Count != 0 && !append)
481 {
482 foreach (SceneObjectGroup g in attachments)
483 {
484 if (g.FromItemID != UUID.Zero)
485 DetachSingleAttachmentToInv(sp, g);
486 }
487 }
488  
489 lock (sp.AttachmentsSyncLock)
490 {
491 if (addToInventory && sp.PresenceType != PresenceType.Npc)
492 UpdateUserInventoryWithAttachment(sp, group, attachmentPt, append);
493  
494 AttachToAgent(sp, group, attachmentPt, attachPos, silent);
495  
496 if (resumeScripts)
497 {
498 // Fire after attach, so we don't get messy perms dialogs
499 // 4 == AttachedRez
500 group.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4);
501 group.ResumeScripts();
502 }
503  
504 // Do this last so that event listeners have access to all the effects of the attachment
505 m_scene.EventManager.TriggerOnAttach(group.LocalId, group.FromItemID, sp.UUID);
506 }
507  
508 return true;
509 }
510  
511 private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool append)
512 {
513 // Add the new attachment to inventory if we don't already have it.
514 UUID newAttachmentItemID = group.FromItemID;
515 if (newAttachmentItemID == UUID.Zero)
516 newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID;
517  
518 ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group, append);
519 }
520  
521 public SceneObjectGroup RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt)
522 {
523 if (!Enabled)
524 return null;
525  
526 if (DebugLevel > 0)
527 m_log.DebugFormat(
528 "[ATTACHMENTS MODULE]: RezSingleAttachmentFromInventory to point {0} from item {1} for {2} in {3}",
529 (AttachmentPoint)AttachmentPt, itemID, sp.Name, m_scene.Name);
530  
531 // We check the attachments in the avatar appearance here rather than the objects attached to the
532 // ScenePresence itself so that we can ignore calls by viewer 2/3 to attach objects on startup. We are
533 // already doing this in ScenePresence.MakeRootAgent(). Simulator-side attaching needs to be done
534 // because pre-outfit folder viewers (most version 1 viewers) require it.
535 bool alreadyOn = false;
536 List<AvatarAttachment> existingAttachments = sp.Appearance.GetAttachments();
537 foreach (AvatarAttachment existingAttachment in existingAttachments)
538 {
539 if (existingAttachment.ItemID == itemID)
540 {
541 alreadyOn = true;
542 break;
543 }
544 }
545  
546 if (alreadyOn)
547 {
548 if (DebugLevel > 0)
549 m_log.DebugFormat(
550 "[ATTACHMENTS MODULE]: Ignoring request by {0} to wear item {1} at {2} since it is already worn",
551 sp.Name, itemID, AttachmentPt);
552  
553 return null;
554 }
555  
556 bool append = (AttachmentPt & 0x80) != 0;
557 AttachmentPt &= 0x7f;
558  
559 return RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt, append);
560 }
561  
562 public void RezMultipleAttachmentsFromInventory(IScenePresence sp, List<KeyValuePair<UUID, uint>> rezlist)
563 {
564 if (!Enabled)
565 return;
566  
567 if (DebugLevel > 0)
568 m_log.DebugFormat(
569 "[ATTACHMENTS MODULE]: Rezzing {0} attachments from inventory for {1} in {2}",
570 rezlist.Count, sp.Name, m_scene.Name);
571  
572 foreach (KeyValuePair<UUID, uint> rez in rezlist)
573 {
574 RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value);
575 }
576 }
577  
578 public void DetachSingleAttachmentToGround(IScenePresence sp, uint soLocalId)
579 {
580 DetachSingleAttachmentToGround(sp, soLocalId, sp.AbsolutePosition, Quaternion.Identity);
581 }
582  
583 public void DetachSingleAttachmentToGround(IScenePresence sp, uint soLocalId, Vector3 absolutePos, Quaternion absoluteRot)
584 {
585 if (!Enabled)
586 return;
587  
588 if (DebugLevel > 0)
589 m_log.DebugFormat(
590 "[ATTACHMENTS MODULE]: DetachSingleAttachmentToGround() for {0}, object {1}",
591 sp.UUID, soLocalId);
592  
593 SceneObjectGroup so = m_scene.GetGroupByPrim(soLocalId);
594  
595 if (so == null)
596 return;
597  
598 if (so.AttachedAvatar != sp.UUID)
599 return;
600  
601 UUID inventoryID = so.FromItemID;
602  
603 // As per Linden spec, drop is disabled for temp attachs
604 if (inventoryID == UUID.Zero)
605 return;
606  
607 if (DebugLevel > 0)
608 m_log.DebugFormat(
609 "[ATTACHMENTS MODULE]: In DetachSingleAttachmentToGround(), object is {0} {1}, associated item is {2}",
610 so.Name, so.LocalId, inventoryID);
611  
612 lock (sp.AttachmentsSyncLock)
613 {
614 if (!m_scene.Permissions.CanRezObject(
615 so.PrimCount, sp.UUID, sp.AbsolutePosition))
616 return;
617  
618 bool changed = false;
619 if (inventoryID != UUID.Zero)
620 changed = sp.Appearance.DetachAttachment(inventoryID);
621 if (changed && m_scene.AvatarFactory != null)
622 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
623  
624 sp.RemoveAttachment(so);
625 so.FromItemID = UUID.Zero;
626  
627 SceneObjectPart rootPart = so.RootPart;
628 so.AbsolutePosition = absolutePos;
629 if (absoluteRot != Quaternion.Identity)
630 {
631 so.UpdateGroupRotationR(absoluteRot);
632 }
633 so.AttachedAvatar = UUID.Zero;
634 rootPart.SetParentLocalId(0);
635 so.ClearPartAttachmentData();
636 rootPart.ApplyPhysics(rootPart.GetEffectiveObjectFlags(), rootPart.VolumeDetectActive);
637 so.HasGroupChanged = true;
638 so.RootPart.Shape.LastAttachPoint = (byte)so.AttachmentPoint;
639 rootPart.Rezzed = DateTime.Now;
640 rootPart.RemFlag(PrimFlags.TemporaryOnRez);
641 so.AttachToBackup();
642 m_scene.EventManager.TriggerParcelPrimCountTainted();
643 rootPart.ScheduleFullUpdate();
644 rootPart.ClearUndoState();
645  
646 List<UUID> uuids = new List<UUID>();
647 uuids.Add(inventoryID);
648 m_scene.InventoryService.DeleteItems(sp.UUID, uuids);
649 sp.ControllingClient.SendRemoveInventoryItem(inventoryID);
650 }
651  
652 m_scene.EventManager.TriggerOnAttach(so.LocalId, so.UUID, UUID.Zero);
653 }
654  
655 public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so)
656 {
657 if (so.AttachedAvatar != sp.UUID)
658 {
659 m_log.WarnFormat(
660 "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}",
661 so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName);
662  
663 return;
664 }
665  
666 if (DebugLevel > 0)
667 m_log.DebugFormat(
668 "[ATTACHMENTS MODULE]: Detaching object {0} {1} (FromItemID {2}) for {3} in {4}",
669 so.Name, so.LocalId, so.FromItemID, sp.Name, m_scene.Name);
670  
671 // Scripts MUST be snapshotted before the object is
672 // removed from the scene because doing otherwise will
673 // clobber the run flag
674 // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
675 // scripts performing attachment operations at the same time. Getting object states stops the scripts.
676 string scriptedState = PrepareScriptInstanceForSave(so, true);
677  
678 lock (sp.AttachmentsSyncLock)
679 {
680 // Save avatar attachment information
681 // m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID);
682  
683 bool changed = sp.Appearance.DetachAttachment(so.FromItemID);
684 if (changed && m_scene.AvatarFactory != null)
685 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
686  
687 sp.RemoveAttachment(so);
688 UpdateDetachedObject(sp, so, scriptedState);
689 }
690 }
691  
692 public void UpdateAttachmentPosition(SceneObjectGroup sog, Vector3 pos)
693 {
694 if (!Enabled)
695 return;
696  
697 sog.UpdateGroupPosition(pos);
698 sog.HasGroupChanged = true;
699 }
700  
701 #endregion
702  
703 #region AttachmentModule private methods
704  
705 // This is public but is not part of the IAttachmentsModule interface.
706 // RegionCombiner module needs to poke at it to deliver client events.
707 // This breaks the encapsulation of the module and should get fixed somehow.
708 public void SubscribeToClientEvents(IClientAPI client)
709 {
710 client.OnRezSingleAttachmentFromInv += Client_OnRezSingleAttachmentFromInv;
711 client.OnRezMultipleAttachmentsFromInv += Client_OnRezMultipleAttachmentsFromInv;
712 client.OnObjectAttach += Client_OnObjectAttach;
713 client.OnObjectDetach += Client_OnObjectDetach;
714 client.OnDetachAttachmentIntoInv += Client_OnDetachAttachmentIntoInv;
715 client.OnObjectDrop += Client_OnObjectDrop;
716 }
717  
718 // This is public but is not part of the IAttachmentsModule interface.
719 // RegionCombiner module needs to poke at it to deliver client events.
720 // This breaks the encapsulation of the module and should get fixed somehow.
721 public void UnsubscribeFromClientEvents(IClientAPI client)
722 {
723 client.OnRezSingleAttachmentFromInv -= Client_OnRezSingleAttachmentFromInv;
724 client.OnRezMultipleAttachmentsFromInv -= Client_OnRezMultipleAttachmentsFromInv;
725 client.OnObjectAttach -= Client_OnObjectAttach;
726 client.OnObjectDetach -= Client_OnObjectDetach;
727 client.OnDetachAttachmentIntoInv -= Client_OnDetachAttachmentIntoInv;
728 client.OnObjectDrop -= Client_OnObjectDrop;
729 }
730  
731 /// <summary>
732 /// Update the attachment asset for the new sog details if they have changed.
733 /// </summary>
734 /// <remarks>
735 /// This is essential for preserving attachment attributes such as permission. Unlike normal scene objects,
736 /// these details are not stored on the region.
737 /// </remarks>
738 /// <param name="sp"></param>
739 /// <param name="grp"></param>
740 /// <param name="saveAllScripted"></param>
741 private void UpdateKnownItem(IScenePresence sp, SceneObjectGroup grp, string scriptedState)
742 {
743 if (grp.FromItemID == UUID.Zero)
744 {
745 // We can't save temp attachments
746 grp.HasGroupChanged = false;
747 return;
748 }
749  
750 // Saving attachments for NPCs messes them up for the real owner!
751 INPCModule module = m_scene.RequestModuleInterface<INPCModule>();
752 if (module != null)
753 {
754 if (module.IsNPC(sp.UUID, m_scene))
755 return;
756 }
757  
758 if (grp.HasGroupChanged)
759 {
760 m_log.DebugFormat(
761 "[ATTACHMENTS MODULE]: Updating asset for attachment {0}, attachpoint {1}",
762 grp.UUID, grp.AttachmentPoint);
763  
764 string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp, scriptedState);
765  
766 InventoryItemBase item = new InventoryItemBase(grp.FromItemID, sp.UUID);
767 item = m_scene.InventoryService.GetItem(item);
768  
769 if (item != null)
770 {
771 AssetBase asset = m_scene.CreateAsset(
772 grp.GetPartName(grp.LocalId),
773 grp.GetPartDescription(grp.LocalId),
774 (sbyte)AssetType.Object,
775 Utils.StringToBytes(sceneObjectXml),
776 sp.UUID);
777  
778 IInventoryAccessModule invAccess = m_scene.RequestModuleInterface<IInventoryAccessModule>();
779  
780 invAccess.UpdateInventoryItemAsset(sp.UUID, item, asset);
781  
782 // If the name of the object has been changed whilst attached then we want to update the inventory
783 // item in the viewer.
784 if (sp.ControllingClient != null)
785 sp.ControllingClient.SendInventoryItemCreateUpdate(item, 0);
786 }
787  
788 grp.HasGroupChanged = false; // Prevent it being saved over and over
789 }
790 else if (DebugLevel > 0)
791 {
792 m_log.DebugFormat(
793 "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}",
794 grp.UUID, grp.AttachmentPoint);
795 }
796 }
797  
798 /// <summary>
799 /// Attach this scene object to the given avatar.
800 /// </summary>
801 /// <remarks>
802 /// This isn't publicly available since attachments should always perform the corresponding inventory
803 /// operation (to show the attach in user inventory and update the asset with positional information).
804 /// </remarks>
805 /// <param name="sp"></param>
806 /// <param name="so"></param>
807 /// <param name="attachmentpoint"></param>
808 /// <param name="attachOffset"></param>
809 /// <param name="silent"></param>
810 private void AttachToAgent(
811 IScenePresence sp, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent)
812 {
813 if (DebugLevel > 0)
814 m_log.DebugFormat(
815 "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} in pt {2} pos {3} {4}",
816 so.Name, sp.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos);
817  
818 so.DetachFromBackup();
819  
820 // Remove from database and parcel prim count
821 m_scene.DeleteFromStorage(so.UUID);
822 m_scene.EventManager.TriggerParcelPrimCountTainted();
823  
824 so.AttachedAvatar = sp.UUID;
825  
826 if (so.RootPart.PhysActor != null)
827 so.RootPart.RemoveFromPhysics();
828  
829 so.AbsolutePosition = attachOffset;
830 so.RootPart.AttachedPos = attachOffset;
831 so.IsAttachment = true;
832 so.RootPart.SetParentLocalId(sp.LocalId);
833 so.AttachmentPoint = attachmentpoint;
834  
835 sp.AddAttachment(so);
836  
837 if (!silent)
838 {
839 if (so.HasPrivateAttachmentPoint)
840 {
841 if (DebugLevel > 0)
842 m_log.DebugFormat(
843 "[ATTACHMENTS MODULE]: Killing private HUD {0} for avatars other than {1} at attachment point {2}",
844 so.Name, sp.Name, so.AttachmentPoint);
845  
846 // As this scene object can now only be seen by the attaching avatar, tell everybody else in the
847 // scene that it's no longer in their awareness.
848 m_scene.ForEachClient(
849 client =>
850 { if (client.AgentId != so.AttachedAvatar)
851 client.SendKillObject(new List<uint>() { so.LocalId });
852 });
853 }
854  
855 // Fudge below is an extremely unhelpful comment. It's probably here so that the scheduled full update
856 // will succeed, as that will not update if an attachment is selected.
857 so.IsSelected = false; // fudge....
858  
859 so.ScheduleGroupForFullUpdate();
860 }
861  
862 // In case it is later dropped again, don't let
863 // it get cleaned up
864 so.RootPart.RemFlag(PrimFlags.TemporaryOnRez);
865 }
866  
867 /// <summary>
868 /// Add a scene object as a new attachment in the user inventory.
869 /// </summary>
870 /// <param name="remoteClient"></param>
871 /// <param name="grp"></param>
872 /// <returns>The user inventory item created that holds the attachment.</returns>
873 private InventoryItemBase AddSceneObjectAsNewAttachmentInInv(IScenePresence sp, SceneObjectGroup grp)
874 {
875 if (m_invAccessModule == null)
876 return null;
877  
878 if (DebugLevel > 0)
879 m_log.DebugFormat(
880 "[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {0} {1} for {2}",
881 grp.Name, grp.LocalId, sp.Name);
882  
883 InventoryItemBase newItem
884 = m_invAccessModule.CopyToInventory(
885 DeRezAction.TakeCopy,
886 m_scene.InventoryService.GetFolderForType(sp.UUID, AssetType.Object).ID,
887 new List<SceneObjectGroup> { grp },
888 sp.ControllingClient, true)[0];
889  
890 // sets itemID so client can show item as 'attached' in inventory
891 grp.FromItemID = newItem.ID;
892  
893 return newItem;
894 }
895  
896 /// <summary>
897 /// Prepares the script instance for save.
898 /// </summary>
899 /// <remarks>
900 /// This involves triggering the detach event and getting the script state (which also stops the script)
901 /// This MUST be done outside sp.AttachmentsSyncLock, since otherwise there is a chance of deadlock if a
902 /// running script is performing attachment operations.
903 /// </remarks>
904 /// <returns>
905 /// The script state ready for persistence.
906 /// </returns>
907 /// <param name='grp'>
908 /// </param>
909 /// <param name='fireDetachEvent'>
910 /// If true, then fire the script event before we save its state.
911 /// </param>
912 private string PrepareScriptInstanceForSave(SceneObjectGroup grp, bool fireDetachEvent)
913 {
914 if (fireDetachEvent)
915 m_scene.EventManager.TriggerOnAttach(grp.LocalId, grp.FromItemID, UUID.Zero);
916  
917 using (StringWriter sw = new StringWriter())
918 {
919 using (XmlTextWriter writer = new XmlTextWriter(sw))
920 {
921 grp.SaveScriptedState(writer);
922 }
923  
924 return sw.ToString();
925 }
926 }
927  
928 private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so, string scriptedState)
929 {
930 // Don't save attachments for HG visitors, it
931 // messes up their inventory. When a HG visitor logs
932 // out on a foreign grid, their attachments will be
933 // reloaded in the state they were in when they left
934 // the home grid. This is best anyway as the visited
935 // grid may use an incompatible script engine.
936 bool saveChanged
937 = sp.PresenceType != PresenceType.Npc
938 && (m_scene.UserManagementModule == null
939 || m_scene.UserManagementModule.IsLocalGridUser(sp.UUID));
940  
941 // Remove the object from the scene so no more updates
942 // are sent. Doing this before the below changes will ensure
943 // updates can't cause "HUD artefacts"
944 m_scene.DeleteSceneObject(so, false, false);
945  
946 // Prepare sog for storage
947 so.AttachedAvatar = UUID.Zero;
948 so.RootPart.SetParentLocalId(0);
949 so.IsAttachment = false;
950  
951 if (saveChanged)
952 {
953 // We cannot use AbsolutePosition here because that would
954 // attempt to cross the prim as it is detached
955 so.ForEachPart(x => { x.GroupPosition = so.RootPart.AttachedPos; });
956  
957 UpdateKnownItem(sp, so, scriptedState);
958 }
959  
960 // Now, remove the scripts
961 so.RemoveScriptInstances(true);
962 }
963  
964 protected SceneObjectGroup RezSingleAttachmentFromInventoryInternal(
965 IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt, bool append)
966 {
967 if (m_invAccessModule == null)
968 return null;
969  
970 SceneObjectGroup objatt;
971  
972 if (itemID != UUID.Zero)
973 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
974 itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
975 false, false, sp.UUID, true);
976 else
977 objatt = m_invAccessModule.RezObject(sp.ControllingClient,
978 null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
979 false, false, sp.UUID, true);
980  
981 if (objatt == null)
982 {
983 m_log.WarnFormat(
984 "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}",
985 itemID, sp.Name, attachmentPt);
986  
987 return null;
988 }
989 else if (itemID == UUID.Zero)
990 {
991 // We need to have a FromItemID for multiple attachments on a single attach point to appear. This is
992 // true on Singularity 1.8.5 and quite possibly other viewers as well. As NPCs don't have an inventory
993 // we will satisfy this requirement by inserting a random UUID.
994 objatt.FromItemID = UUID.Random();
995 }
996  
997 if (DebugLevel > 0)
998 m_log.DebugFormat(
999 "[ATTACHMENTS MODULE]: Rezzed single object {0} with {1} prims for attachment to {2} on point {3} in {4}",
1000 objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name);
1001  
1002 // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller.
1003 objatt.HasGroupChanged = false;
1004 bool tainted = false;
1005 if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint)
1006 tainted = true;
1007  
1008 // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal
1009 // course of events. If not, then it's probably not worth trying to recover the situation
1010 // since this is more likely to trigger further exceptions and confuse later debugging. If
1011 // exceptions can be thrown in expected error conditions (not NREs) then make this consistent
1012 // since other normal error conditions will simply return false instead.
1013 // This will throw if the attachment fails
1014 try
1015 {
1016 AttachObjectInternal(sp, objatt, attachmentPt, false, true, true, append);
1017 }
1018 catch (Exception e)
1019 {
1020 m_log.ErrorFormat(
1021 "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}",
1022 objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace);
1023  
1024 // Make sure the object doesn't stick around and bail
1025 sp.RemoveAttachment(objatt);
1026 m_scene.DeleteSceneObject(objatt, false);
1027 return null;
1028 }
1029  
1030 if (tainted)
1031 objatt.HasGroupChanged = true;
1032  
1033 if (ThrottlePer100PrimsRezzed > 0)
1034 {
1035 int throttleMs = (int)Math.Round((float)objatt.PrimCount / 100 * ThrottlePer100PrimsRezzed);
1036  
1037 if (DebugLevel > 0)
1038 m_log.DebugFormat(
1039 "[ATTACHMENTS MODULE]: Throttling by {0}ms after rez of {1} with {2} prims for attachment to {3} on point {4} in {5}",
1040 throttleMs, objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name);
1041  
1042 Thread.Sleep(throttleMs);
1043 }
1044  
1045 return objatt;
1046 }
1047  
1048 /// <summary>
1049 /// Update the user inventory to reflect an attachment
1050 /// </summary>
1051 /// <param name="sp"></param>
1052 /// <param name="AttachmentPt"></param>
1053 /// <param name="itemID"></param>
1054 /// <param name="att"></param>
1055 private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att, bool append)
1056 {
1057 // m_log.DebugFormat(
1058 // "[USER INVENTORY]: Updating attachment {0} for {1} at {2} using item ID {3}",
1059 // att.Name, sp.Name, AttachmentPt, itemID);
1060  
1061 if (UUID.Zero == itemID)
1062 {
1063 m_log.Error("[ATTACHMENTS MODULE]: Unable to save attachment. Error inventory item ID.");
1064 return;
1065 }
1066  
1067 if (0 == AttachmentPt)
1068 {
1069 m_log.Error("[ATTACHMENTS MODULE]: Unable to save attachment. Error attachment point.");
1070 return;
1071 }
1072  
1073 InventoryItemBase item = new InventoryItemBase(itemID, sp.UUID);
1074 item = m_scene.InventoryService.GetItem(item);
1075 if (item == null)
1076 return;
1077  
1078 int attFlag = append ? 0x80 : 0;
1079 bool changed = sp.Appearance.SetAttachment((int)AttachmentPt | attFlag, itemID, item.AssetID);
1080 if (changed && m_scene.AvatarFactory != null)
1081 {
1082 if (DebugLevel > 0)
1083 m_log.DebugFormat(
1084 "[ATTACHMENTS MODULE]: Queueing appearance save for {0}, attachment {1} point {2} in ShowAttachInUserInventory()",
1085 sp.Name, att.Name, AttachmentPt);
1086  
1087 m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
1088 }
1089 }
1090  
1091 #endregion
1092  
1093 #region Client Event Handlers
1094  
1095 private ISceneEntity Client_OnRezSingleAttachmentFromInv(IClientAPI remoteClient, UUID itemID, uint AttachmentPt)
1096 {
1097 if (!Enabled)
1098 return null;
1099  
1100 if (DebugLevel > 0)
1101 m_log.DebugFormat(
1102 "[ATTACHMENTS MODULE]: Rezzing attachment to point {0} from item {1} for {2}",
1103 (AttachmentPoint)AttachmentPt, itemID, remoteClient.Name);
1104  
1105 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1106  
1107 if (sp == null)
1108 {
1109 m_log.ErrorFormat(
1110 "[ATTACHMENTS MODULE]: Could not find presence for client {0} {1} in RezSingleAttachmentFromInventory()",
1111 remoteClient.Name, remoteClient.AgentId);
1112 return null;
1113 }
1114  
1115 return RezSingleAttachmentFromInventory(sp, itemID, AttachmentPt);
1116 }
1117  
1118 private void Client_OnRezMultipleAttachmentsFromInv(IClientAPI remoteClient, List<KeyValuePair<UUID, uint>> rezlist)
1119 {
1120 if (!Enabled)
1121 return;
1122  
1123 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1124 if (sp != null)
1125 RezMultipleAttachmentsFromInventory(sp, rezlist);
1126 else
1127 m_log.ErrorFormat(
1128 "[ATTACHMENTS MODULE]: Could not find presence for client {0} {1} in RezMultipleAttachmentsFromInventory()",
1129 remoteClient.Name, remoteClient.AgentId);
1130 }
1131  
1132 private void Client_OnObjectAttach(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, bool silent)
1133 {
1134 if (DebugLevel > 0)
1135 m_log.DebugFormat(
1136 "[ATTACHMENTS MODULE]: Attaching object local id {0} to {1} point {2} from ground (silent = {3})",
1137 objectLocalID, remoteClient.Name, AttachmentPt, silent);
1138  
1139 if (!Enabled)
1140 return;
1141  
1142 try
1143 {
1144 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1145  
1146 if (sp == null)
1147 {
1148 m_log.ErrorFormat(
1149 "[ATTACHMENTS MODULE]: Could not find presence for client {0} {1}", remoteClient.Name, remoteClient.AgentId);
1150 return;
1151 }
1152  
1153 // If we can't take it, we can't attach it!
1154 SceneObjectPart part = m_scene.GetSceneObjectPart(objectLocalID);
1155 if (part == null)
1156 return;
1157  
1158 if (!m_scene.Permissions.CanTakeObject(part.UUID, remoteClient.AgentId))
1159 {
1160 remoteClient.SendAgentAlertMessage(
1161 "You don't have sufficient permissions to attach this object", false);
1162  
1163 return;
1164 }
1165  
1166 bool append = (AttachmentPt & 0x80) != 0;
1167 AttachmentPt &= 0x7f;
1168  
1169 // Calls attach with a Zero position
1170 if (AttachObject(sp, part.ParentGroup, AttachmentPt, false, true, append))
1171 {
1172 if (DebugLevel > 0)
1173 m_log.Debug(
1174 "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId
1175 + ", AttachmentPoint: " + AttachmentPt);
1176  
1177 // Save avatar attachment information
1178 m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId);
1179 }
1180 }
1181 catch (Exception e)
1182 {
1183 m_log.ErrorFormat("[ATTACHMENTS MODULE]: exception upon Attach Object {0}{1}", e.Message, e.StackTrace);
1184 }
1185 }
1186  
1187 private void Client_OnObjectDetach(uint objectLocalID, IClientAPI remoteClient)
1188 {
1189 if (!Enabled)
1190 return;
1191  
1192 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1193 SceneObjectGroup group = m_scene.GetGroupByPrim(objectLocalID);
1194  
1195 if (sp != null && group != null && group.FromItemID != UUID.Zero)
1196 DetachSingleAttachmentToInv(sp, group);
1197 }
1198  
1199 private void Client_OnDetachAttachmentIntoInv(UUID itemID, IClientAPI remoteClient)
1200 {
1201 if (!Enabled)
1202 return;
1203  
1204 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1205 if (sp != null)
1206 {
1207 List<SceneObjectGroup> attachments = sp.GetAttachments();
1208  
1209 foreach (SceneObjectGroup group in attachments)
1210 {
1211 if (group.FromItemID == itemID && group.FromItemID != UUID.Zero)
1212 {
1213 DetachSingleAttachmentToInv(sp, group);
1214 return;
1215 }
1216 }
1217 }
1218 }
1219  
1220 private void Client_OnObjectDrop(uint soLocalId, IClientAPI remoteClient)
1221 {
1222 if (!Enabled)
1223 return;
1224  
1225 ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId);
1226 if (sp != null)
1227 DetachSingleAttachmentToGround(sp, soLocalId);
1228 }
1229  
1230 #endregion
1231 }
1232 }