diff options
author | Master ScienceSim | 2010-10-21 16:48:58 -0700 |
---|---|---|
committer | Master ScienceSim | 2010-10-21 16:48:58 -0700 |
commit | 267f18925d06ca05e2a5ffbfbb63582783762439 (patch) | |
tree | 3f2d00b3269a97ad75094f58137f56ebbb36aa35 | |
parent | Major refactoring of appearance handling. (diff) | |
download | opensim-SC-267f18925d06ca05e2a5ffbfbb63582783762439.zip opensim-SC-267f18925d06ca05e2a5ffbfbb63582783762439.tar.gz opensim-SC-267f18925d06ca05e2a5ffbfbb63582783762439.tar.bz2 opensim-SC-267f18925d06ca05e2a5ffbfbb63582783762439.tar.xz |
First attempt to get multiple attachments working to support viewer2.
The attachment code appears to work correctly for 1.23 viewers so, in
spite of some big changes in the internal representation, there don't
appear to be regressions. That being said, I still can't get a viewer2
avatar to show correctly.
6 files changed, 100 insertions, 111 deletions
diff --git a/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs b/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs index 0589748..1829c8d 100644 --- a/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs +++ b/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs | |||
@@ -1614,12 +1614,12 @@ namespace OpenSim.ApplicationPlugins.RemoteController | |||
1614 | } | 1614 | } |
1615 | 1615 | ||
1616 | // Attachments | 1616 | // Attachments |
1617 | Dictionary<int, AvatarAttachment> attachments = avatarAppearance.Attachments; | 1617 | List<AvatarAttachment> attachments = avatarAppearance.GetAttachments(); |
1618 | 1618 | ||
1619 | foreach (KeyValuePair<int, AvatarAttachment> attachment in attachments) | 1619 | foreach (AvatarAttachment attachment in attachments) |
1620 | { | 1620 | { |
1621 | int attachpoint = attachment.Value.AttachPoint; | 1621 | int attachpoint = attachment.AttachPoint; |
1622 | UUID itemID = attachment.Value.ItemID; | 1622 | UUID itemID = attachment.ItemID; |
1623 | 1623 | ||
1624 | if (itemID != UUID.Zero) | 1624 | if (itemID != UUID.Zero) |
1625 | { | 1625 | { |
diff --git a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs index 8271d76..3f6d4d6 100644 --- a/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs +++ b/OpenSim/ApplicationPlugins/Rest/Inventory/RestAppearanceServices.cs | |||
@@ -27,6 +27,7 @@ | |||
27 | 27 | ||
28 | using System; | 28 | using System; |
29 | using System.Collections; | 29 | using System.Collections; |
30 | using System.Collections.Generic; | ||
30 | using System.Xml; | 31 | using System.Xml; |
31 | using OpenMetaverse; | 32 | using OpenMetaverse; |
32 | using OpenSim.Framework; | 33 | using OpenSim.Framework; |
@@ -765,25 +766,19 @@ namespace OpenSim.ApplicationPlugins.Rest.Inventory | |||
765 | FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset); | 766 | FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset); |
766 | FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset); | 767 | FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset); |
767 | 768 | ||
768 | Hashtable attachments = rdata.userAppearance.GetAttachments(); | 769 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId); |
769 | 770 | ||
770 | if (attachments != null) | 771 | rdata.writer.WriteStartElement("Attachments"); |
772 | List<AvatarAttachment> attachments = rdata.userAppearance.GetAttachments(); | ||
773 | foreach (AvatarAttachment attach in attachments) | ||
771 | { | 774 | { |
772 | 775 | rdata.writer.WriteStartElement("Attachment"); | |
773 | Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId); | 776 | rdata.writer.WriteAttributeString("AtPoint", attach.AttachPoint.ToString()); |
774 | 777 | rdata.writer.WriteAttributeString("Item", attach.ItemID.ToString()); | |
775 | rdata.writer.WriteStartElement("Attachments"); | 778 | rdata.writer.WriteAttributeString("Asset", attach.AssetID.ToString()); |
776 | for (int i = 0; i < attachments.Count; i++) | ||
777 | { | ||
778 | Hashtable attachment = attachments[i] as Hashtable; | ||
779 | rdata.writer.WriteStartElement("Attachment"); | ||
780 | rdata.writer.WriteAttributeString("AtPoint", i.ToString()); | ||
781 | rdata.writer.WriteAttributeString("Item", (string) attachment["item"]); | ||
782 | rdata.writer.WriteAttributeString("Asset", (string) attachment["asset"]); | ||
783 | rdata.writer.WriteEndElement(); | ||
784 | } | ||
785 | rdata.writer.WriteEndElement(); | 779 | rdata.writer.WriteEndElement(); |
786 | } | 780 | } |
781 | rdata.writer.WriteEndElement(); | ||
787 | 782 | ||
788 | Primitive.TextureEntry texture = rdata.userAppearance.Texture; | 783 | Primitive.TextureEntry texture = rdata.userAppearance.Texture; |
789 | 784 | ||
diff --git a/OpenSim/Framework/AvatarAppearance.cs b/OpenSim/Framework/AvatarAppearance.cs index 55646dd..ba0cad2 100644 --- a/OpenSim/Framework/AvatarAppearance.cs +++ b/OpenSim/Framework/AvatarAppearance.cs | |||
@@ -51,7 +51,7 @@ namespace OpenSim.Framework | |||
51 | protected byte[] m_visualparams; | 51 | protected byte[] m_visualparams; |
52 | protected Primitive.TextureEntry m_texture; | 52 | protected Primitive.TextureEntry m_texture; |
53 | protected AvatarWearable[] m_wearables; | 53 | protected AvatarWearable[] m_wearables; |
54 | protected Dictionary<int, AvatarAttachment> m_attachments; | 54 | protected Dictionary<int, List<AvatarAttachment>> m_attachments; |
55 | protected float m_avatarHeight = 0; | 55 | protected float m_avatarHeight = 0; |
56 | protected float m_hipOffset = 0; | 56 | protected float m_hipOffset = 0; |
57 | 57 | ||
@@ -85,11 +85,6 @@ namespace OpenSim.Framework | |||
85 | set { m_wearables = value; } | 85 | set { m_wearables = value; } |
86 | } | 86 | } |
87 | 87 | ||
88 | public virtual Dictionary<int, AvatarAttachment> Attachments | ||
89 | { | ||
90 | get { return m_attachments; } | ||
91 | } | ||
92 | |||
93 | public virtual UUID BodyItem { | 88 | public virtual UUID BodyItem { |
94 | get { return m_wearables[AvatarWearable.BODY].ItemID; } | 89 | get { return m_wearables[AvatarWearable.BODY].ItemID; } |
95 | set { m_wearables[AvatarWearable.BODY].ItemID = value; } | 90 | set { m_wearables[AvatarWearable.BODY].ItemID = value; } |
@@ -246,7 +241,7 @@ namespace OpenSim.Framework | |||
246 | SetDefaultParams(); | 241 | SetDefaultParams(); |
247 | SetHeight(); | 242 | SetHeight(); |
248 | 243 | ||
249 | m_attachments = new Dictionary<int, AvatarAttachment>(); | 244 | m_attachments = new Dictionary<int, List<AvatarAttachment>>(); |
250 | } | 245 | } |
251 | 246 | ||
252 | public AvatarAppearance(UUID avatarID, OSDMap map) | 247 | public AvatarAppearance(UUID avatarID, OSDMap map) |
@@ -284,7 +279,7 @@ namespace OpenSim.Framework | |||
284 | 279 | ||
285 | SetHeight(); | 280 | SetHeight(); |
286 | 281 | ||
287 | m_attachments = new Dictionary<int, AvatarAttachment>(); | 282 | m_attachments = new Dictionary<int, List<AvatarAttachment>>(); |
288 | } | 283 | } |
289 | 284 | ||
290 | public AvatarAppearance(AvatarAppearance appearance) | 285 | public AvatarAppearance(AvatarAppearance appearance) |
@@ -302,7 +297,7 @@ namespace OpenSim.Framework | |||
302 | SetDefaultParams(); | 297 | SetDefaultParams(); |
303 | SetHeight(); | 298 | SetHeight(); |
304 | 299 | ||
305 | m_attachments = new Dictionary<int, AvatarAttachment>(); | 300 | m_attachments = new Dictionary<int, List<AvatarAttachment>>(); |
306 | 301 | ||
307 | return; | 302 | return; |
308 | } | 303 | } |
@@ -329,9 +324,10 @@ namespace OpenSim.Framework | |||
329 | if (appearance.VisualParams != null) | 324 | if (appearance.VisualParams != null) |
330 | m_visualparams = (byte[])appearance.VisualParams.Clone(); | 325 | m_visualparams = (byte[])appearance.VisualParams.Clone(); |
331 | 326 | ||
332 | m_attachments = new Dictionary<int, AvatarAttachment>(); | 327 | // Copy the attachment, force append mode since that ensures consistency |
333 | foreach (KeyValuePair<int, AvatarAttachment> kvp in appearance.Attachments) | 328 | m_attachments = new Dictionary<int, List<AvatarAttachment>>(); |
334 | m_attachments[kvp.Key] = new AvatarAttachment(kvp.Value); | 329 | foreach (AvatarAttachment attachment in appearance.GetAttachments()) |
330 | AppendAttachment(new AvatarAttachment(attachment)); | ||
335 | } | 331 | } |
336 | 332 | ||
337 | protected virtual void SetDefaultWearables() | 333 | protected virtual void SetDefaultWearables() |
@@ -487,12 +483,41 @@ namespace OpenSim.Framework | |||
487 | } | 483 | } |
488 | // DEBUG OFF | 484 | // DEBUG OFF |
489 | 485 | ||
490 | public void SetAttachments(AvatarAttachment[] data) | 486 | /// <summary> |
487 | /// Get a list of the attachments, note that there may be | ||
488 | /// duplicate attachpoints | ||
489 | /// </summary> | ||
490 | public List<AvatarAttachment> GetAttachments() | ||
491 | { | ||
492 | List<AvatarAttachment> alist = new List<AvatarAttachment>(); | ||
493 | foreach (KeyValuePair<int, List<AvatarAttachment>> kvp in m_attachments) | ||
494 | { | ||
495 | foreach (AvatarAttachment attach in kvp.Value) | ||
496 | alist.Add(new AvatarAttachment(attach)); | ||
497 | } | ||
498 | |||
499 | return alist; | ||
500 | } | ||
501 | |||
502 | internal void AppendAttachment(AvatarAttachment attach) | ||
491 | { | 503 | { |
492 | foreach (AvatarAttachment attach in data) | 504 | if (! m_attachments.ContainsKey(attach.AttachPoint)) |
493 | m_attachments[attach.AttachPoint] = new AvatarAttachment(attach); | 505 | m_attachments[attach.AttachPoint] = new List<AvatarAttachment>(); |
506 | m_attachments[attach.AttachPoint].Add(attach); | ||
494 | } | 507 | } |
495 | 508 | ||
509 | internal void ReplaceAttachment(AvatarAttachment attach) | ||
510 | { | ||
511 | m_attachments[attach.AttachPoint] = new List<AvatarAttachment>(); | ||
512 | m_attachments[attach.AttachPoint].Add(attach); | ||
513 | } | ||
514 | |||
515 | /// <summary> | ||
516 | /// Add an attachment, if the attachpoint has the | ||
517 | /// 0x80 bit set then we assume this is an append | ||
518 | /// operation otherwise we replace whatever is | ||
519 | /// currently attached at the attachpoint | ||
520 | /// </summary> | ||
496 | public void SetAttachment(int attachpoint, UUID item, UUID asset) | 521 | public void SetAttachment(int attachpoint, UUID item, UUID asset) |
497 | { | 522 | { |
498 | if (attachpoint == 0) | 523 | if (attachpoint == 0) |
@@ -505,67 +530,47 @@ namespace OpenSim.Framework | |||
505 | return; | 530 | return; |
506 | } | 531 | } |
507 | 532 | ||
508 | m_attachments[attachpoint] = new AvatarAttachment(attachpoint,item,asset); | 533 | // check if this is an append or a replace, 0x80 marks it as an append |
509 | } | 534 | if ((attachpoint & 0x80) > 0) |
510 | |||
511 | public Hashtable GetAttachments() | ||
512 | { | ||
513 | if (m_attachments.Count == 0) | ||
514 | return null; | ||
515 | |||
516 | Hashtable ret = new Hashtable(); | ||
517 | |||
518 | foreach (KeyValuePair<int, AvatarAttachment> kvp in m_attachments) | ||
519 | { | 535 | { |
520 | Hashtable data = new Hashtable(); | 536 | // strip the append bit |
521 | data["item"] = kvp.Value.ItemID.ToString(); | 537 | int point = attachpoint & 0x7F; |
522 | data["asset"] = kvp.Value.AssetID.ToString(); | 538 | AppendAttachment(new AvatarAttachment(point, item, asset)); |
523 | 539 | } | |
524 | ret[kvp.Key] = data; | 540 | else |
541 | { | ||
542 | ReplaceAttachment(new AvatarAttachment(attachpoint,item,asset)); | ||
525 | } | 543 | } |
526 | |||
527 | return ret; | ||
528 | } | ||
529 | |||
530 | public List<int> GetAttachedPoints() | ||
531 | { | ||
532 | return new List<int>(m_attachments.Keys); | ||
533 | } | ||
534 | |||
535 | public UUID GetAttachedItem(int attachpoint) | ||
536 | { | ||
537 | if (!m_attachments.ContainsKey(attachpoint)) | ||
538 | return UUID.Zero; | ||
539 | |||
540 | return m_attachments[attachpoint].ItemID; | ||
541 | } | ||
542 | |||
543 | public UUID GetAttachedAsset(int attachpoint) | ||
544 | { | ||
545 | if (!m_attachments.ContainsKey(attachpoint)) | ||
546 | return UUID.Zero; | ||
547 | |||
548 | return m_attachments[attachpoint].AssetID; | ||
549 | } | 544 | } |
550 | 545 | ||
551 | public int GetAttachpoint(UUID itemID) | 546 | public int GetAttachpoint(UUID itemID) |
552 | { | 547 | { |
553 | foreach (KeyValuePair<int, AvatarAttachment> kvp in m_attachments) | 548 | foreach (KeyValuePair<int, List<AvatarAttachment>> kvp in m_attachments) |
554 | { | 549 | { |
555 | if (kvp.Value.ItemID == itemID) | 550 | int index = kvp.Value.FindIndex(delegate(AvatarAttachment a) { return a.ItemID == itemID; }); |
556 | { | 551 | if (index >= 0) |
557 | return kvp.Key; | 552 | return kvp.Key; |
558 | } | ||
559 | } | 553 | } |
554 | |||
560 | return 0; | 555 | return 0; |
561 | } | 556 | } |
562 | 557 | ||
563 | public void DetachAttachment(UUID itemID) | 558 | public void DetachAttachment(UUID itemID) |
564 | { | 559 | { |
565 | int attachpoint = GetAttachpoint(itemID); | 560 | foreach (KeyValuePair<int, List<AvatarAttachment>> kvp in m_attachments) |
561 | { | ||
562 | int index = kvp.Value.FindIndex(delegate(AvatarAttachment a) { return a.ItemID == itemID; }); | ||
563 | if (index >= 0) | ||
564 | { | ||
565 | // Remove it from the list of attachments at that attach point | ||
566 | m_attachments[kvp.Key].RemoveAt(index); | ||
566 | 567 | ||
567 | if (attachpoint > 0) | 568 | // And remove the list if there are no more attachments here |
568 | m_attachments.Remove(attachpoint); | 569 | if (m_attachments[kvp.Key].Count == 0) |
570 | m_attachments.Remove(kvp.Key); | ||
571 | return; | ||
572 | } | ||
573 | } | ||
569 | } | 574 | } |
570 | 575 | ||
571 | public void ClearAttachments() | 576 | public void ClearAttachments() |
@@ -607,8 +612,8 @@ namespace OpenSim.Framework | |||
607 | 612 | ||
608 | // Attachments | 613 | // Attachments |
609 | OSDArray attachs = new OSDArray(m_attachments.Count); | 614 | OSDArray attachs = new OSDArray(m_attachments.Count); |
610 | foreach (KeyValuePair<int, AvatarAttachment> kvp in m_attachments) | 615 | foreach (AvatarAttachment attach in GetAttachments()) |
611 | attachs.Add(kvp.Value.Pack()); | 616 | attachs.Add(attach.Pack()); |
612 | data["attachments"] = attachs; | 617 | data["attachments"] = attachs; |
613 | 618 | ||
614 | return data; | 619 | return data; |
@@ -675,15 +680,12 @@ namespace OpenSim.Framework | |||
675 | } | 680 | } |
676 | 681 | ||
677 | // Attachments | 682 | // Attachments |
678 | m_attachments = new Dictionary<int, AvatarAttachment>(); | 683 | m_attachments = new Dictionary<int, List<AvatarAttachment>>(); |
679 | if ((data != null) && (data["attachments"] != null) && (data["attachments"]).Type == OSDType.Array) | 684 | if ((data != null) && (data["attachments"] != null) && (data["attachments"]).Type == OSDType.Array) |
680 | { | 685 | { |
681 | OSDArray attachs = (OSDArray)(data["attachments"]); | 686 | OSDArray attachs = (OSDArray)(data["attachments"]); |
682 | for (int i = 0; i < attachs.Count; i++) | 687 | for (int i = 0; i < attachs.Count; i++) |
683 | { | 688 | AppendAttachment(new AvatarAttachment((OSDMap)attachs[i])); |
684 | AvatarAttachment attach = new AvatarAttachment((OSDMap)attachs[i]); | ||
685 | m_attachments[attach.AttachPoint] = attach; | ||
686 | } | ||
687 | } | 689 | } |
688 | } | 690 | } |
689 | catch (Exception e) | 691 | catch (Exception e) |
diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 5dc48d7..f828a2d 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs | |||
@@ -3657,15 +3657,16 @@ namespace OpenSim.Region.Framework.Scenes | |||
3657 | return; | 3657 | return; |
3658 | } | 3658 | } |
3659 | 3659 | ||
3660 | List<int> attPoints = m_appearance.GetAttachedPoints(); | 3660 | List<AvatarAttachment> attachments = m_appearance.GetAttachments(); |
3661 | foreach (int p in attPoints) | 3661 | foreach (AvatarAttachment attach in attachments) |
3662 | { | 3662 | { |
3663 | if (m_isDeleted) | 3663 | if (m_isDeleted) |
3664 | return; | 3664 | return; |
3665 | 3665 | ||
3666 | UUID itemID = m_appearance.GetAttachedItem(p); | 3666 | int p = attach.AttachPoint; |
3667 | UUID itemID = attach.ItemID; | ||
3667 | 3668 | ||
3668 | //UUID assetID = m_appearance.GetAttachedAsset(p); | 3669 | //UUID assetID = attach.AssetID; |
3669 | // For some reason assetIDs are being written as Zero's in the DB -- need to track tat down | 3670 | // For some reason assetIDs are being written as Zero's in the DB -- need to track tat down |
3670 | // But they're not used anyway, the item is being looked up for now, so let's proceed. | 3671 | // But they're not used anyway, the item is being looked up for now, so let's proceed. |
3671 | //if (UUID.Zero == assetID) | 3672 | //if (UUID.Zero == assetID) |
diff --git a/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs b/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs index 0786bd9..922eaaf 100644 --- a/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs +++ b/OpenSim/Region/OptionalModules/Scripting/Minimodule/SPAvatar.cs | |||
@@ -29,6 +29,7 @@ using System.Collections; | |||
29 | using System.Collections.Generic; | 29 | using System.Collections.Generic; |
30 | using System.Security; | 30 | using System.Security; |
31 | using OpenMetaverse; | 31 | using OpenMetaverse; |
32 | using OpenSim.Framework; | ||
32 | using OpenSim.Region.Framework.Scenes; | 33 | using OpenSim.Region.Framework.Scenes; |
33 | using OpenSim.Region.Framework.Interfaces; | 34 | using OpenSim.Region.Framework.Interfaces; |
34 | 35 | ||
@@ -81,16 +82,12 @@ namespace OpenSim.Region.OptionalModules.Scripting.Minimodule | |||
81 | get { | 82 | get { |
82 | List<IAvatarAttachment> attachments = new List<IAvatarAttachment>(); | 83 | List<IAvatarAttachment> attachments = new List<IAvatarAttachment>(); |
83 | 84 | ||
84 | Hashtable internalAttachments = GetSP().Appearance.GetAttachments(); | 85 | List<AvatarAttachment> internalAttachments = GetSP().Appearance.GetAttachments(); |
85 | if (internalAttachments != null) | 86 | foreach (AvatarAttachment attach in internalAttachments) |
86 | { | 87 | { |
87 | foreach (DictionaryEntry element in internalAttachments) | 88 | attachments.Add(new SPAvatarAttachment(m_rootScene, this, attach.AttachPoint, |
88 | { | 89 | new UUID(attach.ItemID), |
89 | Hashtable attachInfo = (Hashtable)element.Value; | 90 | new UUID(attach.AssetID), m_security)); |
90 | attachments.Add(new SPAvatarAttachment(m_rootScene, this, (int) element.Key, | ||
91 | new UUID((string) attachInfo["item"]), | ||
92 | new UUID((string) attachInfo["asset"]), m_security)); | ||
93 | } | ||
94 | } | 91 | } |
95 | 92 | ||
96 | return attachments.ToArray(); | 93 | return attachments.ToArray(); |
diff --git a/OpenSim/Services/Interfaces/IAvatarService.cs b/OpenSim/Services/Interfaces/IAvatarService.cs index 93b977b..eaa6534 100644 --- a/OpenSim/Services/Interfaces/IAvatarService.cs +++ b/OpenSim/Services/Interfaces/IAvatarService.cs | |||
@@ -178,17 +178,11 @@ namespace OpenSim.Services.Interfaces | |||
178 | Data["UnderShirtAsset"] = appearance.UnderShirtAsset.ToString(); | 178 | Data["UnderShirtAsset"] = appearance.UnderShirtAsset.ToString(); |
179 | 179 | ||
180 | // Attachments | 180 | // Attachments |
181 | Hashtable attachs = appearance.GetAttachments(); | 181 | List<AvatarAttachment> attachments = appearance.GetAttachments(); |
182 | if (attachs != null) | 182 | foreach (AvatarAttachment attach in attachments) |
183 | foreach (DictionaryEntry dentry in attachs) | 183 | { |
184 | { | 184 | Data["_ap_" + attach.AttachPoint] = attach.ItemID.ToString(); |
185 | if (dentry.Value != null) | 185 | } |
186 | { | ||
187 | Hashtable tab = (Hashtable)dentry.Value; | ||
188 | if (tab.ContainsKey("item") && tab["item"] != null) | ||
189 | Data["_ap_" + dentry.Key] = tab["item"].ToString(); | ||
190 | } | ||
191 | } | ||
192 | } | 186 | } |
193 | 187 | ||
194 | public AvatarAppearance ToAvatarAppearance(UUID owner) | 188 | public AvatarAppearance ToAvatarAppearance(UUID owner) |