Merge branch 'master' of /home/opensim/var/repo/opensim

integration
BlueWall 2012-07-10 14:43:24 -04:00
commit bffe1ecaed
11 changed files with 332 additions and 29 deletions

View File

@ -251,6 +251,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments
// m_log.DebugFormat( // m_log.DebugFormat(
// "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})", // "[ATTACHMENTS MODULE]: Attaching object {0} {1} to {2} point {3} from ground (silent = {4})",
// group.Name, group.LocalId, sp.Name, attachmentPt, silent); // group.Name, group.LocalId, sp.Name, attachmentPt, silent);
if (group.GetSittingAvatarsCount() != 0)
{
// m_log.WarnFormat(
// "[ATTACHMENTS MODULE]: Ignoring request to attach {0} {1} to {2} on {3} since {4} avatars are still sitting on it",
// group.Name, group.LocalId, sp.Name, attachmentPt, group.GetSittingAvatarsCount());
return false;
}
if (sp.GetAttachments(attachmentPt).Contains(group)) if (sp.GetAttachments(attachmentPt).Contains(group))
{ {

View File

@ -118,7 +118,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests
Scene scene = CreateDefaultTestScene(); Scene scene = CreateDefaultTestScene();
UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1);
ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1.PrincipalID); ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1);
string attName = "att"; string attName = "att";
@ -154,6 +154,36 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests
// TestHelpers.DisableLogging(); // TestHelpers.DisableLogging();
} }
/// <summary>
/// Test that we do not attempt to attach an in-world object that someone else is sitting on.
/// </summary>
[Test]
public void TestAddSatOnAttachmentFromGround()
{
TestHelpers.InMethod();
// TestHelpers.EnableLogging();
Scene scene = CreateDefaultTestScene();
UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1);
ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1);
string attName = "att";
SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, attName, sp.UUID);
UserAccount ua2 = UserAccountHelpers.CreateUserWithInventory(scene, 0x2);
ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, ua2);
// Put avatar within 10m of the prim so that sit doesn't fail.
sp2.AbsolutePosition = new Vector3(0, 0, 0);
sp2.HandleAgentRequestSit(sp2.ControllingClient, sp2.UUID, so.UUID, Vector3.Zero);
scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false);
Assert.That(sp.HasAttachments(), Is.False);
Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1));
}
[Test] [Test]
public void TestAddAttachmentFromInventory() public void TestAddAttachmentFromInventory()
{ {

View File

@ -3401,6 +3401,20 @@ namespace OpenSim.Region.Framework.Scenes
return count; return count;
} }
/// <summary>
/// Gets the number of sitting avatars.
/// </summary>
/// <remarks>This applies to all sitting avatars whether there is a sit target set or not.</remarks>
/// <returns></returns>
public int GetSittingAvatarsCount()
{
int count = 0;
Array.ForEach<SceneObjectPart>(m_parts.GetArray(), p => count += p.GetSittingAvatarsCount());
return count;
}
public override string ToString() public override string ToString()
{ {
return String.Format("{0} {1} ({2})", Name, UUID, AbsolutePosition); return String.Format("{0} {1} ({2})", Name, UUID, AbsolutePosition);

View File

@ -374,7 +374,6 @@ namespace OpenSim.Region.Framework.Scenes
private uint _category; private uint _category;
private Int32 _creationDate; private Int32 _creationDate;
private uint _parentID = 0; private uint _parentID = 0;
private UUID m_sitTargetAvatar = UUID.Zero;
private uint _baseMask = (uint)PermissionMask.All; private uint _baseMask = (uint)PermissionMask.All;
private uint _ownerMask = (uint)PermissionMask.All; private uint _ownerMask = (uint)PermissionMask.All;
private uint _groupMask = (uint)PermissionMask.None; private uint _groupMask = (uint)PermissionMask.None;
@ -1233,13 +1232,20 @@ namespace OpenSim.Region.Framework.Scenes
} }
/// <summary> /// <summary>
/// ID of the avatar that is sat on us. If there is no such avatar then is UUID.Zero /// ID of the avatar that is sat on us if we have a sit target. If there is no such avatar then is UUID.Zero
/// </summary> /// </summary>
public UUID SitTargetAvatar public UUID SitTargetAvatar { get; set; }
{
get { return m_sitTargetAvatar; } /// <summary>
set { m_sitTargetAvatar = value; } /// IDs of all avatars start on this object part.
} /// </summary>
/// <remarks>
/// We need to track this so that we can stop sat upon prims from being attached.
/// </remarks>
/// <value>
/// null if there are no sitting avatars. This is to save us create a hashset for every prim in a scene.
/// </value>
private HashSet<UUID> m_sittingAvatars;
public virtual UUID RegionID public virtual UUID RegionID
{ {
@ -4493,5 +4499,83 @@ namespace OpenSim.Region.Framework.Scenes
Color color = Color; Color color = Color;
return new Color4(color.R, color.G, color.B, (byte)(0xFF - color.A)); return new Color4(color.R, color.G, color.B, (byte)(0xFF - color.A));
} }
/// <summary>
/// Record an avatar sitting on this part.
/// </summary>
/// <remarks>This is called for all the sitting avatars whether there is a sit target set or not.</remarks>
/// <returns>
/// true if the avatar was not already recorded, false otherwise.
/// </returns>
/// <param name='avatarId'></param>
protected internal bool AddSittingAvatar(UUID avatarId)
{
HashSet<UUID> sittingAvatars = m_sittingAvatars;
if (sittingAvatars == null)
sittingAvatars = new HashSet<UUID>();
lock (sittingAvatars)
{
m_sittingAvatars = sittingAvatars;
return m_sittingAvatars.Add(avatarId);
}
}
/// <summary>
/// Remove an avatar recorded as sitting on this part.
/// </summary>
/// <remarks>This applies to all sitting avatars whether there is a sit target set or not.</remarks>
/// <returns>
/// true if the avatar was present and removed, false if it was not present.
/// </returns>
/// <param name='avatarId'></param>
protected internal bool RemoveSittingAvatar(UUID avatarId)
{
HashSet<UUID> sittingAvatars = m_sittingAvatars;
// This can occur under a race condition where another thread
if (sittingAvatars == null)
return false;
lock (sittingAvatars)
{
if (sittingAvatars.Remove(avatarId))
{
if (sittingAvatars.Count == 0)
m_sittingAvatars = null;
return true;
}
}
return false;
}
/// <summary>
/// Get a copy of the list of sitting avatars.
/// </summary>
/// <remarks>This applies to all sitting avatars whether there is a sit target set or not.</remarks>
/// <returns></returns>
public HashSet<UUID> GetSittingAvatars()
{
return new HashSet<UUID>(m_sittingAvatars);
}
/// <summary>
/// Gets the number of sitting avatars.
/// </summary>
/// <remarks>This applies to all sitting avatars whether there is a sit target set or not.</remarks>
/// <returns></returns>
public int GetSittingAvatarsCount()
{
HashSet<UUID> sittingAvatars = m_sittingAvatars;
if (sittingAvatars == null)
return 0;
lock (sittingAvatars)
return sittingAvatars.Count;
}
} }
} }

