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