diff options
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs')
-rw-r--r-- | OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs | 553 |
1 files changed, 286 insertions, 267 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs index fd7cad2..394b90a 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs | |||
@@ -50,7 +50,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
50 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 50 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
51 | 51 | ||
52 | private Scene m_scene; | 52 | private Scene m_scene; |
53 | private IDialogModule m_dialogModule; | 53 | private IInventoryAccessModule m_invAccessModule; |
54 | 54 | ||
55 | /// <summary> | 55 | /// <summary> |
56 | /// Are attachments enabled? | 56 | /// Are attachments enabled? |
@@ -72,7 +72,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
72 | public void AddRegion(Scene scene) | 72 | public void AddRegion(Scene scene) |
73 | { | 73 | { |
74 | m_scene = scene; | 74 | m_scene = scene; |
75 | m_dialogModule = m_scene.RequestModuleInterface<IDialogModule>(); | ||
76 | m_scene.RegisterModuleInterface<IAttachmentsModule>(this); | 75 | m_scene.RegisterModuleInterface<IAttachmentsModule>(this); |
77 | 76 | ||
78 | if (Enabled) | 77 | if (Enabled) |
@@ -89,7 +88,10 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
89 | m_scene.EventManager.OnNewClient -= SubscribeToClientEvents; | 88 | m_scene.EventManager.OnNewClient -= SubscribeToClientEvents; |
90 | } | 89 | } |
91 | 90 | ||
92 | public void RegionLoaded(Scene scene) {} | 91 | public void RegionLoaded(Scene scene) |
92 | { | ||
93 | m_invAccessModule = m_scene.RequestModuleInterface<IInventoryAccessModule>(); | ||
94 | } | ||
93 | 95 | ||
94 | public void Close() | 96 | public void Close() |
95 | { | 97 | { |
@@ -100,6 +102,56 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
100 | 102 | ||
101 | #region IAttachmentsModule | 103 | #region IAttachmentsModule |
102 | 104 | ||
105 | public void CopyAttachments(IScenePresence sp, AgentData ad) | ||
106 | { | ||
107 | lock (sp.AttachmentsSyncLock) | ||
108 | { | ||
109 | // Attachment objects | ||
110 | List<SceneObjectGroup> attachments = sp.GetAttachments(); | ||
111 | if (attachments.Count > 0) | ||
112 | { | ||
113 | ad.AttachmentObjects = new List<ISceneObject>(); | ||
114 | ad.AttachmentObjectStates = new List<string>(); | ||
115 | // IScriptModule se = m_scene.RequestModuleInterface<IScriptModule>(); | ||
116 | sp.InTransitScriptStates.Clear(); | ||
117 | |||
118 | foreach (SceneObjectGroup sog in attachments) | ||
119 | { | ||
120 | // We need to make a copy and pass that copy | ||
121 | // because of transfers withn the same sim | ||
122 | ISceneObject clone = sog.CloneForNewScene(); | ||
123 | // Attachment module assumes that GroupPosition holds the offsets...! | ||
124 | ((SceneObjectGroup)clone).RootPart.GroupPosition = sog.RootPart.AttachedPos; | ||
125 | ((SceneObjectGroup)clone).IsAttachment = false; | ||
126 | ad.AttachmentObjects.Add(clone); | ||
127 | string state = sog.GetStateSnapshot(); | ||
128 | ad.AttachmentObjectStates.Add(state); | ||
129 | sp.InTransitScriptStates.Add(state); | ||
130 | // Let's remove the scripts of the original object here | ||
131 | sog.RemoveScriptInstances(true); | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | |||
137 | public void CopyAttachments(AgentData ad, IScenePresence sp) | ||
138 | { | ||
139 | if (ad.AttachmentObjects != null && ad.AttachmentObjects.Count > 0) | ||
140 | { | ||
141 | lock (sp.AttachmentsSyncLock) | ||
142 | sp.ClearAttachments(); | ||
143 | |||
144 | int i = 0; | ||
145 | foreach (ISceneObject so in ad.AttachmentObjects) | ||
146 | { | ||
147 | ((SceneObjectGroup)so).LocalId = 0; | ||
148 | ((SceneObjectGroup)so).RootPart.ClearUpdateSchedule(); | ||
149 | so.SetState(ad.AttachmentObjectStates[i++], m_scene); | ||
150 | m_scene.IncomingCreateObject(Vector3.Zero, so); | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | |||
103 | /// <summary> | 155 | /// <summary> |
104 | /// RezAttachments. This should only be called upon login on the first region. | 156 | /// RezAttachments. This should only be called upon login on the first region. |
105 | /// Attachment rezzings on crossings and TPs are done in a different way. | 157 | /// Attachment rezzings on crossings and TPs are done in a different way. |
@@ -185,40 +237,55 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
185 | if (sp.PresenceType == PresenceType.Npc) | 237 | if (sp.PresenceType == PresenceType.Npc) |
186 | RezSingleAttachmentFromInventoryInternal(sp, UUID.Zero, attach.AssetID, p, null); | 238 | RezSingleAttachmentFromInventoryInternal(sp, UUID.Zero, attach.AssetID, p, null); |
187 | else | 239 | else |
188 | RezSingleAttachmentFromInventory(sp, attach.ItemID, p, true, d); | 240 | RezSingleAttachmentFromInventory(sp, attach.ItemID, p, d); |
189 | } | 241 | } |
190 | catch (Exception e) | 242 | catch (Exception e) |
191 | { | 243 | { |
192 | m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment: {0}{1}", e.Message, e.StackTrace); | 244 | UUID agentId = (sp.ControllingClient == null) ? (UUID)null : sp.ControllingClient.AgentId; |
245 | m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}", | ||
246 | attach.ItemID, attach.AssetID, p, agentId, e.Message, e.StackTrace); | ||
193 | } | 247 | } |
194 | } | 248 | } |
195 | } | 249 | } |
196 | 250 | ||
197 | public void SaveChangedAttachments(IScenePresence sp, bool saveAllScripted) | 251 | public void DeRezAttachments(IScenePresence sp, bool saveChanged, bool saveAllScripted) |
198 | { | 252 | { |
199 | // m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name); | ||
200 | |||
201 | if (!Enabled) | 253 | if (!Enabled) |
202 | return; | 254 | return; |
203 | 255 | ||
204 | foreach (SceneObjectGroup grp in sp.GetAttachments()) | 256 | // m_log.DebugFormat("[ATTACHMENTS MODULE]: Saving changed attachments for {0}", sp.Name); |
257 | |||
258 | lock (sp.AttachmentsSyncLock) | ||
205 | { | 259 | { |
206 | grp.IsAttachment = false; | 260 | foreach (SceneObjectGroup so in sp.GetAttachments()) |
207 | grp.AbsolutePosition = grp.RootPart.AttachedPos; | 261 | { |
208 | UpdateKnownItem(sp, grp, saveAllScripted); | 262 | // We can only remove the script instances from the script engine after we've retrieved their xml state |
209 | grp.IsAttachment = true; | 263 | // when we update the attachment item. |
264 | m_scene.DeleteSceneObject(so, false, false); | ||
265 | |||
266 | if (saveChanged || saveAllScripted) | ||
267 | { | ||
268 | so.IsAttachment = false; | ||
269 | so.AbsolutePosition = so.RootPart.AttachedPos; | ||
270 | UpdateKnownItem(sp, so, saveAllScripted); | ||
271 | } | ||
272 | |||
273 | so.RemoveScriptInstances(true); | ||
274 | } | ||
275 | |||
276 | sp.ClearAttachments(); | ||
210 | } | 277 | } |
211 | } | 278 | } |
212 | 279 | ||
213 | public void DeleteAttachmentsFromScene(IScenePresence sp, bool silent) | 280 | public void DeleteAttachmentsFromScene(IScenePresence sp, bool silent) |
214 | { | 281 | { |
215 | // m_log.DebugFormat( | ||
216 | // "[ATTACHMENTS MODULE]: Deleting attachments from scene {0} for {1}, silent = {2}", | ||
217 | // m_scene.RegionInfo.RegionName, sp.Name, silent); | ||
218 | |||
219 | if (!Enabled) | 282 | if (!Enabled) |
220 | return; | 283 | return; |
221 | 284 | ||
285 | // m_log.DebugFormat( | ||
286 | // "[ATTACHMENTS MODULE]: Deleting attachments from scene {0} for {1}, silent = {2}", | ||
287 | // m_scene.RegionInfo.RegionName, sp.Name, silent); | ||
288 | |||
222 | foreach (SceneObjectGroup sop in sp.GetAttachments()) | 289 | foreach (SceneObjectGroup sop in sp.GetAttachments()) |
223 | { | 290 | { |
224 | sop.Scene.DeleteSceneObject(sop, silent); | 291 | sop.Scene.DeleteSceneObject(sop, silent); |
@@ -234,6 +301,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
234 | // m_log.DebugFormat( | 301 | // m_log.DebugFormat( |
235 | // "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})", | 302 | // "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})", |
236 | // group.Name, group.LocalId, sp.Name, attachmentPt, silent); | 303 | // group.Name, group.LocalId, sp.Name, attachmentPt, silent); |
304 | |||
305 | if (group.GetSittingAvatarsCount() != 0) | ||
306 | { | ||
307 | // m_log.WarnFormat( | ||
308 | // "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it", | ||
309 | // group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount()); | ||
310 | |||
311 | return false; | ||
312 | } | ||
237 | 313 | ||
238 | if (sp.GetAttachments(attachmentPt).Contains(group)) | 314 | if (sp.GetAttachments(attachmentPt).Contains(group)) |
239 | { | 315 | { |
@@ -294,32 +370,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
294 | group.AttachmentPoint = attachmentPt; | 370 | group.AttachmentPoint = attachmentPt; |
295 | group.AbsolutePosition = attachPos; | 371 | group.AbsolutePosition = attachPos; |
296 | 372 | ||
297 | // We also don't want to do any of the inventory operations for an NPC. | ||
298 | if (sp.PresenceType != PresenceType.Npc) | 373 | if (sp.PresenceType != PresenceType.Npc) |
299 | { | 374 | UpdateUserInventoryWithAttachment(sp, group, attachmentPt); |
300 | // Remove any previous attachments | ||
301 | List<SceneObjectGroup> attachments = sp.GetAttachments(attachmentPt); | ||
302 | |||
303 | // At the moment we can only deal with a single attachment | ||
304 | if (attachments.Count != 0) | ||
305 | { | ||
306 | UUID oldAttachmentItemID = attachments[0].FromItemID; | ||
307 | |||
308 | if (oldAttachmentItemID != UUID.Zero) | ||
309 | DetachSingleAttachmentToInvInternal(sp, oldAttachmentItemID); | ||
310 | else | ||
311 | m_log.WarnFormat( | ||
312 | "[ATTACHMENTS MODULE]: When detaching existing attachment {0} {1} at point {2} to make way for {3} {4} for {5}, couldn't find the associated item ID to adjust inventory attachment record!", | ||
313 | attachments[0].Name, attachments[0].LocalId, attachmentPt, group.Name, group.LocalId, sp.Name); | ||
314 | } | ||
315 | |||
316 | // Add the new attachment to inventory if we don't already have it. | ||
317 | UUID newAttachmentItemID = group.FromItemID; | ||
318 | if (newAttachmentItemID == UUID.Zero) | ||
319 | newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID; | ||
320 | |||
321 | ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group); | ||
322 | } | ||
323 | 375 | ||
324 | AttachToAgent(sp, group, attachmentPt, attachPos, silent); | 376 | AttachToAgent(sp, group, attachmentPt, attachPos, silent); |
325 | } | 377 | } |
@@ -327,12 +379,36 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
327 | return true; | 379 | return true; |
328 | } | 380 | } |
329 | 381 | ||
382 | private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt) | ||
383 | { | ||
384 | // Remove any previous attachments | ||
385 | List<SceneObjectGroup> attachments = sp.GetAttachments(attachmentPt); | ||
386 | |||
387 | // At the moment we can only deal with a single attachment | ||
388 | if (attachments.Count != 0) | ||
389 | { | ||
390 | if (attachments[0].FromItemID != UUID.Zero) | ||
391 | DetachSingleAttachmentToInvInternal(sp, attachments[0]); | ||
392 | else | ||
393 | m_log.WarnFormat( | ||
394 | "[ATTACHMENTS MODULE]: When detaching existing attachment {0} {1} at point {2} to make way for {3} {4} for {5}, couldn't find the associated item ID to adjust inventory attachment record!", | ||
395 | attachments[0].Name, attachments[0].LocalId, attachmentPt, group.Name, group.LocalId, sp.Name); | ||
396 | } | ||
397 | |||
398 | // Add the new attachment to inventory if we don't already have it. | ||
399 | UUID newAttachmentItemID = group.FromItemID; | ||
400 | if (newAttachmentItemID == UUID.Zero) | ||
401 | newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID; | ||
402 | |||
403 | ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group); | ||
404 | } | ||
405 | |||
330 | public ISceneEntity RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt) | 406 | public ISceneEntity RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt) |
331 | { | 407 | { |
332 | return RezSingleAttachmentFromInventory(sp, itemID, AttachmentPt, true, null); | 408 | return RezSingleAttachmentFromInventory(sp, itemID, AttachmentPt, null); |
333 | } | 409 | } |
334 | 410 | ||
335 | public ISceneEntity RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt, bool updateInventoryStatus, XmlDocument doc) | 411 | public ISceneEntity RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt, XmlDocument doc) |
336 | { | 412 | { |
337 | if (!Enabled) | 413 | if (!Enabled) |
338 | return null; | 414 | return null; |
@@ -371,12 +447,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
371 | return null; | 447 | return null; |
372 | } | 448 | } |
373 | 449 | ||
374 | SceneObjectGroup att = RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt, doc); | 450 | return RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt, doc); |
375 | |||
376 | if (att == null) | ||
377 | DetachSingleAttachmentToInv(sp, itemID); | ||
378 | |||
379 | return att; | ||
380 | } | 451 | } |
381 | 452 | ||
382 | public void RezMultipleAttachmentsFromInventory(IScenePresence sp, List<KeyValuePair<UUID, uint>> rezlist) | 453 | public void RezMultipleAttachmentsFromInventory(IScenePresence sp, List<KeyValuePair<UUID, uint>> rezlist) |
@@ -453,18 +524,27 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
453 | m_scene.EventManager.TriggerOnAttach(so.LocalId, so.UUID, UUID.Zero); | 524 | m_scene.EventManager.TriggerOnAttach(so.LocalId, so.UUID, UUID.Zero); |
454 | } | 525 | } |
455 | 526 | ||
456 | public void DetachSingleAttachmentToInv(IScenePresence sp, UUID itemID) | 527 | public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so) |
457 | { | 528 | { |
458 | lock (sp.AttachmentsSyncLock) | 529 | lock (sp.AttachmentsSyncLock) |
459 | { | 530 | { |
460 | // Save avatar attachment information | 531 | // Save avatar attachment information |
461 | m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID); | 532 | // m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID); |
533 | |||
534 | if (so.AttachedAvatar != sp.UUID) | ||
535 | { | ||
536 | m_log.WarnFormat( | ||
537 | "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}", | ||
538 | so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName); | ||
462 | 539 | ||
463 | bool changed = sp.Appearance.DetachAttachment(itemID); | 540 | return; |
541 | } | ||
542 | |||
543 | bool changed = sp.Appearance.DetachAttachment(so.FromItemID); | ||
464 | if (changed && m_scene.AvatarFactory != null) | 544 | if (changed && m_scene.AvatarFactory != null) |
465 | m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); | 545 | m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); |
466 | 546 | ||
467 | DetachSingleAttachmentToInvInternal(sp, itemID); | 547 | DetachSingleAttachmentToInvInternal(sp, so); |
468 | } | 548 | } |
469 | } | 549 | } |
470 | 550 | ||
@@ -473,17 +553,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
473 | if (!Enabled) | 553 | if (!Enabled) |
474 | return; | 554 | return; |
475 | 555 | ||
476 | // First we save the | ||
477 | // attachment point information, then we update the relative | ||
478 | // positioning. Then we have to mark the object as NOT an | ||
479 | // attachment. This is necessary in order to correctly save | ||
480 | // and retrieve GroupPosition information for the attachment. | ||
481 | // Finally, we restore the object's attachment status. | ||
482 | uint attachmentPoint = sog.AttachmentPoint; | ||
483 | sog.UpdateGroupPosition(pos); | 556 | sog.UpdateGroupPosition(pos); |
484 | sog.IsAttachment = false; | ||
485 | sog.AbsolutePosition = sog.RootPart.AttachedPos; | ||
486 | sog.AttachmentPoint = attachmentPoint; | ||
487 | sog.HasGroupChanged = true; | 557 | sog.HasGroupChanged = true; |
488 | } | 558 | } |
489 | 559 | ||
@@ -526,6 +596,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
526 | /// </remarks> | 596 | /// </remarks> |
527 | /// <param name="sp"></param> | 597 | /// <param name="sp"></param> |
528 | /// <param name="grp"></param> | 598 | /// <param name="grp"></param> |
599 | /// <param name="saveAllScripted"></param> | ||
529 | private void UpdateKnownItem(IScenePresence sp, SceneObjectGroup grp, bool saveAllScripted) | 600 | private void UpdateKnownItem(IScenePresence sp, SceneObjectGroup grp, bool saveAllScripted) |
530 | { | 601 | { |
531 | // Saving attachments for NPCs messes them up for the real owner! | 602 | // Saving attachments for NPCs messes them up for the real owner! |
@@ -538,9 +609,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
538 | 609 | ||
539 | if (grp.HasGroupChanged || (saveAllScripted && grp.ContainsScripts())) | 610 | if (grp.HasGroupChanged || (saveAllScripted && grp.ContainsScripts())) |
540 | { | 611 | { |
541 | m_log.DebugFormat( | 612 | // m_log.DebugFormat( |
542 | "[ATTACHMENTS MODULE]: Updating asset for attachment {0}, attachpoint {1}", | 613 | // "[ATTACHMENTS MODULE]: Updating asset for attachment {0}, attachpoint {1}", |
543 | grp.UUID, grp.AttachmentPoint); | 614 | // grp.UUID, grp.AttachmentPoint); |
544 | 615 | ||
545 | string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp); | 616 | string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp); |
546 | 617 | ||
@@ -571,12 +642,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
571 | } | 642 | } |
572 | grp.HasGroupChanged = false; // Prevent it being saved over and over | 643 | grp.HasGroupChanged = false; // Prevent it being saved over and over |
573 | } | 644 | } |
574 | else | 645 | // else |
575 | { | 646 | // { |
576 | m_log.DebugFormat( | 647 | // m_log.DebugFormat( |
577 | "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}", | 648 | // "[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {0}, attachpoint {1}", |
578 | grp.UUID, grp.AttachmentPoint); | 649 | // grp.UUID, grp.AttachmentPoint); |
579 | } | 650 | // } |
580 | } | 651 | } |
581 | 652 | ||
582 | /// <summary> | 653 | /// <summary> |
@@ -594,9 +665,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
594 | private void AttachToAgent( | 665 | private void AttachToAgent( |
595 | IScenePresence sp, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent) | 666 | IScenePresence sp, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent) |
596 | { | 667 | { |
597 | // m_log.DebugFormat( | 668 | // m_log.DebugFormat( |
598 | // "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} in pt {2} pos {3} {4}", | 669 | // "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} in pt {2} pos {3} {4}", |
599 | // so.Name, avatar.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos); | 670 | // so.Name, sp.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos); |
600 | 671 | ||
601 | so.DetachFromBackup(); | 672 | so.DetachFromBackup(); |
602 | 673 | ||
@@ -627,6 +698,20 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
627 | { | 698 | { |
628 | m_scene.SendKillObject(new List<uint> { so.RootPart.LocalId }); | 699 | m_scene.SendKillObject(new List<uint> { so.RootPart.LocalId }); |
629 | } | 700 | } |
701 | else if (so.HasPrivateAttachmentPoint) | ||
702 | { | ||
703 | // m_log.DebugFormat( | ||
704 | // "[ATTACHMENTS MODULE]: Killing private HUD {0} for avatars other than {1} at attachment point {2}", | ||
705 | // so.Name, sp.Name, so.AttachmentPoint); | ||
706 | |||
707 | // As this scene object can now only be seen by the attaching avatar, tell everybody else in the | ||
708 | // scene that it's no longer in their awareness. | ||
709 | m_scene.ForEachClient( | ||
710 | client => | ||
711 | { if (client.AgentId != so.AttachedAvatar) | ||
712 | client.SendKillObject(m_scene.RegionInfo.RegionHandle, new List<uint>() { so.LocalId }); | ||
713 | }); | ||
714 | } | ||
630 | 715 | ||
631 | so.IsSelected = false; // fudge.... | 716 | so.IsSelected = false; // fudge.... |
632 | so.ScheduleGroupForFullUpdate(); | 717 | so.ScheduleGroupForFullUpdate(); |
@@ -645,210 +730,124 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
645 | /// <returns>The user inventory item created that holds the attachment.</returns> | 730 | /// <returns>The user inventory item created that holds the attachment.</returns> |
646 | private InventoryItemBase AddSceneObjectAsNewAttachmentInInv(IScenePresence sp, SceneObjectGroup grp) | 731 | private InventoryItemBase AddSceneObjectAsNewAttachmentInInv(IScenePresence sp, SceneObjectGroup grp) |
647 | { | 732 | { |
733 | if (m_invAccessModule == null) | ||
734 | return null; | ||
735 | |||
648 | // m_log.DebugFormat( | 736 | // m_log.DebugFormat( |
649 | // "[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {0} {1} for {2}", | 737 | // "[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {0} {1} for {2}", |
650 | // grp.Name, grp.LocalId, remoteClient.Name); | 738 | // grp.Name, grp.LocalId, remoteClient.Name); |
651 | 739 | ||
652 | // Vector3 inventoryStoredPosition = new Vector3 | 740 | InventoryItemBase newItem |
653 | // (((grp.AbsolutePosition.X > (int)Constants.RegionSize) | 741 | = m_invAccessModule.CopyToInventory( |
654 | // ? (float)Constants.RegionSize - 6 | 742 | DeRezAction.TakeCopy, |
655 | // : grp.AbsolutePosition.X) | 743 | m_scene.InventoryService.GetFolderForType(sp.UUID, AssetType.Object).ID, |
656 | // , | 744 | new List<SceneObjectGroup> { grp }, |
657 | // (grp.AbsolutePosition.Y > (int)Constants.RegionSize) | 745 | sp.ControllingClient, true)[0]; |
658 | // ? (float)Constants.RegionSize - 6 | ||
659 | // : grp.AbsolutePosition.Y, | ||
660 | // grp.AbsolutePosition.Z); | ||
661 | // | ||
662 | // Vector3 originalPosition = grp.AbsolutePosition; | ||
663 | // | ||
664 | // grp.AbsolutePosition = inventoryStoredPosition; | ||
665 | |||
666 | // If we're being called from a script, then trying to serialize that same script's state will not complete | ||
667 | // in any reasonable time period. Therefore, we'll avoid it. The worst that can happen is that if | ||
668 | // the client/server crashes rather than logging out normally, the attachment's scripts will resume | ||
669 | // without state on relog. Arguably, this is what we want anyway. | ||
670 | string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp, false); | ||
671 | |||
672 | // grp.AbsolutePosition = originalPosition; | ||
673 | |||
674 | AssetBase asset = m_scene.CreateAsset( | ||
675 | grp.GetPartName(grp.LocalId), | ||
676 | grp.GetPartDescription(grp.LocalId), | ||
677 | (sbyte)AssetType.Object, | ||
678 | Utils.StringToBytes(sceneObjectXml), | ||
679 | sp.UUID); | ||
680 | |||
681 | m_scene.AssetService.Store(asset); | ||
682 | |||
683 | InventoryItemBase item = new InventoryItemBase(); | ||
684 | item.CreatorId = grp.RootPart.CreatorID.ToString(); | ||
685 | item.CreatorData = grp.RootPart.CreatorData; | ||
686 | item.Owner = sp.UUID; | ||
687 | item.ID = UUID.Random(); | ||
688 | item.AssetID = asset.FullID; | ||
689 | item.Description = asset.Description; | ||
690 | item.Name = asset.Name; | ||
691 | item.AssetType = asset.Type; | ||
692 | item.InvType = (int)InventoryType.Object; | ||
693 | |||
694 | InventoryFolderBase folder = m_scene.InventoryService.GetFolderForType(sp.UUID, AssetType.Object); | ||
695 | if (folder != null) | ||
696 | item.Folder = folder.ID; | ||
697 | else // oopsies | ||
698 | item.Folder = UUID.Zero; | ||
699 | |||
700 | // Nix the special bits we used to use for slam and the folded perms | ||
701 | uint allowablePermissionsMask = (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify | PermissionMask.Move); | ||
702 | |||
703 | if ((sp.UUID != grp.RootPart.OwnerID) && m_scene.Permissions.PropagatePermissions()) | ||
704 | { | ||
705 | item.BasePermissions = grp.RootPart.BaseMask & grp.RootPart.NextOwnerMask & allowablePermissionsMask; | ||
706 | item.CurrentPermissions = grp.RootPart.BaseMask & grp.RootPart.NextOwnerMask & allowablePermissionsMask; | ||
707 | item.NextPermissions = grp.RootPart.NextOwnerMask & allowablePermissionsMask; | ||
708 | item.EveryOnePermissions = grp.RootPart.EveryoneMask & grp.RootPart.NextOwnerMask & allowablePermissionsMask; | ||
709 | item.GroupPermissions = grp.RootPart.GroupMask & grp.RootPart.NextOwnerMask & allowablePermissionsMask; | ||
710 | } | ||
711 | else | ||
712 | { | ||
713 | item.BasePermissions = grp.RootPart.BaseMask & allowablePermissionsMask; | ||
714 | item.CurrentPermissions = grp.RootPart.OwnerMask & allowablePermissionsMask; | ||
715 | item.NextPermissions = grp.RootPart.NextOwnerMask & allowablePermissionsMask; | ||
716 | item.EveryOnePermissions = grp.RootPart.EveryoneMask & allowablePermissionsMask; | ||
717 | item.GroupPermissions = grp.RootPart.GroupMask & allowablePermissionsMask; | ||
718 | } | ||
719 | item.CreationDate = Util.UnixTimeSinceEpoch(); | ||
720 | 746 | ||
721 | // sets itemID so client can show item as 'attached' in inventory | 747 | // sets itemID so client can show item as 'attached' in inventory |
722 | grp.FromItemID = item.ID; | 748 | grp.FromItemID = newItem.ID; |
723 | |||
724 | if (m_scene.AddInventoryItem(item)) | ||
725 | { | ||
726 | sp.ControllingClient.SendInventoryItemCreateUpdate(item, 0); | ||
727 | } | ||
728 | else | ||
729 | { | ||
730 | if (m_dialogModule != null) | ||
731 | m_dialogModule.SendAlertToUser(sp.ControllingClient, "Operation failed"); | ||
732 | } | ||
733 | 749 | ||
734 | return item; | 750 | return newItem; |
735 | } | 751 | } |
736 | 752 | ||
737 | // What makes this method odd and unique is it tries to detach using an UUID.... Yay for standards. | 753 | private void DetachSingleAttachmentToInvInternal(IScenePresence sp, SceneObjectGroup so) |
738 | // To LocalId or UUID, *THAT* is the question. How now Brown UUID?? | ||
739 | private void DetachSingleAttachmentToInvInternal(IScenePresence sp, UUID itemID) | ||
740 | { | 754 | { |
741 | // m_log.DebugFormat("[ATTACHMENTS MODULE]: Detaching item {0} to inventory for {1}", itemID, sp.Name); | 755 | // m_log.DebugFormat("[ATTACHMENTS MODULE]: Detaching item {0} to inventory for {1}", itemID, sp.Name); |
742 | 756 | ||
743 | if (itemID == UUID.Zero) // If this happened, someone made a mistake.... | 757 | m_scene.EventManager.TriggerOnAttach(so.LocalId, so.FromItemID, UUID.Zero); |
744 | return; | 758 | sp.RemoveAttachment(so); |
745 | |||
746 | // We can NOT use the dictionries here, as we are looking | ||
747 | // for an entity by the fromAssetID, which is NOT the prim UUID | ||
748 | EntityBase[] detachEntities = m_scene.GetEntities(); | ||
749 | SceneObjectGroup group; | ||
750 | |||
751 | lock (sp.AttachmentsSyncLock) | ||
752 | { | ||
753 | foreach (EntityBase entity in detachEntities) | ||
754 | { | ||
755 | if (entity is SceneObjectGroup) | ||
756 | { | ||
757 | group = (SceneObjectGroup)entity; | ||
758 | if (group.FromItemID == itemID) | ||
759 | { | ||
760 | m_scene.EventManager.TriggerOnAttach(group.LocalId, itemID, UUID.Zero); | ||
761 | sp.RemoveAttachment(group); | ||
762 | 759 | ||
763 | // Prepare sog for storage | 760 | // We can only remove the script instances from the script engine after we've retrieved their xml state |
764 | group.AttachedAvatar = UUID.Zero; | 761 | // when we update the attachment item. |
765 | group.RootPart.SetParentLocalId(0); | 762 | m_scene.DeleteSceneObject(so, false, false); |
766 | group.IsAttachment = false; | ||
767 | group.AbsolutePosition = group.RootPart.AttachedPos; | ||
768 | 763 | ||
769 | UpdateKnownItem(sp, group, true); | 764 | // Prepare sog for storage |
770 | m_scene.DeleteSceneObject(group, false); | 765 | so.AttachedAvatar = UUID.Zero; |
766 | so.RootPart.SetParentLocalId(0); | ||
767 | so.IsAttachment = false; | ||
768 | so.AbsolutePosition = so.RootPart.AttachedPos; | ||
771 | 769 | ||
772 | return; | 770 | UpdateKnownItem(sp, so, true); |
773 | } | 771 | so.RemoveScriptInstances(true); |
774 | } | ||
775 | } | ||
776 | } | ||
777 | } | 772 | } |
778 | 773 | ||
779 | protected SceneObjectGroup RezSingleAttachmentFromInventoryInternal( | 774 | protected SceneObjectGroup RezSingleAttachmentFromInventoryInternal( |
780 | IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt, XmlDocument doc) | 775 | IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt, XmlDocument doc) |
781 | { | 776 | { |
782 | IInventoryAccessModule invAccess = m_scene.RequestModuleInterface<IInventoryAccessModule>(); | 777 | if (m_invAccessModule == null) |
783 | if (invAccess != null) | 778 | return null; |
779 | |||
780 | lock (sp.AttachmentsSyncLock) | ||
784 | { | 781 | { |
785 | lock (sp.AttachmentsSyncLock) | 782 | SceneObjectGroup objatt; |
783 | |||
784 | if (itemID != UUID.Zero) | ||
785 | objatt = m_invAccessModule.RezObject(sp.ControllingClient, | ||
786 | itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, | ||
787 | false, false, sp.UUID, true); | ||
788 | else | ||
789 | objatt = m_invAccessModule.RezObject(sp.ControllingClient, | ||
790 | null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, | ||
791 | false, false, sp.UUID, true); | ||
792 | |||
793 | if (objatt != null) | ||
786 | { | 794 | { |
787 | SceneObjectGroup objatt; | 795 | // m_log.DebugFormat( |
788 | 796 | // "[ATTACHMENTS MODULE]: Rezzed single object {0} for attachment to {1} on point {2} in {3}", | |
789 | if (itemID != UUID.Zero) | 797 | // objatt.Name, sp.Name, attachmentPt, m_scene.Name); |
790 | objatt = invAccess.RezObject(sp.ControllingClient, | 798 | |
791 | itemID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, | 799 | // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller. |
792 | false, false, sp.UUID, true); | 800 | objatt.HasGroupChanged = false; |
793 | else | 801 | bool tainted = false; |
794 | objatt = invAccess.RezObject(sp.ControllingClient, | 802 | if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint) |
795 | null, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true, | 803 | tainted = true; |
796 | false, false, sp.UUID, true); | 804 | |
797 | 805 | // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal | |
798 | // m_log.DebugFormat( | 806 | // course of events. If not, then it's probably not worth trying to recover the situation |
799 | // "[ATTACHMENTS MODULE]: Retrieved single object {0} for attachment to {1} on point {2}", | 807 | // since this is more likely to trigger further exceptions and confuse later debugging. If |
800 | // objatt.Name, remoteClient.Name, AttachmentPt); | 808 | // exceptions can be thrown in expected error conditions (not NREs) then make this consistent |
801 | 809 | // since other normal error conditions will simply return false instead. | |
802 | if (objatt != null) | 810 | // This will throw if the attachment fails |
811 | try | ||
803 | { | 812 | { |
804 | // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller. | 813 | AttachObject(sp, objatt, attachmentPt, false, false); |
805 | objatt.HasGroupChanged = false; | ||
806 | bool tainted = false; | ||
807 | if (attachmentPt != 0 && attachmentPt != objatt.AttachmentPoint) | ||
808 | tainted = true; | ||
809 | |||
810 | // This will throw if the attachment fails | ||
811 | try | ||
812 | { | ||
813 | AttachObject(sp, objatt, attachmentPt, false, false); | ||
814 | } | ||
815 | catch (Exception e) | ||
816 | { | ||
817 | m_log.ErrorFormat( | ||
818 | "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}", | ||
819 | objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace); | ||
820 | |||
821 | // Make sure the object doesn't stick around and bail | ||
822 | sp.RemoveAttachment(objatt); | ||
823 | m_scene.DeleteSceneObject(objatt, false); | ||
824 | return null; | ||
825 | } | ||
826 | |||
827 | if (tainted) | ||
828 | objatt.HasGroupChanged = true; | ||
829 | |||
830 | if (doc != null) | ||
831 | { | ||
832 | objatt.LoadScriptState(doc); | ||
833 | objatt.ResetOwnerChangeFlag(); | ||
834 | } | ||
835 | |||
836 | // Fire after attach, so we don't get messy perms dialogs | ||
837 | // 4 == AttachedRez | ||
838 | objatt.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4); | ||
839 | objatt.ResumeScripts(); | ||
840 | |||
841 | // Do this last so that event listeners have access to all the effects of the attachment | ||
842 | m_scene.EventManager.TriggerOnAttach(objatt.LocalId, itemID, sp.UUID); | ||
843 | |||
844 | return objatt; | ||
845 | } | 814 | } |
846 | else | 815 | catch (Exception e) |
816 | { | ||
817 | m_log.ErrorFormat( | ||
818 | "[ATTACHMENTS MODULE]: Failed to attach {0} {1} for {2}, exception {3}{4}", | ||
819 | objatt.Name, objatt.UUID, sp.Name, e.Message, e.StackTrace); | ||
820 | |||
821 | // Make sure the object doesn't stick around and bail | ||
822 | sp.RemoveAttachment(objatt); | ||
823 | m_scene.DeleteSceneObject(objatt, false); | ||
824 | return null; | ||
825 | } | ||
826 | |||
827 | if (tainted) | ||
828 | objatt.HasGroupChanged = true; | ||
829 | |||
830 | if (doc != null) | ||
847 | { | 831 | { |
848 | m_log.WarnFormat( | 832 | objatt.LoadScriptState(doc); |
849 | "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}", | 833 | objatt.ResetOwnerChangeFlag(); |
850 | itemID, sp.Name, attachmentPt); | ||
851 | } | 834 | } |
835 | |||
836 | // Fire after attach, so we don't get messy perms dialogs | ||
837 | // 4 == AttachedRez | ||
838 | objatt.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4); | ||
839 | objatt.ResumeScripts(); | ||
840 | |||
841 | // Do this last so that event listeners have access to all the effects of the attachment | ||
842 | m_scene.EventManager.TriggerOnAttach(objatt.LocalId, itemID, sp.UUID); | ||
843 | |||
844 | return objatt; | ||
845 | } | ||
846 | else | ||
847 | { | ||
848 | m_log.WarnFormat( | ||
849 | "[ATTACHMENTS MODULE]: Could not retrieve item {0} for attaching to avatar {1} at point {2}", | ||
850 | itemID, sp.Name, attachmentPt); | ||
852 | } | 851 | } |
853 | } | 852 | } |
854 | 853 | ||
@@ -864,9 +863,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
864 | /// <param name="att"></param> | 863 | /// <param name="att"></param> |
865 | private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att) | 864 | private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att) |
866 | { | 865 | { |
867 | // m_log.DebugFormat( | 866 | // m_log.DebugFormat( |
868 | // "[USER INVENTORY]: Updating attachment {0} for {1} at {2} using item ID {3}", | 867 | // "[USER INVENTORY]: Updating attachment {0} for {1} at {2} using item ID {3}", |
869 | // att.Name, sp.Name, AttachmentPt, itemID); | 868 | // att.Name, sp.Name, AttachmentPt, itemID); |
870 | 869 | ||
871 | if (UUID.Zero == itemID) | 870 | if (UUID.Zero == itemID) |
872 | { | 871 | { |
@@ -884,7 +883,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
884 | item = m_scene.InventoryService.GetItem(item); | 883 | item = m_scene.InventoryService.GetItem(item); |
885 | bool changed = sp.Appearance.SetAttachment((int)AttachmentPt, itemID, item.AssetID); | 884 | bool changed = sp.Appearance.SetAttachment((int)AttachmentPt, itemID, item.AssetID); |
886 | if (changed && m_scene.AvatarFactory != null) | 885 | if (changed && m_scene.AvatarFactory != null) |
886 | { | ||
887 | // m_log.DebugFormat( | ||
888 | // "[ATTACHMENTS MODULE]: Queueing appearance save for {0}, attachment {1} point {2} in ShowAttachInUserInventory()", | ||
889 | // sp.Name, att.Name, AttachmentPt); | ||
890 | |||
887 | m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); | 891 | m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID); |
892 | } | ||
888 | } | 893 | } |
889 | 894 | ||
890 | #endregion | 895 | #endregion |
@@ -929,9 +934,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
929 | 934 | ||
930 | private void Client_OnObjectAttach(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, bool silent) | 935 | private void Client_OnObjectAttach(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, bool silent) |
931 | { | 936 | { |
932 | // m_log.DebugFormat( | 937 | // m_log.DebugFormat( |
933 | // "[ATTACHMENTS MODULE]: Attaching object local id {0} to {1} point {2} from ground (silent = {3})", | 938 | // "[ATTACHMENTS MODULE]: Attaching object local id {0} to {1} point {2} from ground (silent = {3})", |
934 | // objectLocalID, remoteClient.Name, AttachmentPt, silent); | 939 | // objectLocalID, remoteClient.Name, AttachmentPt, silent); |
935 | 940 | ||
936 | if (!Enabled) | 941 | if (!Enabled) |
937 | return; | 942 | return; |
@@ -967,13 +972,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
967 | // Calls attach with a Zero position | 972 | // Calls attach with a Zero position |
968 | if (AttachObject(sp, part.ParentGroup, AttachmentPt, false, true)) | 973 | if (AttachObject(sp, part.ParentGroup, AttachmentPt, false, true)) |
969 | { | 974 | { |
970 | m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId); | 975 | // m_log.Debug( |
976 | // "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId | ||
977 | // + ", AttachmentPoint: " + AttachmentPt); | ||
971 | 978 | ||
972 | // Save avatar attachment information | 979 | // Save avatar attachment information |
973 | m_log.Debug( | 980 | m_scene.EventManager.TriggerOnAttach(objectLocalID, part.ParentGroup.FromItemID, remoteClient.AgentId); |
974 | "[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: " + remoteClient.AgentId | ||
975 | + ", AttachmentPoint: " + AttachmentPt); | ||
976 | |||
977 | } | 981 | } |
978 | } | 982 | } |
979 | catch (Exception e) | 983 | catch (Exception e) |
@@ -989,8 +993,9 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
989 | 993 | ||
990 | ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); | 994 | ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); |
991 | SceneObjectGroup group = m_scene.GetGroupByPrim(objectLocalID); | 995 | SceneObjectGroup group = m_scene.GetGroupByPrim(objectLocalID); |
996 | |||
992 | if (sp != null && group != null) | 997 | if (sp != null && group != null) |
993 | DetachSingleAttachmentToInv(sp, group.FromItemID); | 998 | DetachSingleAttachmentToInv(sp, group); |
994 | } | 999 | } |
995 | 1000 | ||
996 | private void Client_OnDetachAttachmentIntoInv(UUID itemID, IClientAPI remoteClient) | 1001 | private void Client_OnDetachAttachmentIntoInv(UUID itemID, IClientAPI remoteClient) |
@@ -1000,7 +1005,21 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments | |||
1000 | 1005 | ||
1001 | ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); | 1006 | ScenePresence sp = m_scene.GetScenePresence(remoteClient.AgentId); |
1002 | if (sp != null) | 1007 | if (sp != null) |
1003 | DetachSingleAttachmentToInv(sp, itemID); | 1008 | { |
1009 | lock (sp.AttachmentsSyncLock) | ||
1010 | { | ||
1011 | List<SceneObjectGroup> attachments = sp.GetAttachments(); | ||
1012 | |||
1013 | foreach (SceneObjectGroup group in attachments) | ||
1014 | { | ||
1015 | if (group.FromItemID == itemID) | ||
1016 | { | ||
1017 | DetachSingleAttachmentToInv(sp, group); | ||
1018 | return; | ||
1019 | } | ||
1020 | } | ||
1021 | } | ||
1022 | } | ||
1004 | } | 1023 | } |
1005 | 1024 | ||
1006 | private void Client_OnObjectDrop(uint soLocalId, IClientAPI remoteClient) | 1025 | private void Client_OnObjectDrop(uint soLocalId, IClientAPI remoteClient) |