AttachmentsModule.cs 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516
  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. using System;
  28. using System.Collections.Generic;
  29. using System.Reflection;
  30. using System.IO;
  31. using System.Text;
  32. using System.Threading;
  33. using System.Xml;
  34. using log4net;
  35. using Mono.Addins;
  36. using Nini.Config;
  37. using OpenMetaverse;
  38. using OpenSim.Framework;
  39. using OpenSim.Framework.Console;
  40. using OpenSim.Region.Framework.Interfaces;
  41. using OpenSim.Region.Framework.Scenes;
  42. using OpenSim.Region.Framework.Scenes.Serialization;
  43. using OpenSim.Services.Interfaces;
  44. using PermissionMask = OpenSim.Framework.PermissionMask;
  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. public int DebugLevel { get; set; }
  53. private Scene m_scene;
  54. private IRegionConsole m_regionConsole;
  55. private IInventoryAccessModule m_invAccessModule;
  56. private bool m_wearReplacesAllOption = true;
  57. /// <summary>
  58. /// Are attachments enabled?
  59. /// </summary>
  60. public bool Enabled { get; private set; }
  61. public string Name { get { return "Attachments Module"; } }
  62. public Type ReplaceableInterface { get { return null; } }
  63. public void Initialise(IConfigSource source)
  64. {
  65. IConfig config = source.Configs["Attachments"];
  66. if (config is not null)
  67. {
  68. Enabled = config.GetBoolean("Enabled", true);
  69. m_wearReplacesAllOption = config.GetBoolean("WearReplacesAll", true);
  70. }
  71. else
  72. {
  73. Enabled = true;
  74. }
  75. }
  76. public void AddRegion(Scene scene)
  77. {
  78. m_scene = scene;
  79. if (Enabled)
  80. {
  81. // Only register module with scene if it is enabled. All callers check for a null attachments module.
  82. // Ideally, there should be a null attachments module for when this core attachments module has been
  83. // disabled. Registering only when enabled allows for other attachments module implementations.
  84. m_scene.RegisterModuleInterface<IAttachmentsModule>(this);
  85. m_scene.EventManager.OnNewClient += SubscribeToClientEvents;
  86. m_scene.EventManager.OnStartScript += OnScriptStarted;
  87. m_scene.EventManager.OnStopScript += OnScriptStopped;
  88. }
  89. // TODO: Should probably be subscribing to CloseClient too, but this doesn't yet give us IClientAPI
  90. }
  91. public void RemoveRegion(Scene scene)
  92. {
  93. if (!Enabled)
  94. return;
  95. m_scene.UnregisterModuleInterface<IAttachmentsModule>(this);
  96. m_scene.EventManager.OnNewClient -= SubscribeToClientEvents;
  97. m_scene.EventManager.OnStartScript -= OnScriptStarted;
  98. m_scene.EventManager.OnStopScript -= OnScriptStopped;
  99. }
  100. public void RegionLoaded(Scene scene)
  101. {
  102. if (!Enabled)
  103. return;
  104. m_invAccessModule = m_scene.RequestModuleInterface<IInventoryAccessModule>();
  105. if (m_invAccessModule is null)
  106. {
  107. Enabled = false;
  108. m_scene.UnregisterModuleInterface<IAttachmentsModule>(this);
  109. m_scene.EventManager.OnNewClient -= SubscribeToClientEvents;
  110. m_scene.EventManager.OnStartScript -= OnScriptStarted;
  111. m_scene.EventManager.OnStopScript -= OnScriptStopped;
  112. m_log.WarnFormat("[ATTACHMENTS MODULE]: InventoryService unvailable!. Module disabled");
  113. return;
  114. }
  115. m_regionConsole = scene.RequestModuleInterface<IRegionConsole>();
  116. m_regionConsole?.AddCommand("AttachModule", false, "set auto_grant_attach_perms", "set auto_grant_attach_perms true|false", "Allow objects owned by the region owner or estate managers to obtain attach permissions without asking the user", HandleSetAutoGrantAttachPerms);
  117. scene.AddCommand(
  118. "Debug",
  119. this,
  120. "debug attachments log",
  121. "debug attachments log [0|1]",
  122. "Turn on attachments debug logging",
  123. " <= 0 - turns off debug logging\n"
  124. + " >= 1 - turns on attachment message debug logging",
  125. HandleDebugAttachmentsLog);
  126. scene.AddCommand(
  127. "Debug",
  128. this,
  129. "debug attachments status",
  130. "debug attachments status",
  131. "Show current attachments debug status",
  132. HandleDebugAttachmentsStatus);
  133. // next should work on console root also
  134. MainConsole.Instance.Commands.AddCommand(
  135. "Users", true, "attachments show",
  136. "attachments show [<first-name> <last-name>]",
  137. "Show attachment information for avatars in this simulator.",
  138. "If no name is supplied then information for all avatars is shown.",
  139. HandleShowAttachmentsCommand);
  140. }
  141. public void Close()
  142. {
  143. if (!Enabled)
  144. return;
  145. RemoveRegion(m_scene);
  146. }
  147. private void HandleDebugAttachmentsLog(string module, string[] args)
  148. {
  149. if (!(args.Length == 4 && int.TryParse(args[3], out int debugLevel)))
  150. {
  151. MainConsole.Instance.Output("Usage: debug attachments log [0|1]");
  152. }
  153. else
  154. {
  155. DebugLevel = debugLevel;
  156. MainConsole.Instance.Output($"Set attachments debug level to {DebugLevel} in {m_scene.Name}");
  157. }
  158. }
  159. private void HandleDebugAttachmentsStatus(string module, string[] args)
  160. {
  161. MainConsole.Instance.Output($"Settings for {m_scene.Name}");
  162. MainConsole.Instance.Output($"Debug logging level: {DebugLevel}");
  163. }
  164. protected void HandleShowAttachmentsCommand(string module, string[] cmd)
  165. {
  166. if (cmd.Length != 2 && cmd.Length < 4)
  167. {
  168. MainConsole.Instance.Output("Usage: attachments show [<first-name> <last-name>]");
  169. return;
  170. }
  171. SceneManager sm = SceneManager.Instance;
  172. if(sm is null || sm.Scenes.Count == 0)
  173. return;
  174. bool targetNameSupplied = false;
  175. string optionalTargetFirstName = null;
  176. string optionalTargetLastName = null;
  177. if (cmd.Length >= 4)
  178. {
  179. targetNameSupplied = true;
  180. optionalTargetFirstName = cmd[2];
  181. optionalTargetLastName = cmd[3];
  182. }
  183. StringBuilder sb = new();
  184. sm.ForEachSelectedScene(
  185. scene =>
  186. {
  187. if (targetNameSupplied)
  188. {
  189. ScenePresence sp = scene.GetScenePresence(optionalTargetFirstName, optionalTargetLastName);
  190. if (sp is not null && !sp.IsChildAgent)
  191. GetAttachmentsReport(sp, sb);
  192. }
  193. else
  194. {
  195. sb.Append("--- All attachments for region ");
  196. sb.AppendLine(scene.Name);
  197. scene.ForEachRootScenePresence(sp => GetAttachmentsReport(sp, sb));
  198. }
  199. });
  200. MainConsole.Instance.Output(sb.ToString());
  201. }
  202. private static void GetAttachmentsReport(ScenePresence sp, StringBuilder sb)
  203. {
  204. sb.Append("Attachments for ");
  205. sb.AppendLine(sp.Name);
  206. ConsoleDisplayList ct = new();
  207. int totalprims = 0;
  208. List<SceneObjectGroup> attachmentObjects = sp.GetAttachments();
  209. for (int i = 0; i < attachmentObjects.Count; ++i)
  210. {
  211. SceneObjectGroup attachmentObject = attachmentObjects[i];
  212. ct.Indent = 2;
  213. ct.AddRow("Attachment Name", attachmentObject.Name);
  214. ct.AddRow("Local ID", attachmentObject.LocalId);
  215. ct.AddRow("Item ID", attachmentObject.UUID);
  216. if(attachmentObject.FromItemID.IsZero())
  217. ct.AddRow("Temporary", "");
  218. else
  219. ct.AddRow("From Item ID", attachmentObject.FromItemID);
  220. ct.AddRow("Attach Point", ((AttachmentPoint)attachmentObject.AttachmentPoint));
  221. ct.AddRow("Prims", attachmentObject.PrimCount);
  222. ct.AddRow("Position", attachmentObject.RootPart.AttachedPos + "\n");
  223. totalprims += attachmentObject.PrimCount;
  224. }
  225. sb.AppendFormat("--Total Attachment prims for {0} : {1}\n\n", sp.Name, totalprims);
  226. ct.AddToStringBuilder(sb);
  227. }
  228. private void SendConsoleOutput(UUID agentID, string text)
  229. {
  230. if (m_regionConsole is null)
  231. return;
  232. m_regionConsole.SendConsoleOutput(agentID, text);
  233. }
  234. private void HandleSetAutoGrantAttachPerms(string module, string[] parms)
  235. {
  236. UUID agentID = new(parms[^1]);
  237. Array.Resize(ref parms, parms.Length - 1);
  238. if (parms.Length != 3)
  239. {
  240. SendConsoleOutput(agentID, "Command parameter error");
  241. return;
  242. }
  243. string val = parms[2];
  244. if (val != "true" && val != "false")
  245. {
  246. SendConsoleOutput(agentID, "Command parameter error");
  247. return;
  248. }
  249. m_scene.StoreExtraSetting("auto_grant_attach_perms", val);
  250. SendConsoleOutput(agentID, $"auto_grant_attach_perms set to {val}");
  251. }
  252. /// <summary>
  253. /// Listen for client triggered running state changes so that we can persist the script's object if necessary.
  254. /// </summary>
  255. /// <param name='localID'></param>
  256. /// <param name='itemID'></param>
  257. private void OnScriptStarted(uint localID, UUID itemID)
  258. {
  259. SceneObjectGroup sog = m_scene.GetGroupByPrim(localID);
  260. if (sog is not null && sog.IsAttachment)
  261. sog.HasGroupChanged = true;
  262. }
  263. private void OnScriptStopped(uint localID, UUID itemID)
  264. {
  265. SceneObjectGroup sog = m_scene.GetGroupByPrim(localID);
  266. if (sog is not null && sog.IsAttachment)
  267. {
  268. // FIXME: This is a convoluted way for working out whether the script state has changed to stop
  269. // because it has been manually stopped or because the stop was called in UpdateDetachedObject() below
  270. // This needs to be handled in a less tangled way.
  271. ScenePresence sp = m_scene.GetScenePresence(sog.AttachedAvatar);
  272. if (sp is not null && sp.ControllingClient.IsActive)
  273. sog.HasGroupChanged = true;
  274. }
  275. }
  276. #endregion
  277. #region IAttachmentsModule
  278. public void CopyAttachments(IScenePresence sp, AgentData ad)
  279. {
  280. lock (sp.AttachmentsSyncLock)
  281. {
  282. // Attachment objects
  283. List<SceneObjectGroup> attachments = sp.GetAttachments();
  284. if (attachments.Count > 0)
  285. {
  286. ad.AttachmentObjects = new List<ISceneObject>(attachments.Count);
  287. ad.AttachmentObjectStates = new List<string>(attachments.Count);
  288. sp.InTransitScriptStates.Clear();
  289. foreach (SceneObjectGroup sog in attachments)
  290. {
  291. // We need to make a copy and pass that copy
  292. // because of transfers with the same sim
  293. SceneObjectGroup clone = (SceneObjectGroup)sog.CloneForNewScene();
  294. // Attachment module assumes that GroupPosition holds the offsets...!
  295. clone.RootPart.GroupPosition = sog.RootPart.AttachedPos;
  296. clone.IsAttachment = false;
  297. ad.AttachmentObjects.Add(clone);
  298. string state = sog.GetStateSnapshot();
  299. ad.AttachmentObjectStates.Add(state);
  300. sp.InTransitScriptStates.Add(state);
  301. // Scripts of the originals will be removed when the Agent is successfully removed.
  302. // sog.RemoveScriptInstances(true);
  303. }
  304. }
  305. }
  306. }
  307. public void CopyAttachments(AgentData ad, IScenePresence isp)
  308. {
  309. ScenePresence sp = isp as ScenePresence;
  310. if (ad.AttachmentObjects is not null && ad.AttachmentObjects.Count > 0)
  311. {
  312. lock (isp.AttachmentsSyncLock)
  313. {
  314. if(sp.IsDeleted)
  315. return;
  316. DeleteAttachmentsFromScene(isp, true); // delete
  317. }
  318. List<SceneObjectGroup> attachments = new(ad.AttachmentObjects.Count);
  319. int i = 0;
  320. for (int indx = 0; indx < ad.AttachmentObjects.Count; ++indx)
  321. {
  322. if(ad.AttachmentObjects[indx] is SceneObjectGroup sog && sog.OwnerID.Equals(sp.UUID))
  323. {
  324. sog.LocalId = 0;
  325. sog.RootPart.ClearUpdateSchedule();
  326. sog.SetState(ad.AttachmentObjectStates[i++], m_scene);
  327. attachments.Add(sog);
  328. }
  329. }
  330. ad.AttachmentObjects = null;
  331. ad.AttachmentObjectStates = null;
  332. if (attachments.Count > 0)
  333. m_scene.IncomingAttechments(sp, attachments);
  334. else
  335. sp.GotAttachmentsData = true;
  336. }
  337. else
  338. sp.GotAttachmentsData = true;
  339. }
  340. public void RezAttachments(IScenePresence sp)
  341. {
  342. if (!Enabled)
  343. return;
  344. if (sp.Appearance is null)
  345. {
  346. m_log.Warn($"[ATTACHMENTS MODULE]: Appearance has not been initialized for agent {sp.UUID}");
  347. return;
  348. }
  349. if (sp.GetAttachmentsCount() > 0)
  350. {
  351. if (DebugLevel > 0)
  352. m_log.Debug(
  353. $"[ATTACHMENTS MODULE]: Not doing attachment rez for {sp.Name} in {m_scene.Name} as their viewer has already rezzed attachments");
  354. return;
  355. }
  356. if (DebugLevel > 0)
  357. m_log.Debug($"[ATTACHMENTS MODULE]: Rezzing any attachments for {sp.Name} from simulator-side");
  358. XmlDocument doc = new();
  359. IAttachmentsService attServ = m_scene.RequestModuleInterface<IAttachmentsService>();
  360. if (attServ is not null)
  361. {
  362. m_log.Debug("[ATTACHMENT]: Loading attachment data from attachment service");
  363. string stateData = attServ.Get(sp.UUID.ToString());
  364. if (!string.IsNullOrEmpty(stateData))
  365. {
  366. try
  367. {
  368. doc.LoadXml(stateData);
  369. }
  370. catch { }
  371. }
  372. }
  373. Dictionary<UUID, string> itemData = new();
  374. XmlNodeList nodes = doc.GetElementsByTagName("Attachment");
  375. if (nodes.Count > 0)
  376. {
  377. foreach (XmlNode n in nodes)
  378. {
  379. XmlElement elem = (XmlElement)n;
  380. string itemID = elem.GetAttribute("ItemID");
  381. string xml = elem.InnerXml;
  382. itemData[new UUID(itemID)] = xml;
  383. }
  384. }
  385. List<AvatarAttachment> attachments = sp.Appearance.GetAttachments();
  386. if(sp.IsNPC)
  387. {
  388. for (int indx = 0; indx < attachments.Count; ++indx)
  389. {
  390. AvatarAttachment attach = attachments[indx];
  391. if(attach.AssetID.IsZero())
  392. continue;
  393. uint attachmentPt = (uint)attach.AttachPoint;
  394. //m_log.DebugFormat(
  395. // "[ATTACHMENTS MODULE]: Doing initial rez of attachment with itemID {0}, assetID {1}, point {2} for {3} in {4}",
  396. // attach.ItemID, attach.AssetID, p, sp.Name, m_scene.RegionInfo.RegionName);
  397. try
  398. {
  399. XmlDocument d = null;
  400. if (itemData.TryGetValue(attach.ItemID, out string xmlData))
  401. {
  402. d = new XmlDocument();
  403. d.LoadXml(xmlData);
  404. m_log.Info($"[ATTACHMENT]: Found saved state for item {attach.ItemID}, loading it");
  405. }
  406. // If we're an NPC then skip all the item checks and manipulations since we don't have an
  407. // inventory right now.
  408. RezSingleAttachmentFromInventoryInternal(sp, UUID.Zero, attach.AssetID, attachmentPt, true, d);
  409. }
  410. catch (Exception e)
  411. {
  412. UUID agentId = (sp.ControllingClient is null) ? UUID.Zero : sp.ControllingClient.AgentId;
  413. m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}",
  414. attach.ItemID, attach.AssetID, attachmentPt, agentId, e.Message, e.StackTrace);
  415. }
  416. }
  417. }
  418. else
  419. {
  420. // Let's get all items at once, so they get cached
  421. UUID[] items = new UUID[attachments.Count];
  422. for (int i = 0; i < attachments.Count; ++i)
  423. items[i] = attachments[i].ItemID;
  424. InventoryItemBase[] attItems = m_scene.InventoryService.GetMultipleItems(sp.UUID, items);
  425. if(attItems is null)
  426. return;
  427. for (int indx = 0; indx < attachments.Count; ++indx)
  428. {
  429. InventoryItemBase attItem = attItems[indx];
  430. if (attItem is null || attItem.Owner.NotEqual(sp.UUID))
  431. continue;
  432. AvatarAttachment attach = attachments[indx];
  433. uint attachmentPt = (uint)attach.AttachPoint;
  434. //m_log.DebugFormat(
  435. // "[ATTACHMENTS MODULE]: Doing initial rez of attachment with itemID {0}, assetID {1}, point {2} for {3} in {4}",
  436. // attach.ItemID, attach.AssetID, p, sp.Name, m_scene.RegionInfo.RegionName);
  437. try
  438. {
  439. XmlDocument d = null;
  440. if (itemData.TryGetValue(attach.ItemID, out string xmlData))
  441. {
  442. d = new XmlDocument();
  443. d.LoadXml(xmlData);
  444. m_log.Info($"[ATTACHMENT]: Found saved state for item {attach.ItemID}, loading it");
  445. }
  446. // If we're an NPC then skip all the item checks and manipulations since we don't have an
  447. // inventory right now.
  448. RezSingleAttachmentFromInventoryInternal(sp, attach.ItemID, attach.AssetID, attachmentPt, true, d);
  449. }
  450. catch (Exception e)
  451. {
  452. UUID agentId = (sp.ControllingClient is null) ? UUID.Zero : sp.ControllingClient.AgentId;
  453. m_log.ErrorFormat("[ATTACHMENTS MODULE]: Unable to rez attachment with itemID {0}, assetID {1}, point {2} for {3}: {4}\n{5}",
  454. attach.ItemID, attach.AssetID, attachmentPt, agentId, e.Message, e.StackTrace);
  455. }
  456. }
  457. }
  458. }
  459. public void DeRezAttachments(IScenePresence sp)
  460. {
  461. if (!Enabled)
  462. return;
  463. List<SceneObjectGroup> attachments = sp.GetAttachments();
  464. if (DebugLevel > 0)
  465. m_log.Debug(
  466. $"[ATTACHMENTS MODULE]: Saving {attachments.Count} attachments for {sp.Name} in {m_scene.Name}");
  467. if (attachments.Count <= 0)
  468. return;
  469. lock (sp.AttachmentsSyncLock)
  470. {
  471. if (sp.IsNPC)
  472. {
  473. foreach (SceneObjectGroup sog in attachments)
  474. UpdateDetachedObject(sp, sog, string.Empty);
  475. }
  476. else
  477. {
  478. foreach (SceneObjectGroup sog in attachments)
  479. UpdateDetachedObject(sp, sog, PrepareScriptInstanceForSave(sog, false));
  480. }
  481. sp.ClearAttachments();
  482. }
  483. }
  484. public void DeleteAttachmentsFromScene(IScenePresence sp, bool silent)
  485. {
  486. if (!Enabled)
  487. return;
  488. if (DebugLevel > 0)
  489. m_log.Debug(
  490. $"[ATTACHMENTS MODULE]: Deleting {sp.Name} attachments from scene {m_scene.Name}, silent = {silent}");
  491. List<SceneObjectGroup> attachments = sp.GetAttachments();
  492. foreach(SceneObjectGroup sog in attachments)
  493. {
  494. sog.Scene.DeleteSceneObject(sog, silent);
  495. }
  496. sp.ClearAttachments();
  497. }
  498. public bool AttachObject(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool silent,
  499. bool addToInventory, bool append)
  500. {
  501. if (!Enabled)
  502. return false;
  503. return AttachObjectInternal(sp, group, attachmentPt, silent, addToInventory, false, append);
  504. }
  505. /// <summary>
  506. /// Internal method which actually does all the work for attaching an object.
  507. /// </summary>
  508. /// <returns>The object attached.</returns>
  509. /// <param name='sp'></param>
  510. /// <param name='group'>The object to attach.</param>
  511. /// <param name='attachmentPt'></param>
  512. /// <param name='silent'></param>
  513. /// <param name='addToInventory'>If true then add object to user inventory.</param>
  514. /// <param name='resumeScripts'>If true then scripts are resumed on the attached object.</param>
  515. private bool AttachObjectInternal(IScenePresence sp, SceneObjectGroup group, uint attachmentPt,
  516. bool silent, bool addToInventory, bool resumeScripts, bool append)
  517. {
  518. //m_log.DebugFormat(
  519. // "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})",
  520. // group.Name, group.LocalId, sp.Name, attachmentPt, silent);
  521. int sittingAvs = group.GetSittingAvatarsCount();
  522. if (sittingAvs > 0)
  523. {
  524. if (DebugLevel > 0)
  525. m_log.Warn(
  526. $"[ATTACHMENTS MODULE]: Ignoring request to attach {group.Name}({group.UUID}) to {sp.Name} at point {attachmentPt} since {sittingAvs} avatars are still sitting on it");
  527. return false;
  528. }
  529. Vector3 attachPos = group.AbsolutePosition;
  530. // TODO: this short circuits multiple attachments functionality in LL viewer 2.1+ and should
  531. // be removed when that functionality is implemented in opensim
  532. attachmentPt &= 0x7f;
  533. // If the attachment point isn't the same as the one previously used
  534. // set it's offset position = 0 so that it appears on the attachment point
  535. // and not in a weird location somewhere unknown.
  536. if (attachmentPt != (uint)AttachmentPoint.Default && attachmentPt != group.AttachmentPoint)
  537. {
  538. attachPos = Vector3.Zero;
  539. }
  540. // if the attachment point is the same as previous, make sure we get the saved
  541. // position info.
  542. if (attachmentPt != 0 && attachmentPt == group.RootPart.Shape.LastAttachPoint)
  543. {
  544. attachPos = group.RootPart.AttachedPos;
  545. }
  546. // AttachmentPt 0 means the client chose to 'wear' the attachment.
  547. if (attachmentPt == (uint)AttachmentPoint.Default)
  548. {
  549. // Check object for stored attachment point
  550. attachmentPt = group.AttachmentPoint;
  551. }
  552. // if we didn't find an attach point, look for where it was last attached
  553. if (attachmentPt == 0)
  554. {
  555. attachmentPt = (uint)group.RootPart.Shape.LastAttachPoint;
  556. attachPos = group.RootPart.AttachedPos;
  557. }
  558. // if we still didn't find a suitable attachment point.......
  559. if (attachmentPt == 0)
  560. {
  561. // Stick it on left hand with Zero Offset from the attachment point.
  562. attachmentPt = (uint)AttachmentPoint.LeftHand;
  563. attachPos = Vector3.Zero;
  564. }
  565. if(attachmentPt > (uint)AttachmentPoint.LastValid)
  566. {
  567. m_log.Warn($"[ATTACHMENTS MODULE]: Invalid attachment point {attachmentPt} SP {sp.Name}");
  568. return false;
  569. }
  570. List<SceneObjectGroup> attachments = sp.GetAttachments();
  571. List<SceneObjectGroup> toRemove = new(attachments.Count);
  572. bool doRemCheck = !append;
  573. foreach(SceneObjectGroup sog in attachments)
  574. {
  575. // duplications ?
  576. if (group.UUID.Equals(sog.UUID))
  577. {
  578. // if (DebugLevel > 0)
  579. // m_log.WarnFormat(
  580. // "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since it's already attached",
  581. // group.Name, group.LocalId, sp.Name, attachmentPt);
  582. return false;
  583. }
  584. if(doRemCheck)
  585. {
  586. if(sog.AttachmentPoint == attachmentPt)
  587. {
  588. toRemove.Add(sog);
  589. if(!m_wearReplacesAllOption)
  590. doRemCheck = false;
  591. }
  592. }
  593. }
  594. if(attachments.Count - toRemove.Count >= Constants.MaxAgentAttachments)
  595. {
  596. m_log.Warn($"[ATTACHMENTS MODULE]: Max attachments exceded {sp.Name}");
  597. return false;
  598. }
  599. group.DetachFromBackup();
  600. group.AttachmentPoint = attachmentPt;
  601. group.RootPart.AttachedPos = attachPos;
  602. // If we're not appending, remove the rest as well
  603. lock (sp.AttachmentsSyncLock)
  604. {
  605. if (toRemove.Count > 0)
  606. {
  607. foreach (SceneObjectGroup g in toRemove)
  608. {
  609. if (g.FromItemID.IsNotZero())
  610. DetachSingleAttachmentToInv(sp, g);
  611. }
  612. }
  613. if (addToInventory && sp.PresenceType != PresenceType.Npc)
  614. UpdateUserInventoryWithAttachment(sp, group, attachmentPt, append);
  615. AttachToAgent(sp, group, attachmentPt, attachPos, silent);
  616. if (resumeScripts)
  617. {
  618. // Fire after attach, so we don't get messy perms dialogs
  619. // 4 == AttachedRez
  620. group.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 4);
  621. group.ResumeScripts();
  622. }
  623. else
  624. // Do this last so that event listeners have access to all the effects of the attachment
  625. // this can't be done when creating scripts:
  626. // scripts do internal enqueue of attach event
  627. // and not all scripts are loaded at this point
  628. m_scene.EventManager.TriggerOnAttach(group.LocalId, group.FromItemID, sp.UUID);
  629. }
  630. return true;
  631. }
  632. private void UpdateUserInventoryWithAttachment(IScenePresence sp, SceneObjectGroup group, uint attachmentPt, bool append)
  633. {
  634. // Add the new attachment to inventory if we don't already have it.
  635. UUID newAttachmentItemID = group.FromItemID;
  636. if (newAttachmentItemID.IsZero())
  637. newAttachmentItemID = AddSceneObjectAsNewAttachmentInInv(sp, group).ID;
  638. ShowAttachInUserInventory(sp, attachmentPt, newAttachmentItemID, group, append);
  639. }
  640. public ISceneEntity RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt)
  641. {
  642. return RezSingleAttachmentFromInventory(sp, itemID, AttachmentPt, null);
  643. }
  644. public ISceneEntity RezSingleAttachmentFromInventory(IScenePresence sp, UUID itemID, uint AttachmentPt, XmlDocument doc)
  645. {
  646. if (!Enabled)
  647. return null;
  648. if (DebugLevel > 0)
  649. m_log.DebugFormat(
  650. "[ATTACHMENTS MODULE]: RezSingleAttachmentFromInventory to point {0} from item {1} for {2} in {3}",
  651. (AttachmentPoint)AttachmentPt, itemID, sp.Name, m_scene.Name);
  652. // We check the attachments in the avatar appearance here rather than the objects attached to the
  653. // ScenePresence itself so that we can ignore calls by viewer 2/3 to attach objects on startup. We are
  654. // already doing this in ScenePresence.MakeRootAgent(). Simulator-side attaching needs to be done
  655. // because pre-outfit folder viewers (most version 1 viewers) require it.
  656. bool alreadyOn = false;
  657. List<AvatarAttachment> existingAttachments = sp.Appearance.GetAttachments();
  658. foreach (AvatarAttachment existingAttachment in existingAttachments)
  659. {
  660. if (existingAttachment.ItemID.Equals(itemID))
  661. {
  662. alreadyOn = true;
  663. break;
  664. }
  665. }
  666. if (alreadyOn)
  667. {
  668. if (DebugLevel > 0)
  669. m_log.Debug(
  670. $"[ATTACHMENTS MODULE]: Ignoring request by {sp.Name} to wear item {itemID} at {AttachmentPt} since it is already worn");
  671. return null;
  672. }
  673. bool append = (AttachmentPt & 0x80) != 0;
  674. AttachmentPt &= 0x7f;
  675. return RezSingleAttachmentFromInventoryInternal(sp, itemID, UUID.Zero, AttachmentPt, append, doc);
  676. }
  677. public void RezMultipleAttachmentsFromInventory(IScenePresence sp, List<KeyValuePair<UUID, uint>> rezlist)
  678. {
  679. if (!Enabled)
  680. return;
  681. if (DebugLevel > 0)
  682. m_log.Debug(
  683. $"[ATTACHMENTS MODULE]: Rezzing {rezlist.Count} attachments from inventory for {sp.Name} in {m_scene.Name}");
  684. foreach (KeyValuePair<UUID, uint> rez in rezlist)
  685. {
  686. RezSingleAttachmentFromInventory(sp, rez.Key, rez.Value);
  687. }
  688. }
  689. public void DetachSingleAttachmentToGround(IScenePresence sp, uint soLocalId)
  690. {
  691. Vector3 pos = new(2.5f, 0f, 0f);
  692. pos *= ((ScenePresence)sp).Rotation;
  693. pos += sp.AbsolutePosition;
  694. DetachSingleAttachmentToGround(sp, soLocalId, pos, Quaternion.Identity);
  695. }
  696. public void DetachSingleAttachmentToGround(IScenePresence sp, uint soLocalId, Vector3 absolutePos, Quaternion absoluteRot)
  697. {
  698. if (!Enabled)
  699. return;
  700. if (DebugLevel > 0)
  701. m_log.Debug(
  702. $"[ATTACHMENTS MODULE]: DetachSingleAttachmentToGround() for {sp.UUID}, object {soLocalId}");
  703. SceneObjectGroup so = m_scene.GetGroupByPrim(soLocalId);
  704. if (so is null)
  705. return;
  706. if (so.AttachedAvatar.NotEqual(sp.UUID))
  707. return;
  708. UUID inventoryID = so.FromItemID;
  709. // As per Linden spec, drop is disabled for temp attachs
  710. if (inventoryID.IsZero())
  711. return;
  712. if (DebugLevel > 0)
  713. m_log.Debug(
  714. $"[ATTACHMENTS MODULE]: In DetachSingleAttachmentToGround(), object is {so.Name} {so.LocalId}, associated item is {inventoryID}");
  715. lock (sp.AttachmentsSyncLock)
  716. {
  717. if (!m_scene.Permissions.CanRezObject(so.PrimCount, sp.UUID, sp.AbsolutePosition))
  718. return;
  719. bool changed = false;
  720. if (inventoryID.IsNotZero())
  721. changed = sp.Appearance.DetachAttachment(inventoryID);
  722. if (changed && m_scene.AvatarFactory != null)
  723. m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
  724. so.RootPart.Shape.LastAttachPoint = (byte)so.AttachmentPoint;
  725. sp.RemoveAttachment(so);
  726. so.FromItemID = UUID.Zero;
  727. so.AttachedAvatar = UUID.Zero;
  728. so.ClearPartAttachmentData();
  729. SceneObjectPart rootPart = so.RootPart;
  730. rootPart.SetParentLocalId(0);
  731. so.AbsolutePosition = absolutePos;
  732. if (absoluteRot.NotEqual(Quaternion.Identity))
  733. {
  734. so.UpdateGroupRotationR(absoluteRot);
  735. }
  736. rootPart.RemFlag(PrimFlags.TemporaryOnRez);
  737. so.ApplyPhysics();
  738. rootPart.Rezzed = DateTime.Now;
  739. so.AttachToBackup();
  740. m_scene.EventManager.TriggerParcelPrimCountTainted();
  741. rootPart.ClearUndoState();
  742. List<UUID> uuids = new() { inventoryID };
  743. m_scene.InventoryService.DeleteItems(sp.UUID, uuids);
  744. sp.ControllingClient.SendRemoveInventoryItem(inventoryID);
  745. }
  746. m_scene.EventManager.TriggerOnAttach(so.LocalId, so.UUID, UUID.Zero);
  747. // Attach (NULL) stops scripts. We don't want that. Resume them.
  748. so.RemoveScriptsPermissions(4 | 2048); // take controls and camera control
  749. so.ResumeScripts();
  750. so.HasGroupChanged = true;
  751. so.RootPart.ScheduleFullUpdate();
  752. so.ScheduleGroupForTerseUpdate();
  753. }
  754. public void DetachSingleAttachmentToInv(IScenePresence sp, SceneObjectGroup so)
  755. {
  756. if (so.AttachedAvatar.NotEqual(sp.UUID))
  757. {
  758. m_log.WarnFormat(
  759. "[ATTACHMENTS MODULE]: Tried to detach object {0} from {1} {2} but attached avatar id was {3} in {4}",
  760. so.Name, sp.Name, sp.UUID, so.AttachedAvatar, m_scene.RegionInfo.RegionName);
  761. return;
  762. }
  763. so.RemoveScriptsPermissions(4 | 2048); // take controls and camera control
  764. // If this didn't come from inventory, it also shouldn't go there
  765. // on detach. It's likely a temp attachment.
  766. if (so.FromItemID.IsZero())
  767. {
  768. PrepareScriptInstanceForSave(so, true);
  769. lock (sp.AttachmentsSyncLock)
  770. {
  771. bool changed = sp.Appearance.DetachAttachment(so.FromItemID);
  772. if (changed && m_scene.AvatarFactory != null)
  773. m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
  774. sp.RemoveAttachment(so);
  775. }
  776. m_scene.DeleteSceneObject(so, false, false);
  777. so.RemoveScriptInstances(true);
  778. so.Dispose();
  779. return;
  780. }
  781. if (DebugLevel > 0)
  782. m_log.DebugFormat(
  783. "[ATTACHMENTS MODULE]: Detaching object {0} {1} (FromItemID {2}) for {3} in {4}",
  784. so.Name, so.LocalId, so.FromItemID, sp.Name, m_scene.Name);
  785. // Scripts MUST be snapshotted before the object is
  786. // removed from the scene because doing otherwise will
  787. // clobber the run flag
  788. // This must be done outside the sp.AttachmentSyncLock so that there is no risk of a deadlock from
  789. // scripts performing attachment operations at the same time. Getting object states stops the scripts.
  790. string scriptedState = PrepareScriptInstanceForSave(so, true);
  791. lock (sp.AttachmentsSyncLock)
  792. {
  793. // Save avatar attachment information
  794. //m_log.Debug("[ATTACHMENTS MODULE]: Detaching from UserID: " + sp.UUID + ", ItemID: " + itemID);
  795. bool changed = sp.Appearance.DetachAttachment(so.FromItemID);
  796. if (changed && m_scene.AvatarFactory is not null)
  797. m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
  798. sp.RemoveAttachment(so);
  799. UpdateDetachedObject(sp, so, scriptedState);
  800. }
  801. }
  802. public void UpdateAttachmentPosition(SceneObjectGroup sog, Vector3 pos)
  803. {
  804. if (!Enabled)
  805. return;
  806. sog.UpdateGroupPosition(pos);
  807. sog.HasGroupChanged = true;
  808. }
  809. #endregion
  810. #region AttachmentModule private methods
  811. // This is public but is not part of the IAttachmentsModule interface.
  812. // RegionCombiner module needs to poke at it to deliver client events.
  813. // This breaks the encapsulation of the module and should get fixed somehow.
  814. public void SubscribeToClientEvents(IClientAPI client)
  815. {
  816. client.OnRezSingleAttachmentFromInv += Client_OnRezSingleAttachmentFromInv;
  817. client.OnRezMultipleAttachmentsFromInv += Client_OnRezMultipleAttachmentsFromInv;
  818. client.OnObjectAttach += Client_OnObjectAttach;
  819. client.OnObjectDetach += Client_OnObjectDetach;
  820. client.OnDetachAttachmentIntoInv += Client_OnDetachAttachmentIntoInv;
  821. client.OnObjectDrop += Client_OnObjectDrop;
  822. }
  823. // This is public but is not part of the IAttachmentsModule interface.
  824. // RegionCombiner module needs to poke at it to deliver client events.
  825. // This breaks the encapsulation of the module and should get fixed somehow.
  826. public void UnsubscribeFromClientEvents(IClientAPI client)
  827. {
  828. client.OnRezSingleAttachmentFromInv -= Client_OnRezSingleAttachmentFromInv;
  829. client.OnRezMultipleAttachmentsFromInv -= Client_OnRezMultipleAttachmentsFromInv;
  830. client.OnObjectAttach -= Client_OnObjectAttach;
  831. client.OnObjectDetach -= Client_OnObjectDetach;
  832. client.OnDetachAttachmentIntoInv -= Client_OnDetachAttachmentIntoInv;
  833. client.OnObjectDrop -= Client_OnObjectDrop;
  834. }
  835. /// <summary>
  836. /// Update the attachment asset for the new sog details if they have changed.
  837. /// </summary>
  838. /// <remarks>
  839. /// This is essential for preserving attachment attributes such as permission. Unlike normal scene objects,
  840. /// these details are not stored on the region.
  841. /// </remarks>
  842. /// <param name="sp"></param>
  843. /// <param name="grp"></param>
  844. /// <param name="saveAllScripted"></param>
  845. private void UpdateKnownItem(IScenePresence sp, SceneObjectGroup grp, string scriptedState)
  846. {
  847. if(!grp.HasGroupChanged)
  848. {
  849. if (DebugLevel > 0)
  850. {
  851. m_log.Debug(
  852. $"[ATTACHMENTS MODULE]: Don't need to update asset for unchanged attachment {grp.UUID}, attachpoint {grp.AttachmentPoint}");
  853. }
  854. return;
  855. }
  856. grp.HasGroupChanged = false;
  857. if (m_invAccessModule is null)
  858. return;
  859. if (grp.FromItemID.IsZero())
  860. {
  861. // We can't save temp attachments
  862. return;
  863. }
  864. if (sp.IsNPC)
  865. {
  866. return;
  867. }
  868. m_log.Debug($"[ATTACHMENTS MODULE]: Updating asset for attachment {grp.UUID}, attachpoint {grp.AttachmentPoint}");
  869. InventoryItemBase item = m_scene.InventoryService.GetItem(sp.UUID, grp.FromItemID);
  870. if (item is not null)
  871. {
  872. if (item.Owner.NotEqual(sp.UUID))
  873. {
  874. m_log.Debug($"[ATTACHMENTS MODULE]: Updating asset for attachment owner mismach: agent {sp.UUID}, owner{item.Owner}");
  875. return;
  876. }
  877. string sceneObjectXml = SceneObjectSerializer.ToOriginalXmlFormat(grp, scriptedState);
  878. // attach is rez, need to update permissions
  879. item.Flags &= ~(uint)(InventoryItemFlags.ObjectSlamPerm | InventoryItemFlags.ObjectOverwriteBase |
  880. InventoryItemFlags.ObjectOverwriteOwner | InventoryItemFlags.ObjectOverwriteGroup |
  881. InventoryItemFlags.ObjectOverwriteEveryone | InventoryItemFlags.ObjectOverwriteNextOwner);
  882. uint permsBase = (uint)(PermissionMask.Copy | PermissionMask.Transfer |
  883. PermissionMask.Modify | PermissionMask.Move |
  884. PermissionMask.Export | PermissionMask.FoldedMask);
  885. permsBase &= grp.CurrentAndFoldedNextPermissions();
  886. permsBase |= (uint)PermissionMask.Move;
  887. item.BasePermissions = permsBase;
  888. item.CurrentPermissions = permsBase;
  889. item.NextPermissions = permsBase & grp.RootPart.NextOwnerMask | (uint)PermissionMask.Move;
  890. item.EveryOnePermissions = permsBase & grp.RootPart.EveryoneMask;
  891. item.GroupPermissions = permsBase & grp.RootPart.GroupMask;
  892. item.CurrentPermissions &=
  893. ((uint)PermissionMask.Copy |
  894. (uint)PermissionMask.Transfer |
  895. (uint)PermissionMask.Modify |
  896. (uint)PermissionMask.Move |
  897. (uint)PermissionMask.Export |
  898. (uint)PermissionMask.FoldedMask); // Preserve folded permissions ??
  899. string name = grp.RootPart.Name;
  900. string desc = grp.RootPart.Description;
  901. AssetBase asset = m_scene.CreateAsset(name, desc, (sbyte)AssetType.Object,
  902. Utils.StringToBytes(sceneObjectXml), sp.UUID);
  903. item.Name = name;
  904. item.Description = desc;
  905. item.AssetID = asset.FullID;
  906. item.AssetType = (int)AssetType.Object;
  907. item.InvType = (int)InventoryType.Object;
  908. if(m_invAccessModule.UpdateInventoryItemAsset(sp.UUID, item, asset))
  909. sp.ControllingClient?.SendInventoryItemCreateUpdate(item, 0);
  910. }
  911. }
  912. /// <summary>
  913. /// Attach this scene object to the given avatar.
  914. /// </summary>
  915. /// <remarks>
  916. /// This isn't publicly available since attachments should always perform the corresponding inventory
  917. /// operation (to show the attach in user inventory and update the asset with positional information).
  918. /// </remarks>
  919. /// <param name="sp"></param>
  920. /// <param name="so"></param>
  921. /// <param name="attachmentpoint"></param>
  922. /// <param name="attachOffset"></param>
  923. /// <param name="silent"></param>
  924. private void AttachToAgent(IScenePresence sp, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent)
  925. {
  926. if (DebugLevel > 0)
  927. m_log.DebugFormat(
  928. "[ATTACHMENTS MODULE]: Adding attachment {0} to avatar {1} at pt {2} pos {3} {4} in {5}",
  929. so.Name, sp.Name, attachmentpoint, attachOffset, so.RootPart.AttachedPos, m_scene.Name);
  930. // Remove from database and parcel prim count
  931. m_scene.DeleteFromStorage(so.UUID);
  932. m_scene.EventManager.TriggerParcelPrimCountTainted();
  933. foreach (SceneObjectPart part in so.Parts)
  934. {
  935. //if (part.KeyframeMotion != null)
  936. // part.KeyframeMotion.Suspend();
  937. if (part.PhysActor is not null)
  938. {
  939. part.RemoveFromPhysics();
  940. }
  941. }
  942. so.RootPart.SetParentLocalId(sp.LocalId);
  943. so.AttachedAvatar = sp.UUID;
  944. so.AttachmentPoint = attachmentpoint;
  945. so.RootPart.AttachedPos = attachOffset;
  946. so.RootPart.GroupPosition = attachOffset; // can not set absolutepos
  947. so.IsAttachment = true;
  948. sp.AddAttachment(so);
  949. if (!silent)
  950. {
  951. if (so.HasPrivateAttachmentPoint)
  952. {
  953. if (DebugLevel > 0)
  954. m_log.Debug(
  955. $"[ATTACHMENTS MODULE]: Killing private HUD {so.Name} for avatars other than {sp.Name} at attachment point {so.AttachmentPoint}");
  956. // As this scene object can now only be seen by the attaching avatar, tell everybody else in the
  957. // scene that it's no longer in their awareness.
  958. m_scene.ForEachClient(
  959. client =>
  960. { if (client.IsActive && client.AgentId.NotEqual(so.AttachedAvatar))
  961. client.SendKillObject(new List<uint>() { so.LocalId });
  962. });
  963. }
  964. // Fudge below is an extremely unhelpful comment. It's probably here so that the scheduled full update
  965. // will succeed, as that will not update if an attachment is selected.
  966. so.IsSelected = false; // fudge....
  967. so.ScheduleGroupForUpdate(PrimUpdateFlags.FullUpdatewithAnimMatOvr);
  968. }
  969. // In case it is later dropped again, don't let
  970. // it get cleaned up
  971. so.RootPart.RemFlag(PrimFlags.TemporaryOnRez);
  972. }
  973. /// <summary>
  974. /// Add a scene object as a new attachment in the user inventory.
  975. /// </summary>
  976. /// <param name="remoteClient"></param>
  977. /// <param name="grp"></param>
  978. /// <returns>The user inventory item created that holds the attachment.</returns>
  979. private InventoryItemBase AddSceneObjectAsNewAttachmentInInv(IScenePresence sp, SceneObjectGroup grp)
  980. {
  981. if (m_invAccessModule is null)
  982. return null;
  983. if (DebugLevel > 0)
  984. m_log.Debug(
  985. $"[ATTACHMENTS MODULE]: Called AddSceneObjectAsAttachment for object {grp.Name} {grp.LocalId} for {sp.Name}");
  986. InventoryItemBase newItem = m_invAccessModule.CopyToInventory(
  987. DeRezAction.TakeCopy,
  988. m_scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object).ID,
  989. new List<SceneObjectGroup> { grp },
  990. sp.ControllingClient, true)[0];
  991. // sets itemID so client can show item as 'attached' in inventory
  992. grp.FromItemID = newItem.ID;
  993. return newItem;
  994. }
  995. /// <summary>
  996. /// Prepares the script instance for save.
  997. /// </summary>
  998. /// <remarks>
  999. /// This involves triggering the detach event and getting the script state (which also stops the script)
  1000. /// This MUST be done outside sp.AttachmentsSyncLock, since otherwise there is a chance of deadlock if a
  1001. /// running script is performing attachment operations.
  1002. /// </remarks>
  1003. /// <returns>
  1004. /// The script state ready for persistence.
  1005. /// </returns>
  1006. /// <param name='grp'>
  1007. /// </param>
  1008. /// <param name='fireDetachEvent'>
  1009. /// If true, then fire the script event before we save its state.
  1010. /// </param>
  1011. private string PrepareScriptInstanceForSave(SceneObjectGroup grp, bool fireDetachEvent)
  1012. {
  1013. if (fireDetachEvent)
  1014. {
  1015. m_scene.EventManager.TriggerOnAttach(grp.LocalId, grp.FromItemID, UUID.Zero);
  1016. // Allow detach event time to do some work before stopping the script
  1017. Thread.Sleep(30);
  1018. }
  1019. using StringWriter sw = new();
  1020. using XmlTextWriter writer = new(sw);
  1021. grp.SaveScriptedState(writer);
  1022. return sw.ToString();
  1023. }
  1024. private void UpdateDetachedObject(IScenePresence sp, SceneObjectGroup so, string scriptedState)
  1025. {
  1026. // Don't save attachments for HG visitors, it
  1027. // messes up their inventory. When a HG visitor logs
  1028. // out on a foreign grid, their attachments will be
  1029. // reloaded in the state they were in when they left
  1030. // the home grid. This is best anyway as the visited
  1031. // grid may use an incompatible script engine.
  1032. bool saveChanged = sp.PresenceType != PresenceType.Npc
  1033. && (m_scene.UserManagementModule is null || m_scene.UserManagementModule.IsLocalGridUser(sp.UUID));
  1034. // Remove the object from the scene so no more updates
  1035. // are sent. Doing this before the below changes will ensure
  1036. // updates can't cause "HUD artefacts"
  1037. m_scene.DeleteSceneObject(so, false, false);
  1038. // Prepare sog for storage
  1039. so.AttachedAvatar = UUID.Zero;
  1040. so.RootPart.SetParentLocalId(0);
  1041. so.IsAttachment = false;
  1042. if (saveChanged)
  1043. {
  1044. // We cannot use AbsolutePosition here because that would
  1045. // attempt to cross the prim as it is detached
  1046. so.ForEachPart(x => { x.GroupPosition = so.RootPart.AttachedPos; });
  1047. UpdateKnownItem(sp, so, scriptedState);
  1048. }
  1049. // Now, remove the scripts
  1050. so.RemoveScriptInstances(true);
  1051. so.Dispose();
  1052. }
  1053. protected SceneObjectGroup RezSingleAttachmentFromInventoryInternal(
  1054. IScenePresence sp, UUID itemID, UUID assetID, uint attachmentPt, bool append, XmlDocument doc)
  1055. {
  1056. if (m_invAccessModule is null)
  1057. return null;
  1058. SceneObjectGroup objatt;
  1059. UUID rezGroupID;
  1060. // This will fail if the user aborts login. sp will exist
  1061. // but ControllintClient will be null.
  1062. try
  1063. {
  1064. rezGroupID = sp.ControllingClient.ActiveGroupId;
  1065. }
  1066. catch
  1067. {
  1068. return null;
  1069. }
  1070. bool ItemIDNotZero = itemID.IsNotZero();
  1071. if (ItemIDNotZero)
  1072. objatt = m_invAccessModule.RezObject(sp.ControllingClient,
  1073. itemID, rezGroupID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
  1074. false, false, sp.UUID, true);
  1075. else
  1076. objatt = m_invAccessModule.RezObject(sp.ControllingClient,
  1077. null, rezGroupID, assetID, Vector3.Zero, Vector3.Zero, UUID.Zero, (byte)1, true,
  1078. false, false, sp.UUID, true);
  1079. if (objatt is null)
  1080. {
  1081. if(ItemIDNotZero)
  1082. {
  1083. m_log.Warn($"[ATTACHMENTS MODULE]: did not attach item {itemID} to avatar {sp.Name} at point {attachmentPt}");
  1084. }
  1085. else
  1086. {
  1087. m_log.Warn($"[ATTACHMENTS MODULE]: did not attach item with asset {assetID} to avatar {sp.Name} at point {attachmentPt}");
  1088. }
  1089. return null;
  1090. }
  1091. if (!ItemIDNotZero)
  1092. {
  1093. // We need to have a FromItemID for multiple attachments on a single attach point to appear. This is
  1094. // true on Singularity 1.8.5 and quite possibly other viewers as well. As NPCs don't have an inventory
  1095. // we will satisfy this requirement by inserting a random UUID.
  1096. objatt.FromItemID = UUID.Random();
  1097. }
  1098. if (DebugLevel > 0)
  1099. m_log.DebugFormat(
  1100. "[ATTACHMENTS MODULE]: Rezzed single object {0} with {1} prims for attachment to {2} on point {3} in {4}",
  1101. objatt.Name, objatt.PrimCount, sp.Name, attachmentPt, m_scene.Name);
  1102. // HasGroupChanged is being set from within RezObject. Ideally it would be set by the caller.
  1103. objatt.HasGroupChanged = false;
  1104. Vector3 lastPos = objatt.RootPart.OffsetPosition;
  1105. Vector3 lastAttPos = objatt.RootPart.AttachedPos;
  1106. uint lastattPoint = objatt.AttachmentPoint;
  1107. bool doneAttach;
  1108. // FIXME: Detect whether it's really likely for AttachObject to throw an exception in the normal
  1109. // course of events. If not, then it's probably not worth trying to recover the situation
  1110. // since this is more likely to trigger further exceptions and confuse later debugging. If
  1111. // exceptions can be thrown in expected error conditions (not NREs) then make this consistent
  1112. // since other normal error conditions will simply return false instead.
  1113. // This will throw if the attachment fails
  1114. try
  1115. {
  1116. if (doc is not null)
  1117. {
  1118. objatt.LoadScriptState(doc);
  1119. objatt.ResetOwnerChangeFlag();
  1120. }
  1121. doneAttach = AttachObjectInternal(sp, objatt, attachmentPt, false, true, true, append);
  1122. }
  1123. catch (Exception e)
  1124. {
  1125. m_log.Error(
  1126. $"[ATTACHMENTS MODULE]: Failed to attach {objatt.Name} {objatt.UUID} for {sp.Name}, Error: {e.Message}");
  1127. doneAttach = false;
  1128. }
  1129. if(!doneAttach)
  1130. {
  1131. // Make sure the object doesn't stick around and bail
  1132. sp.RemoveAttachment(objatt);
  1133. m_scene.DeleteSceneObject(objatt, false);
  1134. return null;
  1135. }
  1136. if (lastattPoint != objatt.AttachmentPoint ||
  1137. !lastPos.Equals(objatt.RootPart.OffsetPosition) ||
  1138. !lastAttPos.Equals(objatt.RootPart.AttachedPos))
  1139. objatt.HasGroupChanged = true;
  1140. return objatt;
  1141. }
  1142. /// <summary>
  1143. /// Update the user inventory to reflect an attachment
  1144. /// </summary>
  1145. /// <param name="sp"></param>
  1146. /// <param name="AttachmentPt"></param>
  1147. /// <param name="itemID"></param>
  1148. /// <param name="att"></param>
  1149. private void ShowAttachInUserInventory(IScenePresence sp, uint AttachmentPt, UUID itemID, SceneObjectGroup att, bool append)
  1150. {
  1151. //m_log.DebugFormat(
  1152. // "[USER INVENTORY]: Updating attachment {0} for {1} at {2} using item ID {3}",
  1153. // att.Name, sp.Name, AttachmentPt, itemID);
  1154. if (itemID.IsZero())
  1155. {
  1156. m_log.Error("[ATTACHMENTS MODULE]: Unable to save attachment. Error inventory item ID");
  1157. return;
  1158. }
  1159. if (AttachmentPt == 0)
  1160. {
  1161. m_log.Error("[ATTACHMENTS MODULE]: Unable to save attachment. Error attachment point");
  1162. return;
  1163. }
  1164. InventoryItemBase item = m_scene.InventoryService.GetItem(sp.UUID, itemID);
  1165. if (item is null)
  1166. return;
  1167. int attFlag = append ? 0x80 : 0;
  1168. bool changed = sp.Appearance.SetAttachment((int)AttachmentPt | attFlag, itemID, item.AssetID);
  1169. if (changed && m_scene.AvatarFactory is not null)
  1170. {
  1171. if (DebugLevel > 0)
  1172. m_log.Debug(
  1173. $"[ATTACHMENTS MODULE]: Queueing appearance save for {sp.Name}, attachment {att.Name} point {AttachmentPt}");
  1174. m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
  1175. }
  1176. }
  1177. #endregion
  1178. #region Client Event Handlers
  1179. private ISceneEntity Client_OnRezSingleAttachmentFromInv(IClientAPI remoteClient, UUID itemID, uint AttachmentPt)
  1180. {
  1181. if (DebugLevel > 0)
  1182. m_log.Debug(
  1183. $"[ATTACHMENTS MODULE]: Rezzing attachment to point {(AttachmentPoint)AttachmentPt} from item {itemID} for {remoteClient.Name}");
  1184. if (remoteClient.SceneAgent is not ScenePresence sp)
  1185. {
  1186. m_log.Error(
  1187. $"[ATTACHMENTS MODULE]: Could not find presence {remoteClient.Name} {remoteClient.AgentId} in RezSingleAttachmentFromInventory()");
  1188. return null;
  1189. }
  1190. return RezSingleAttachmentFromInventory(sp, itemID, AttachmentPt);
  1191. }
  1192. private void Client_OnRezMultipleAttachmentsFromInv(IClientAPI remoteClient, List<KeyValuePair<UUID, uint>> rezlist)
  1193. {
  1194. if (remoteClient.SceneAgent is ScenePresence sp)
  1195. RezMultipleAttachmentsFromInventory(sp, rezlist);
  1196. else
  1197. m_log.ErrorFormat(
  1198. $"[ATTACHMENTS MODULE]: Could not find presence {remoteClient.Name} {remoteClient.AgentId} in RezMultipleAttachmentsFromInventory()");
  1199. }
  1200. private void Client_OnObjectAttach(IClientAPI remoteClient, uint objectLocalID, uint AttachmentPt, bool silent)
  1201. {
  1202. if (!Enabled)
  1203. return;
  1204. if (DebugLevel > 0)
  1205. m_log.DebugFormat(
  1206. $"[ATTACHMENTS MODULE]: Attaching object with localid {objectLocalID} to {remoteClient.Name} point {AttachmentPt} from ground (silent = {silent})");
  1207. try
  1208. {
  1209. if(remoteClient.SceneAgent is not ScenePresence sp)
  1210. {
  1211. m_log.ErrorFormat(
  1212. $"[ATTACHMENTS MODULE]: Could not find presence {remoteClient.Name} {remoteClient.AgentId}");
  1213. return;
  1214. }
  1215. // If we can't take it, we can't attach it!
  1216. SceneObjectPart part = m_scene.GetSceneObjectPart(objectLocalID);
  1217. if (part is null)
  1218. return;
  1219. SceneObjectGroup group = part.ParentGroup;
  1220. if (!m_scene.Permissions.CanTakeObject(group, sp))
  1221. {
  1222. remoteClient.SendAgentAlertMessage("You don't have sufficient permissions to attach this object", false);
  1223. return;
  1224. }
  1225. bool append = (AttachmentPt & 0x80) != 0;
  1226. AttachmentPt &= 0x7f;
  1227. // Calls attach with a Zero position
  1228. if (AttachObject(sp, group , AttachmentPt, false, true, append))
  1229. {
  1230. if (DebugLevel > 0)
  1231. m_log.Debug(
  1232. $"[ATTACHMENTS MODULE]: Saving avatar attachment. AgentID: {remoteClient.AgentId}, AttachmentPoint: {AttachmentPt}");
  1233. // Save avatar attachment information
  1234. m_scene.AvatarFactory.QueueAppearanceSave(sp.UUID);
  1235. }
  1236. }
  1237. catch (Exception e)
  1238. {
  1239. m_log.Error($"[ATTACHMENTS MODULE]: exception upon Attach Object {e.Message}");
  1240. }
  1241. }
  1242. private void Client_OnObjectDetach(uint objectLocalID, IClientAPI remoteClient)
  1243. {
  1244. if (!Enabled)
  1245. return;
  1246. if(remoteClient.SceneAgent is not ScenePresence sp)
  1247. return;
  1248. SceneObjectGroup group = m_scene.GetGroupByPrim(objectLocalID);
  1249. if (group is not null)
  1250. DetachSingleAttachmentToInv(sp, group);
  1251. }
  1252. private void Client_OnDetachAttachmentIntoInv(UUID itemID, IClientAPI remoteClient)
  1253. {
  1254. if (!Enabled)
  1255. return;
  1256. if (remoteClient.SceneAgent is ScenePresence sp)
  1257. {
  1258. List<SceneObjectGroup> attachments = sp.GetAttachments();
  1259. foreach (SceneObjectGroup group in attachments)
  1260. {
  1261. if (group.FromItemID.Equals(itemID) && group.FromItemID.IsNotZero())
  1262. {
  1263. DetachSingleAttachmentToInv(sp, group);
  1264. return;
  1265. }
  1266. }
  1267. }
  1268. }
  1269. private void Client_OnObjectDrop(uint soLocalId, IClientAPI remoteClient)
  1270. {
  1271. if (!Enabled)
  1272. return;
  1273. if (remoteClient.SceneAgent is ScenePresence sp)
  1274. DetachSingleAttachmentToGround(sp, soLocalId);
  1275. }
  1276. #endregion
  1277. }
  1278. }