View File

@ -577,6 +577,12 @@ namespace OpenSim.Region.Framework.Scenes
/// </summary> /// </summary>
public uint ParentID { get; set; } public uint ParentID { get; set; }
/// <summary>
/// Are we sitting on an object?
/// </summary>
/// <remarks>A more readable way of testing presence sit status than ParentID == 0</remarks>
public bool IsSatOnObject { get { return ParentID != 0; } }
/// <summary> /// <summary>
/// If the avatar is sitting, the prim that it's sitting on. If not sitting then null. /// If the avatar is sitting, the prim that it's sitting on. If not sitting then null.
/// </summary> /// </summary>
@ -1808,6 +1814,8 @@ namespace OpenSim.Region.Framework.Scenes
SendAvatarDataToAllAgents(); SendAvatarDataToAllAgents();
m_requestedSitTargetID = 0; m_requestedSitTargetID = 0;
part.RemoveSittingAvatar(UUID);
if (part != null) if (part != null)
part.ParentGroup.TriggerScriptChangedEvent(Changed.LINK); part.ParentGroup.TriggerScriptChangedEvent(Changed.LINK);
} }
@ -1887,7 +1895,7 @@ namespace OpenSim.Region.Framework.Scenes
) )
)); ));
// m_log.DebugFormat("[SCENE PRESENCE]: {0} {1}", SitTargetisSet, SitTargetUnOccupied); m_log.DebugFormat("[SCENE PRESENCE]: {0} {1}", SitTargetisSet, SitTargetUnOccupied);
if (PhysicsActor != null) if (PhysicsActor != null)
m_sitAvatarHeight = PhysicsActor.Size.Z; m_sitAvatarHeight = PhysicsActor.Size.Z;
@ -1920,6 +1928,12 @@ namespace OpenSim.Region.Framework.Scenes
AbsolutePosition = pos + new Vector3(0.0f, 0.0f, m_sitAvatarHeight); AbsolutePosition = pos + new Vector3(0.0f, 0.0f, m_sitAvatarHeight);
canSit = true; canSit = true;
} }
// else
// {
// m_log.DebugFormat(
// "[SCENE PRESENCE]: Ignoring sit request of {0} on {1} {2} because sit target is unset and outside 10m",
// Name, part.Name, part.LocalId);
// }
} }
if (canSit) if (canSit)
@ -1930,6 +1944,8 @@ namespace OpenSim.Region.Framework.Scenes
RemoveFromPhysicalScene(); RemoveFromPhysicalScene();
} }
part.AddSittingAvatar(UUID);
cameraAtOffset = part.GetCameraAtOffset(); cameraAtOffset = part.GetCameraAtOffset();
cameraEyeOffset = part.GetCameraEyeOffset(); cameraEyeOffset = part.GetCameraEyeOffset();
forceMouselook = part.GetForceMouselook(); forceMouselook = part.GetForceMouselook();
@ -2203,6 +2219,15 @@ namespace OpenSim.Region.Framework.Scenes
if (part != null) if (part != null)
{ {
if (part.ParentGroup.IsAttachment)
{
m_log.WarnFormat(
"[SCENE PRESENCE]: Avatar {0} tried to sit on part {1} from object {2} in {3} but this is an attachment for avatar id {4}",
Name, part.Name, part.ParentGroup.Name, Scene.Name, part.ParentGroup.AttachedAvatar);
return;
}
if (part.SitTargetAvatar == UUID) if (part.SitTargetAvatar == UUID)
{ {
Vector3 sitTargetPos = part.SitTargetPosition; Vector3 sitTargetPos = part.SitTargetPosition;

View File

@ -3190,13 +3190,32 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
{ {
CheckThreatLevel(ThreatLevel.High, "osForceAttachToAvatarFromInventory"); CheckThreatLevel(ThreatLevel.High, "osForceAttachToAvatarFromInventory");
m_host.AddScriptLPS(1);
ForceAttachToAvatarFromInventory(m_host.OwnerID, itemName, attachmentPoint);
}
public void osForceAttachToOtherAvatarFromInventory(string rawAvatarId, string itemName, int attachmentPoint)
{
CheckThreatLevel(ThreatLevel.Severe, "osForceAttachToOtherAvatarFromInventory");
m_host.AddScriptLPS(1);
UUID avatarId;
if (!UUID.TryParse(rawAvatarId, out avatarId))
return;
ForceAttachToAvatarFromInventory(avatarId, itemName, attachmentPoint);
}
public void ForceAttachToAvatarFromInventory(UUID avatarId, string itemName, int attachmentPoint)
{
IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule; IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule;
if (attachmentsModule == null) if (attachmentsModule == null)
return; return;
m_host.AddScriptLPS(1);
InitLSL(); InitLSL();
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(itemName); TaskInventoryItem item = m_host.Inventory.GetInventoryItem(itemName);
@ -3219,7 +3238,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
return; return;
} }
ScenePresence sp = World.GetScenePresence(m_host.OwnerID); ScenePresence sp = World.GetScenePresence(avatarId);
if (sp == null) if (sp == null)
return; return;

View File

@ -101,18 +101,32 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces
// Attachment commands // Attachment commands
/// <summary> /// <summary>
/// Attach the object containing this script to the avatar that owns it without checking for PERMISSION_ATTACH /// Attach the object containing this script to the avatar that owns it without asking for PERMISSION_ATTACH
/// </summary> /// </summary>
/// <param name='attachment'>The attachment point. For example, ATTACH_CHEST</param> /// <param name='attachment'>The attachment point. For example, ATTACH_CHEST</param>
void osForceAttachToAvatar(int attachment); void osForceAttachToAvatar(int attachment);
/// <summary> /// <summary>
/// Attach the inventory item in the object containing this script to the avatar that owns it without checking for PERMISSION_ATTACH /// Attach an inventory item in the object containing this script to the avatar that owns it without asking for PERMISSION_ATTACH
/// </summary> /// </summary>
/// <remarks>
/// Nothing happens if the owner is not in the region.
/// </remarks>
/// <param name='itemName'>Tha name of the item. If this is not found then a warning is said to the owner</param> /// <param name='itemName'>Tha name of the item. If this is not found then a warning is said to the owner</param>
/// <param name='attachment'>The attachment point. For example, ATTACH_CHEST</param> /// <param name='attachment'>The attachment point. For example, ATTACH_CHEST</param>
void osForceAttachToAvatarFromInventory(string itemName, int attachment); void osForceAttachToAvatarFromInventory(string itemName, int attachment);
/// <summary>
/// Attach an inventory item in the object containing this script to any avatar in the region without asking for PERMISSION_ATTACH
/// </summary>
/// <remarks>
/// Nothing happens if the avatar is not in the region.
/// </remarks>
/// <param name='rawAvatarId'>The UUID of the avatar to which to attach. Nothing happens if this is not a UUID</para>
/// <param name='itemName'>The name of the item. If this is not found then a warning is said to the owner</param>
/// <param name='attachment'>The attachment point. For example, ATTACH_CHEST</param>
void osForceAttachToOtherAvatarFromInventory(string rawAvatarId, string itemName, int attachmentPoint);
/// <summary> /// <summary>
/// Detach the object containing this script from the avatar it is attached to without checking for PERMISSION_ATTACH /// Detach the object containing this script from the avatar it is attached to without checking for PERMISSION_ATTACH
/// </summary> /// </summary>

View File

@ -301,6 +301,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase
m_OSSL_Functions.osForceAttachToAvatarFromInventory(itemName, attachmentPoint); m_OSSL_Functions.osForceAttachToAvatarFromInventory(itemName, attachmentPoint);
} }
public void osForceAttachToOtherAvatarFromInventory(string rawAvatarId, string itemName, int attachmentPoint)
{
m_OSSL_Functions.osForceAttachToOtherAvatarFromInventory(rawAvatarId, itemName, attachmentPoint);
}
public void osForceDetachFromAvatar() public void osForceDetachFromAvatar()
{ {
m_OSSL_Functions.osForceDetachFromAvatar(); m_OSSL_Functions.osForceDetachFromAvatar();

View File

@ -174,5 +174,58 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
List<AvatarAttachment> attachmentsInAppearance = sp.Appearance.GetAttachments(); List<AvatarAttachment> attachmentsInAppearance = sp.Appearance.GetAttachments();
Assert.That(attachmentsInAppearance.Count, Is.EqualTo(0)); Assert.That(attachmentsInAppearance.Count, Is.EqualTo(0));
} }
[Test]
public void TestOsForceAttachToOtherAvatarFromInventory()
{
TestHelpers.InMethod();
TestHelpers.EnableLogging();
string taskInvObjItemName = "sphere";
UUID taskInvObjItemId = UUID.Parse("00000000-0000-0000-0000-100000000000");
AttachmentPoint attachPoint = AttachmentPoint.Chin;
UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(m_scene, "user", "one", 0x1, "pass");
UserAccount ua2 = UserAccountHelpers.CreateUserWithInventory(m_scene, "user", "two", 0x2, "pass");
ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, ua1);
SceneObjectGroup inWorldObj = SceneHelpers.AddSceneObject(m_scene, "inWorldObj", ua1.PrincipalID);
TaskInventoryItem scriptItem = TaskInventoryHelpers.AddScript(m_scene, inWorldObj.RootPart);
new LSL_Api().Initialize(m_engine, inWorldObj.RootPart, scriptItem);
OSSL_Api osslApi = new OSSL_Api();
osslApi.Initialize(m_engine, inWorldObj.RootPart, scriptItem);
// Create an object embedded inside the first
TaskInventoryHelpers.AddSceneObject(m_scene, inWorldObj.RootPart, taskInvObjItemName, taskInvObjItemId, ua1.PrincipalID);
ScenePresence sp2 = SceneHelpers.AddScenePresence(m_scene, ua2);
osslApi.osForceAttachToOtherAvatarFromInventory(sp2.UUID.ToString(), taskInvObjItemName, (int)attachPoint);
// Check scene presence status
Assert.That(sp.HasAttachments(), Is.False);
List<SceneObjectGroup> attachments = sp.GetAttachments();
Assert.That(attachments.Count, Is.EqualTo(0));
Assert.That(sp2.HasAttachments(), Is.True);
List<SceneObjectGroup> attachments2 = sp2.GetAttachments();
Assert.That(attachments2.Count, Is.EqualTo(1));
SceneObjectGroup attSo = attachments2[0];
Assert.That(attSo.Name, Is.EqualTo(taskInvObjItemName));
Assert.That(attSo.OwnerID, Is.EqualTo(ua2.PrincipalID));
Assert.That(attSo.AttachmentPoint, Is.EqualTo((uint)attachPoint));
Assert.That(attSo.IsAttachment);
Assert.That(attSo.UsesPhysics, Is.False);
Assert.That(attSo.IsTemporary, Is.False);
// Check appearance status
List<AvatarAttachment> attachmentsInAppearance = sp.Appearance.GetAttachments();
Assert.That(attachmentsInAppearance.Count, Is.EqualTo(0));
List<AvatarAttachment> attachmentsInAppearance2 = sp2.Appearance.GetAttachments();
Assert.That(attachmentsInAppearance2.Count, Is.EqualTo(1));
Assert.That(sp2.Appearance.GetAttachpoint(attachmentsInAppearance2[0].ItemID), Is.EqualTo((uint)attachPoint));
}
} }
} }

View File

@ -412,26 +412,49 @@ namespace OpenSim.Tests.Common
/// <returns></returns> /// <returns></returns>
public static AgentCircuitData GenerateAgentData(UUID agentId) public static AgentCircuitData GenerateAgentData(UUID agentId)
{ {
string firstName = "testfirstname"; AgentCircuitData acd = GenerateCommonAgentData();
AgentCircuitData agentData = new AgentCircuitData(); acd.AgentID = agentId;
agentData.AgentID = agentId; acd.firstname = "testfirstname";
agentData.firstname = firstName; acd.lastname = "testlastname";
agentData.lastname = "testlastname"; acd.ServiceURLs = new Dictionary<string, object>();
return acd;
}
/// <summary>
/// Generate some standard agent connection data.
/// </summary>
/// <param name="agentId"></param>
/// <returns></returns>
public static AgentCircuitData GenerateAgentData(UserAccount ua)
{
AgentCircuitData acd = GenerateCommonAgentData();
acd.AgentID = ua.PrincipalID;
acd.firstname = ua.FirstName;
acd.lastname = ua.LastName;
acd.ServiceURLs = ua.ServiceURLs;
return acd;
}
private static AgentCircuitData GenerateCommonAgentData()
{
AgentCircuitData acd = new AgentCircuitData();
// XXX: Sessions must be unique, otherwise one presence can overwrite another in NullPresenceData. // XXX: Sessions must be unique, otherwise one presence can overwrite another in NullPresenceData.
agentData.SessionID = UUID.Random(); acd.SessionID = UUID.Random();
agentData.SecureSessionID = UUID.Random(); acd.SecureSessionID = UUID.Random();
agentData.circuitcode = 123; acd.circuitcode = 123;
agentData.BaseFolder = UUID.Zero; acd.BaseFolder = UUID.Zero;
agentData.InventoryFolder = UUID.Zero; acd.InventoryFolder = UUID.Zero;
agentData.startpos = Vector3.Zero; acd.startpos = Vector3.Zero;
agentData.CapsPath = "http://wibble.com"; acd.CapsPath = "http://wibble.com";
agentData.ServiceURLs = new Dictionary<string, object>(); acd.Appearance = new AvatarAppearance();
agentData.Appearance = new AvatarAppearance();
return agentData; return acd;
} }
/// <summary> /// <summary>
@ -440,6 +463,9 @@ namespace OpenSim.Tests.Common
/// <remarks> /// <remarks>
/// This can be used for tests where there is only one region or where there are multiple non-neighbour regions /// This can be used for tests where there is only one region or where there are multiple non-neighbour regions
/// and teleport doesn't take place. /// and teleport doesn't take place.
///
/// XXX: Use the version of this method that takes the UserAccount structure wherever possible - this will
/// make the agent circuit data (e.g. first, lastname) consistent with the user account data.
/// </remarks> /// </remarks>
/// <param name="scene"></param> /// <param name="scene"></param>
/// <param name="agentId"></param> /// <param name="agentId"></param>
@ -452,6 +478,10 @@ namespace OpenSim.Tests.Common
/// <summary> /// <summary>
/// Add a root agent where the details of the agent connection (apart from the id) are unimportant for the test /// Add a root agent where the details of the agent connection (apart from the id) are unimportant for the test
/// </summary> /// </summary>
/// <remarks>
/// XXX: Use the version of this method that takes the UserAccount structure wherever possible - this will
/// make the agent circuit data (e.g. first, lastname) consistent with the user account data.
/// </remarks>
/// <param name="scene"></param> /// <param name="scene"></param>
/// <param name="agentId"></param> /// <param name="agentId"></param>
/// <param name="sceneManager"></param> /// <param name="sceneManager"></param>
@ -461,6 +491,17 @@ namespace OpenSim.Tests.Common
return AddScenePresence(scene, GenerateAgentData(agentId), sceneManager); return AddScenePresence(scene, GenerateAgentData(agentId), sceneManager);
} }
/// <summary>
/// Add a root agent.
/// </summary>
/// <param name="scene"></param>
/// <param name="ua"></param>
/// <returns></returns>
public static ScenePresence AddScenePresence(Scene scene, UserAccount ua)
{
return AddScenePresence(scene, GenerateAgentData(ua));
}
/// <summary> /// <summary>
/// Add a root agent. /// Add a root agent.
/// </summary> /// </summary>

View File

@ -138,6 +138,15 @@ namespace OpenSim.Tests.Common
CreateUserWithInventory(scene, ua, pw); CreateUserWithInventory(scene, ua, pw);
return ua; return ua;
} }
public static UserAccount CreateUserWithInventory(
Scene scene, string firstName, string lastName, int userId, string pw)
{
UserAccount ua
= new UserAccount(TestHelpers.ParseTail(userId)) { FirstName = firstName, LastName = lastName };
CreateUserWithInventory(scene, ua, pw);
return ua;
}
public static void CreateUserWithInventory(Scene scene, UserAccount ua, string pw) public static void CreateUserWithInventory(Scene scene, UserAccount ua, string pw)
{ {