From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Thu, 3 Nov 2016 21:44:39 +1000 Subject: Initial update to OpenSim 0.8.2.1 source code. --- .../Framework/Scenes/Animation/AnimationSet.cs | 119 + .../Framework/Scenes/Animation/BinBVHAnimation.cs | 49 +- .../Scenes/Animation/DefaultAvatarAnimations.cs | 26 + .../Scenes/Animation/ScenePresenceAnimator.cs | 38 +- .../Scenes/AsyncSceneObjectGroupDeleter.cs | 10 +- OpenSim/Region/Framework/Scenes/Border.cs | 7 +- OpenSim/Region/Framework/Scenes/EventManager.cs | 291 ++- OpenSim/Region/Framework/Scenes/KeyframeMotion.cs | 829 +++++++ OpenSim/Region/Framework/Scenes/Prioritizer.cs | 2 +- .../Region/Framework/Scenes/RegionStatsHandler.cs | 27 +- OpenSim/Region/Framework/Scenes/Scene.Inventory.cs | 575 +++-- .../Framework/Scenes/Scene.PacketHandlers.cs | 68 +- OpenSim/Region/Framework/Scenes/Scene.cs | 2597 ++++++++++++-------- OpenSim/Region/Framework/Scenes/SceneBase.cs | 78 +- .../Framework/Scenes/SceneCommunicationService.cs | 64 +- OpenSim/Region/Framework/Scenes/SceneGraph.cs | 175 +- OpenSim/Region/Framework/Scenes/SceneManager.cs | 48 +- .../Framework/Scenes/SceneObjectGroup.Inventory.cs | 8 + .../Region/Framework/Scenes/SceneObjectGroup.cs | 509 +++- OpenSim/Region/Framework/Scenes/SceneObjectPart.cs | 918 +++++-- .../Framework/Scenes/SceneObjectPartInventory.cs | 132 +- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 2595 +++++++++++++------ .../Framework/Scenes/ScenePresenceStateMachine.cs | 113 + .../Framework/Scenes/Scripting/IScriptHost.cs | 46 - .../Framework/Scenes/Scripting/NullScriptHost.cs | 91 - .../Scenes/Scripting/ScriptEngineInterface.cs | 38 - .../Scenes/Scripting/ScriptEngineLoader.cs | 119 - .../Framework/Scenes/Scripting/ScriptUtils.cs | 107 + .../CoalescedSceneObjectsSerializer.cs | 87 +- .../Scenes/Serialization/SceneObjectSerializer.cs | 604 +++-- .../Scenes/Serialization/SceneXmlLoader.cs | 2 +- .../Region/Framework/Scenes/SimStatsReporter.cs | 259 +- OpenSim/Region/Framework/Scenes/TerrainChannel.cs | 346 ++- .../Region/Framework/Scenes/TerrainCompressor.cs | 948 +++++++ .../Framework/Scenes/Tests/EntityManagerTests.cs | 1 - .../Framework/Scenes/Tests/SceneGraphTests.cs | 2 - .../Framework/Scenes/Tests/SceneManagerTests.cs | 2 - .../Scenes/Tests/SceneObjectBasicTests.cs | 2 - .../Framework/Scenes/Tests/SceneObjectCopyTests.cs | 346 +++ .../Scenes/Tests/SceneObjectCrossingTests.cs | 259 ++ .../Scenes/Tests/SceneObjectDeRezTests.cs | 111 +- .../Scenes/Tests/SceneObjectLinkingTests.cs | 39 +- .../Scenes/Tests/SceneObjectResizeTests.cs | 2 - .../Scenes/Tests/SceneObjectScriptTests.cs | 2 - .../Scenes/Tests/SceneObjectSerializationTests.cs | 135 + .../Scenes/Tests/SceneObjectSpatialTests.cs | 2 - .../Scenes/Tests/SceneObjectStatusTests.cs | 21 +- .../Scenes/Tests/SceneObjectUndoRedoTests.cs | 6 +- .../Scenes/Tests/SceneObjectUserGroupTests.cs | 2 - .../Scenes/Tests/ScenePresenceAgentTests.cs | 218 +- .../Scenes/Tests/ScenePresenceAnimationTests.cs | 4 +- .../Scenes/Tests/ScenePresenceAutopilotTests.cs | 2 - .../Scenes/Tests/ScenePresenceCapabilityTests.cs | 86 + .../Scenes/Tests/ScenePresenceCrossingTests.cs | 247 ++ .../Scenes/Tests/ScenePresenceSitTests.cs | 81 +- .../Scenes/Tests/ScenePresenceTeleportTests.cs | 302 ++- .../Framework/Scenes/Tests/SceneStatisticsTests.cs | 69 + .../Framework/Scenes/Tests/SceneTelehubTests.cs | 118 + .../Region/Framework/Scenes/Tests/SceneTests.cs | 25 +- .../Scenes/Tests/SharedRegionModuleTests.cs | 249 ++ .../Framework/Scenes/Tests/TaskInventoryTests.cs | 18 +- .../Framework/Scenes/Tests/UserInventoryTests.cs | 18 +- .../Framework/Scenes/Tests/UuidGathererTests.cs | 91 +- OpenSim/Region/Framework/Scenes/UuidGatherer.cs | 604 +++-- 64 files changed, 11162 insertions(+), 3827 deletions(-) create mode 100644 OpenSim/Region/Framework/Scenes/KeyframeMotion.cs mode change 100644 => 100755 OpenSim/Region/Framework/Scenes/Scene.cs mode change 100644 => 100755 OpenSim/Region/Framework/Scenes/SceneGraph.cs create mode 100644 OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs delete mode 100644 OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs delete mode 100644 OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs delete mode 100644 OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs delete mode 100644 OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineLoader.cs create mode 100644 OpenSim/Region/Framework/Scenes/Scripting/ScriptUtils.cs mode change 100644 => 100755 OpenSim/Region/Framework/Scenes/SimStatsReporter.cs create mode 100644 OpenSim/Region/Framework/Scenes/TerrainCompressor.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneObjectCopyTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneObjectCrossingTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneObjectSerializationTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCapabilityTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneStatisticsTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SharedRegionModuleTests.cs (limited to 'OpenSim/Region/Framework/Scenes') diff --git a/OpenSim/Region/Framework/Scenes/Animation/AnimationSet.cs b/OpenSim/Region/Framework/Scenes/Animation/AnimationSet.cs index 66edfed..eb1a970 100644 --- a/OpenSim/Region/Framework/Scenes/Animation/AnimationSet.cs +++ b/OpenSim/Region/Framework/Scenes/Animation/AnimationSet.cs @@ -28,8 +28,11 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Text; using log4net; using OpenMetaverse; +using OpenMetaverse.StructuredData; + using OpenSim.Framework; using Animation = OpenSim.Framework.Animation; @@ -60,6 +63,12 @@ namespace OpenSim.Region.Framework.Scenes.Animation ResetDefaultAnimation(); } + public AnimationSet(OSDArray pArray) + { + ResetDefaultAnimation(); + FromOSDArray(pArray); + } + public bool HasAnimation(UUID animID) { if (m_defaultAnimation.AnimID == animID) @@ -218,5 +227,115 @@ namespace OpenSim.Region.Framework.Scenes.Animation foreach (OpenSim.Framework.Animation anim in theArray) m_animations.Add(anim); } + + // Create representation of this AnimationSet as an OSDArray. + // First two entries in the array are the default and implicitDefault animations + // followed by the other animations. + public OSDArray ToOSDArray() + { + OSDArray ret = new OSDArray(); + ret.Add(DefaultAnimation.PackUpdateMessage()); + ret.Add(ImplicitDefaultAnimation.PackUpdateMessage()); + + foreach (OpenSim.Framework.Animation anim in m_animations) + ret.Add(anim.PackUpdateMessage()); + + return ret; + } + + public void FromOSDArray(OSDArray pArray) + { + this.Clear(); + + if (pArray.Count >= 1) + { + m_defaultAnimation = new OpenSim.Framework.Animation((OSDMap)pArray[0]); + } + if (pArray.Count >= 2) + { + m_implicitDefaultAnimation = new OpenSim.Framework.Animation((OSDMap)pArray[1]); + } + for (int ii = 2; ii < pArray.Count; ii++) + { + m_animations.Add(new OpenSim.Framework.Animation((OSDMap)pArray[ii])); + } + } + + // Compare two AnimationSets and return 'true' if the default animations are the same + // and all of the animations in the list are equal. + public override bool Equals(object obj) + { + AnimationSet other = obj as AnimationSet; + if (other != null) + { + if (this.DefaultAnimation.Equals(other.DefaultAnimation) + && this.ImplicitDefaultAnimation.Equals(other.ImplicitDefaultAnimation)) + { + // The defaults are the same. Is the list of animations the same? + OpenSim.Framework.Animation[] thisAnims = this.ToArray(); + OpenSim.Framework.Animation[] otherAnims = other.ToArray(); + if (thisAnims.Length == 0 && otherAnims.Length == 0) + return true; // the common case + if (thisAnims.Length == otherAnims.Length) + { + // Do this the hard way but since the list is usually short this won't take long. + foreach (OpenSim.Framework.Animation thisAnim in thisAnims) + { + bool found = false; + foreach (OpenSim.Framework.Animation otherAnim in otherAnims) + { + if (thisAnim.Equals(otherAnim)) + { + found = true; + break; + } + } + if (!found) + { + // If anything is not in the other list, these are not equal + return false; + } + } + // Found everything in the other list. Since lists are equal length, they must be equal. + return true; + } + } + return false; + } + // Don't know what was passed, but the base system will figure it out for me. + return base.Equals(obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override string ToString() + { + StringBuilder buff = new StringBuilder(); + buff.Append("dflt="); + buff.Append(DefaultAnimation.ToString()); + buff.Append(",iDflt="); + if (DefaultAnimation.Equals(ImplicitDefaultAnimation)) + buff.Append("same"); + else + buff.Append(ImplicitDefaultAnimation.ToString()); + if (m_animations.Count > 0) + { + buff.Append(",anims="); + bool firstTime = true; + foreach (OpenSim.Framework.Animation anim in m_animations) + { + if (!firstTime) + buff.Append(","); + buff.Append("<"); + buff.Append(anim.ToString()); + buff.Append(">"); + firstTime = false; + } + } + return buff.ToString(); + } } } diff --git a/OpenSim/Region/Framework/Scenes/Animation/BinBVHAnimation.cs b/OpenSim/Region/Framework/Scenes/Animation/BinBVHAnimation.cs index 3afc87f..b3b38b2 100644 --- a/OpenSim/Region/Framework/Scenes/Animation/BinBVHAnimation.cs +++ b/OpenSim/Region/Framework/Scenes/Animation/BinBVHAnimation.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -113,31 +113,34 @@ namespace OpenSim.Region.Framework.Scenes.Animation public byte[] ToBytes() { - byte[] outputbytes = new byte[0]; + byte[] outputbytes; - BinaryWriter iostream = new BinaryWriter(new MemoryStream()); - iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(unknown0))); - iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(unknown1))); - iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Priority))); - iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(Length))); - iostream.Write(BinBVHUtil.WriteNullTerminatedString(ExpressionName)); - iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(InPoint))); - iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(OutPoint))); - iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Loop ? 1 : 0))); - iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(EaseInTime))); - iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(EaseOutTime))); - iostream.Write(BinBVHUtil.ES(Utils.UIntToBytes(HandPose))); - iostream.Write(BinBVHUtil.ES(Utils.UIntToBytes((uint)(Joints.Length)))); - - for (int i = 0; i < Joints.Length; i++) + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter iostream = new BinaryWriter(ms)) { - Joints[i].WriteBytesToStream(iostream, InPoint, OutPoint); + iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(unknown0))); + iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(unknown1))); + iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Priority))); + iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(Length))); + iostream.Write(BinBVHUtil.WriteNullTerminatedString(ExpressionName)); + iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(InPoint))); + iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(OutPoint))); + iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Loop ? 1 : 0))); + iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(EaseInTime))); + iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(EaseOutTime))); + iostream.Write(BinBVHUtil.ES(Utils.UIntToBytes(HandPose))); + iostream.Write(BinBVHUtil.ES(Utils.UIntToBytes((uint)(Joints.Length)))); + + for (int i = 0; i < Joints.Length; i++) + { + Joints[i].WriteBytesToStream(iostream, InPoint, OutPoint); + } + iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(0))); + + using (MemoryStream ms2 = (MemoryStream)iostream.BaseStream) + outputbytes = ms2.ToArray(); } - iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(0))); - MemoryStream ms = (MemoryStream)iostream.BaseStream; - outputbytes = ms.ToArray(); - ms.Close(); - iostream.Close(); + return outputbytes; } diff --git a/OpenSim/Region/Framework/Scenes/Animation/DefaultAvatarAnimations.cs b/OpenSim/Region/Framework/Scenes/Animation/DefaultAvatarAnimations.cs index c2b0468..b79dd8f 100644 --- a/OpenSim/Region/Framework/Scenes/Animation/DefaultAvatarAnimations.cs +++ b/OpenSim/Region/Framework/Scenes/Animation/DefaultAvatarAnimations.cs @@ -104,5 +104,31 @@ namespace OpenSim.Region.Framework.Scenes.Animation return UUID.Zero; } + + /// + /// Get the name of the animation given a UUID. If there is no matching animation + /// return the UUID as a string. + /// + public static string GetDefaultAnimationName(UUID uuid) + { + string ret = "unknown"; + if (AnimsUUID.ContainsValue(uuid)) + { + foreach (KeyValuePair kvp in AnimsUUID) + { + if (kvp.Value == uuid) + { + ret = kvp.Key; + break; + } + } + } + else + { + ret = uuid.ToString(); + } + + return ret; + } } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Animation/ScenePresenceAnimator.cs b/OpenSim/Region/Framework/Scenes/Animation/ScenePresenceAnimator.cs index e92a087..6d51029 100644 --- a/OpenSim/Region/Framework/Scenes/Animation/ScenePresenceAnimator.cs +++ b/OpenSim/Region/Framework/Scenes/Animation/ScenePresenceAnimator.cs @@ -35,7 +35,7 @@ using OpenMetaverse; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; namespace OpenSim.Region.Framework.Scenes.Animation { @@ -92,7 +92,10 @@ namespace OpenSim.Region.Framework.Scenes.Animation GetAnimName(animID), animID, m_scenePresence.Name); if (m_animations.Add(animID, m_scenePresence.ControllingClient.NextAnimationSequenceNumber, objectID)) + { SendAnimPack(); + m_scenePresence.TriggerScenePresenceUpdated(); + } } // Called from scripts @@ -131,7 +134,10 @@ namespace OpenSim.Region.Framework.Scenes.Animation GetAnimName(animID), animID, m_scenePresence.Name); if (m_animations.Remove(animID, allowNoDefault)) + { SendAnimPack(); + m_scenePresence.TriggerScenePresenceUpdated(); + } } // Called from scripts @@ -163,8 +169,10 @@ namespace OpenSim.Region.Framework.Scenes.Animation /// The movement animation is reserved for "main" animations /// that are mutually exclusive, e.g. flying and sitting. /// - public void TrySetMovementAnimation(string anim) + /// 'true' if the animation was updated + public bool TrySetMovementAnimation(string anim) { + bool ret = false; if (!m_scenePresence.IsChildAgent) { // m_log.DebugFormat( @@ -181,6 +189,7 @@ namespace OpenSim.Region.Framework.Scenes.Animation // 16384 is CHANGED_ANIMATION m_scenePresence.SendScriptEventToAttachments("changed", new Object[] { (int)Changed.ANIMATION}); SendAnimPack(); + ret = true; } } else @@ -189,6 +198,7 @@ namespace OpenSim.Region.Framework.Scenes.Animation "[SCENE PRESENCE ANIMATOR]: Tried to set movement animation {0} on child presence {1}", anim, m_scenePresence.Name); } + return ret; } /// @@ -393,11 +403,18 @@ namespace OpenSim.Region.Framework.Scenes.Animation Falling = false; // Walking / crouchwalking / running if (move.Z < 0f) + { return "CROUCHWALK"; - else if (m_scenePresence.SetAlwaysRun) - return "RUN"; - else - return "WALK"; + } + // We need to prevent these animations if the user tries to make their avatar walk or run whilst + // specifying AGENT_CONTROL_STOP (pressing down space on viewers). + else if (!m_scenePresence.AgentControlStopActive) + { + if (m_scenePresence.SetAlwaysRun) + return "RUN"; + else + return "WALK"; + } } else if (!m_jumping) { @@ -422,8 +439,12 @@ namespace OpenSim.Region.Framework.Scenes.Animation /// /// Update the movement animation of this avatar according to its current state /// - public void UpdateMovementAnimations() + /// 'true' if the animation was changed + public bool UpdateMovementAnimations() { +// m_log.DebugFormat("[SCENE PRESENCE ANIMATOR]: Updating movement animations for {0}", m_scenePresence.Name); + + bool ret = false; lock (m_animations) { string newMovementAnimation = DetermineMovementAnimation(); @@ -437,9 +458,10 @@ namespace OpenSim.Region.Framework.Scenes.Animation // Only set it if it's actually changed, give a script // a chance to stop a default animation - TrySetMovementAnimation(CurrentMovementAnimation); + ret = TrySetMovementAnimation(CurrentMovementAnimation); } } + return ret; } public UUID[] GetAnimationArray() diff --git a/OpenSim/Region/Framework/Scenes/AsyncSceneObjectGroupDeleter.cs b/OpenSim/Region/Framework/Scenes/AsyncSceneObjectGroupDeleter.cs index f555b49..11a0146 100644 --- a/OpenSim/Region/Framework/Scenes/AsyncSceneObjectGroupDeleter.cs +++ b/OpenSim/Region/Framework/Scenes/AsyncSceneObjectGroupDeleter.cs @@ -104,14 +104,8 @@ namespace OpenSim.Region.Framework.Scenes // better than losing the object for now. if (permissionToDelete) { - List killIDs = new List(); - foreach (SceneObjectGroup g in objectGroups) - { killIDs.Add(g.LocalId); - g.DeleteGroupFromScene(true); - } - - m_scene.SendKillObject(killIDs); + g.DeleteGroupFromScene(false); } } @@ -160,7 +154,7 @@ namespace OpenSim.Region.Framework.Scenes if (x.permissionToDelete) { foreach (SceneObjectGroup g in x.objectGroups) - m_scene.DeleteSceneObject(g, false); + m_scene.DeleteSceneObject(g, true); } } catch (Exception e) diff --git a/OpenSim/Region/Framework/Scenes/Border.cs b/OpenSim/Region/Framework/Scenes/Border.cs index c6a6511..08c0c31 100644 --- a/OpenSim/Region/Framework/Scenes/Border.cs +++ b/OpenSim/Region/Framework/Scenes/Border.cs @@ -33,8 +33,7 @@ using OpenMetaverse; namespace OpenSim.Region.Framework.Scenes { public class Border - { - + { /// /// Line perpendicular to the Direction Cardinal. Z value is the /// @@ -81,6 +80,10 @@ namespace OpenSim.Region.Framework.Scenes TriggerRegionY = triggerRegionY; } + /// + /// Tests to see if the given position would cross this border. + /// + /// public bool TestCross(Vector3 position) { bool result = false; diff --git a/OpenSim/Region/Framework/Scenes/EventManager.cs b/OpenSim/Region/Framework/Scenes/EventManager.cs index 4c49b71..97b9482 100644 --- a/OpenSim/Region/Framework/Scenes/EventManager.cs +++ b/OpenSim/Region/Framework/Scenes/EventManager.cs @@ -144,7 +144,7 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered when a new presence is added to the scene /// /// - /// Triggered in which is used by both + /// Triggered in which is used by both /// users and NPCs /// public event OnNewPresenceDelegate OnNewPresence; @@ -155,7 +155,7 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered when a presence is removed from the scene /// /// - /// Triggered in which is used by both + /// Triggered in which is used by both /// users and NPCs /// /// Triggered under per-agent lock. So if you want to perform any long-running operations, please @@ -339,6 +339,8 @@ namespace OpenSim.Region.Framework.Scenes /// in /// via , /// via + /// XXX: This is only triggered when it is the client that starts the script, not in other situations where + /// a script is started, unlike OnStopScript! /// public event StartScript OnStartScript; @@ -352,6 +354,7 @@ namespace OpenSim.Region.Framework.Scenes /// in , /// , /// + /// XXX: This is triggered when a sciprt is stopped for any reason, unlike OnStartScript! /// public event StopScript OnStopScript; @@ -427,6 +430,9 @@ namespace OpenSim.Region.Framework.Scenes public delegate void IncomingInstantMessage(GridInstantMessage message); public event IncomingInstantMessage OnIncomingInstantMessage; + public delegate void CrossAgentToNewRegion(ScenePresence sp, bool isFlying, GridRegion newRegion); + public event CrossAgentToNewRegion OnCrossAgentToNewRegion; + public event IncomingInstantMessage OnUnhandledInstantMessage; public delegate void ClientClosed(UUID clientID, Scene scene); @@ -546,6 +552,20 @@ namespace OpenSim.Region.Framework.Scenes /// public event ScriptControlEvent OnScriptControlEvent; + public delegate void ScriptMovingStartEvent(uint localID); + + /// + /// TODO: Should be triggered when a physics object starts moving. + /// + public event ScriptMovingStartEvent OnScriptMovingStartEvent; + + public delegate void ScriptMovingEndEvent(uint localID); + + /// + /// TODO: Should be triggered when a physics object stops moving. + /// + public event ScriptMovingEndEvent OnScriptMovingEndEvent; + public delegate void ScriptAtTargetEvent(uint localID, uint handle, Vector3 targetpos, Vector3 atpos); /// @@ -612,8 +632,8 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered by /// in /// via - /// via - /// via + /// via + /// via /// public event ScriptColliding OnScriptColliderStart; @@ -626,8 +646,8 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered by /// in /// via - /// via - /// via + /// via + /// via /// public event ScriptColliding OnScriptColliding; @@ -639,8 +659,8 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered by /// in /// via - /// via - /// via + /// via + /// via /// public event ScriptColliding OnScriptCollidingEnd; @@ -652,8 +672,8 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered by /// in /// via - /// via - /// via + /// via + /// via /// public event ScriptColliding OnScriptLandColliderStart; @@ -665,8 +685,8 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered by /// in /// via - /// via - /// via + /// via + /// via /// public event ScriptColliding OnScriptLandColliding; @@ -678,8 +698,8 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered by /// in /// via - /// via - /// via + /// via + /// via /// public event ScriptColliding OnScriptLandColliderEnd; @@ -725,7 +745,7 @@ namespace OpenSim.Region.Framework.Scenes public event OnIncomingSceneObjectDelegate OnIncomingSceneObject; public delegate void OnIncomingSceneObjectDelegate(SceneObjectGroup so); - public delegate void NewInventoryItemUploadComplete(UUID avatarID, UUID assetID, string name, int userlevel); + public delegate void NewInventoryItemUploadComplete(UUID avatarID, AssetType type, UUID assetID, string name, int userlevel); public event NewInventoryItemUploadComplete OnNewInventoryItemUploadComplete; @@ -751,7 +771,7 @@ namespace OpenSim.Region.Framework.Scenes public event ScriptTimerEvent OnScriptTimerEvent; */ - public delegate void EstateToolsSunUpdate(ulong regionHandle, bool FixedTime, bool EstateSun, float LindenHour); + public delegate void EstateToolsSunUpdate(ulong regionHandle); public delegate void GetScriptRunning(IClientAPI controllingClient, UUID objectID, UUID itemID); public event EstateToolsSunUpdate OnEstateToolsSunUpdate; @@ -766,12 +786,20 @@ namespace OpenSim.Region.Framework.Scenes /// /// public event Action OnObjectAddedToScene; - + /// - /// Delegate for + /// When a client sends a derez request for an object inworld + /// but before the object is deleted /// - /// The object being removed from the scene - public delegate void ObjectBeingRemovedFromScene(SceneObjectGroup obj); + public event DeRezRequested OnDeRezRequested; + /// + /// Triggered when a client sends a derez request for an object inworld + /// + /// The client question (it can be null) + /// The object in question + /// The exact derez action + /// Flag indicating whether the object should be deleted from the scene or not + public delegate bool DeRezRequested(IClientAPI remoteClient, List objs, DeRezAction action); /// /// Triggered when an object is removed from the scene. @@ -781,6 +809,24 @@ namespace OpenSim.Region.Framework.Scenes /// in /// public event ObjectBeingRemovedFromScene OnObjectBeingRemovedFromScene; + /// + /// Delegate for + /// + /// The object being removed from the scene + public delegate void ObjectBeingRemovedFromScene(SceneObjectGroup obj); + + /// + /// Triggered when an object is placed into the physical scene (PhysicsActor created). + /// + public event Action OnObjectAddedToPhysicalScene; + /// + /// Triggered when an object is removed from the physical scene (PhysicsActor destroyed). + /// + /// + /// Note: this is triggered just before the PhysicsActor is removed from the + /// physics engine so the receiver can do any necessary cleanup before its destruction. + /// + public event Action OnObjectRemovedFromPhysicalScene; public delegate void NoticeNoLandDataFromStorage(); public event NoticeNoLandDataFromStorage OnNoticeNoLandDataFromStorage; @@ -939,6 +985,8 @@ namespace OpenSim.Region.Framework.Scenes public delegate void RegionStarted(Scene scene); public event RegionStarted OnRegionStarted; + public delegate void RegionHeartbeatStart(Scene scene); + public event RegionHeartbeatStart OnRegionHeartbeatStart; public delegate void RegionHeartbeatEnd(Scene scene); public event RegionHeartbeatEnd OnRegionHeartbeatEnd; @@ -989,6 +1037,16 @@ namespace OpenSim.Region.Framework.Scenes /// public event TeleportFail OnTeleportFail; +// public delegate void GatherUuids(SceneObjectPart sop, IDictionary assetUuids); +// +// /// +// /// Triggered when UUIDs referenced by a scene object are being gathered for archiving, hg transfer, etc. +// /// +// /// +// /// The listener should add references to the IDictionary as appropriate. +// /// +// public event GatherUuids OnGatherUuids; + public class MoneyTransferArgs : EventArgs { public UUID sender; @@ -1060,7 +1118,7 @@ namespace OpenSim.Region.Framework.Scenes /// Triggered in /// via /// via - /// via + /// via /// public event MoneyTransferEvent OnMoneyTransfer; @@ -1476,8 +1534,33 @@ namespace OpenSim.Region.Framework.Scenes } } } - } - + } + + public bool TriggerDeRezRequested(IClientAPI client, List objs, DeRezAction action) + { + bool canDeRez = true; + + DeRezRequested handlerDeRezRequested = OnDeRezRequested; + if (handlerDeRezRequested != null) + { + foreach (DeRezRequested d in handlerDeRezRequested.GetInvocationList()) + { + try + { + canDeRez &= d(client, objs, action); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerDeRezRequested failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + + return canDeRez; + } + public void TriggerObjectBeingRemovedFromScene(SceneObjectGroup obj) { ObjectBeingRemovedFromScene handlerObjectBeingRemovedFromScene = OnObjectBeingRemovedFromScene; @@ -1499,6 +1582,48 @@ namespace OpenSim.Region.Framework.Scenes } } + public void TriggerObjectAddedToPhysicalScene(SceneObjectPart obj) + { + Action handler = OnObjectAddedToPhysicalScene; + if (handler != null) + { + foreach (Action d in handler.GetInvocationList()) + { + try + { + d(obj); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerObjectAddedToPhysicalScene failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + + public void TriggerObjectRemovedFromPhysicalScene(SceneObjectPart obj) + { + Action handler = OnObjectRemovedFromPhysicalScene; + if (handler != null) + { + foreach (Action d in handler.GetInvocationList()) + { + try + { + d(obj); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerObjectRemovedFromPhysicalScene failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + public void TriggerShutdown() { Action handlerShutdown = OnShutdown; @@ -1876,6 +2001,27 @@ namespace OpenSim.Region.Framework.Scenes } } + public void TriggerCrossAgentToNewRegion(ScenePresence agent, bool isFlying, GridRegion newRegion) + { + CrossAgentToNewRegion handlerCrossAgentToNewRegion = OnCrossAgentToNewRegion; + if (handlerCrossAgentToNewRegion != null) + { + foreach (CrossAgentToNewRegion d in handlerCrossAgentToNewRegion.GetInvocationList()) + { + try + { + d(agent, isFlying, newRegion); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerCrossAgentToNewRegion failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + public void TriggerIncomingInstantMessage(GridInstantMessage message) { IncomingInstantMessage handlerIncomingInstantMessage = OnIncomingInstantMessage; @@ -2062,7 +2208,7 @@ namespace OpenSim.Region.Framework.Scenes } } - public void TriggerOnNewInventoryItemUploadComplete(UUID agentID, UUID AssetID, String AssetName, int userlevel) + public void TriggerOnNewInventoryItemUploadComplete(UUID agentID, AssetType type, UUID AssetID, String AssetName, int userlevel) { NewInventoryItemUploadComplete handlerNewInventoryItemUpdateComplete = OnNewInventoryItemUploadComplete; if (handlerNewInventoryItemUpdateComplete != null) @@ -2071,7 +2217,7 @@ namespace OpenSim.Region.Framework.Scenes { try { - d(agentID, AssetID, AssetName, userlevel); + d(agentID, type, AssetID, AssetName, userlevel); } catch (Exception e) { @@ -2209,6 +2355,48 @@ namespace OpenSim.Region.Framework.Scenes } } + public void TriggerMovingStartEvent(uint localID) + { + ScriptMovingStartEvent handlerScriptMovingStartEvent = OnScriptMovingStartEvent; + if (handlerScriptMovingStartEvent != null) + { + foreach (ScriptMovingStartEvent d in handlerScriptMovingStartEvent.GetInvocationList()) + { + try + { + d(localID); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerMovingStartEvent failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + + public void TriggerMovingEndEvent(uint localID) + { + ScriptMovingEndEvent handlerScriptMovingEndEvent = OnScriptMovingEndEvent; + if (handlerScriptMovingEndEvent != null) + { + foreach (ScriptMovingEndEvent d in handlerScriptMovingEndEvent.GetInvocationList()) + { + try + { + d(localID); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerMovingEndEvent failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + public void TriggerRequestChangeWaterHeight(float height) { if (height < 0) @@ -2507,13 +2695,10 @@ namespace OpenSim.Region.Framework.Scenes } /// - /// Updates the system as to how the position of the sun should be handled. + /// Called when the sun's position parameters have changed in the Region and/or Estate /// - /// - /// True if the Sun Position is fixed - /// True if the Estate Settings should be used instead of region - /// The hour 0.0 <= FixedSunHour <= 24.0 at which the sun is fixed at. Sun Hour 0 is sun-rise, when Day/Night ratio is 1:1 - public void TriggerEstateToolsSunUpdate(ulong regionHandle, bool FixedTime, bool useEstateTime, float FixedSunHour) + /// The region that changed + public void TriggerEstateToolsSunUpdate(ulong regionHandle) { EstateToolsSunUpdate handlerEstateToolsSunUpdate = OnEstateToolsSunUpdate; if (handlerEstateToolsSunUpdate != null) @@ -2522,7 +2707,7 @@ namespace OpenSim.Region.Framework.Scenes { try { - d(regionHandle, FixedTime, useEstateTime, FixedSunHour); + d(regionHandle); } catch (Exception e) { @@ -2957,6 +3142,27 @@ namespace OpenSim.Region.Framework.Scenes } } + public void TriggerRegionHeartbeatStart(Scene scene) + { + RegionHeartbeatStart handler = OnRegionHeartbeatStart; + + if (handler != null) + { + foreach (RegionHeartbeatStart d in handler.GetInvocationList()) + { + try + { + d(scene); + } + catch (Exception e) + { + m_log.ErrorFormat("[EVENT MANAGER]: Delegate for OnRegionHeartbeatStart failed - continuing {0} - {1}", + e.Message, e.StackTrace); + } + } + } + } + public void TriggerRegionHeartbeatEnd(Scene scene) { RegionHeartbeatEnd handler = OnRegionHeartbeatEnd; @@ -3103,5 +3309,26 @@ namespace OpenSim.Region.Framework.Scenes } } } + +// public void TriggerGatherUuids(SceneObjectPart sop, IDictionary assetUuids) +// { +// GatherUuids handler = OnGatherUuids; +// +// if (handler != null) +// { +// foreach (GatherUuids d in handler.GetInvocationList()) +// { +// try +// { +// d(sop, assetUuids); +// } +// catch (Exception e) +// { +// m_log.ErrorFormat("[EVENT MANAGER]: Delegate for TriggerUuidGather failed - continuing {0} - {1}", +// e.Message, e.StackTrace); +// } +// } +// } +// } } } diff --git a/OpenSim/Region/Framework/Scenes/KeyframeMotion.cs b/OpenSim/Region/Framework/Scenes/KeyframeMotion.cs new file mode 100644 index 0000000..bbf3b51 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/KeyframeMotion.cs @@ -0,0 +1,829 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Timers; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Diagnostics; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.PhysicsModules.SharedBase; +using OpenSim.Region.Framework.Scenes.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Runtime.Serialization; +using Timer = System.Timers.Timer; +using log4net; + +namespace OpenSim.Region.Framework.Scenes +{ + public class KeyframeTimer + { + private static Dictionary m_timers = + new Dictionary(); + + private Timer m_timer; + private Dictionary m_motions = new Dictionary(); + private object m_lockObject = new object(); + private object m_timerLock = new object(); + private const double m_tickDuration = 50.0; + + public double TickDuration + { + get { return m_tickDuration; } + } + + public KeyframeTimer(Scene scene) + { + m_timer = new Timer(); + m_timer.Interval = TickDuration; + m_timer.AutoReset = true; + m_timer.Elapsed += OnTimer; + } + + public void Start() + { + lock (m_timer) + { + if (!m_timer.Enabled) + m_timer.Start(); + } + } + + private void OnTimer(object sender, ElapsedEventArgs ea) + { + if (!Monitor.TryEnter(m_timerLock)) + return; + + try + { + List motions; + + lock (m_lockObject) + { + motions = new List(m_motions.Keys); + } + + foreach (KeyframeMotion m in motions) + { + try + { + m.OnTimer(TickDuration); + } + catch (Exception) + { + // Don't stop processing + } + } + } + catch (Exception) + { + // Keep running no matter what + } + finally + { + Monitor.Exit(m_timerLock); + } + } + + public static void Add(KeyframeMotion motion) + { + KeyframeTimer timer; + + if (motion.Scene == null) + return; + + lock (m_timers) + { + if (!m_timers.TryGetValue(motion.Scene, out timer)) + { + timer = new KeyframeTimer(motion.Scene); + m_timers[motion.Scene] = timer; + + if (!SceneManager.Instance.AllRegionsReady) + { + // Start the timers only once all the regions are ready. This is required + // when using megaregions, because the megaregion is correctly configured + // only after all the regions have been loaded. (If we don't do this then + // when the prim moves it might think that it crossed into a region.) + SceneManager.Instance.OnRegionsReadyStatusChange += delegate(SceneManager sm) + { + if (sm.AllRegionsReady) + timer.Start(); + }; + } + + // Check again, in case the regions were started while we were adding the event handler + if (SceneManager.Instance.AllRegionsReady) + { + timer.Start(); + } + } + } + + lock (timer.m_lockObject) + { + timer.m_motions[motion] = null; + } + } + + public static void Remove(KeyframeMotion motion) + { + KeyframeTimer timer; + + if (motion.Scene == null) + return; + + lock (m_timers) + { + if (!m_timers.TryGetValue(motion.Scene, out timer)) + { + return; + } + } + + lock (timer.m_lockObject) + { + timer.m_motions.Remove(motion); + } + } + } + + [Serializable] + public class KeyframeMotion + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public enum PlayMode : int + { + Forward = 0, + Reverse = 1, + Loop = 2, + PingPong = 3 + }; + + [Flags] + public enum DataFormat : int + { + Translation = 2, + Rotation = 1 + } + + [Serializable] + public struct Keyframe + { + public Vector3? Position; + public Quaternion? Rotation; + public Quaternion StartRotation; + public int TimeMS; + public int TimeTotal; + public Vector3 AngularVelocity; + public Vector3 StartPosition; + }; + + private Vector3 m_serializedPosition; + private Vector3 m_basePosition; + private Quaternion m_baseRotation; + + private Keyframe m_currentFrame; + + private List m_frames = new List(); + + private Keyframe[] m_keyframes; + + // skip timer events. + //timer.stop doesn't assure there aren't event threads still being fired + [NonSerialized()] + private bool m_timerStopped; + + [NonSerialized()] + private bool m_isCrossing; + + [NonSerialized()] + private bool m_waitingCrossing; + + // retry position for cross fail + [NonSerialized()] + private Vector3 m_nextPosition; + + [NonSerialized()] + private SceneObjectGroup m_group; + + private PlayMode m_mode = PlayMode.Forward; + private DataFormat m_data = DataFormat.Translation | DataFormat.Rotation; + + private bool m_running = false; + + [NonSerialized()] + private bool m_selected = false; + + private int m_iterations = 0; + + private int m_skipLoops = 0; + + [NonSerialized()] + private Scene m_scene; + + public Scene Scene + { + get { return m_scene; } + } + + public DataFormat Data + { + get { return m_data; } + } + + public bool Selected + { + set + { + if (m_group != null) + { + if (!value) + { + // Once we're let go, recompute positions + if (m_selected) + UpdateSceneObject(m_group); + } + else + { + // Save selection position in case we get moved + if (!m_selected) + { + StopTimer(); + m_serializedPosition = m_group.AbsolutePosition; + } + } + } + m_isCrossing = false; + m_waitingCrossing = false; + m_selected = value; + } + } + + private void StartTimer() + { + KeyframeTimer.Add(this); + m_timerStopped = false; + } + + private void StopTimer() + { + m_timerStopped = true; + KeyframeTimer.Remove(this); + } + + public static KeyframeMotion FromData(SceneObjectGroup grp, Byte[] data) + { + KeyframeMotion newMotion = null; + + try + { + using (MemoryStream ms = new MemoryStream(data)) + { + BinaryFormatter fmt = new BinaryFormatter(); + newMotion = (KeyframeMotion)fmt.Deserialize(ms); + } + + newMotion.m_group = grp; + + if (grp != null) + { + newMotion.m_scene = grp.Scene; + if (grp.IsSelected) + newMotion.m_selected = true; + } + + newMotion.m_timerStopped = false; + newMotion.m_running = true; + newMotion.m_isCrossing = false; + newMotion.m_waitingCrossing = false; + } + catch + { + newMotion = null; + } + + return newMotion; + } + + public void UpdateSceneObject(SceneObjectGroup grp) + { + m_isCrossing = false; + m_waitingCrossing = false; + StopTimer(); + + if (grp == null) + return; + + m_group = grp; + m_scene = grp.Scene; + + Vector3 grppos = grp.AbsolutePosition; + Vector3 offset = grppos - m_serializedPosition; + // avoid doing it more than once + // current this will happen dragging a prim to other region + m_serializedPosition = grppos; + + m_basePosition += offset; + m_nextPosition += offset; + + m_currentFrame.StartPosition += offset; + m_currentFrame.Position += offset; + + for (int i = 0; i < m_frames.Count; i++) + { + Keyframe k = m_frames[i]; + k.StartPosition += offset; + k.Position += offset; + m_frames[i]=k; + } + + if (m_running) + Start(); + } + + public KeyframeMotion(SceneObjectGroup grp, PlayMode mode, DataFormat data) + { + m_mode = mode; + m_data = data; + + m_group = grp; + if (grp != null) + { + m_basePosition = grp.AbsolutePosition; + m_baseRotation = grp.GroupRotation; + m_scene = grp.Scene; + } + + m_timerStopped = true; + m_isCrossing = false; + m_waitingCrossing = false; + } + + public void SetKeyframes(Keyframe[] frames) + { + m_keyframes = frames; + } + + public KeyframeMotion Copy(SceneObjectGroup newgrp) + { + StopTimer(); + + KeyframeMotion newmotion = new KeyframeMotion(null, m_mode, m_data); + + newmotion.m_group = newgrp; + newmotion.m_scene = newgrp.Scene; + + if (m_keyframes != null) + { + newmotion.m_keyframes = new Keyframe[m_keyframes.Length]; + m_keyframes.CopyTo(newmotion.m_keyframes, 0); + } + + newmotion.m_frames = new List(m_frames); + + newmotion.m_basePosition = m_basePosition; + newmotion.m_baseRotation = m_baseRotation; + + if (m_selected) + newmotion.m_serializedPosition = m_serializedPosition; + else + { + if (m_group != null) + newmotion.m_serializedPosition = m_group.AbsolutePosition; + else + newmotion.m_serializedPosition = m_serializedPosition; + } + + newmotion.m_currentFrame = m_currentFrame; + + newmotion.m_iterations = m_iterations; + newmotion.m_running = m_running; + + if (m_running && !m_waitingCrossing) + StartTimer(); + + return newmotion; + } + + public void Delete() + { + m_running = false; + StopTimer(); + m_isCrossing = false; + m_waitingCrossing = false; + m_frames.Clear(); + m_keyframes = null; + } + + public void Start() + { + m_isCrossing = false; + m_waitingCrossing = false; + if (m_keyframes != null && m_group != null && m_keyframes.Length > 0) + { + StartTimer(); + m_running = true; + m_group.Scene.EventManager.TriggerMovingStartEvent(m_group.RootPart.LocalId); + } + else + { + m_running = false; + StopTimer(); + } + } + + public void Stop() + { + m_running = false; + m_isCrossing = false; + m_waitingCrossing = false; + + StopTimer(); + + m_basePosition = m_group.AbsolutePosition; + m_baseRotation = m_group.GroupRotation; + + m_group.RootPart.Velocity = Vector3.Zero; + m_group.RootPart.AngularVelocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + m_frames.Clear(); + } + + public void Pause() + { + m_running = false; + StopTimer(); + + m_group.RootPart.Velocity = Vector3.Zero; + m_group.RootPart.AngularVelocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + + } + + private void GetNextList() + { + m_frames.Clear(); + Vector3 pos = m_basePosition; + Quaternion rot = m_baseRotation; + + if (m_mode == PlayMode.Loop || m_mode == PlayMode.PingPong || m_iterations == 0) + { + int direction = 1; + if (m_mode == PlayMode.Reverse || ((m_mode == PlayMode.PingPong) && ((m_iterations & 1) != 0))) + direction = -1; + + int start = 0; + int end = m_keyframes.Length; + + if (direction < 0) + { + start = m_keyframes.Length - 1; + end = -1; + } + + for (int i = start; i != end ; i += direction) + { + Keyframe k = m_keyframes[i]; + + k.StartPosition = pos; + if (k.Position.HasValue) + { + k.Position = (k.Position * direction); +// k.Velocity = (Vector3)k.Position / (k.TimeMS / 1000.0f); + k.Position += pos; + } + else + { + k.Position = pos; +// k.Velocity = Vector3.Zero; + } + + k.StartRotation = rot; + if (k.Rotation.HasValue) + { + if (direction == -1) + k.Rotation = Quaternion.Conjugate((Quaternion)k.Rotation); + k.Rotation = rot * k.Rotation; + } + else + { + k.Rotation = rot; + } + +/* ang vel not in use for now + + float angle = 0; + + float aa = k.StartRotation.X * k.StartRotation.X + k.StartRotation.Y * k.StartRotation.Y + k.StartRotation.Z * k.StartRotation.Z + k.StartRotation.W * k.StartRotation.W; + float bb = ((Quaternion)k.Rotation).X * ((Quaternion)k.Rotation).X + ((Quaternion)k.Rotation).Y * ((Quaternion)k.Rotation).Y + ((Quaternion)k.Rotation).Z * ((Quaternion)k.Rotation).Z + ((Quaternion)k.Rotation).W * ((Quaternion)k.Rotation).W; + float aa_bb = aa * bb; + + if (aa_bb == 0) + { + angle = 0; + } + else + { + float ab = k.StartRotation.X * ((Quaternion)k.Rotation).X + + k.StartRotation.Y * ((Quaternion)k.Rotation).Y + + k.StartRotation.Z * ((Quaternion)k.Rotation).Z + + k.StartRotation.W * ((Quaternion)k.Rotation).W; + float q = (ab * ab) / aa_bb; + + if (q > 1.0f) + { + angle = 0; + } + else + { + angle = (float)Math.Acos(2 * q - 1); + } + } + + k.AngularVelocity = (new Vector3(0, 0, 1) * (Quaternion)k.Rotation) * (angle / (k.TimeMS / 1000)); + */ + k.TimeTotal = k.TimeMS; + + m_frames.Add(k); + + pos = (Vector3)k.Position; + rot = (Quaternion)k.Rotation; + } + + m_basePosition = pos; + m_baseRotation = rot; + + m_iterations++; + } + } + + public void OnTimer(double tickDuration) + { + if (m_skipLoops > 0) + { + m_skipLoops--; + return; + } + + if (m_timerStopped) // trap events still in air even after a timer.stop + return; + + if (m_group == null) + return; + + bool update = false; + + if (m_selected) + { + if (m_group.RootPart.Velocity != Vector3.Zero) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); + + } + return; + } + + if (m_isCrossing) + { + // if crossing and timer running then cross failed + // wait some time then + // retry to set the position that evtually caused the outbound + // if still outside region this will call startCrossing below + m_isCrossing = false; + m_group.AbsolutePosition = m_nextPosition; + if (!m_isCrossing) + { + StopTimer(); + StartTimer(); + } + return; + } + + if (m_frames.Count == 0) + { + if (!m_running) return; + + GetNextList(); + + if (m_frames.Count == 0) + { + Stop(); +// Scene scene = m_group.Scene; +// +// IScriptModule[] scriptModules = scene.RequestModuleInterfaces(); +// foreach (IScriptModule m in scriptModules) +// { +// if (m == null) +// continue; +// m.PostObjectEvent(m_group.RootPart.UUID, "moving_end", new object[0]); +// } + + m_group.Scene.EventManager.TriggerMovingEndEvent(m_group.RootPart.LocalId); + + return; + } + + m_currentFrame = m_frames[0]; + m_currentFrame.TimeMS += (int)tickDuration; + + //force a update on a keyframe transition + update = true; + } + + m_currentFrame.TimeMS -= (int)tickDuration; + + // Do the frame processing + double remainingSteps = (double)m_currentFrame.TimeMS / tickDuration; + + if (remainingSteps <= 0.0) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.RootPart.AngularVelocity = Vector3.Zero; + + m_nextPosition = (Vector3)m_currentFrame.Position; + m_group.AbsolutePosition = m_nextPosition; + + // we are sending imediate updates, no doing force a extra terseUpdate + // m_group.UpdateGroupRotationR((Quaternion)m_currentFrame.Rotation); + + m_group.RootPart.RotationOffset = (Quaternion)m_currentFrame.Rotation; + m_frames.RemoveAt(0); + if (m_frames.Count > 0) + m_currentFrame = m_frames[0]; + + update = true; + } + else + { + float completed = ((float)m_currentFrame.TimeTotal - (float)m_currentFrame.TimeMS) / (float)m_currentFrame.TimeTotal; + bool lastStep = m_currentFrame.TimeMS <= tickDuration; + + Vector3 positionThisStep = m_currentFrame.StartPosition + (m_currentFrame.Position.Value - m_currentFrame.StartPosition) * completed; + Vector3 motionThisStep = positionThisStep - m_group.AbsolutePosition; + + float mag = Vector3.Mag(motionThisStep); + + if ((mag >= 0.02f) || lastStep) + { + m_nextPosition = m_group.AbsolutePosition + motionThisStep; + m_group.AbsolutePosition = m_nextPosition; + update = true; + } + + //int totalSteps = m_currentFrame.TimeTotal / (int)tickDuration; + //m_log.DebugFormat("KeyframeMotion.OnTimer: step {0}/{1}, curPosition={2}, finalPosition={3}, motionThisStep={4} (scene {5})", + // totalSteps - remainingSteps + 1, totalSteps, m_group.AbsolutePosition, m_currentFrame.Position, motionThisStep, m_scene.RegionInfo.RegionName); + + if ((Quaternion)m_currentFrame.Rotation != m_group.GroupRotation) + { + Quaternion current = m_group.GroupRotation; + + Quaternion step = Quaternion.Slerp(m_currentFrame.StartRotation, (Quaternion)m_currentFrame.Rotation, completed); + step.Normalize(); +/* use simpler change detection +* float angle = 0; + + float aa = current.X * current.X + current.Y * current.Y + current.Z * current.Z + current.W * current.W; + float bb = step.X * step.X + step.Y * step.Y + step.Z * step.Z + step.W * step.W; + float aa_bb = aa * bb; + + if (aa_bb == 0) + { + angle = 0; + } + else + { + float ab = current.X * step.X + + current.Y * step.Y + + current.Z * step.Z + + current.W * step.W; + float q = (ab * ab) / aa_bb; + + if (q > 1.0f) + { + angle = 0; + } + else + { + angle = (float)Math.Acos(2 * q - 1); + } + } + + if (angle > 0.01f) +*/ + if(Math.Abs(step.X - current.X) > 0.001f + || Math.Abs(step.Y - current.Y) > 0.001f + || Math.Abs(step.Z - current.Z) > 0.001f + || lastStep) + // assuming w is a dependente var + + { +// m_group.UpdateGroupRotationR(step); + m_group.RootPart.RotationOffset = step; + + //m_group.RootPart.UpdateAngularVelocity(m_currentFrame.AngularVelocity / 2); + update = true; + } + } + } + + if (update) + { + m_group.SendGroupRootTerseUpdate(); + } + } + + public Byte[] Serialize() + { + StopTimer(); + + SceneObjectGroup tmp = m_group; + m_group = null; + if (!m_selected && tmp != null) + m_serializedPosition = tmp.AbsolutePosition; + + using (MemoryStream ms = new MemoryStream()) + { + BinaryFormatter fmt = new BinaryFormatter(); + fmt.Serialize(ms, this); + m_group = tmp; + if (m_running && !m_waitingCrossing) + StartTimer(); + + return ms.ToArray(); + } + } + + public void StartCrossingCheck() + { + // timer will be restart by crossingFailure + // or never since crossing worked and this + // should be deleted + StopTimer(); + + m_isCrossing = true; + m_waitingCrossing = true; + + // to remove / retune to smoth crossings + if (m_group.RootPart.Velocity != Vector3.Zero) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + } + } + + public void CrossingFailure() + { + m_waitingCrossing = false; + + if (m_group != null) + { + m_group.RootPart.Velocity = Vector3.Zero; + m_group.SendGroupRootTerseUpdate(); +// m_group.RootPart.ScheduleTerseUpdate(); + + if (m_running) + { + StopTimer(); + m_skipLoops = 1200; // 60 seconds + StartTimer(); + } + } + } + } +} diff --git a/OpenSim/Region/Framework/Scenes/Prioritizer.cs b/OpenSim/Region/Framework/Scenes/Prioritizer.cs index 1b10e3c..ae85560 100644 --- a/OpenSim/Region/Framework/Scenes/Prioritizer.cs +++ b/OpenSim/Region/Framework/Scenes/Prioritizer.cs @@ -31,7 +31,7 @@ using log4net; using Nini.Config; using OpenSim.Framework; using OpenMetaverse; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; /* * Steps to add a new prioritization policy: diff --git a/OpenSim/Region/Framework/Scenes/RegionStatsHandler.cs b/OpenSim/Region/Framework/Scenes/RegionStatsHandler.cs index c11174d..3b31281 100644 --- a/OpenSim/Region/Framework/Scenes/RegionStatsHandler.cs +++ b/OpenSim/Region/Framework/Scenes/RegionStatsHandler.cs @@ -35,7 +35,6 @@ using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Console; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; @@ -46,47 +45,33 @@ using OpenSim.Region.Framework.Scenes; namespace OpenSim.Region.Framework.Scenes { - public class RegionStatsHandler : IStreamedRequestHandler + public class RegionStatsHandler : BaseStreamHandler { //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private string osRXStatsURI = String.Empty; private string osXStatsURI = String.Empty; //private string osSecret = String.Empty; private OpenSim.Framework.RegionInfo regionInfo; public string localZone = TimeZone.CurrentTimeZone.StandardName; public TimeSpan utcOffset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now); - public string Name { get { return "RegionStats"; } } - public string Description { get { return "Region Statistics"; } } - - public RegionStatsHandler(RegionInfo region_info) + public RegionStatsHandler(RegionInfo region_info) + : base("GET", "/" + Util.SHA1Hash(region_info.regionSecret), "RegionStats", "Region Statistics") { regionInfo = region_info; - osRXStatsURI = Util.SHA1Hash(regionInfo.regionSecret); osXStatsURI = Util.SHA1Hash(regionInfo.osSecret); } - public byte[] Handle(string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + protected override byte[] ProcessRequest( + string path, Stream request, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { return Util.UTF8.GetBytes(Report()); } - public string ContentType + public override string ContentType { get { return "text/plain"; } } - - public string HttpMethod - { - get { return "GET"; } - } - - public string Path - { - // This is for the region and is the regionSecret hashed - get { return "/" + osRXStatsURI; } - } private string Report() { diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index a6db4de..b838177 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -30,22 +30,27 @@ using System.Collections.Generic; using System.Collections; using System.Reflection; using System.Text; +using System.Threading; using System.Timers; +using System.Xml; using OpenMetaverse; using OpenMetaverse.Packets; using log4net; using OpenSim.Framework; +using OpenSim.Framework.Serialization.External; using OpenSim.Region.Framework; using OpenSim.Framework.Client; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.Framework.Scenes { public partial class Scene { - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + //private static readonly string LogHeader = "[SCENE INVENTORY]"; /// /// Allows asynchronous derezzing of objects from the scene into a client's inventory. @@ -125,11 +130,16 @@ namespace OpenSim.Region.Framework.Scenes } } + public bool AddInventoryItem(InventoryItemBase item) + { + return AddInventoryItem(item, true); + } + /// /// Add the given inventory item to a user's inventory. /// /// - public bool AddInventoryItem(InventoryItemBase item) + public bool AddInventoryItem(InventoryItemBase item, bool trigger) { if (item.Folder != UUID.Zero && InventoryService.AddItem(item)) { @@ -138,14 +148,17 @@ namespace OpenSim.Region.Framework.Scenes { userlevel = 1; } - EventManager.TriggerOnNewInventoryItemUploadComplete(item.Owner, item.AssetID, item.Name, userlevel); + if (trigger) + EventManager.TriggerOnNewInventoryItemUploadComplete(item.Owner, (AssetType)item.AssetType, item.AssetID, item.Name, userlevel); return true; } // OK so either the viewer didn't send a folderID or AddItem failed UUID originalFolder = item.Folder; - InventoryFolderBase f = InventoryService.GetFolderForType(item.Owner, (AssetType)item.AssetType); + InventoryFolderBase f = null; + if (Enum.IsDefined(typeof(FolderType), (sbyte)item.AssetType)) + f = InventoryService.GetFolderForType(item.Owner, (FolderType)item.AssetType); if (f != null) { m_log.DebugFormat( @@ -177,7 +190,8 @@ namespace OpenSim.Region.Framework.Scenes { userlevel = 1; } - EventManager.TriggerOnNewInventoryItemUploadComplete(item.Owner, item.AssetID, item.Name, userlevel); + if (trigger) + EventManager.TriggerOnNewInventoryItemUploadComplete(item.Owner, (AssetType)item.AssetType, item.AssetID, item.Name, userlevel); if (originalFolder != UUID.Zero) { @@ -325,7 +339,7 @@ namespace OpenSim.Region.Framework.Scenes // Update item with new asset item.AssetID = asset.FullID; if (group.UpdateInventoryItem(item)) - remoteClient.SendAgentAlertMessage("Script saved", false); + remoteClient.SendAlertMessage("Script saved"); part.SendPropertiesToClient(remoteClient); @@ -341,7 +355,7 @@ namespace OpenSim.Region.Framework.Scenes } else { - remoteClient.SendAgentAlertMessage("Script saved", false); + remoteClient.SendAlertMessage("Script saved"); } // Tell anyone managing scripts that a script has been reloaded/changed @@ -415,20 +429,61 @@ namespace OpenSim.Region.Framework.Scenes // itemUpd.NextPermissions, itemUpd.GroupPermissions, itemUpd.EveryOnePermissions, item.Flags, // item.NextPermissions, item.GroupPermissions, item.EveryOnePermissions, item.CurrentPermissions); + bool sendUpdate = false; + if (itemUpd.NextPermissions != 0) // Use this to determine validity. Can never be 0 if valid { + // Create a set of base permissions that will not include export if the user + // is not allowed to change the export flag. + bool denyExportChange = false; + +// m_log.DebugFormat("[XXX]: B: {0} O: {1} E: {2}", itemUpd.BasePermissions, itemUpd.CurrentPermissions, itemUpd.EveryOnePermissions); + + // If the user is not the creator or doesn't have "E" in both "B" and "O", deny setting export + if ((item.BasePermissions & (uint)(PermissionMask.All | PermissionMask.Export)) != (uint)(PermissionMask.All | PermissionMask.Export) || (item.CurrentPermissions & (uint)PermissionMask.Export) == 0 || item.CreatorIdAsUuid != item.Owner) + denyExportChange = true; + +// m_log.DebugFormat("[XXX]: Deny Export Update {0}", denyExportChange); + + // If it is already set, force it set and also force full perm + // else prevent setting it. It can and should never be set unless + // set in base, so the condition above is valid + if (denyExportChange) + { + // If we are not allowed to change it, then force it to the + // original item's setting and if it was on, also force full perm + if ((item.EveryOnePermissions & (uint)PermissionMask.Export) != 0) + { + itemUpd.NextPermissions = (uint)(PermissionMask.All); + itemUpd.EveryOnePermissions |= (uint)PermissionMask.Export; + } + else + { + itemUpd.EveryOnePermissions &= ~(uint)PermissionMask.Export; + } + } + else + { + // If the new state is exportable, force full perm + if ((itemUpd.EveryOnePermissions & (uint)PermissionMask.Export) != 0) + { +// m_log.DebugFormat("[XXX]: Force full perm"); + itemUpd.NextPermissions = (uint)(PermissionMask.All); + } + } + if (item.NextPermissions != (itemUpd.NextPermissions & item.BasePermissions)) item.Flags |= (uint)InventoryItemFlags.ObjectOverwriteNextOwner; item.NextPermissions = itemUpd.NextPermissions & item.BasePermissions; + if (item.EveryOnePermissions != (itemUpd.EveryOnePermissions & item.BasePermissions)) item.Flags |= (uint)InventoryItemFlags.ObjectOverwriteEveryone; item.EveryOnePermissions = itemUpd.EveryOnePermissions & item.BasePermissions; + if (item.GroupPermissions != (itemUpd.GroupPermissions & item.BasePermissions)) item.Flags |= (uint)InventoryItemFlags.ObjectOverwriteGroup; - -// m_log.DebugFormat("[USER INVENTORY]: item.Flags {0}", item.Flags); - item.GroupPermissions = itemUpd.GroupPermissions & item.BasePermissions; + item.GroupID = itemUpd.GroupID; item.GroupOwned = itemUpd.GroupOwned; item.CreationDate = itemUpd.CreationDate; @@ -449,6 +504,12 @@ namespace OpenSim.Region.Framework.Scenes item.SalePrice = itemUpd.SalePrice; item.SaleType = itemUpd.SaleType; + if (item.InvType == (int)InventoryType.Wearable && (item.Flags & 0xf) == 0 && (itemUpd.Flags & 0xf) != 0) + { + item.Flags = (uint)(item.Flags & 0xfffffff0) | (itemUpd.Flags & 0xf); + sendUpdate = true; + } + InventoryService.UpdateItem(item); } @@ -459,6 +520,17 @@ namespace OpenSim.Region.Framework.Scenes AgentTransactionsModule.HandleItemUpdateFromTransaction(remoteClient, transactionID, item); } } + else + { + // This MAY be problematic, if it is, another solution + // needs to be found. If inventory item flags are updated + // the viewer's notion of the item needs to be refreshed. + // + // In other situations we cannot send out a bulk update here, since this will cause editing of clothing to start + // failing frequently. Possibly this is a race with a separate transaction that uploads the asset. + if (sendUpdate) + remoteClient.SendBulkUpdateInventory(item); + } } else { @@ -474,9 +546,9 @@ namespace OpenSim.Region.Framework.Scenes /// /// ID of the sender of the item /// - public virtual void GiveInventoryItem(IClientAPI recipientClient, UUID senderId, UUID itemId) + public virtual void GiveInventoryItem(IClientAPI recipientClient, UUID senderId, UUID itemId, out string message) { - InventoryItemBase itemCopy = GiveInventoryItem(recipientClient.AgentId, senderId, itemId); + InventoryItemBase itemCopy = GiveInventoryItem(recipientClient.AgentId, senderId, itemId, out message); if (itemCopy != null) recipientClient.SendBulkUpdateInventory(itemCopy); @@ -489,9 +561,9 @@ namespace OpenSim.Region.Framework.Scenes /// ID of the sender of the item /// /// The inventory item copy given, null if the give was unsuccessful - public virtual InventoryItemBase GiveInventoryItem(UUID recipient, UUID senderId, UUID itemId) + public virtual InventoryItemBase GiveInventoryItem(UUID recipient, UUID senderId, UUID itemId, out string message) { - return GiveInventoryItem(recipient, senderId, itemId, UUID.Zero); + return GiveInventoryItem(recipient, senderId, itemId, UUID.Zero, out message); } /// @@ -508,10 +580,16 @@ namespace OpenSim.Region.Framework.Scenes /// The inventory item copy given, null if the give was unsuccessful /// public virtual InventoryItemBase GiveInventoryItem( - UUID recipient, UUID senderId, UUID itemId, UUID recipientFolderId) + UUID recipient, UUID senderId, UUID itemId, UUID recipientFolderId, out string message) { //Console.WriteLine("Scene.Inventory.cs: GiveInventoryItem"); + if (!Permissions.CanTransferUserInventory(itemId, senderId, recipient)) + { + message = "Not allowed to transfer this item."; + return null; + } + InventoryItemBase item = new InventoryItemBase(itemId, senderId); item = InventoryService.GetItem(item); @@ -519,6 +597,7 @@ namespace OpenSim.Region.Framework.Scenes { m_log.WarnFormat( "[AGENT INVENTORY]: Failed to find item {0} sent by {1} to {2}", itemId, senderId, recipient); + message = string.Format("Item not found: {0}.", itemId); return null; } @@ -527,6 +606,7 @@ namespace OpenSim.Region.Framework.Scenes m_log.WarnFormat( "[AGENT INVENTORY]: Attempt to send item {0} {1} to {2} failed because sender {3} did not match item owner {4}", item.Name, item.ID, recipient, senderId, item.Owner); + message = "Sender did not match item owner."; return null; } @@ -537,7 +617,10 @@ namespace OpenSim.Region.Framework.Scenes if (!Permissions.BypassPermissions()) { if ((item.CurrentPermissions & (uint)PermissionMask.Transfer) == 0) + { + message = "Item doesn't have the Transfer permission."; return null; + } } // Insert a copy of the item into the recipient @@ -606,17 +689,13 @@ namespace OpenSim.Region.Framework.Scenes // a mask if (item.InvType == (int)InventoryType.Object) { - // Create a safe mask for the current perms - uint foldedPerms = (item.CurrentPermissions & 7) << 13; - foldedPerms |= permsMask; - bool isRootMod = (item.CurrentPermissions & (uint)PermissionMask.Modify) != 0 ? true : false; // Mask the owner perms to the folded perms - ownerPerms &= foldedPerms; - basePerms &= foldedPerms; + PermissionsUtil.ApplyFoldedPermissions(item.CurrentPermissions, ref ownerPerms); + PermissionsUtil.ApplyFoldedPermissions(item.CurrentPermissions, ref basePerms); // If the root was mod, let the mask reflect that // We also need to adjust the base here, because @@ -666,7 +745,9 @@ namespace OpenSim.Region.Framework.Scenes if (itemCopy.Folder == UUID.Zero) { - InventoryFolderBase folder = InventoryService.GetFolderForType(recipient, (AssetType)itemCopy.AssetType); + InventoryFolderBase folder = null; + if (Enum.IsDefined(typeof(FolderType), (sbyte)item.AssetType)) + folder = InventoryService.GetFolderForType(recipient, (FolderType)itemCopy.AssetType); if (folder != null) { @@ -677,9 +758,14 @@ namespace OpenSim.Region.Framework.Scenes InventoryFolderBase root = InventoryService.GetRootFolder(recipient); if (root != null) + { itemCopy.Folder = root.ID; + } else - return null; // No destination + { + message = "Can't find a folder to add the item to."; + return null; + } } } @@ -692,7 +778,7 @@ namespace OpenSim.Region.Framework.Scenes IInventoryAccessModule invAccess = RequestModuleInterface(); if (invAccess != null) invAccess.TransferInventoryAssets(itemCopy, senderId, recipient); - AddInventoryItem(itemCopy); + AddInventoryItem(itemCopy, false); if (!Permissions.BypassPermissions()) { @@ -704,6 +790,7 @@ namespace OpenSim.Region.Framework.Scenes } } + message = null; return itemCopy; } @@ -721,11 +808,11 @@ namespace OpenSim.Region.Framework.Scenes /// /// The inventory folder copy given, null if the copy was unsuccessful /// - public virtual InventoryFolderBase GiveInventoryFolder( + public virtual InventoryFolderBase GiveInventoryFolder(IClientAPI client, UUID recipientId, UUID senderId, UUID folderId, UUID recipientParentFolderId) { //// Retrieve the folder from the sender - InventoryFolderBase folder = InventoryService.GetFolder(new InventoryFolderBase(folderId)); + InventoryFolderBase folder = InventoryService.GetFolder(new InventoryFolderBase(folderId, senderId)); if (null == folder) { m_log.ErrorFormat( @@ -756,13 +843,18 @@ namespace OpenSim.Region.Framework.Scenes InventoryCollection contents = InventoryService.GetFolderContent(senderId, folderId); foreach (InventoryFolderBase childFolder in contents.Folders) { - GiveInventoryFolder(recipientId, senderId, childFolder.ID, newFolder.ID); + GiveInventoryFolder(client, recipientId, senderId, childFolder.ID, newFolder.ID); } // Give all the items foreach (InventoryItemBase item in contents.Items) { - GiveInventoryItem(recipientId, senderId, item.ID, newFolder.ID); + string message; + if (GiveInventoryItem(recipientId, senderId, item.ID, newFolder.ID, out message) == null) + { + if (client != null) + client.SendAgentAlertMessage(message, false); + } } return newFolder; @@ -794,51 +886,34 @@ namespace OpenSim.Region.Framework.Scenes return; } - AssetBase asset = AssetService.Get(item.AssetID.ToString()); + if (newName == String.Empty) + newName = item.Name; - if (asset != null) + if (remoteClient.AgentId == oldAgentID + || (LibraryService != null + && LibraryService.LibraryRootFolder != null + && oldAgentID == LibraryService.LibraryRootFolder.Owner)) { - if (newName != String.Empty) - { - asset.Name = newName; - } - else - { - newName = item.Name; - } - - if (remoteClient.AgentId == oldAgentID - || (LibraryService != null - && LibraryService.LibraryRootFolder != null - && oldAgentID == LibraryService.LibraryRootFolder.Owner)) + CreateNewInventoryItem( + remoteClient, item.CreatorId, item.CreatorData, newFolderID, + newName, item.Description, item.Flags, callbackID, item.AssetID, (sbyte)item.AssetType, (sbyte)item.InvType, + item.BasePermissions, item.CurrentPermissions, item.EveryOnePermissions, + item.NextPermissions, item.GroupPermissions, Util.UnixTimeSinceEpoch(), false); + } + else + { + // If item is transfer or permissions are off or calling agent is allowed to copy item owner's inventory item. + if (((item.CurrentPermissions & (uint)PermissionMask.Transfer) != 0) + && (m_permissions.BypassPermissions() + || m_permissions.CanCopyUserInventory(remoteClient.AgentId, oldItemID))) { CreateNewInventoryItem( - remoteClient, item.CreatorId, item.CreatorData, newFolderID, - newName, item.Description, item.Flags, callbackID, asset, (sbyte)item.InvType, - item.BasePermissions, item.CurrentPermissions, item.EveryOnePermissions, - item.NextPermissions, item.GroupPermissions, Util.UnixTimeSinceEpoch()); - } - else - { - // If item is transfer or permissions are off or calling agent is allowed to copy item owner's inventory item. - if (((item.CurrentPermissions & (uint)PermissionMask.Transfer) != 0) - && (m_permissions.BypassPermissions() - || m_permissions.CanCopyUserInventory(remoteClient.AgentId, oldItemID))) - { - CreateNewInventoryItem( - remoteClient, item.CreatorId, item.CreatorData, newFolderID, newName, item.Description, item.Flags, callbackID, - asset, (sbyte) item.InvType, - item.NextPermissions, item.NextPermissions, item.EveryOnePermissions & item.NextPermissions, - item.NextPermissions, item.GroupPermissions, Util.UnixTimeSinceEpoch()); - } + remoteClient, item.CreatorId, item.CreatorData, newFolderID, newName, item.Description, item.Flags, callbackID, + item.AssetID, (sbyte)item.AssetType, (sbyte) item.InvType, + item.NextPermissions, item.NextPermissions, item.EveryOnePermissions & item.NextPermissions, + item.NextPermissions, item.GroupPermissions, Util.UnixTimeSinceEpoch(), false); } } - else - { - m_log.ErrorFormat( - "[AGENT INVENTORY]: Could not copy item {0} since asset {1} could not be found", - item.Name, item.AssetID); - } } /// @@ -888,11 +963,12 @@ namespace OpenSim.Region.Framework.Scenes public void CreateNewInventoryItem( IClientAPI remoteClient, string creatorID, string creatorData, UUID folderID, string name, string description, uint flags, uint callbackID, - AssetBase asset, sbyte invType, uint nextOwnerMask, int creationDate) + UUID assetID, sbyte assetType, sbyte invType, uint nextOwnerMask, int creationDate) { CreateNewInventoryItem( - remoteClient, creatorID, creatorData, folderID, name, description, flags, callbackID, asset, invType, - (uint)PermissionMask.All, (uint)PermissionMask.All, 0, nextOwnerMask, 0, creationDate); + remoteClient, creatorID, creatorData, folderID, name, description, flags, callbackID, assetID, assetType, invType, + (uint)PermissionMask.All | (uint)PermissionMask.Export, (uint)PermissionMask.All | (uint)PermissionMask.Export, 0, nextOwnerMask, 0, + creationDate, true); } /// @@ -916,19 +992,20 @@ namespace OpenSim.Region.Framework.Scenes /// Unix timestamp at which this item was created. private void CreateNewInventoryItem( IClientAPI remoteClient, string creatorID, string creatorData, UUID folderID, - string name, string description, uint flags, uint callbackID, AssetBase asset, sbyte invType, - uint baseMask, uint currentMask, uint everyoneMask, uint nextOwnerMask, uint groupMask, int creationDate) + string name, string description, uint flags, uint callbackID, UUID assetID, sbyte assetType, sbyte invType, + uint baseMask, uint currentMask, uint everyoneMask, uint nextOwnerMask, uint groupMask, int creationDate, + bool assetUpload) { InventoryItemBase item = new InventoryItemBase(); item.Owner = remoteClient.AgentId; item.CreatorId = creatorID; item.CreatorData = creatorData; item.ID = UUID.Random(); - item.AssetID = asset.FullID; + item.AssetID = assetID; item.Name = name; item.Description = description; item.Flags = flags; - item.AssetType = asset.Type; + item.AssetType = assetType; item.InvType = invType; item.Folder = folderID; item.CurrentPermissions = currentMask; @@ -938,7 +1015,7 @@ namespace OpenSim.Region.Framework.Scenes item.BasePermissions = baseMask; item.CreationDate = creationDate; - if (AddInventoryItem(item)) + if (AddInventoryItem(item, assetUpload)) { remoteClient.SendInventoryItemCreateUpdate(item, callbackID); } @@ -1001,17 +1078,12 @@ namespace OpenSim.Region.Framework.Scenes // return; // } - AssetBase asset = new AssetBase(); - asset.FullID = olditemID; - asset.Type = type; - asset.Name = name; - asset.Description = description; - CreateNewInventoryItem( remoteClient, remoteClient.AgentId.ToString(), string.Empty, folderID, - name, description, 0, callbackID, asset, invType, - (uint)PermissionMask.All, (uint)PermissionMask.All, (uint)PermissionMask.All, - (uint)PermissionMask.All, (uint)PermissionMask.All, Util.UnixTimeSinceEpoch()); + name, description, 0, callbackID, olditemID, type, invType, + (uint)PermissionMask.All | (uint)PermissionMask.Export, (uint)PermissionMask.All | (uint)PermissionMask.Export, (uint)PermissionMask.All, + (uint)PermissionMask.All | (uint)PermissionMask.Export, (uint)PermissionMask.All | (uint)PermissionMask.Export, Util.UnixTimeSinceEpoch(), + false); } else { @@ -1087,13 +1159,21 @@ namespace OpenSim.Region.Framework.Scenes if (item == null) return; - InventoryFolderBase destFolder = InventoryService.GetFolderForType(remoteClient.AgentId, AssetType.TrashFolder); + InventoryFolderBase destFolder = InventoryService.GetFolderForType(remoteClient.AgentId, FolderType.Trash); - // Move the item to trash. If this is a copiable item, only + // Move the item to trash. If this is a copyable item, only // a copy will be moved and we will still need to delete - // the item from the prim. If it was no copy, is will be + // the item from the prim. If it was no copy, it will be // deleted by this method. - MoveTaskInventoryItem(remoteClient, destFolder.ID, part, itemID); + string message; + InventoryItemBase item2 = MoveTaskInventoryItem(remoteClient, destFolder.ID, part, itemID, out message); + + if (item2 == null) + { + m_log.WarnFormat("[SCENE INVENTORY]: RemoveTaskInventory of item {0} failed: {1}", itemID, message); + remoteClient.SendAgentAlertMessage(message, false); + return; + } if (group.GetInventoryItem(localID, itemID) != null) { @@ -1105,11 +1185,16 @@ namespace OpenSim.Region.Framework.Scenes group.RemoveInventoryItem(localID, itemID); } + part.SendPropertiesToClient(remoteClient); } } - private InventoryItemBase CreateAgentInventoryItemFromTask(UUID destAgent, SceneObjectPart part, UUID itemId) + + /// + /// Creates (in memory only) a user inventory item that will contain a copy of a task inventory item. + /// + private InventoryItemBase CreateAgentInventoryItemFromTask(UUID destAgent, SceneObjectPart part, UUID itemId, out string message) { TaskInventoryItem taskItem = part.Inventory.GetInventoryItem(itemId); @@ -1120,12 +1205,13 @@ namespace OpenSim.Region.Framework.Scenes + " inventory item from a prim's inventory item " + " but the required item does not exist in the prim's inventory", itemId, part.Name, part.UUID); - + message = "Item not found: " + itemId; return null; } if ((destAgent != taskItem.OwnerID) && ((taskItem.CurrentPermissions & (uint)PermissionMask.Transfer) == 0)) { + message = "Item doesn't have the Transfer permission."; return null; } @@ -1146,9 +1232,21 @@ namespace OpenSim.Region.Framework.Scenes { agentItem.BasePermissions = taskItem.BasePermissions & (taskItem.NextPermissions | (uint)PermissionMask.Move); if (taskItem.InvType == (int)InventoryType.Object) - agentItem.CurrentPermissions = agentItem.BasePermissions & (((taskItem.CurrentPermissions & 7) << 13) | (taskItem.CurrentPermissions & (uint)PermissionMask.Move)); - else - agentItem.CurrentPermissions = agentItem.BasePermissions & taskItem.CurrentPermissions; + { + // Bake the new base permissions from folded permissions + // The folded perms are in the lowest 3 bits of the current perms + // We use base permissions here to avoid baking the "Locked" status + // into the item as it is passed. + uint perms = taskItem.BasePermissions & taskItem.NextPermissions; + PermissionsUtil.ApplyFoldedPermissions(taskItem.CurrentPermissions, ref perms); + // Avoid the "lock trap" - move must always be enabled but the above may remove it + // Add it back here. + agentItem.BasePermissions = perms | (uint)PermissionMask.Move; + // Newly given items cannot be "locked" on rez. Make sure by + // setting current equal to base. + } + + agentItem.CurrentPermissions = agentItem.BasePermissions; agentItem.Flags |= (uint)InventoryItemFlags.ObjectSlamPerm; agentItem.NextPermissions = taskItem.NextPermissions; @@ -1164,11 +1262,24 @@ namespace OpenSim.Region.Framework.Scenes agentItem.GroupPermissions = taskItem.GroupPermissions; } + message = null; + return agentItem; + } + + /// + /// If the task item is not-copyable then remove it from the prim. + /// + private void RemoveNonCopyTaskItemFromPrim(SceneObjectPart part, UUID itemId) + { + TaskInventoryItem taskItem = part.Inventory.GetInventoryItem(itemId); + if (taskItem == null) + return; + if (!Permissions.BypassPermissions()) { if ((taskItem.CurrentPermissions & (uint)PermissionMask.Copy) == 0) { - if (taskItem.Type == 10) + if (taskItem.Type == (int)AssetType.LSLText) { part.RemoveScriptEvents(itemId); EventManager.TriggerRemoveScript(part.LocalId, itemId); @@ -1177,8 +1288,6 @@ namespace OpenSim.Region.Framework.Scenes part.Inventory.RemoveInventoryItem(itemId); } } - - return agentItem; } /// @@ -1188,19 +1297,22 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - public InventoryItemBase MoveTaskInventoryItem(IClientAPI remoteClient, UUID folderId, SceneObjectPart part, UUID itemId) + public InventoryItemBase MoveTaskInventoryItem(IClientAPI remoteClient, UUID folderId, SceneObjectPart part, UUID itemId, out string message) { m_log.DebugFormat( "[PRIM INVENTORY]: Adding item {0} from {1} to folder {2} for {3}", itemId, part.Name, folderId, remoteClient.Name); - InventoryItemBase agentItem = CreateAgentInventoryItemFromTask(remoteClient.AgentId, part, itemId); - + InventoryItemBase agentItem = CreateAgentInventoryItemFromTask(remoteClient.AgentId, part, itemId, out message); if (agentItem == null) return null; agentItem.Folder = folderId; AddInventoryItem(remoteClient, agentItem); + + RemoveNonCopyTaskItemFromPrim(part, itemId); + + message = null; return agentItem; } @@ -1251,7 +1363,11 @@ namespace OpenSim.Region.Framework.Scenes return; } - MoveTaskInventoryItem(remoteClient, folderId, part, itemId); + string message; + InventoryItemBase item = MoveTaskInventoryItem(remoteClient, folderId, part, itemId, out message); + + if (item == null) + remoteClient.SendAgentAlertMessage(message, false); } /// @@ -1265,17 +1381,17 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - public InventoryItemBase MoveTaskInventoryItem(UUID avatarId, UUID folderId, SceneObjectPart part, UUID itemId) + public InventoryItemBase MoveTaskInventoryItem(UUID avatarId, UUID folderId, SceneObjectPart part, UUID itemId, out string message) { ScenePresence avatar; if (TryGetScenePresence(avatarId, out avatar)) { - return MoveTaskInventoryItem(avatar.ControllingClient, folderId, part, itemId); + return MoveTaskInventoryItem(avatar.ControllingClient, folderId, part, itemId, out message); } else { - InventoryItemBase agentItem = CreateAgentInventoryItemFromTask(avatarId, part, itemId); + InventoryItemBase agentItem = CreateAgentInventoryItemFromTask(avatarId, part, itemId, out message); if (agentItem == null) return null; @@ -1284,6 +1400,8 @@ namespace OpenSim.Region.Framework.Scenes AddInventoryItem(agentItem); + RemoveNonCopyTaskItemFromPrim(part, itemId); + return agentItem; } } @@ -1389,6 +1507,11 @@ namespace OpenSim.Region.Framework.Scenes public UUID MoveTaskInventoryItems(UUID destID, string category, SceneObjectPart host, List items) { + ScenePresence avatar; + IClientAPI remoteClient = null; + if (TryGetScenePresence(destID, out avatar)) + remoteClient = avatar.ControllingClient; + InventoryFolderBase rootFolder = InventoryService.GetRootFolder(destID); UUID newFolderID = UUID.Random(); @@ -1398,26 +1521,28 @@ namespace OpenSim.Region.Framework.Scenes foreach (UUID itemID in items) { - InventoryItemBase agentItem = CreateAgentInventoryItemFromTask(destID, host, itemID); + string message; + InventoryItemBase agentItem = CreateAgentInventoryItemFromTask(destID, host, itemID, out message); if (agentItem != null) { agentItem.Folder = newFolderID; AddInventoryItem(agentItem); + + RemoveNonCopyTaskItemFromPrim(host, itemID); + } + else + { + if (remoteClient != null) + remoteClient.SendAgentAlertMessage(message, false); } } - ScenePresence avatar = null; - if (TryGetScenePresence(destID, out avatar)) + if (remoteClient != null) { - //profile.SendInventoryDecendents(avatar.ControllingClient, - // profile.RootFolder.ID, true, false); - //profile.SendInventoryDecendents(avatar.ControllingClient, - // newFolderID, false, true); - - SendInventoryUpdate(avatar.ControllingClient, rootFolder, true, false); - SendInventoryUpdate(avatar.ControllingClient, newFolder, false, true); + SendInventoryUpdate(remoteClient, rootFolder, true, false); + SendInventoryUpdate(remoteClient, newFolder, false, true); } return newFolderID; @@ -1572,11 +1697,11 @@ namespace OpenSim.Region.Framework.Scenes remoteClient, part, transactionID, currentItem); if ((InventoryType)itemInfo.InvType == InventoryType.Notecard) - remoteClient.SendAgentAlertMessage("Notecard saved", false); + remoteClient.SendAlertMessage("Notecard saved"); else if ((InventoryType)itemInfo.InvType == InventoryType.LSL) - remoteClient.SendAgentAlertMessage("Script saved", false); + remoteClient.SendAlertMessage("Script saved"); else - remoteClient.SendAgentAlertMessage("Item saved", false); + remoteClient.SendAlertMessage("Item saved"); } // Base ALWAYS has move @@ -1739,6 +1864,21 @@ namespace OpenSim.Region.Framework.Scenes /// The part where the script was rezzed if successful. False otherwise. public SceneObjectPart RezNewScript(UUID agentID, InventoryItemBase itemBase) { + return RezNewScript( + agentID, + itemBase, + "default\n{\n state_entry()\n {\n llSay(0, \"Script running\");\n }\n}"); + } + + /// + /// Rez a new script from nothing with given script text. + /// + /// + /// Template item. + /// + /// The part where the script was rezzed if successful. False otherwise. + public SceneObjectPart RezNewScript(UUID agentID, InventoryItemBase itemBase, string scriptText) + { // The part ID is the folder ID! SceneObjectPart part = GetSceneObjectPart(itemBase.Folder); if (part == null) @@ -1758,9 +1898,14 @@ namespace OpenSim.Region.Framework.Scenes return null; } - AssetBase asset = CreateAsset(itemBase.Name, itemBase.Description, (sbyte)itemBase.AssetType, - Encoding.ASCII.GetBytes("default\n{\n state_entry()\n {\n llSay(0, \"Script running\");\n }\n}"), - agentID); + AssetBase asset + = CreateAsset( + itemBase.Name, + itemBase.Description, + (sbyte)itemBase.AssetType, + Encoding.ASCII.GetBytes(scriptText), + agentID); + AssetService.Store(asset); TaskInventoryItem taskItem = new TaskInventoryItem(); @@ -1801,8 +1946,11 @@ namespace OpenSim.Region.Framework.Scenes /// Rez a script into a prim's inventory from another prim /// /// - /// - /// + /// + /// + /// + /// + /// public void RezScriptFromPrim(UUID srcId, SceneObjectPart srcPart, UUID destId, int pin, int running, int start_param) { TaskInventoryItem srcTaskItem = srcPart.Inventory.GetInventoryItem(srcId); @@ -1822,12 +1970,11 @@ namespace OpenSim.Region.Framework.Scenes if (destPart == null) { m_log.ErrorFormat( - "[PRIM INVENTORY]: " + - "Could not find script for ID {0}", - destId); + "[PRIM INVENTORY]: Could not find part {0} to insert script item {1} from {2} {3} in {4}", + destId, srcId, srcPart.Name, srcPart.UUID, Name); return; } - + // Must own the object, and have modify rights if (srcPart.OwnerID != destPart.OwnerID) { @@ -1835,12 +1982,14 @@ namespace OpenSim.Region.Framework.Scenes if ((destPart.GroupID == UUID.Zero) || (destPart.GroupID != srcPart.GroupID) || ((destPart.GroupMask & (uint)PermissionMask.Modify) == 0)) return; - } else { + } + else + { if ((destPart.OwnerMask & (uint)PermissionMask.Modify) == 0) return; } - if (destPart.ScriptAccessPin != pin) + if (destPart.ScriptAccessPin == 0 || destPart.ScriptAccessPin != pin) { m_log.WarnFormat( "[PRIM INVENTORY]: " + @@ -1954,7 +2103,7 @@ namespace OpenSim.Region.Framework.Scenes deleteGroups.Add(grp); // If child prims have invalid perms, fix them - grp.AdjustChildPrimPermissions(); + grp.AdjustChildPrimPermissions(false); if (remoteClient == null) { @@ -2004,7 +2153,10 @@ namespace OpenSim.Region.Framework.Scenes { // If we don't have permission, stop right here if (!permissionToTakeCopy) + { + remoteClient.SendAlertMessage("You don't have permission to take the object"); return; + } permissionToTake = true; // Don't delete @@ -2036,13 +2188,16 @@ namespace OpenSim.Region.Framework.Scenes } } + // OK, we're done with permissions. Let's check if any part of the code prevents the objects from being deleted + bool canDelete = EventManager.TriggerDeRezRequested(remoteClient, deleteGroups, action); + if (permissionToTake && (action != DeRezAction.Delete || this.m_useTrashOnDelete)) { m_asyncSceneObjectDeleter.DeleteToInventory( action, destinationID, deleteGroups, remoteClient, - permissionToDelete); + permissionToDelete && canDelete); } - else if (permissionToDelete) + else if (permissionToDelete && canDelete) { foreach (SceneObjectGroup g in deleteGroups) DeleteSceneObject(g, false); @@ -2050,6 +2205,112 @@ namespace OpenSim.Region.Framework.Scenes } /// + /// Returns the list of Scene Objects in an asset. + /// + /// + /// Returns one object if the asset is a regular object, and multiple objects for a coalesced object. + /// + /// Asset data + /// True if the object is an attachment. + /// The objects included in the asset + /// Relative positions of the objects + /// Bounding box of all the objects + /// Offset in the Z axis from the centre of the bounding box + /// to the centre of the root prim (relevant only when returning a single object) + /// + /// true if returning a single object or deserialization fails, false if returning the coalesced + /// list of objects + /// + public bool GetObjectsToRez( + byte[] assetData, bool isAttachment, out List objlist, out List veclist, + out Vector3 bbox, out float offsetHeight) + { + objlist = new List(); + veclist = new List(); + bbox = Vector3.Zero; + offsetHeight = 0; + + string xmlData = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(assetData)); + + try + { + using (XmlTextReader wrappedReader = new XmlTextReader(xmlData, XmlNodeType.Element, null)) + { + using (XmlReader reader = XmlReader.Create(wrappedReader, new XmlReaderSettings() { IgnoreWhitespace = true, ConformanceLevel = ConformanceLevel.Fragment })) + { + reader.Read(); + bool isSingleObject = reader.Name != "CoalescedObject"; + + if (isSingleObject || isAttachment) + { + SceneObjectGroup g; + try + { + g = SceneObjectSerializer.FromOriginalXmlFormat(reader); + } + catch (Exception e) + { + m_log.Error("[AGENT INVENTORY]: Deserialization of xml failed ", e); + Util.LogFailedXML("[AGENT INVENTORY]:", xmlData); + g = null; + } + + if (g != null) + { + objlist.Add(g); + veclist.Add(Vector3.Zero); + bbox = g.GetAxisAlignedBoundingBox(out offsetHeight); + } + + return true; + } + else + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xmlData); + XmlElement e = (XmlElement)doc.SelectSingleNode("/CoalescedObject"); + XmlElement coll = (XmlElement)e; + float bx = Convert.ToSingle(coll.GetAttribute("x")); + float by = Convert.ToSingle(coll.GetAttribute("y")); + float bz = Convert.ToSingle(coll.GetAttribute("z")); + bbox = new Vector3(bx, by, bz); + offsetHeight = 0; + + XmlNodeList groups = e.SelectNodes("SceneObjectGroup"); + foreach (XmlNode n in groups) + { + SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(n.OuterXml); + if (g != null) + { + objlist.Add(g); + + XmlElement el = (XmlElement)n; + string rawX = el.GetAttribute("offsetx"); + string rawY = el.GetAttribute("offsety"); + string rawZ = el.GetAttribute("offsetz"); + + float x = Convert.ToSingle(rawX); + float y = Convert.ToSingle(rawY); + float z = Convert.ToSingle(rawZ); + veclist.Add(new Vector3(x, y, z)); + } + } + + return false; + } + } + } + } + catch (Exception e) + { + m_log.Error("[AGENT INVENTORY]: Deserialization of xml failed when looking for CoalescedObject tag ", e); + Util.LogFailedXML("[AGENT INVENTORY]:", xmlData); + } + + return true; + } + + /// /// Event Handler Rez an object into a scene /// Calls the non-void event handler /// @@ -2124,19 +2385,25 @@ namespace OpenSim.Region.Framework.Scenes /// will be used if it exists. /// The velocity of the rezzed object. /// - /// The SceneObjectGroup rezzed or null if rez was unsuccessful - public virtual SceneObjectGroup RezObject( + /// The SceneObjectGroup(s) rezzed, or null if rez was unsuccessful + public virtual List RezObject( SceneObjectPart sourcePart, TaskInventoryItem item, Vector3 pos, Quaternion? rot, Vector3 vel, int param) { if (null == item) return null; + + List objlist; + List veclist; - SceneObjectGroup group = sourcePart.Inventory.GetRezReadySceneObject(item); - - if (null == group) + bool success = sourcePart.Inventory.GetRezReadySceneObjects(item, out objlist, out veclist); + if (!success) return null; - - if (!Permissions.CanRezObject(group.PrimCount, item.OwnerID, pos)) + + int totalPrims = 0; + foreach (SceneObjectGroup group in objlist) + totalPrims += group.PrimCount; + + if (!Permissions.CanRezObject(totalPrims, item.OwnerID, pos)) return null; if (!Permissions.BypassPermissions()) @@ -2145,16 +2412,28 @@ namespace OpenSim.Region.Framework.Scenes sourcePart.Inventory.RemoveInventoryItem(item.ItemID); } - group.FromPartID = sourcePart.UUID; - AddNewSceneObject(group, true, pos, rot, vel); - - // We can only call this after adding the scene object, since the scene object references the scene - // to find out if scripts should be activated at all. - group.CreateScriptInstances(param, true, DefaultScriptEngine, 3); - - group.ScheduleGroupForFullUpdate(); - - return group; + for (int i = 0; i < objlist.Count; i++) + { + SceneObjectGroup group = objlist[i]; + Vector3 curpos = pos + veclist[i]; + + if (group.IsAttachment == false && group.RootPart.Shape.State != 0) + { + group.RootPart.AttachedPos = group.AbsolutePosition; + group.RootPart.Shape.LastAttachPoint = (byte)group.AttachmentPoint; + } + + group.FromPartID = sourcePart.UUID; + AddNewSceneObject(group, true, curpos, rot, vel); + + // We can only call this after adding the scene object, since the scene object references the scene + // to find out if scripts should be activated at all. + group.CreateScriptInstances(param, true, DefaultScriptEngine, 3); + + group.ScheduleGroupForFullUpdate(); + } + + return objlist; } public virtual bool returnObjects(SceneObjectGroup[] returnobjects, diff --git a/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs b/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs index df43271..8ebcd92 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs @@ -31,7 +31,6 @@ using System.Threading; using OpenMetaverse; using OpenMetaverse.Packets; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Services.Interfaces; namespace OpenSim.Region.Framework.Scenes @@ -145,6 +144,21 @@ namespace OpenSim.Region.Framework.Scenes } /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void SimChatToAgent(UUID targetID, byte[] message, int channel, Vector3 fromPos, string fromName, UUID fromID, bool fromAgent) + { + SimChat(message, ChatTypeEnum.Region, channel, fromPos, fromName, fromID, targetID, fromAgent, false); + } + + /// /// Invoked when the client requests a prim. /// /// @@ -244,25 +258,20 @@ namespace OpenSim.Region.Framework.Scenes if (part.ParentGroup.RootPart.LocalId != part.LocalId) return; - bool isAttachment = false; - // This is wrong, wrong, wrong. Selection should not be // handled by group, but by prim. Legacy cruft. // TODO: Make selection flagging per prim! // part.ParentGroup.IsSelected = false; - if (part.ParentGroup.IsAttachment) - isAttachment = true; - else - part.ParentGroup.ScheduleGroupForFullUpdate(); + part.ParentGroup.ScheduleGroupForFullUpdate(); // If it's not an attachment, and we are allowed to move it, // then we might have done so. If we moved across a parcel // boundary, we will need to recount prims on the parcels. // For attachments, that makes no sense. // - if (!isAttachment) + if (!part.ParentGroup.IsAttachment) { if (Permissions.CanEditObject( part.UUID, remoteClient.AgentId) @@ -390,6 +399,7 @@ namespace OpenSim.Region.Framework.Scenes void ProcessViewerEffect(IClientAPI remoteClient, List args) { // TODO: don't create new blocks if recycling an old packet + bool discardableEffects = true; ViewerEffectPacket.EffectBlock[] effectBlockArray = new ViewerEffectPacket.EffectBlock[args.Count]; for (int i = 0; i < args.Count; i++) { @@ -401,17 +411,34 @@ namespace OpenSim.Region.Framework.Scenes effect.Type = args[i].Type; effect.TypeData = args[i].TypeData; effectBlockArray[i] = effect; + + if ((EffectType)effect.Type != EffectType.LookAt && (EffectType)effect.Type != EffectType.Beam) + discardableEffects = false; + + //m_log.DebugFormat("[YYY]: VE {0} {1} {2}", effect.AgentID, effect.Duration, (EffectType)effect.Type); } - ForEachClient( - delegate(IClientAPI client) + ForEachScenePresence(sp => { - if (client.AgentId != remoteClient.AgentId) - client.SendViewerEffect(effectBlockArray); - } - ); + if (sp.ControllingClient.AgentId != remoteClient.AgentId) + { + if (!discardableEffects || + (discardableEffects && ShouldSendDiscardableEffect(remoteClient, sp))) + { + //m_log.DebugFormat("[YYY]: Sending to {0}", sp.UUID); + sp.ControllingClient.SendViewerEffect(effectBlockArray); + } + //else + // m_log.DebugFormat("[YYY]: Not sending to {0}", sp.UUID); + } + }); } - + + private bool ShouldSendDiscardableEffect(IClientAPI thisClient, ScenePresence other) + { + return Vector3.Distance(other.CameraPosition, thisClient.SceneAgent.AbsolutePosition) < 10; + } + /// /// Tell the client about the various child items and folders contained in the requested folder. /// @@ -459,7 +486,16 @@ namespace OpenSim.Region.Framework.Scenes void SendInventoryAsync(IClientAPI remoteClient, UUID folderID, UUID ownerID, bool fetchFolders, bool fetchItems, int sortOrder) { - SendInventoryUpdate(remoteClient, new InventoryFolderBase(folderID), fetchFolders, fetchItems); + try + { + SendInventoryUpdate(remoteClient, new InventoryFolderBase(folderID), fetchFolders, fetchItems); + } + catch (Exception e) + { + m_log.Error( + string.Format( + "[AGENT INVENTORY]: Error in SendInventoryAsync() for {0} with folder ID {1}. Exception ", e)); + } } void SendInventoryComplete(IAsyncResult iar) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs old mode 100644 new mode 100755 index f87d469..2fe6e22 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -6,7 +6,7 @@ * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyrightD + * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the @@ -39,18 +39,19 @@ using Nini.Config; using OpenMetaverse; using OpenMetaverse.Packets; using OpenMetaverse.Imaging; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Framework.Monitoring; using OpenSim.Services.Interfaces; -using OpenSim.Framework.Communications; using OpenSim.Framework.Console; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes.Scripting; using OpenSim.Region.Framework.Scenes.Serialization; -using OpenSim.Region.Physics.Manager; -using Timer=System.Timers.Timer; +using OpenSim.Region.PhysicsModules.SharedBase; +using Timer = System.Timers.Timer; using TPFlags = OpenSim.Framework.Constants.TeleportFlags; using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.Framework.Scenes { @@ -60,6 +61,8 @@ namespace OpenSim.Region.Framework.Scenes { private const long DEFAULT_MIN_TIME_FOR_PERSISTENCE = 60L; private const long DEFAULT_MAX_TIME_FOR_PERSISTENCE = 600L; + + public const int m_defaultNumberFramesStored = 10; public delegate void SynchronizeSceneHandler(Scene scene); @@ -102,15 +105,37 @@ namespace OpenSim.Region.Framework.Scenes /// /// If false then physical objects are disabled, though collisions will continue as normal. /// - public bool PhysicsEnabled { get; set; } + public bool PhysicsEnabled + { + get + { + return m_physicsEnabled; + } + + set + { + m_physicsEnabled = value; + + if (PhysicsScene != null) + { + IPhysicsParameters physScene = PhysicsScene as IPhysicsParameters; + + if (physScene != null) + physScene.SetPhysicsParameter( + "Active", m_physicsEnabled.ToString(), PhysParameterEntry.APPLY_TO_NONE); + } + } + } + + private bool m_physicsEnabled; /// /// If false then scripts are not enabled on the smiulator /// - public bool ScriptsEnabled - { + public bool ScriptsEnabled + { get { return m_scripts_enabled; } - set + set { if (m_scripts_enabled != value) { @@ -128,7 +153,7 @@ namespace OpenSim.Region.Framework.Scenes else { m_log.Info("Starting all Scripts in Scene"); - + EntityBase[] entities = Entities.GetEntities(); foreach (EntityBase ent in entities) { @@ -150,7 +175,7 @@ namespace OpenSim.Region.Framework.Scenes public SynchronizeSceneHandler SynchronizeScene; /// - /// Used to prevent simultaneous calls to RemoveClient() for the same agent from interfering with each other. + /// Used to prevent simultaneous calls to code that adds and removes agents. /// private object m_removeClientLock = new object(); @@ -159,11 +184,6 @@ namespace OpenSim.Region.Framework.Scenes /// public SimStatsReporter StatsReporter { get; private set; } - public List NorthBorders = new List(); - public List EastBorders = new List(); - public List SouthBorders = new List(); - public List WestBorders = new List(); - /// /// Controls whether physics can be applied to prims. Even if false, prims still have entries in a /// PhysicsScene in order to perform collision detection @@ -196,7 +216,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// Maximum value of the size of a physical prim in each axis /// - public float m_maxPhys = 10; + public float m_maxPhys = 64; /// /// Max prims an object will hold @@ -205,7 +225,13 @@ namespace OpenSim.Region.Framework.Scenes public bool m_clampPrimSize; public bool m_trustBinaries; - public bool m_allowScriptCrossings; + public bool m_allowScriptCrossings = true; + + /// + /// Can avatars cross from and to this region? + /// + public bool AllowAvatarCrossing { get; set; } + public bool m_useFlySlow; public bool m_useTrashOnDelete = true; @@ -214,21 +240,58 @@ namespace OpenSim.Region.Framework.Scenes /// public bool SendPeriodicAppearanceUpdates { get; set; } + /// + /// How much a root agent has to change position before updates are sent to viewers. + /// + public float RootPositionUpdateTolerance { get; set; } + + /// + /// How much a root agent has to rotate before updates are sent to viewers. + /// + public float RootRotationUpdateTolerance { get; set; } + + /// + /// How much a root agent has to change velocity before updates are sent to viewers. + /// + public float RootVelocityUpdateTolerance { get; set; } + + /// + /// If greater than 1, we only send terse updates to other root agents on every n updates. + /// + public int RootTerseUpdatePeriod { get; set; } + + /// + /// If greater than 1, we only send terse updates to child agents on every n updates. + /// + public int ChildTerseUpdatePeriod { get; set; } + protected float m_defaultDrawDistance = 255.0f; - public float DefaultDrawDistance + public float DefaultDrawDistance { - get { return m_defaultDrawDistance; } + // get { return m_defaultDrawDistance; } + get + { + if (RegionInfo != null) + { + float largestDimension = Math.Max(RegionInfo.RegionSizeX, RegionInfo.RegionSizeY); + m_defaultDrawDistance = Math.Max(m_defaultDrawDistance, largestDimension); + + } + return m_defaultDrawDistance; + } } private List m_AllowedViewers = new List(); private List m_BannedViewers = new List(); - + // TODO: need to figure out how allow client agents but deny // root agents when ACL denies access to root agent public bool m_strictAccessControl = true; public int MaxUndoCount { get; set; } + public bool SeeIntoRegion { get; set; } + // Using this for RegionReady module to prevent LoginsDisabled from changing under our feet; public bool LoginLock = false; @@ -262,6 +325,7 @@ namespace OpenSim.Region.Framework.Scenes protected IUserAccountService m_UserAccountService; protected IAvatarService m_AvatarService; protected IGridUserService m_GridUserService; + protected IAgentPreferencesService m_AgentPreferencesService; protected IXMLRPC m_xmlrpcModule; protected IWorldComm m_worldCommModule; @@ -275,6 +339,17 @@ namespace OpenSim.Region.Framework.Scenes private Dictionary m_extraSettings; /// + /// If true then the next time the scene loop is activated, updates will be performed by firing of a timer + /// rather than on a single thread that sleeps. + /// + public bool UpdateOnTimer { get; set; } + + /// + /// Only used if we are updating scene on a timer rather than sleeping a thread. + /// + private Timer m_sceneUpdateTimer; + + /// /// Current scene frame number /// public uint Frame @@ -289,30 +364,59 @@ namespace OpenSim.Region.Framework.Scenes public uint MaintenanceRun { get; private set; } /// - /// The minimum length of time in seconds that will be taken for a scene frame. If the frame takes less time then we + /// The minimum length of time in milliseconds that will be taken for a scene frame. If the frame takes less time then we /// will sleep for the remaining period. /// /// /// One can tweak this number to experiment. One current effect of reducing it is to make avatar animations /// occur too quickly (viewer 1) or with even more slide (viewer 2). /// - public float MinFrameTime { get; private set; } + public int MinFrameTicks + { + get { return m_minFrameTicks; } + private set + { + m_minFrameTicks = value; + MinFrameSeconds = (float)m_minFrameTicks / 1000; + } + } + private int m_minFrameTicks; + + public int FrameTimeWarnPercent { get; private set; } + public int FrameTimeCritPercent { get; private set; } + + // Normalize the frame related stats to nominal 55fps for viewer and scripts option + // see SimStatsReporter.cs + public bool Normalized55FPS { get; private set; } + + /// + /// The minimum length of time in seconds that will be taken for a scene frame. + /// + /// + /// Always derived from MinFrameTicks. + /// + public float MinFrameSeconds { get; private set; } /// - /// The minimum length of time in seconds that will be taken for a maintenance run. + /// The minimum length of time in milliseconds that will be taken for a scene frame. If the frame takes less time then we + /// will sleep for the remaining period. /// - public float MinMaintenanceTime { get; private set; } + /// + /// One can tweak this number to experiment. One current effect of reducing it is to make avatar animations + /// occur too quickly (viewer 1) or with even more slide (viewer 2). + /// + public int MinMaintenanceTicks { get; set; } private int m_update_physics = 1; private int m_update_entitymovement = 1; private int m_update_objects = 1; - private int m_update_temp_cleaning = 1000; private int m_update_presences = 1; // Update scene presence movements private int m_update_events = 1; private int m_update_backup = 200; private int m_update_terrain = 50; -// private int m_update_land = 1; + // private int m_update_land = 1; private int m_update_coarse_locations = 50; + private int m_update_temp_cleaning = 180; private int agentMS; private int frameMS; @@ -326,6 +430,17 @@ namespace OpenSim.Region.Framework.Scenes private int landMS; private int spareMS; + // A temporary configuration flag to enable using FireAndForget to process + // collisions from the physics engine. There is a problem with collisions + // stopping sometimes and MB's suspicion is some race condition passing + // collisions from the physics engine callback to the script engine. + // This causes the collision events to be passed with a FireAndForget + // call which should eliminate that linkage. Testers can turn this on + // and see if collisions stop. If they don't, the problem is somewhere else. + // This feature defaults to 'off' so, by default, the simulator operation + // is not changed. + public bool ShouldUseFireAndForgetForCollisions = false; + /// /// Tick at which the last frame was processed. /// @@ -337,16 +452,28 @@ namespace OpenSim.Region.Framework.Scenes private int m_lastMaintenanceTick; /// + /// Total script execution time (in Stopwatch Ticks) since the last frame + /// + private long m_scriptExecutionTime = 0; + + /// /// Signals whether temporary objects are currently being cleaned up. Needed because this is launched /// asynchronously from the update loop. /// private bool m_cleaningTemps = false; -// private Object m_heartbeatLock = new Object(); + /// + /// Used to control main scene thread looping time when not updating via timer. + /// + private ManualResetEvent m_updateWaitEvent = new ManualResetEvent(false); + + /// + /// Used to control maintenance thread runs. + /// + private ManualResetEvent m_maintenanceWaitEvent = new ManualResetEvent(false); // TODO: Possibly stop other classes being able to manipulate this directly. private SceneGraph m_sceneGraph; - private volatile int m_bordersLocked; private readonly Timer m_restartTimer = new Timer(15000); // Wait before firing private volatile bool m_backingup; private Dictionary m_returns = new Dictionary(); @@ -354,6 +481,12 @@ namespace OpenSim.Region.Framework.Scenes private string m_defaultScriptEngine; + private int m_unixStartTime; + public int UnixStartTime + { + get { return m_unixStartTime; } + } + /// /// Tick at which the last login occurred. /// @@ -377,7 +510,8 @@ namespace OpenSim.Region.Framework.Scenes /// Is the scene active? /// /// - /// If false, maintenance and update loops are not being run. Updates can still be triggered manually if + /// If false, maintenance and update loops are not being run, though after setting to false update may still + /// be active for a period (and IsRunning will still be true). Updates can still be triggered manually if /// the scene is not active. /// public bool Active @@ -388,24 +522,23 @@ namespace OpenSim.Region.Framework.Scenes if (value) { if (!m_active) - Start(); + Start(false); } else { + // This appears assymetric with Start() above but is not - setting m_active = false stops the loops + // XXX: Possibly this should be in an explicit Stop() method for symmetry. m_active = false; } } } private volatile bool m_active; -// private int m_lastUpdate; -// private bool m_firstHeartbeat = true; - - private UpdatePrioritizationSchemes m_priorityScheme = UpdatePrioritizationSchemes.Time; - private bool m_reprioritizationEnabled = true; - private double m_reprioritizationInterval = 5000.0; - private double m_rootReprioritizationDistance = 10.0; - private double m_childReprioritizationDistance = 20.0; + /// + /// If true then updates are running. This may be true for a short period after a scene is de-activated. + /// + public bool IsRunning { get { return m_isRunning; } } + private volatile bool m_isRunning; private Timer m_mapGenerationTimer = new Timer(); private bool m_generateMaptiles; @@ -421,23 +554,18 @@ namespace OpenSim.Region.Framework.Scenes set { m_splitRegionID = value; } } - public bool BordersLocked - { - get { return m_bordersLocked == 1; } - set - { - if (value == true) - m_bordersLocked = 1; - else - m_bordersLocked = 0; - } - } - public new float TimeDilation { get { return m_sceneGraph.PhysicsScene.TimeDilation; } } + public void setThreadCount(int inUseThreads) + { + // Just pass the thread count information on its way as the Scene + // does not require the value for anything at this time + StatsReporter.SetThreadCount(inUseThreads); + } + public SceneCommunicationService SceneGridService { get { return m_sceneGridService; } @@ -496,7 +624,7 @@ namespace OpenSim.Region.Framework.Scenes return m_AssetService; } } - + public IAuthorizationService AuthorizationService { get @@ -624,6 +752,16 @@ namespace OpenSim.Region.Framework.Scenes } } + public IAgentPreferencesService AgentPreferencesService + { + get + { + if (m_AgentPreferencesService == null) + m_AgentPreferencesService = RequestModuleInterface(); + return m_AgentPreferencesService; + } + } + public IAttachmentsModule AttachmentsModule { get; set; } public IEntityTransferModule EntityTransferModule { get; private set; } public IAgentAssetTransactions AgentTransactionsModule { get; private set; } @@ -633,7 +771,7 @@ namespace OpenSim.Region.Framework.Scenes { get { return m_AvatarFactory; } } - + public ICapabilitiesModule CapsModule { get { return m_capsModule; } @@ -650,11 +788,11 @@ namespace OpenSim.Region.Framework.Scenes public int MonitorLandTime { get { return landMS; } } public int MonitorLastFrameTick { get { return m_lastFrameTick; } } - public UpdatePrioritizationSchemes UpdatePrioritizationScheme { get { return m_priorityScheme; } } - public bool IsReprioritizationEnabled { get { return m_reprioritizationEnabled; } } - public double ReprioritizationInterval { get { return m_reprioritizationInterval; } } - public double RootReprioritizationDistance { get { return m_rootReprioritizationDistance; } } - public double ChildReprioritizationDistance { get { return m_childReprioritizationDistance; } } + public UpdatePrioritizationSchemes UpdatePrioritizationScheme { get; set; } + public bool IsReprioritizationEnabled { get; set; } + public double ReprioritizationInterval { get; set; } + public double RootReprioritizationDistance { get; set; } + public double ChildReprioritizationDistance { get; set; } public AgentCircuitManager AuthenticateHandler { @@ -699,43 +837,46 @@ namespace OpenSim.Region.Framework.Scenes get { return m_sceneGraph.Entities; } } - + // used in sequence see: SpawnPoint() private int m_SpawnPoint; // can be closest/random/sequence public string SpawnPointRouting { - get; private set; + get; + private set; } // allow landmarks to pass public bool TelehubAllowLandmarks { - get; private set; + get; + private set; } #endregion Properties #region Constructors - public Scene(RegionInfo regInfo, AgentCircuitManager authen, - SceneCommunicationService sceneGridService, + public Scene(RegionInfo regInfo, AgentCircuitManager authen, ISimulationDataService simDataService, IEstateDataService estateDataService, - bool dumpAssetsToFile, IConfigSource config, string simulatorVersion) : this(regInfo) { m_config = config; - MinFrameTime = 0.089f; - MinMaintenanceTime = 1; + MinFrameTicks = 89; + FrameTimeWarnPercent = 60; + FrameTimeCritPercent = 40; + Normalized55FPS = true; + MinMaintenanceTicks = 1000; + SeeIntoRegion = true; Random random = new Random(); m_lastAllocatedLocalId = (uint)(random.NextDouble() * (double)(uint.MaxValue / 2)) + (uint)(uint.MaxValue / 4); m_authenticateHandler = authen; - m_sceneGridService = sceneGridService; + m_sceneGridService = new SceneCommunicationService(); m_SimulationDataService = simDataService; m_EstateDataService = estateDataService; - m_regionHandle = RegionInfo.RegionHandle; m_asyncSceneObjectDeleter = new AsyncSceneObjectGroupDeleter(this); m_asyncSceneObjectDeleter.Enabled = true; @@ -794,25 +935,8 @@ namespace OpenSim.Region.Framework.Scenes EventManager.OnLandObjectRemoved += new EventManager.LandObjectRemoved(simDataService.RemoveLandObject); - m_sceneGraph = new SceneGraph(this); - - // If the scene graph has an Unrecoverable error, restart this sim. - // Currently the only thing that causes it to happen is two kinds of specific - // Physics based crashes. - // - // Out of memory - // Operating system has killed the plugin - m_sceneGraph.UnRecoverableError - += () => - { - m_log.ErrorFormat("[SCENE]: Restarting region {0} due to unrecoverable physics crash", Name); - RestartNow(); - }; - RegisterDefaultSceneEvents(); - DumpAssetsToFile = dumpAssetsToFile; - // XXX: Don't set the public property since we don't want to activate here. This needs to be handled // better in the future. m_scripts_enabled = !RegionInfo.RegionSettings.DisableScripts; @@ -835,10 +959,12 @@ namespace OpenSim.Region.Framework.Scenes UseBackup = startupConfig.GetBoolean("UseSceneBackup", UseBackup); if (!UseBackup) m_log.InfoFormat("[SCENE]: Backup has been disabled for {0}", RegionInfo.RegionName); - + //Animation states m_useFlySlow = startupConfig.GetBoolean("enableflyslow", false); + SeeIntoRegion = startupConfig.GetBoolean("see_into_region", SeeIntoRegion); + MaxUndoCount = startupConfig.GetInt("MaxPrimUndos", 20); PhysicalPrims = startupConfig.GetBoolean("physical_prim", PhysicalPrims); @@ -887,10 +1013,10 @@ namespace OpenSim.Region.Framework.Scenes m_trustBinaries = startupConfig.GetBoolean("TrustBinaries", m_trustBinaries); m_allowScriptCrossings = startupConfig.GetBoolean("AllowScriptCrossing", m_allowScriptCrossings); m_dontPersistBefore = - startupConfig.GetLong("MinimumTimeBeforePersistenceConsidered", DEFAULT_MIN_TIME_FOR_PERSISTENCE); + startupConfig.GetLong("MinimumTimeBeforePersistenceConsidered", DEFAULT_MIN_TIME_FOR_PERSISTENCE); m_dontPersistBefore *= 10000000; m_persistAfter = - startupConfig.GetLong("MaximumTimeBeforePersistenceConsidered", DEFAULT_MAX_TIME_FOR_PERSISTENCE); + startupConfig.GetLong("MaximumTimeBeforePersistenceConsidered", DEFAULT_MAX_TIME_FOR_PERSISTENCE); m_persistAfter *= 10000000; m_defaultScriptEngine = startupConfig.GetString("DefaultScriptEngine", "XEngine"); @@ -900,10 +1026,15 @@ namespace OpenSim.Region.Framework.Scenes m_strictAccessControl = startupConfig.GetBoolean("StrictAccessControl", m_strictAccessControl); - m_generateMaptiles = startupConfig.GetBoolean("GenerateMaptiles", true); + string[] possibleMapConfigSections = new string[] { "Map", "Startup" }; + + m_generateMaptiles + = Util.GetConfigVarFromSections(config, "GenerateMaptiles", possibleMapConfigSections, true); + if (m_generateMaptiles) { - int maptileRefresh = startupConfig.GetInt("MaptileRefresh", 0); + int maptileRefresh = Util.GetConfigVarFromSections(config, "MaptileRefresh", possibleMapConfigSections, 0); + m_log.InfoFormat("[SCENE]: Region {0}, WORLD MAP refresh time set to {1} seconds", RegionInfo.RegionName, maptileRefresh); if (maptileRefresh != 0) { m_mapGenerationTimer.Interval = maptileRefresh * 1000; @@ -914,16 +1045,29 @@ namespace OpenSim.Region.Framework.Scenes } else { - string tile = startupConfig.GetString("MaptileStaticUUID", UUID.Zero.ToString()); + string tile + = Util.GetConfigVarFromSections( + config, "MaptileStaticUUID", possibleMapConfigSections, UUID.Zero.ToString()); + UUID tileID; - if (UUID.TryParse(tile, out tileID)) + if (tile != UUID.Zero.ToString() && UUID.TryParse(tile, out tileID)) { RegionInfo.RegionSettings.TerrainImageID = tileID; } + else + { + RegionInfo.RegionSettings.TerrainImageID = RegionInfo.MaptileStaticUUID; + m_log.InfoFormat("[SCENE]: Region {0}, maptile set to {1}", RegionInfo.RegionName, RegionInfo.MaptileStaticUUID.ToString()); + } } - string grant = startupConfig.GetString("AllowedClients", String.Empty); + string[] possibleAccessControlConfigSections = new string[] { "Startup", "AccessControl"}; + + string grant + = Util.GetConfigVarFromSections( + config, "AllowedClients", possibleAccessControlConfigSections, string.Empty); + if (grant.Length > 0) { foreach (string viewer in grant.Split('|')) @@ -932,7 +1076,14 @@ namespace OpenSim.Region.Framework.Scenes } } - grant = startupConfig.GetString("BannedClients", String.Empty); + grant + = Util.GetConfigVarFromSections( + config, "DeniedClients", possibleAccessControlConfigSections, String.Empty); + // Deal with the mess of someone having used a different word at some point + if (grant == String.Empty) + grant = Util.GetConfigVarFromSections( + config, "BannedClients", possibleAccessControlConfigSections, String.Empty); + if (grant.Length > 0) { foreach (string viewer in grant.Split('|')) @@ -941,21 +1092,32 @@ namespace OpenSim.Region.Framework.Scenes } } - MinFrameTime = startupConfig.GetFloat( "MinFrameTime", MinFrameTime); - m_update_backup = startupConfig.GetInt( "UpdateStorageEveryNFrames", m_update_backup); - m_update_coarse_locations = startupConfig.GetInt( "UpdateCoarseLocationsEveryNFrames", m_update_coarse_locations); - m_update_entitymovement = startupConfig.GetInt( "UpdateEntityMovementEveryNFrames", m_update_entitymovement); - m_update_events = startupConfig.GetInt( "UpdateEventsEveryNFrames", m_update_events); - m_update_objects = startupConfig.GetInt( "UpdateObjectsEveryNFrames", m_update_objects); - m_update_physics = startupConfig.GetInt( "UpdatePhysicsEveryNFrames", m_update_physics); - m_update_presences = startupConfig.GetInt( "UpdateAgentsEveryNFrames", m_update_presences); - m_update_terrain = startupConfig.GetInt( "UpdateTerrainEveryNFrames", m_update_terrain); - m_update_temp_cleaning = startupConfig.GetInt( "UpdateTempCleaningEveryNFrames", m_update_temp_cleaning); + if (startupConfig.Contains("MinFrameTime")) + MinFrameTicks = (int)(startupConfig.GetFloat("MinFrameTime") * 1000); + FrameTimeWarnPercent = startupConfig.GetInt( "FrameTimeWarnPercent", FrameTimeWarnPercent); + FrameTimeCritPercent = startupConfig.GetInt( "FrameTimeCritPercent", FrameTimeCritPercent); + Normalized55FPS = startupConfig.GetBoolean( "Normalized55FPS", Normalized55FPS); + + m_update_backup = startupConfig.GetInt("UpdateStorageEveryNFrames", m_update_backup); + m_update_coarse_locations = startupConfig.GetInt("UpdateCoarseLocationsEveryNFrames", m_update_coarse_locations); + m_update_entitymovement = startupConfig.GetInt("UpdateEntityMovementEveryNFrames", m_update_entitymovement); + m_update_events = startupConfig.GetInt("UpdateEventsEveryNFrames", m_update_events); + m_update_objects = startupConfig.GetInt("UpdateObjectsEveryNFrames", m_update_objects); + m_update_physics = startupConfig.GetInt("UpdatePhysicsEveryNFrames", m_update_physics); + m_update_presences = startupConfig.GetInt("UpdateAgentsEveryNFrames", m_update_presences); + m_update_terrain = startupConfig.GetInt("UpdateTerrainEveryNFrames", m_update_terrain); + m_update_temp_cleaning = startupConfig.GetInt("UpdateTempCleaningEveryNSeconds", m_update_temp_cleaning); + + if (startupConfig.Contains("ShouldUseFireAndForgetForCollisions")) + { + ShouldUseFireAndForgetForCollisions = startupConfig.GetBoolean("ShouldUseFireAndForgetForCollisions", false); + } } + // FIXME: Ultimately this should be in a module. - SendPeriodicAppearanceUpdates = true; - + SendPeriodicAppearanceUpdates = false; + IConfig appearanceConfig = m_config.Configs["Appearance"]; if (appearanceConfig != null) { @@ -965,6 +1127,12 @@ namespace OpenSim.Region.Framework.Scenes #endregion Region Config + IConfig entityTransferConfig = m_config.Configs["EntityTransfer"]; + if (entityTransferConfig != null) + { + AllowAvatarCrossing = entityTransferConfig.GetBoolean("AllowAvatarCrossing", AllowAvatarCrossing); + } + #region Interest Management IConfig interestConfig = m_config.Configs["InterestManagement"]; @@ -974,59 +1142,109 @@ namespace OpenSim.Region.Framework.Scenes try { - m_priorityScheme = (UpdatePrioritizationSchemes)Enum.Parse(typeof(UpdatePrioritizationSchemes), update_prioritization_scheme, true); + UpdatePrioritizationScheme = (UpdatePrioritizationSchemes)Enum.Parse(typeof(UpdatePrioritizationSchemes), update_prioritization_scheme, true); } catch (Exception) { m_log.Warn("[PRIORITIZER]: UpdatePrioritizationScheme was not recognized, setting to default prioritizer Time"); - m_priorityScheme = UpdatePrioritizationSchemes.Time; + UpdatePrioritizationScheme = UpdatePrioritizationSchemes.Time; } - m_reprioritizationEnabled = interestConfig.GetBoolean("ReprioritizationEnabled", true); - m_reprioritizationInterval = interestConfig.GetDouble("ReprioritizationInterval", 5000.0); - m_rootReprioritizationDistance = interestConfig.GetDouble("RootReprioritizationDistance", 10.0); - m_childReprioritizationDistance = interestConfig.GetDouble("ChildReprioritizationDistance", 20.0); + IsReprioritizationEnabled + = interestConfig.GetBoolean("ReprioritizationEnabled", IsReprioritizationEnabled); + ReprioritizationInterval + = interestConfig.GetDouble("ReprioritizationInterval", ReprioritizationInterval); + RootReprioritizationDistance + = interestConfig.GetDouble("RootReprioritizationDistance", RootReprioritizationDistance); + ChildReprioritizationDistance + = interestConfig.GetDouble("ChildReprioritizationDistance", ChildReprioritizationDistance); + + RootTerseUpdatePeriod = interestConfig.GetInt("RootTerseUpdatePeriod", RootTerseUpdatePeriod); + ChildTerseUpdatePeriod = interestConfig.GetInt("ChildTerseUpdatePeriod", ChildTerseUpdatePeriod); + + RootPositionUpdateTolerance + = interestConfig.GetFloat("RootPositionUpdateTolerance", RootPositionUpdateTolerance); + RootRotationUpdateTolerance + = interestConfig.GetFloat("RootRotationUpdateTolerance", RootRotationUpdateTolerance); + RootVelocityUpdateTolerance + = interestConfig.GetFloat("RootVelocityUpdateTolerance", RootVelocityUpdateTolerance); } - m_log.DebugFormat("[SCENE]: Using the {0} prioritization scheme", m_priorityScheme); + m_log.DebugFormat("[SCENE]: Using the {0} prioritization scheme", UpdatePrioritizationScheme); #endregion Interest Management - StatsReporter = new SimStatsReporter(this); + // The timer used by the Stopwatch class depends on the system hardware and operating system; inform + // if the timer is based on a high-resolution performance counter or based on the system timer; + // the performance counter will provide a more precise time than the system timer + if (Stopwatch.IsHighResolution) + m_log.InfoFormat("[SCENE]: Using high-resolution performance counter for statistics."); + else + m_log.InfoFormat("[SCENE]: Using system timer for statistics."); + + // Acquire the statistics section of the OpenSim.ini file located + // in the bin directory + IConfig statisticsConfig = m_config.Configs["Statistics"]; + + // Confirm that the statistics section existed in the configuration + // file + if (statisticsConfig != null) + { + // Create the StatsReporter using the number of frames to store + // for the frame time statistics, or 10 frames if the config + // file doesn't contain a value + StatsReporter = new SimStatsReporter(this, + statisticsConfig.GetInt("NumberOfFrames", + m_defaultNumberFramesStored)); + } + else + { + // Create a StatsReporter with the current scene and a default + // 10 frames stored for the frame time statistics + StatsReporter = new SimStatsReporter(this); + } + StatsReporter.OnSendStatsResult += SendSimStatsPackets; StatsReporter.OnStatsIncorrect += m_sceneGraph.RecalculateStats; + } - public Scene(RegionInfo regInfo) : base(regInfo) + public Scene(RegionInfo regInfo) + : base(regInfo) { + m_sceneGraph = new SceneGraph(this); + + // If the scene graph has an Unrecoverable error, restart this sim. + // Currently the only thing that causes it to happen is two kinds of specific + // Physics based crashes. + // + // Out of memory + // Operating system has killed the plugin + m_sceneGraph.UnRecoverableError + += () => + { + m_log.ErrorFormat("[SCENE]: Restarting region {0} due to unrecoverable physics crash", Name); + RestartNow(); + }; + PhysicalPrims = true; CollidablePrims = true; PhysicsEnabled = true; + AllowAvatarCrossing = true; + PeriodicBackup = true; UseBackup = true; - BordersLocked = true; - Border northBorder = new Border(); - northBorder.BorderLine = new Vector3(float.MinValue, float.MaxValue, (int)Constants.RegionSize); //<--- - northBorder.CrossDirection = Cardinals.N; - NorthBorders.Add(northBorder); - - Border southBorder = new Border(); - southBorder.BorderLine = new Vector3(float.MinValue, float.MaxValue,0); //---> - southBorder.CrossDirection = Cardinals.S; - SouthBorders.Add(southBorder); + IsReprioritizationEnabled = true; + UpdatePrioritizationScheme = UpdatePrioritizationSchemes.Time; + ReprioritizationInterval = 5000; - Border eastBorder = new Border(); - eastBorder.BorderLine = new Vector3(float.MinValue, float.MaxValue, (int)Constants.RegionSize); //<--- - eastBorder.CrossDirection = Cardinals.E; - EastBorders.Add(eastBorder); - - Border westBorder = new Border(); - westBorder.BorderLine = new Vector3(float.MinValue, float.MaxValue,0); //---> - westBorder.CrossDirection = Cardinals.W; - WestBorders.Add(westBorder); - BordersLocked = false; + RootRotationUpdateTolerance = 0.1f; + RootVelocityUpdateTolerance = 0.001f; + RootPositionUpdateTolerance = 0.05f; + RootReprioritizationDistance = 10.0; + ChildReprioritizationDistance = 20.0; m_eventManager = new EventManager(); @@ -1046,13 +1264,44 @@ namespace OpenSim.Region.Framework.Scenes get { return m_sceneGraph; } } - protected virtual void RegisterDefaultSceneEvents() + /// + /// Called by the module loader when all modules are loaded, after each module's + /// RegionLoaded hook is called. This is the earliest time where RequestModuleInterface + /// may be used. + /// + public void AllModulesLoaded() { IDialogModule dm = RequestModuleInterface(); if (dm != null) m_eventManager.OnPermissionError += dm.SendAlertToUser; + ISimulatorFeaturesModule fm = RequestModuleInterface(); + if (fm != null) + { + OSD openSimExtras; + OSDMap openSimExtrasMap; + + if (!fm.TryGetFeature("OpenSimExtras", out openSimExtras)) + openSimExtras = new OSDMap(); + + float FrameTime = MinFrameTicks / 1000.0f; + float statisticsFPSfactor = 1.0f; + if(Normalized55FPS) + statisticsFPSfactor = 55.0f * FrameTime; + + openSimExtrasMap = (OSDMap)openSimExtras; + openSimExtrasMap["SimulatorFPS"] = OSD.FromReal(1.0f / FrameTime); + openSimExtrasMap["SimulatorFPSFactor"] = OSD.FromReal(statisticsFPSfactor); + openSimExtrasMap["SimulatorFPSWarnPercent"] = OSD.FromInteger(FrameTimeWarnPercent); + openSimExtrasMap["SimulatorFPSCritPercent"] = OSD.FromInteger(FrameTimeCritPercent); + + fm.AddFeature("OpenSimExtras", openSimExtrasMap); + } + } + + protected virtual void RegisterDefaultSceneEvents() + { m_eventManager.OnSignificantClientMovement += HandleOnSignificantClientMovement; } @@ -1074,17 +1323,22 @@ namespace OpenSim.Region.Framework.Scenes /// True after all operations complete, throws exceptions otherwise. public override void OtherRegionUp(GridRegion otherRegion) { - uint xcell = (uint)((int)otherRegion.RegionLocX / (int)Constants.RegionSize); - uint ycell = (uint)((int)otherRegion.RegionLocY / (int)Constants.RegionSize); - //m_log.InfoFormat("[SCENE]: (on region {0}): Region {1} up in coords {2}-{3}", - // RegionInfo.RegionName, otherRegion.RegionName, xcell, ycell); - if (RegionInfo.RegionHandle != otherRegion.RegionHandle) { - // If these are cast to INT because long + negative values + abs returns invalid data - int resultX = Math.Abs((int)xcell - (int)RegionInfo.RegionLocX); - int resultY = Math.Abs((int)ycell - (int)RegionInfo.RegionLocY); - if (resultX <= 1 && resultY <= 1) + //// If these are cast to INT because long + negative values + abs returns invalid data + //int resultX = Math.Abs((int)xcell - (int)RegionInfo.RegionLocX); + //int resultY = Math.Abs((int)ycell - (int)RegionInfo.RegionLocY); + //if (resultX <= 1 && resultY <= 1) + float dist = (float)Math.Max(DefaultDrawDistance, + (float)Math.Max(RegionInfo.RegionSizeX, RegionInfo.RegionSizeY)); + uint newRegionX, newRegionY, thisRegionX, thisRegionY; + Util.RegionHandleToRegionLoc(otherRegion.RegionHandle, out newRegionX, out newRegionY); + Util.RegionHandleToRegionLoc(RegionInfo.RegionHandle, out thisRegionX, out thisRegionY); + + //m_log.InfoFormat("[SCENE]: (on region {0}): Region {1} up in coords {2}-{3}", + // RegionInfo.RegionName, otherRegion.RegionName, newRegionX, newRegionY); + + if (!Util.IsOutsideView(dist, thisRegionX, newRegionX, thisRegionY, newRegionY)) { // Let the grid service module know, so this can be cached m_eventManager.TriggerOnRegionUp(otherRegion); @@ -1147,46 +1401,6 @@ namespace OpenSim.Region.Framework.Scenes return found; } - /// - /// Checks whether this region has a neighbour in the given direction. - /// - /// - /// - /// - /// An integer which represents a compass point. N == 1, going clockwise until we reach NW == 8. - /// Returns a positive integer if there is a region in that direction, a negative integer if not. - /// - public int HaveNeighbor(Cardinals car, ref int[] fix) - { - uint neighbourx = RegionInfo.RegionLocX; - uint neighboury = RegionInfo.RegionLocY; - - int dir = (int)car; - - if (dir > 1 && dir < 5) //Heading East - neighbourx++; - else if (dir > 5) // Heading West - neighbourx--; - - if (dir < 3 || dir == 8) // Heading North - neighboury++; - else if (dir > 3 && dir < 7) // Heading Sout - neighboury--; - - int x = (int)(neighbourx * Constants.RegionSize); - int y = (int)(neighboury * Constants.RegionSize); - GridRegion neighbourRegion = GridService.GetRegionByPosition(RegionInfo.ScopeID, x, y); - - if (neighbourRegion == null) - { - fix[0] = (int)(RegionInfo.RegionLocX - neighbourx); - fix[1] = (int)(RegionInfo.RegionLocY - neighboury); - return dir * (-1); - } - else - return dir; - } - // Alias IncomingHelloNeighbour OtherRegionUp, for now public GridRegion IncomingHelloNeighbour(RegionInfo neighbour) { @@ -1270,14 +1484,14 @@ namespace OpenSim.Region.Framework.Scenes // Kick all ROOT agents with the message, 'The simulator is going down' ForEachScenePresence(delegate(ScenePresence avatar) - { - avatar.RemoveNeighbourRegion(RegionInfo.RegionHandle); + { + avatar.RemoveNeighbourRegion(RegionInfo.RegionHandle); - if (!avatar.IsChildAgent) - avatar.ControllingClient.Kick("The simulator is going down."); + if (!avatar.IsChildAgent) + avatar.ControllingClient.Kick("The simulator is going down."); - avatar.ControllingClient.SendShutdownConnectionNotice(); - }); + avatar.ControllingClient.SendShutdownConnectionNotice(); + }); // Stop updating the scene objects and agents. m_shuttingDown = true; @@ -1288,20 +1502,11 @@ namespace OpenSim.Region.Framework.Scenes Thread.Sleep(500); // Stop all client threads. - ForEachScenePresence(delegate(ScenePresence avatar) { avatar.ControllingClient.Close(); }); + ForEachScenePresence(delegate(ScenePresence avatar) { CloseAgent(avatar.UUID, false); }); m_log.Debug("[SCENE]: Persisting changed objects"); EventManager.TriggerSceneShuttingDown(this); - - EntityBase[] entities = GetEntities(); - foreach (EntityBase entity in entities) - { - if (!entity.IsDeleted && entity is SceneObjectGroup && ((SceneObjectGroup)entity).HasGroupChanged) - { - ((SceneObjectGroup)entity).ProcessBackup(SimulationDataService, false); - } - } - + Backup(false); m_sceneGraph.Close(); if (!GridService.DeregisterRegion(RegionInfo.RegionID)) @@ -1322,28 +1527,38 @@ namespace OpenSim.Region.Framework.Scenes } } + public override void Start() + { + Start(true); + } + /// /// Start the scene /// - public void Start() + /// + /// Start the scripts within the scene. + /// + public void Start(bool startScripts) { + if (IsRunning) + return; + + m_isRunning = true; m_active = true; + m_unixStartTime = Util.UnixTimeSinceEpoch(); // m_log.DebugFormat("[SCENE]: Starting Heartbeat timer for {0}", RegionInfo.RegionName); - - //m_heartbeatTimer.Enabled = true; - //m_heartbeatTimer.Interval = (int)(m_timespan * 1000); - //m_heartbeatTimer.Elapsed += new ElapsedEventHandler(Heartbeat); if (m_heartbeatThread != null) { m_heartbeatThread.Abort(); m_heartbeatThread = null; } -// m_lastUpdate = Util.EnvironmentTickCount(); m_heartbeatThread - = Watchdog.StartThread( - Heartbeat, string.Format("Heartbeat ({0})", RegionInfo.RegionName), ThreadPriority.Normal, false, false); + = WorkManager.StartThread( + Heartbeat, string.Format("Heartbeat-({0})", RegionInfo.RegionName.Replace(" ", "_")), ThreadPriority.Normal, false, false); + + StartScripts(); } /// @@ -1374,15 +1589,6 @@ namespace OpenSim.Region.Framework.Scenes /// private void Heartbeat() { -// if (!Monitor.TryEnter(m_heartbeatLock)) -// { -// Watchdog.RemoveThread(); -// return; -// } - -// try -// { - m_eventManager.TriggerOnRegionStarted(this); // The first frame can take a very long time due to physics actors being added on startup. Therefore, @@ -1390,22 +1596,49 @@ namespace OpenSim.Region.Framework.Scenes // alarms for scenes with many objects. Update(1); - Watchdog.StartThread( - Maintenance, string.Format("Maintenance ({0})", RegionInfo.RegionName), ThreadPriority.Normal, false, true); + WorkManager.StartThread( + Maintenance, string.Format("Maintenance ({0})", RegionInfo.RegionName), ThreadPriority.Normal, false, true); Watchdog.GetCurrentThreadInfo().AlarmIfTimeout = true; - Update(-1); + m_lastFrameTick = Util.EnvironmentTickCount(); -// m_lastUpdate = Util.EnvironmentTickCount(); -// m_firstHeartbeat = false; -// } -// finally -// { -// Monitor.Pulse(m_heartbeatLock); -// Monitor.Exit(m_heartbeatLock); -// } + if (UpdateOnTimer) + { + m_sceneUpdateTimer = new Timer(MinFrameTicks); + m_sceneUpdateTimer.AutoReset = true; + m_sceneUpdateTimer.Elapsed += Update; + m_sceneUpdateTimer.Start(); + } + else + { + Thread.CurrentThread.Priority = ThreadPriority.Highest; + Update(-1); + Watchdog.RemoveThread(); + m_isRunning = false; + } + } - Watchdog.RemoveThread(); + private volatile bool m_isTimerUpdateRunning; + + private void Update(object sender, ElapsedEventArgs e) + { + if (m_isTimerUpdateRunning) + return; + + m_isTimerUpdateRunning = true; + + // If the last frame did not complete on time, then immediately start the next update on the same thread + // and ignore further timed updates until we have a frame that had spare time. + while (!Update(1) && Active) { } + + if (!Active || m_shuttingDown) + { + m_sceneUpdateTimer.Stop(); + m_sceneUpdateTimer = null; + m_isRunning = false; + } + + m_isTimerUpdateRunning = false; } private void Maintenance() @@ -1418,7 +1651,7 @@ namespace OpenSim.Region.Framework.Scenes public void DoMaintenance(int runs) { long? endRun = null; - int runtc; + int runtc, tmpMS; int previousMaintenanceTick; if (runs >= 0) @@ -1432,6 +1665,8 @@ namespace OpenSim.Region.Framework.Scenes runtc = Util.EnvironmentTickCount(); ++MaintenanceRun; + // m_log.DebugFormat("[SCENE]: Maintenance run {0} in {1}", MaintenanceRun, Name); + // Coarse locations relate to positions of green dots on the mini-map (on a SecondLife client) if (MaintenanceRun % (m_update_coarse_locations / 10) == 0) { @@ -1445,7 +1680,7 @@ namespace OpenSim.Region.Framework.Scenes if (SendPeriodicAppearanceUpdates && MaintenanceRun % 60 == 0) { -// m_log.DebugFormat("[SCENE]: Sending periodic appearance updates"); + // m_log.DebugFormat("[SCENE]: Sending periodic appearance updates"); if (AvatarFactory != null) { @@ -1453,29 +1688,44 @@ namespace OpenSim.Region.Framework.Scenes } } + // Delete temp-on-rez stuff + if (MaintenanceRun % m_update_temp_cleaning == 0 && !m_cleaningTemps) + { + // m_log.DebugFormat("[SCENE]: Running temp-on-rez cleaning in {0}", Name); + tmpMS = Util.EnvironmentTickCount(); + m_cleaningTemps = true; + + WorkManager.RunInThread( + delegate { CleanTempObjects(); m_cleaningTemps = false; }, + null, + string.Format("CleanTempObjects ({0})", Name)); + + tempOnRezMS = Util.EnvironmentTickCountSubtract(tmpMS); + } + Watchdog.UpdateThread(); previousMaintenanceTick = m_lastMaintenanceTick; m_lastMaintenanceTick = Util.EnvironmentTickCount(); runtc = Util.EnvironmentTickCountSubtract(m_lastMaintenanceTick, runtc); - runtc = (int)(MinMaintenanceTime * 1000) - runtc; - + runtc = MinMaintenanceTicks - runtc; + if (runtc > 0) - Thread.Sleep(runtc); - + m_maintenanceWaitEvent.WaitOne(runtc); + // Optionally warn if a frame takes double the amount of time that it should. if (DebugUpdates && Util.EnvironmentTickCountSubtract( - m_lastMaintenanceTick, previousMaintenanceTick) > (int)(MinMaintenanceTime * 1000 * 2)) + m_lastMaintenanceTick, previousMaintenanceTick) > MinMaintenanceTicks * 2) m_log.WarnFormat( "[SCENE]: Maintenance took {0} ms (desired max {1} ms) in {2}", Util.EnvironmentTickCountSubtract(m_lastMaintenanceTick, previousMaintenanceTick), - MinMaintenanceTime * 1000, + MinMaintenanceTicks, RegionInfo.RegionName); } } - public override void Update(int frames) + public override bool Update(int frames) { long? endFrame = null; @@ -1484,101 +1734,159 @@ namespace OpenSim.Region.Framework.Scenes float physicsFPS = 0f; int previousFrameTick, tmpMS; - int maintc = Util.EnvironmentTickCount(); + + // These variables will be used to save the precise frame time using the + // Stopwatch class of Microsoft SDK; the times are recorded at the start + // and end of a particular section of code, and then used to calculate + // the frame times, which are the sums of the sections for each given name + double preciseTotalFrameTime = 0.0; + double preciseSimFrameTime = 0.0; + double precisePhysicsFrameTime = 0.0; + Stopwatch totalFrameStopwatch = new Stopwatch(); + Stopwatch simFrameStopwatch = new Stopwatch(); + Stopwatch physicsFrameStopwatch = new Stopwatch(); + + // Begin the stopwatch to keep track of the time that the frame + // started running to determine how long the frame took to complete + totalFrameStopwatch.Start(); while (!m_shuttingDown && ((endFrame == null && Active) || Frame < endFrame)) { ++Frame; -// m_log.DebugFormat("[SCENE]: Processing frame {0} in {1}", Frame, RegionInfo.RegionName); + // m_log.DebugFormat("[SCENE]: Processing frame {0} in {1}", Frame, RegionInfo.RegionName); - agentMS = tempOnRezMS = eventMS = backupMS = terrainMS = landMS = spareMS = 0; + agentMS = eventMS = backupMS = terrainMS = landMS = spareMS = 0; try { + EventManager.TriggerRegionHeartbeatStart(this); + // Apply taints in terrain module to terrain in physics scene if (Frame % m_update_terrain == 0) { + // At several points inside the code there was a need to + // create a more precise measurement of time elapsed. + // This led to the addition of variables that have a + // similar function and thus remain tightly connected to + // their original counterparts. However, the original + // code is not receiving comments from our group because + // we don't feel right modifying the code to that degree + // at this point in time, the precise values all begin + // with the keyword precise tmpMS = Util.EnvironmentTickCount(); + simFrameStopwatch.Start(); UpdateTerrain(); + + // Get the simulation frame time that the avatar force + // input took + simFrameStopwatch.Stop(); + preciseSimFrameTime = + simFrameStopwatch.Elapsed.TotalMilliseconds; terrainMS = Util.EnvironmentTickCountSubtract(tmpMS); } + // At several points inside the code there was a need to + // create a more precise measurement of time elapsed. This + // led to the addition of variables that have a similar + // function and thus remain tightly connected to their + // original counterparts. However, the original code is + // not receiving comments from our group because we don't + // feel right modifying the code to that degree at this + // point in time, the precise values all begin with the + // keyword precise + tmpMS = Util.EnvironmentTickCount(); + + // Begin the stopwatch to track the time to prepare physics + physicsFrameStopwatch.Start(); if (PhysicsEnabled && Frame % m_update_physics == 0) m_sceneGraph.UpdatePreparePhysics(); + + // Get the time it took to prepare the physics, this + // would report the most precise time that physics was + // running on the machine and should the physics not be + // enabled will report the time it took to check if physics + // was enabled + physicsFrameStopwatch.Stop(); + precisePhysicsFrameTime = physicsFrameStopwatch.Elapsed.TotalMilliseconds; physicsMS2 = Util.EnvironmentTickCountSubtract(tmpMS); - + // Apply any pending avatar force input to the avatar's velocity tmpMS = Util.EnvironmentTickCount(); + simFrameStopwatch.Restart(); if (Frame % m_update_entitymovement == 0) m_sceneGraph.UpdateScenePresenceMovement(); + + // Get the simulation frame time that the avatar force input + // took + simFrameStopwatch.Stop(); + preciseSimFrameTime += + simFrameStopwatch.Elapsed.TotalMilliseconds; agentMS = Util.EnvironmentTickCountSubtract(tmpMS); - + // Perform the main physics update. This will do the actual work of moving objects and avatars according to their // velocity tmpMS = Util.EnvironmentTickCount(); + physicsFrameStopwatch.Restart(); if (Frame % m_update_physics == 0) { if (PhysicsEnabled) - physicsFPS = m_sceneGraph.UpdatePhysics(MinFrameTime); - + physicsFPS = m_sceneGraph.UpdatePhysics(MinFrameSeconds); + if (SynchronizeScene != null) SynchronizeScene(this); } + + // Add the main physics update time to the prepare physics time + physicsFrameStopwatch.Stop(); + precisePhysicsFrameTime += physicsFrameStopwatch.Elapsed.TotalMilliseconds; physicsMS = Util.EnvironmentTickCountSubtract(tmpMS); + // Start the stopwatch for the remainder of the simulation + simFrameStopwatch.Restart(); tmpMS = Util.EnvironmentTickCount(); - + // Check if any objects have reached their targets CheckAtTargets(); - + // Update SceneObjectGroups that have scheduled themselves for updates // Objects queue their updates onto all scene presences if (Frame % m_update_objects == 0) m_sceneGraph.UpdateObjectGroups(); - + // Run through all ScenePresences looking for updates // Presence updates and queued object updates for each presence are sent to clients if (Frame % m_update_presences == 0) m_sceneGraph.UpdatePresences(); - + agentMS += Util.EnvironmentTickCountSubtract(tmpMS); - - // Delete temp-on-rez stuff - if (Frame % m_update_temp_cleaning == 0 && !m_cleaningTemps) - { - tmpMS = Util.EnvironmentTickCount(); - m_cleaningTemps = true; - Util.FireAndForget(delegate { CleanTempObjects(); m_cleaningTemps = false; }); - tempOnRezMS = Util.EnvironmentTickCountSubtract(tmpMS); - } - + if (Frame % m_update_events == 0) { tmpMS = Util.EnvironmentTickCount(); UpdateEvents(); eventMS = Util.EnvironmentTickCountSubtract(tmpMS); } - + if (PeriodicBackup && Frame % m_update_backup == 0) { tmpMS = Util.EnvironmentTickCount(); UpdateStorageBackup(); backupMS = Util.EnvironmentTickCountSubtract(tmpMS); } - + //if (Frame % m_update_land == 0) //{ // int ldMS = Util.EnvironmentTickCount(); // UpdateLand(); // landMS = Util.EnvironmentTickCountSubtract(ldMS); //} - + if (!LoginsEnabled && Frame == 20) { - // m_log.DebugFormat("{0} {1} {2}", LoginsDisabled, m_sceneGraph.GetActiveScriptsCount(), LoginLock); - + // m_log.DebugFormat("{0} {1} {2}", LoginsDisabled, m_sceneGraph.GetActiveScriptsCount(), LoginLock); + // In 99.9% of cases it is a bad idea to manually force garbage collection. However, // this is a rare case where we know we have just went through a long cycle of heap // allocations, and there is no more work to be done until someone logs in @@ -1593,7 +1901,7 @@ namespace OpenSim.Region.Framework.Scenes } m_sceneGridService.InformNeighborsThatRegionisUp( - RequestModuleInterface(), RegionInfo); + RequestModuleInterface(), RegionInfo); // Region ready should always be set Ready = true; @@ -1604,7 +1912,7 @@ namespace OpenSim.Region.Framework.Scenes if (m_sceneGraph.GetActiveScriptsCount() == 0) { // In this case, we leave it to the IRegionReadyModule to enable logins - + // LoginLock can currently only be set by a region module implementation. // If somehow this hasn't been done then the quickest way to bugfix is to see the // NullReferenceException @@ -1620,26 +1928,42 @@ namespace OpenSim.Region.Framework.Scenes "[SCENE]: Failed on region {0} with exception {1}{2}", RegionInfo.RegionName, e.Message, e.StackTrace); } - + EventManager.TriggerRegionHeartbeatEnd(this); + otherMS = eventMS + backupMS + terrainMS + landMS; - Watchdog.UpdateThread(); + // Get the elapsed time for the simulation frame + simFrameStopwatch.Stop(); + preciseSimFrameTime += + simFrameStopwatch.Elapsed.TotalMilliseconds; - previousFrameTick = m_lastFrameTick; - m_lastFrameTick = Util.EnvironmentTickCount(); - tmpMS = Util.EnvironmentTickCountSubtract(m_lastFrameTick, maintc); - tmpMS = (int)(MinFrameTime * 1000) - tmpMS; + if (!UpdateOnTimer) + { + Watchdog.UpdateThread(); - if (tmpMS > 0) + spareMS = MinFrameTicks - Util.EnvironmentTickCountSubtract(m_lastFrameTick); + + if (spareMS > 0) + m_updateWaitEvent.WaitOne(spareMS); + else + spareMS = 0; + } + else { - Thread.Sleep(tmpMS); - spareMS += tmpMS; + spareMS = Math.Max(0, MinFrameTicks - physicsMS2 - agentMS - physicsMS - otherMS); } - frameMS = Util.EnvironmentTickCountSubtract(maintc); - maintc = Util.EnvironmentTickCount(); + // Get the total frame time + totalFrameStopwatch.Stop(); + preciseTotalFrameTime = + totalFrameStopwatch.Elapsed.TotalMilliseconds; - otherMS = tempOnRezMS + eventMS + backupMS + terrainMS + landMS; + // Restart the stopwatch for the total time of the next frame + totalFrameStopwatch.Restart(); + + previousFrameTick = m_lastFrameTick; + frameMS = Util.EnvironmentTickCountSubtract(m_lastFrameTick); + m_lastFrameTick = Util.EnvironmentTickCount(); // if (Frame%m_update_avatars == 0) // UpdateInWorldTime(); @@ -1653,18 +1977,53 @@ namespace OpenSim.Region.Framework.Scenes StatsReporter.addOtherMS(otherMS); StatsReporter.AddSpareMS(spareMS); StatsReporter.addScriptLines(m_sceneGraph.GetScriptLPS()); + StatsReporter.AddScriptMS((int) GetAndResetScriptExecutionTime()); + + // Send the correct time values to the stats reporter for the + // frame times + StatsReporter.addFrameTimeMilliseconds(preciseTotalFrameTime, + preciseSimFrameTime, precisePhysicsFrameTime, 0.0); - // Optionally warn if a frame takes double the amount of time that it should. + // Send the correct number of frames that the physics library + // has processed to the stats reporter + StatsReporter.addPhysicsFrame(1); + + // Optionally warn if a frame takes double the amount of time that it should. if (DebugUpdates && Util.EnvironmentTickCountSubtract( - m_lastFrameTick, previousFrameTick) > (int)(MinFrameTime * 1000 * 2)) + m_lastFrameTick, previousFrameTick) > MinFrameTicks * 2) m_log.WarnFormat( "[SCENE]: Frame took {0} ms (desired max {1} ms) in {2}", Util.EnvironmentTickCountSubtract(m_lastFrameTick, previousFrameTick), - MinFrameTime * 1000, + MinFrameTicks, RegionInfo.RegionName); } - } + + // Finished updating scene frame, so stop the total frame's Stopwatch + totalFrameStopwatch.Stop(); + + return spareMS >= 0; + } + + /// + /// Adds the execution time of one script to the total scripts execution time for this region. + /// + /// Elapsed Stopwatch ticks + public void AddScriptExecutionTime(long ticks) + { + Interlocked.Add(ref m_scriptExecutionTime, ticks); + } + + /// + /// Returns the total execution time of all the scripts in the region since the last frame + /// (in milliseconds), and clears the value in preparation for the next frame. + /// + /// Time in milliseconds + private long GetAndResetScriptExecutionTime() + { + long ticks = Interlocked.Exchange(ref m_scriptExecutionTime, 0); + return (ticks * 1000) / Stopwatch.Frequency; + } public void AddGroupTarget(SceneObjectGroup grp) { @@ -1723,7 +2082,7 @@ namespace OpenSim.Region.Framework.Scenes if (!m_backingup) { m_backingup = true; - Util.FireAndForget(BackupWaitCallback); + WorkManager.RunInThread(o => Backup(false), null, string.Format("BackupWaitCallback ({0})", Name)); } } @@ -1736,16 +2095,12 @@ namespace OpenSim.Region.Framework.Scenes } /// - /// Wrapper for Backup() that can be called with Util.FireAndForget() - /// - private void BackupWaitCallback(object o) - { - Backup(false); - } - - /// - /// Backup the scene. This acts as the main method of the backup thread. + /// Backup the scene. /// + /// + /// This acts as the main method of the backup thread. In a regression test whether the backup thread is not + /// running independently this can be invoked directly. + /// /// /// If true, then any changes that have not yet been persisted are persisted. If false, /// then the persistence decision is left to the backup code (in some situations, such as object persistence, @@ -1784,7 +2139,7 @@ namespace OpenSim.Region.Framework.Scenes IMessageTransferModule tr = RequestModuleInterface(); if (tr != null) - tr.SendInstantMessage(msg, delegate(bool success) {}); + tr.SendInstantMessage(msg, delegate(bool success) { }); } m_returns.Clear(); } @@ -1798,6 +2153,7 @@ namespace OpenSim.Region.Framework.Scenes { if (group != null) { + group.HasGroupChanged = true; group.ProcessBackup(SimulationDataService, true); } } @@ -1866,7 +2222,7 @@ namespace OpenSim.Region.Framework.Scenes { try { - double[,] map = SimulationDataService.LoadTerrain(RegionInfo.RegionID); + TerrainData map = SimulationDataService.LoadTerrain(RegionInfo.RegionID, (int)RegionInfo.RegionSizeX, (int)RegionInfo.RegionSizeY, (int)RegionInfo.RegionSizeZ); if (map == null) { // This should be in the Terrain module, but it isn't because @@ -1877,7 +2233,7 @@ namespace OpenSim.Region.Framework.Scenes m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); m_log.InfoFormat("[TERRAIN]: No default terrain. Generating a new terrain {0}.", m_InitialTerrain); - Heightmap = new TerrainChannel(m_InitialTerrain); + Heightmap = new TerrainChannel(m_InitialTerrain, (int)RegionInfo.RegionSizeX, (int)RegionInfo.RegionSizeY, (int)RegionInfo.RegionSizeZ); SimulationDataService.StoreTerrain(Heightmap.GetDoubles(), RegionInfo.RegionID); } @@ -1891,9 +2247,9 @@ namespace OpenSim.Region.Framework.Scenes m_log.WarnFormat( "[TERRAIN]: Scene.cs: LoadWorldMap() - Regenerating as failed with exception {0}{1}", e.Message, e.StackTrace); - + // Non standard region size. If there's an old terrain in the database, it might read past the buffer - #pragma warning disable 0162 +#pragma warning disable 0162 if ((int)Constants.RegionSize != 256) { Heightmap = new TerrainChannel(); @@ -1921,10 +2277,16 @@ namespace OpenSim.Region.Framework.Scenes //// stored in the GridService, because that's what the world map module uses //// to send the map image UUIDs (of other regions) to the viewer... if (m_generateMaptiles) - RegenerateMaptile(); + RegenerateMaptile(); GridRegion region = new GridRegion(RegionInfo); string error = GridService.RegisterRegion(RegionInfo.ScopeID, region); + // m_log.DebugFormat("[SCENE]: RegisterRegionWithGrid. name={0},id={1},loc=<{2},{3}>,size=<{4},{5}>", + // m_regionName, + // RegionInfo.RegionID, + // RegionInfo.RegionLocX, RegionInfo.RegionLocY, + // RegionInfo.RegionSizeX, RegionInfo.RegionSizeY); + if (error != String.Empty) throw new Exception(error); } @@ -1985,13 +2347,26 @@ namespace OpenSim.Region.Framework.Scenes rootPart.TrimPermissions(); // Don't do this here - it will get done later on when sculpt data is loaded. -// group.CheckSculptAndLoad(); + // group.CheckSculptAndLoad(); } LoadingPrims = false; EventManager.TriggerPrimsLoaded(this); } + public bool SupportsRayCastFiltered() + { + if (PhysicsScene == null) + return false; + return PhysicsScene.SupportsRaycastWorldFiltered(); + } + + public object RayCastFiltered(Vector3 position, Vector3 direction, float length, int Count, RayFilterFlags filter) + { + if (PhysicsScene == null) + return null; + return PhysicsScene.RaycastWorld(position, direction, length, Count, filter); + } /// /// Gets a new rez location based on the raycast and the size of the object that is being rezzed. @@ -2020,8 +2395,8 @@ namespace OpenSim.Region.Framework.Scenes SceneObjectPart target = GetSceneObjectPart(RayTargetID); Vector3 direction = Vector3.Normalize(RayEnd - RayStart); - Vector3 AXOrigin = new Vector3(RayStart.X, RayStart.Y, RayStart.Z); - Vector3 AXdirection = new Vector3(direction.X, direction.Y, direction.Z); + Vector3 AXOrigin = RayStart; + Vector3 AXdirection = direction; if (target != null) { @@ -2037,19 +2412,19 @@ namespace OpenSim.Region.Framework.Scenes EntityIntersection ei = target.TestIntersectionOBB(NewRay, Quaternion.Identity, frontFacesOnly, FaceCenter); // Un-comment out the following line to Get Raytrace results printed to the console. - // m_log.Info("[RAYTRACERESULTS]: Hit:" + ei.HitTF.ToString() + " Point: " + ei.ipoint.ToString() + " Normal: " + ei.normal.ToString()); + // m_log.Info("[RAYTRACERESULTS]: Hit:" + ei.HitTF.ToString() + " Point: " + ei.ipoint.ToString() + " Normal: " + ei.normal.ToString()); float ScaleOffset = 0.5f; // If we hit something if (ei.HitTF) { - Vector3 scaleComponent = new Vector3(ei.AAfaceNormal.X, ei.AAfaceNormal.Y, ei.AAfaceNormal.Z); + Vector3 scaleComponent = ei.AAfaceNormal; if (scaleComponent.X != 0) ScaleOffset = scale.X; if (scaleComponent.Y != 0) ScaleOffset = scale.Y; if (scaleComponent.Z != 0) ScaleOffset = scale.Z; ScaleOffset = Math.Abs(ScaleOffset); - Vector3 intersectionpoint = new Vector3(ei.ipoint.X, ei.ipoint.Y, ei.ipoint.Z); - Vector3 normal = new Vector3(ei.normal.X, ei.normal.Y, ei.normal.Z); + Vector3 intersectionpoint = ei.ipoint; + Vector3 normal = ei.normal; // Set the position to the intersection point Vector3 offset = (normal * (ScaleOffset / 2f)); pos = (intersectionpoint + offset); @@ -2058,7 +2433,7 @@ namespace OpenSim.Region.Framework.Scenes //And in cases when we weren't rezzing from inventory we were re-adding the 0.25 straight after calling this method // Un-offset the prim (it gets offset later by the consumer method) //pos.Z -= 0.25F; - + } return pos; @@ -2074,8 +2449,9 @@ namespace OpenSim.Region.Framework.Scenes if (ei.HitTF) { - pos = new Vector3(ei.ipoint.X, ei.ipoint.Y, ei.ipoint.Z); - } else + pos = ei.ipoint; + } + else { // fall back to our stupid functionality pos = RayEnd; @@ -2137,7 +2513,7 @@ namespace OpenSim.Region.Framework.Scenes // "[SCENE]: Scene.AddNewPrim() pcode {0} called for {1} in {2}", shape.PCode, ownerID, RegionInfo.RegionName); SceneObjectGroup sceneObject = null; - + // If an entity creator has been registered for this prim type then use that if (m_entityCreators.ContainsKey((PCode)shape.PCode)) { @@ -2158,7 +2534,7 @@ namespace OpenSim.Region.Framework.Scenes return sceneObject; } - + /// /// Add an object into the scene that has come from storage /// @@ -2191,7 +2567,7 @@ namespace OpenSim.Region.Framework.Scenes return false; } - + /// /// Add an object into the scene that has come from storage /// @@ -2227,7 +2603,7 @@ namespace OpenSim.Region.Framework.Scenes { return AddNewSceneObject(sceneObject, attachToBackup, true); } - + /// /// Add a newly created object to the scene /// @@ -2242,16 +2618,16 @@ namespace OpenSim.Region.Framework.Scenes /// /// true if the object was added. false if not public bool AddNewSceneObject(SceneObjectGroup sceneObject, bool attachToBackup, bool sendClientUpdates) - { + { if (m_sceneGraph.AddNewSceneObject(sceneObject, attachToBackup, sendClientUpdates)) { EventManager.TriggerObjectAddedToScene(sceneObject); - return true; + return true; } - + return false; } - + /// /// Add a newly created object to the scene. /// @@ -2268,7 +2644,7 @@ namespace OpenSim.Region.Framework.Scenes SceneObjectGroup sceneObject, bool attachToBackup, Vector3? pos, Quaternion? rot, Vector3 vel) { if (m_sceneGraph.AddNewSceneObject(sceneObject, attachToBackup, pos, rot, vel)) - { + { EventManager.TriggerObjectAddedToScene(sceneObject); return true; } @@ -2316,8 +2692,8 @@ namespace OpenSim.Region.Framework.Scenes /// Suppress broadcasting changes to other clients. /// If true, then scripts are removed. If false, then they are only stopped. public void DeleteSceneObject(SceneObjectGroup group, bool silent, bool removeScripts) - { -// m_log.DebugFormat("[SCENE]: Deleting scene object {0} {1}", group.Name, group.UUID); + { + // m_log.DebugFormat("[SCENE]: Deleting scene object {0} {1}", group.Name, group.UUID); if (removeScripts) group.RemoveScriptInstances(true); @@ -2328,6 +2704,12 @@ namespace OpenSim.Region.Framework.Scenes foreach (SceneObjectPart part in partList) { + if (part.KeyframeMotion != null) + { + part.KeyframeMotion.Delete(); + part.KeyframeMotion = null; + } + if (part.IsJoint() && ((part.Flags & PrimFlags.Physics) != 0)) { PhysicsScene.RequestJointDeletion(part.Name); // FIXME: what if the name changed? @@ -2346,7 +2728,7 @@ namespace OpenSim.Region.Framework.Scenes group.DeleteGroupFromScene(silent); -// m_log.DebugFormat("[SCENE]: Exit DeleteSceneObject() for {0} {1}", group.Name, group.UUID); + // m_log.DebugFormat("[SCENE]: Exit DeleteSceneObject() for {0} {1}", group.Name, group.UUID); } /// @@ -2362,17 +2744,16 @@ namespace OpenSim.Region.Framework.Scenes { if (!softDelete) { - // Force a database update so that the scene object group ID is accurate. It's possible that the - // group has recently been delinked from another group but that this change has not been persisted - // to the DB. + // If the group contains prims whose SceneGroupID is incorrect then force a + // database update, because RemoveObject() works by searching on the SceneGroupID. // This is an expensive thing to do so only do it if absolutely necessary. - if (so.HasGroupChangedDueToDelink) - ForceSceneObjectBackup(so); - + if (so.GroupContainsForeignPrims) + ForceSceneObjectBackup(so); + so.DetachFromBackup(); SimulationDataService.RemoveObject(so.UUID, RegionInfo.RegionID); } - + // We need to keep track of this state in case this group is still queued for further backup. so.IsDeleted = true; @@ -2431,186 +2812,35 @@ namespace OpenSim.Region.Framework.Scenes EntityTransferModule.Cross(grp, attemptedPosition, silent); } - public Border GetCrossedBorder(Vector3 position, Cardinals gridline) + // Simple test to see if a position is in the current region. + // This test is mostly used to see if a region crossing is necessary. + // Assuming the position is relative to the region so anything outside its bounds. + // Return 'true' if position inside region. + public bool PositionIsInCurrentRegion(Vector3 pos) { - if (BordersLocked) - { - switch (gridline) - { - case Cardinals.N: - lock (NorthBorders) - { - foreach (Border b in NorthBorders) - { - if (b.TestCross(position)) - return b; - } - } - break; - case Cardinals.S: - lock (SouthBorders) - { - foreach (Border b in SouthBorders) - { - if (b.TestCross(position)) - return b; - } - } - - break; - case Cardinals.E: - lock (EastBorders) - { - foreach (Border b in EastBorders) - { - if (b.TestCross(position)) - return b; - } - } - - break; - case Cardinals.W: - - lock (WestBorders) - { - foreach (Border b in WestBorders) - { - if (b.TestCross(position)) - return b; - } - } - break; + bool ret = false; + int xx = (int)Math.Floor(pos.X); + int yy = (int)Math.Floor(pos.Y); + if (xx < 0 || yy < 0) + return false; - } + IRegionCombinerModule regionCombinerModule = RequestModuleInterface(); + if (regionCombinerModule == null) + { + // Regular region. Just check for region size + if (xx < RegionInfo.RegionSizeX && yy < RegionInfo.RegionSizeY) + ret = true; } else { - switch (gridline) - { - case Cardinals.N: - foreach (Border b in NorthBorders) - { - if (b.TestCross(position)) - return b; - } - - break; - case Cardinals.S: - foreach (Border b in SouthBorders) - { - if (b.TestCross(position)) - return b; - } - break; - case Cardinals.E: - foreach (Border b in EastBorders) - { - if (b.TestCross(position)) - return b; - } - - break; - case Cardinals.W: - foreach (Border b in WestBorders) - { - if (b.TestCross(position)) - return b; - } - break; - - } + // We're in a mega-region so see if we are still in that larger region + ret = regionCombinerModule.PositionIsInMegaregion(this.RegionInfo.RegionID, xx, yy); } - - return null; - } + return ret; - public bool TestBorderCross(Vector3 position, Cardinals border) - { - if (BordersLocked) - { - switch (border) - { - case Cardinals.N: - lock (NorthBorders) - { - foreach (Border b in NorthBorders) - { - if (b.TestCross(position)) - return true; - } - } - break; - case Cardinals.E: - lock (EastBorders) - { - foreach (Border b in EastBorders) - { - if (b.TestCross(position)) - return true; - } - } - break; - case Cardinals.S: - lock (SouthBorders) - { - foreach (Border b in SouthBorders) - { - if (b.TestCross(position)) - return true; - } - } - break; - case Cardinals.W: - lock (WestBorders) - { - foreach (Border b in WestBorders) - { - if (b.TestCross(position)) - return true; - } - } - break; - } - } - else - { - switch (border) - { - case Cardinals.N: - foreach (Border b in NorthBorders) - { - if (b.TestCross(position)) - return true; - } - break; - case Cardinals.E: - foreach (Border b in EastBorders) - { - if (b.TestCross(position)) - return true; - } - break; - case Cardinals.S: - foreach (Border b in SouthBorders) - { - if (b.TestCross(position)) - return true; - } - break; - case Cardinals.W: - foreach (Border b in WestBorders) - { - if (b.TestCross(position)) - return true; - } - break; - } - } - return false; } - /// /// Called when objects or attachments cross the border, or teleport, between regions. /// @@ -2632,45 +2862,8 @@ namespace OpenSim.Region.Framework.Scenes return false; } - // If the user is banned, we won't let any of their objects - // enter. Period. - // - if (RegionInfo.EstateSettings.IsBanned(newObject.OwnerID)) - { - m_log.InfoFormat("[INTERREGION]: Denied prim crossing for banned avatar {0}", newObject.OwnerID); + if (!EntityTransferModule.HandleIncomingSceneObject(newObject, newPosition)) return false; - } - - if (newPosition != Vector3.Zero) - newObject.RootPart.GroupPosition = newPosition; - - if (!AddSceneObject(newObject)) - { - m_log.DebugFormat( - "[INTERREGION]: Problem adding scene object {0} in {1} ", newObject.UUID, RegionInfo.RegionName); - return false; - } - - if (!newObject.IsAttachment) - { - // FIXME: It would be better to never add the scene object at all rather than add it and then delete - // it - if (!Permissions.CanObjectEntry(newObject.UUID, true, newObject.AbsolutePosition)) - { - // Deny non attachments based on parcel settings - // - m_log.Info("[INTERREGION]: Denied prim crossing because of parcel settings"); - - DeleteSceneObject(newObject, false); - - return false; - } - - // For attachments, we need to wait until the agent is root - // before we restart the scripts, or else some functions won't work. - newObject.RootPart.ParentGroup.CreateScriptInstances(0, false, DefaultScriptEngine, GetStateSource(newObject)); - newObject.ResumeScripts(); - } // Do this as late as possible so that listeners have full access to the incoming object EventManager.TriggerOnIncomingSceneObject(newObject); @@ -2697,7 +2890,7 @@ namespace OpenSim.Region.Framework.Scenes { sceneObject.RootPart.AddFlag(PrimFlags.TemporaryOnRez); sceneObject.RootPart.AddFlag(PrimFlags.Phantom); - + // Don't sent a full update here because this will cause full updates to be sent twice for // attachments on region crossings, resulting in viewer glitches. AddRestoredSceneObject(sceneObject, false, false, false); @@ -2712,15 +2905,18 @@ namespace OpenSim.Region.Framework.Scenes { SceneObjectGroup grp = sceneObject; -// m_log.DebugFormat( -// "[ATTACHMENT]: Received attachment {0}, inworld asset id {1}", grp.FromItemID, grp.UUID); -// m_log.DebugFormat( -// "[ATTACHMENT]: Attach to avatar {0} at position {1}", sp.UUID, grp.AbsolutePosition); + // m_log.DebugFormat( + // "[ATTACHMENT]: Received attachment {0}, inworld asset id {1}", grp.FromItemID, grp.UUID); + // m_log.DebugFormat( + // "[ATTACHMENT]: Attach to avatar {0} at position {1}", sp.UUID, grp.AbsolutePosition); RootPrim.RemFlag(PrimFlags.TemporaryOnRez); - + + // We must currently not resume scripts at this stage since AttachmentsModule does not have the + // information that this is due to a teleport/border cross rather than an ordinary attachment. + // We currently do this in Scene.MakeRootAgent() instead. if (AttachmentsModule != null) - AttachmentsModule.AttachObject(sp, grp, 0, false, false); + AttachmentsModule.AttachObject(sp, grp, 0, false, false, true); } else { @@ -2736,28 +2932,22 @@ namespace OpenSim.Region.Framework.Scenes return true; } - private int GetStateSource(SceneObjectGroup sog) - { - ScenePresence sp = GetScenePresence(sog.OwnerID); - - if (sp != null) - return sp.GetStateSource(); - - return 2; // StateSource.PrimCrossing - } - #endregion #region Add/Remove Avatar Methods - public override ISceneAgent AddNewClient(IClientAPI client, PresenceType type) + public override ISceneAgent AddNewAgent(IClientAPI client, PresenceType type) { ScenePresence sp; bool vialogin; + bool reallyNew = true; + + // Update the number of users attempting to login + StatsReporter.UpdateUsersLoggingIn(true); // Validation occurs in LLUDPServer // - // XXX: A race condition exists here where two simultaneous calls to AddNewClient can interfere with + // XXX: A race condition exists here where two simultaneous calls to AddNewAgent can interfere with // each other. In practice, this does not currently occur in the code. AgentCircuitData aCircuit = m_authenticateHandler.GetAgentCircuitData(client.CircuitCode); @@ -2765,9 +2955,9 @@ namespace OpenSim.Region.Framework.Scenes // and a simultaneous one that removes it (as can happen if the client is closed at a particular point // whilst connecting). // - // It would be easier to lock across all NewUserConnection(), AddNewClient() and + // It would be easier to lock across all NewUserConnection(), AddNewAgent() and // RemoveClient() calls for all agents, but this would allow a slow call (e.g. because of slow service - // response in some module listening to AddNewClient()) from holding up unrelated agent calls. + // response in some module listening to AddNewAgent()) from holding up unrelated agent calls. // // In practice, the lock (this) in LLUDPServer.AddNewClient() currently lock across all // AddNewClient() operations (though not other ops). @@ -2777,68 +2967,90 @@ namespace OpenSim.Region.Framework.Scenes vialogin = (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0 || (aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaLogin) != 0; - - // CheckHeartbeat(); - + + // CheckHeartbeat(); + sp = GetScenePresence(client.AgentId); // XXX: Not sure how good it is to add a new client if a scene presence already exists. Possibly this // could occur if a viewer crashes and relogs before the old client is kicked out. But this could cause - // other problems, and possible the code calling AddNewClient() should ensure that no client is already + // other problems, and possibly the code calling AddNewAgent() should ensure that no client is already // connected. if (sp == null) { m_log.DebugFormat( "[SCENE]: Adding new child scene presence {0} {1} to scene {2} at pos {3}", client.Name, client.AgentId, RegionInfo.RegionName, client.StartPos); - + + sp = m_sceneGraph.CreateAndAddChildScenePresence(client, aCircuit.Appearance, type); + + // We must set this here so that TriggerOnNewClient and TriggerOnClientLogin can determine whether the + // client is for a root or child agent. + // We must also set this before adding the client to the client manager so that an exception later on + // does not leave a client manager entry without the scene agent set, which will cause other code + // to fail since any entry in the client manager should have a ScenePresence + // + // XXX: This may be better set for a new client before that client is added to the client manager. + // But need to know what happens in the case where a ScenePresence is already present (and if this + // actually occurs). + client.SceneAgent = sp; + m_clientManager.Add(client); SubscribeToClientEvents(client); - - sp = m_sceneGraph.CreateAndAddChildScenePresence(client, aCircuit.Appearance, type); m_eventManager.TriggerOnNewPresence(sp); - + sp.TeleportFlags = (TPFlags)aCircuit.teleportFlags; - - // The first agent upon login is a root agent by design. - // For this agent we will have to rez the attachments. - // All other AddNewClient calls find aCircuit.child to be true. - if (aCircuit.child == false) - { - // We have to set SP to be a root agent here so that SP.MakeRootAgent() will later not try to - // start the scripts again (since this is done in RezAttachments()). - // XXX: This is convoluted. - sp.IsChildAgent = false; - - if (AttachmentsModule != null) - Util.FireAndForget(delegate(object o) { AttachmentsModule.RezAttachments(sp); }); - } } else { + // We must set this here so that TriggerOnNewClient and TriggerOnClientLogin can determine whether the + // client is for a root or child agent. + // XXX: This may be better set for a new client before that client is added to the client manager. + // But need to know what happens in the case where a ScenePresence is already present (and if this + // actually occurs). + client.SceneAgent = sp; + m_log.WarnFormat( "[SCENE]: Already found {0} scene presence for {1} in {2} when asked to add new scene presence", sp.IsChildAgent ? "child" : "root", sp.Name, RegionInfo.RegionName); + + reallyNew = false; } - - // We must set this here so that TriggerOnNewClient and TriggerOnClientLogin can determine whether the - // client is for a root or child agent. - client.SceneAgent = sp; - // Cache the user's name + // This is currently also being done earlier in NewUserConnection for real users to see if this + // resolves problems where HG agents are occasionally seen by others as "Unknown user" in chat and other + // places. However, we still need to do it here for NPCs. CacheUserName(sp, aCircuit); - - EventManager.TriggerOnNewClient(client); + + if (reallyNew) + EventManager.TriggerOnNewClient(client); + if (vialogin) EventManager.TriggerOnClientLogin(client); } + // User has logged into the scene so update the list of users logging + // in + StatsReporter.UpdateUsersLoggingIn(false); + m_LastLogin = Util.EnvironmentTickCount(); return sp; } /// + /// Returns the Home URI of the agent, or null if unknown. + /// + public string GetAgentHomeURI(UUID agentID) + { + AgentCircuitData circuit = AuthenticateHandler.GetAgentCircuitData(agentID); + if (circuit != null && circuit.ServiceURLs != null && circuit.ServiceURLs.ContainsKey("HomeURI")) + return circuit.ServiceURLs["HomeURI"].ToString(); + else + return null; + } + + /// /// Cache the user name for later use. /// /// @@ -2849,7 +3061,7 @@ namespace OpenSim.Region.Framework.Scenes { string first = aCircuit.firstname, last = aCircuit.lastname; - if (sp.PresenceType == PresenceType.Npc) + if (sp != null && sp.PresenceType == PresenceType.Npc) { UserManagementModule.AddUser(aCircuit.AgentID, first, last); } @@ -2878,7 +3090,7 @@ namespace OpenSim.Region.Framework.Scenes private bool VerifyClient(AgentCircuitData aCircuit, System.Net.IPEndPoint ep, out bool vialogin) { vialogin = false; - + // Do the verification here if ((aCircuit.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0) { @@ -2929,7 +3141,7 @@ namespace OpenSim.Region.Framework.Scenes { PresenceService.LogoutAgent(sp.ControllingClient.SessionId); - sp.ControllingClient.Close(); + CloseAgent(sp.UUID, false); } else { @@ -2973,7 +3185,7 @@ namespace OpenSim.Region.Framework.Scenes { client.OnRegionHandShakeReply += SendLayerData; } - + public virtual void SubscribeToClientPrimEvents(IClientAPI client) { client.OnUpdatePrimGroupPosition += m_sceneGraph.UpdatePrimGroupPosition; @@ -2983,7 +3195,7 @@ namespace OpenSim.Region.Framework.Scenes client.OnUpdatePrimGroupMouseRotation += m_sceneGraph.UpdatePrimGroupRotation; client.OnUpdatePrimSingleRotation += m_sceneGraph.UpdatePrimSingleRotation; client.OnUpdatePrimSingleRotationPosition += m_sceneGraph.UpdatePrimSingleRotationPosition; - + client.OnUpdatePrimScale += m_sceneGraph.UpdatePrimScale; client.OnUpdatePrimGroupScale += m_sceneGraph.UpdatePrimGroupScale; client.OnUpdateExtraParams += m_sceneGraph.UpdateExtraParam; @@ -2996,7 +3208,7 @@ namespace OpenSim.Region.Framework.Scenes client.OnSpinStart += m_sceneGraph.SpinStart; client.OnSpinUpdate += m_sceneGraph.SpinObject; client.OnDeRezObject += DeRezObjects; - + client.OnObjectName += m_sceneGraph.PrimName; client.OnObjectClickAction += m_sceneGraph.PrimClickAction; client.OnObjectMaterial += m_sceneGraph.PrimMaterial; @@ -3008,7 +3220,7 @@ namespace OpenSim.Region.Framework.Scenes client.OnRequestObjectPropertiesFamily += m_sceneGraph.RequestObjectPropertiesFamily; client.OnObjectPermissions += HandleObjectPermissionsUpdate; client.OnGrabObject += ProcessObjectGrab; - client.OnGrabUpdate += ProcessObjectGrabUpdate; + client.OnGrabUpdate += ProcessObjectGrabUpdate; client.OnDeGrabObject += ProcessObjectDeGrab; client.OnUndo += m_sceneGraph.HandleUndo; client.OnRedo += m_sceneGraph.HandleRedo; @@ -3068,10 +3280,8 @@ namespace OpenSim.Region.Framework.Scenes { //client.OnNameFromUUIDRequest += HandleUUIDNameRequest; client.OnMoneyTransferRequest += ProcessMoneyTransferRequest; - client.OnSetStartLocationRequest += SetHomeRezPoint; - client.OnRegionHandleRequest += RegionHandleRequest; } - + public virtual void SubscribeToClientNetworkEvents(IClientAPI client) { client.OnNetworkStatsUpdate += StatsReporter.AddPacketsStats; @@ -3193,8 +3403,6 @@ namespace OpenSim.Region.Framework.Scenes { //client.OnNameFromUUIDRequest -= HandleUUIDNameRequest; client.OnMoneyTransferRequest -= ProcessMoneyTransferRequest; - client.OnSetStartLocationRequest -= SetHomeRezPoint; - client.OnRegionHandleRequest -= RegionHandleRequest; } public virtual void UnSubscribeToClientNetworkEvents(IClientAPI client) @@ -3208,17 +3416,18 @@ namespace OpenSim.Region.Framework.Scenes /// /// The avatar's Unique ID /// The IClientAPI for the client - public virtual void TeleportClientHome(UUID agentId, IClientAPI client) + public virtual bool TeleportClientHome(UUID agentId, IClientAPI client) { if (EntityTransferModule != null) { - EntityTransferModule.TeleportHome(agentId, client); + return EntityTransferModule.TeleportHome(agentId, client); } else { m_log.DebugFormat("[SCENE]: Unable to teleport user home: no AgentTransferModule is active"); client.SendTeleportFailed("Unable to perform teleports on this simulator."); } + return false; } /// @@ -3264,8 +3473,8 @@ namespace OpenSim.Region.Framework.Scenes if (target != null && target2 != null) { Vector3 direction = Vector3.Normalize(RayEnd - RayStart); - Vector3 AXOrigin = new Vector3(RayStart.X, RayStart.Y, RayStart.Z); - Vector3 AXdirection = new Vector3(direction.X, direction.Y, direction.Z); + Vector3 AXOrigin = RayStart; + Vector3 AXdirection = direction; pos = target2.AbsolutePosition; //m_log.Info("[OBJECT_REZ]: TargetPos: " + pos.ToString() + ", RayStart: " + RayStart.ToString() + ", RayEnd: " + RayEnd.ToString() + ", Volume: " + Util.GetDistanceTo(RayStart,RayEnd).ToString() + ", mag1: " + Util.GetMagnitude(RayStart).ToString() + ", mag2: " + Util.GetMagnitude(RayEnd).ToString()); @@ -3286,13 +3495,13 @@ namespace OpenSim.Region.Framework.Scenes if (ei.HitTF) { Vector3 scale = target.Scale; - Vector3 scaleComponent = new Vector3(ei.AAfaceNormal.X, ei.AAfaceNormal.Y, ei.AAfaceNormal.Z); + Vector3 scaleComponent = ei.AAfaceNormal; if (scaleComponent.X != 0) ScaleOffset = scale.X; if (scaleComponent.Y != 0) ScaleOffset = scale.Y; if (scaleComponent.Z != 0) ScaleOffset = scale.Z; ScaleOffset = Math.Abs(ScaleOffset); - Vector3 intersectionpoint = new Vector3(ei.ipoint.X, ei.ipoint.Y, ei.ipoint.Z); - Vector3 normal = new Vector3(ei.normal.X, ei.normal.Y, ei.normal.Z); + Vector3 intersectionpoint = ei.ipoint; + Vector3 normal = ei.normal; Vector3 offset = normal * (ScaleOffset / 2f); pos = intersectionpoint + offset; @@ -3312,6 +3521,7 @@ namespace OpenSim.Region.Framework.Scenes { copy = m_sceneGraph.DuplicateObject(localID, pos, target.GetEffectiveObjectFlags(), AgentID, GroupID, Quaternion.Identity); } + if (copy != null) EventManager.TriggerObjectAddedToScene(copy); } @@ -3319,24 +3529,7 @@ namespace OpenSim.Region.Framework.Scenes } /// - /// Sets the Home Point. The LoginService uses this to know where to put a user when they log-in - /// - /// - /// - /// - /// - /// - public virtual void SetHomeRezPoint(IClientAPI remoteClient, ulong regionHandle, Vector3 position, Vector3 lookAt, uint flags) - { - if (GridUserService != null && GridUserService.SetHome(remoteClient.AgentId.ToString(), RegionInfo.RegionID, position, lookAt)) - // FUBAR ALERT: this needs to be "Home position set." so the viewer saves a home-screenshot. - m_dialogModule.SendAlertToUser(remoteClient, "Home position set."); - else - m_dialogModule.SendAlertToUser(remoteClient, "Set Home request Failed."); - } - - /// - /// Get the avatar apperance for the given client. + /// Get the avatar appearance for the given client. /// /// /// @@ -3359,93 +3552,93 @@ namespace OpenSim.Region.Framework.Scenes } } - public override void RemoveClient(UUID agentID, bool closeChildAgents) + /// + /// Remove the given client from the scene. + /// + /// + /// Only clientstack code should call this directly. All other code should call IncomingCloseAgent() instead + /// to properly operate the state machine and avoid race conditions with other close requests (such as directly + /// from viewers). + /// + /// ID of agent to close + /// + /// Close the neighbour child agents associated with this client. + /// + public void RemoveClient(UUID agentID, bool closeChildAgents) { -// CheckHeartbeat(); - bool isChildAgent = false; - AgentCircuitData acd; + AgentCircuitData acd = m_authenticateHandler.GetAgentCircuitData(agentID); - lock (m_removeClientLock) + // Shouldn't be necessary since RemoveClient() is currently only called by IClientAPI.Close() which + // in turn is only called by Scene.IncomingCloseAgent() which checks whether the presence exists or not + // However, will keep for now just in case. + if (acd == null) { - acd = m_authenticateHandler.GetAgentCircuitData(agentID); + m_log.ErrorFormat( + "[SCENE]: No agent circuit found for {0} in {1}, aborting Scene.RemoveClient", agentID, Name); - if (acd == null) - { - m_log.ErrorFormat("[SCENE]: No agent circuit found for {0}, aborting Scene.RemoveClient", agentID); - return; - } - else - { - // We remove the acd up here to avoid later race conditions if two RemoveClient() calls occurred - // simultaneously. - // We also need to remove by agent ID since NPCs will have no circuit code. - m_authenticateHandler.RemoveCircuit(agentID); - } + return; } + // TODO: Can we now remove this lock? lock (acd) { + bool isChildAgent = false; + ScenePresence avatar = GetScenePresence(agentID); - + + // Shouldn't be necessary since RemoveClient() is currently only called by IClientAPI.Close() which + // in turn is only called by Scene.IncomingCloseAgent() which checks whether the presence exists or not + // However, will keep for now just in case. if (avatar == null) { - m_log.WarnFormat( + m_log.ErrorFormat( "[SCENE]: Called RemoveClient() with agent ID {0} but no such presence is in the scene.", agentID); - + m_authenticateHandler.RemoveCircuit(agentID); + return; } - + try { isChildAgent = avatar.IsChildAgent; m_log.DebugFormat( "[SCENE]: Removing {0} agent {1} {2} from {3}", - (isChildAgent ? "child" : "root"), avatar.Name, agentID, RegionInfo.RegionName); - + isChildAgent ? "child" : "root", avatar.Name, agentID, Name); + // Don't do this to root agents, it's not nice for the viewer if (closeChildAgents && isChildAgent) { // Tell a single agent to disconnect from the region. - IEventQueue eq = RequestModuleInterface(); - if (eq != null) - { - eq.DisableSimulator(RegionInfo.RegionHandle, avatar.UUID); - } - else - { - avatar.ControllingClient.SendShutdownConnectionNotice(); - } + // Let's do this via UDP + avatar.ControllingClient.SendShutdownConnectionNotice(); } - + // Only applies to root agents. if (avatar.ParentID != 0) { avatar.StandUp(); } - + m_sceneGraph.removeUserCount(!isChildAgent); - + // TODO: We shouldn't use closeChildAgents here - it's being used by the NPC module to stop // unnecessary operations. This should go away once NPCs have no accompanying IClientAPI if (closeChildAgents && CapsModule != null) CapsModule.RemoveCaps(agentID); - -// // REFACTORING PROBLEM -- well not really a problem, but just to point out that whatever -// // this method is doing is HORRIBLE!!! - // Commented pending deletion since this method no longer appears to do anything at all -// avatar.Scene.NeedSceneCacheClear(avatar.UUID); - + if (closeChildAgents && !isChildAgent) { List regions = avatar.KnownRegionHandles; regions.Remove(RegionInfo.RegionHandle); - m_sceneGridService.SendCloseChildAgentConnections(agentID, regions); + + // This ends up being done asynchronously so that a logout isn't held up where there are many present but unresponsive neighbours. + m_sceneGridService.SendCloseChildAgentConnections(agentID, acd.SessionID.ToString(), regions); } - + m_eventManager.TriggerClientClosed(agentID, this); m_eventManager.TriggerOnRemovePresence(agentID); - + if (!isChildAgent) { if (AttachmentsModule != null) @@ -3457,11 +3650,11 @@ namespace OpenSim.Region.Framework.Scenes delegate(IClientAPI client) { //We can safely ignore null reference exceptions. It means the avatar is dead and cleaned up anyway - try { client.SendKillObject(avatar.RegionHandle, new List { avatar.LocalId }); } + try { client.SendKillObject(new List { avatar.LocalId }); } catch (NullReferenceException) { } }); } - + // It's possible for child agents to have transactions if changes are being made cross-border. if (AgentTransactionsModule != null) AgentTransactionsModule.RemoveAgentAssetTransactions(agentID); @@ -3478,9 +3671,10 @@ namespace OpenSim.Region.Framework.Scenes // Always clean these structures up so that any failure above doesn't cause them to remain in the // scene with possibly bad effects (e.g. continually timing out on unacked packets and triggering // the same cleanup exception continually. + m_authenticateHandler.RemoveCircuit(agentID); m_sceneGraph.RemoveScenePresence(agentID); m_clientManager.Remove(agentID); - + avatar.Close(); } catch (Exception e) @@ -3537,7 +3731,8 @@ namespace OpenSim.Region.Framework.Scenes } deleteIDs.Add(localID); } - ForEachClient(delegate(IClientAPI client) { client.SendKillObject(m_regionHandle, deleteIDs); }); + + ForEachClient(c => c.SendKillObject(deleteIDs)); } #endregion @@ -3549,12 +3744,13 @@ namespace OpenSim.Region.Framework.Scenes /// /// CircuitData of the agent who is connecting /// + /// Source region (may be null) /// Outputs the reason for the false response on this string /// True if the region accepts this agent. False if it does not. False will /// also return a reason. - public bool NewUserConnection(AgentCircuitData agent, uint teleportFlags, out string reason) + public bool NewUserConnection(AgentCircuitData agent, uint teleportFlags, GridRegion source, out string reason) { - return NewUserConnection(agent, teleportFlags, out reason, true); + return NewUserConnection(agent, teleportFlags, source, out reason, true); } /// @@ -3573,13 +3769,14 @@ namespace OpenSim.Region.Framework.Scenes /// is activated later when the viewer sends the initial UseCircuitCodePacket UDP packet (in the case of /// the LLUDP stack). /// - /// CircuitData of the agent who is connecting + /// CircuitData of the agent who is connecting + /// Source region (may be null) /// Outputs the reason for the false response on this string /// True for normal presence. False for NPC /// or other applications where a full grid/Hypergrid presence may not be required. /// True if the region accepts this agent. False if it does not. False will /// also return a reason. - public bool NewUserConnection(AgentCircuitData agent, uint teleportFlags, out string reason, bool requirePresenceLookup) + public bool NewUserConnection(AgentCircuitData acd, uint teleportFlags, GridRegion source, out string reason, bool requirePresenceLookup) { bool vialogin = ((teleportFlags & (uint)TPFlags.ViaLogin) != 0 || (teleportFlags & (uint)TPFlags.ViaHGLogin) != 0); @@ -3596,18 +3793,20 @@ namespace OpenSim.Region.Framework.Scenes // TeleportFlags.ViaLandmark | TeleportFlags.ViaLocation | TeleportFlags.ViaLandmark | TeleportFlags.Default - Regular Teleport // Don't disable this log message - it's too helpful + string curViewer = Util.GetViewerName(acd); m_log.DebugFormat( - "[SCENE]: Region {0} told of incoming {1} agent {2} {3} {4} (circuit code {5}, IP {6}, viewer {7}, teleportflags ({8}), position {9})", + "[SCENE]: Region {0} told of incoming {1} agent {2} {3} {4} (circuit code {5}, IP {6}, viewer {7}, teleportflags ({8}), position {9}. {10}", RegionInfo.RegionName, - (agent.child ? "child" : "root"), - agent.firstname, - agent.lastname, - agent.AgentID, - agent.circuitcode, - agent.IPAddress, - agent.Viewer, + (acd.child ? "child" : "root"), + acd.firstname, + acd.lastname, + acd.AgentID, + acd.circuitcode, + acd.IPAddress, + curViewer, ((TPFlags)teleportFlags).ToString(), - agent.startpos + acd.startpos, + (source == null) ? "" : string.Format("From region {0} ({1}){2}", source.RegionName, source.RegionID, (source.RawServerURI == null) ? "" : " @ " + source.ServerURI) ); if (!LoginsEnabled) @@ -3625,7 +3824,7 @@ namespace OpenSim.Region.Framework.Scenes { foreach (string viewer in m_AllowedViewers) { - if (viewer == agent.Viewer.Substring(0, viewer.Length).Trim().ToLower()) + if (viewer == curViewer.Substring(0, Math.Min(viewer.Length, curViewer.Length)).Trim().ToLower()) { ViewerDenied = false; break; @@ -3642,7 +3841,7 @@ namespace OpenSim.Region.Framework.Scenes { foreach (string viewer in m_BannedViewers) { - if (viewer == agent.Viewer.Substring(0, viewer.Length).Trim().ToLower()) + if (viewer == curViewer.Substring(0, Math.Min(viewer.Length, curViewer.Length)).Trim().ToLower()) { ViewerDenied = true; break; @@ -3654,81 +3853,178 @@ namespace OpenSim.Region.Framework.Scenes { m_log.DebugFormat( "[SCENE]: Access denied for {0} {1} using {2}", - agent.firstname, agent.lastname, agent.Viewer); + acd.firstname, acd.lastname, curViewer); reason = "Access denied, your viewer is banned by the region owner"; return false; } ILandObject land; + ScenePresence sp; - lock (agent) + lock (m_removeClientLock) { - ScenePresence sp = GetScenePresence(agent.AgentID); - - if (sp != null && !sp.IsChildAgent) + sp = GetScenePresence(acd.AgentID); + + // We need to ensure that we are not already removing the scene presence before we ask it not to be + // closed. + if (sp != null && sp.IsChildAgent + && (sp.LifecycleState == ScenePresenceState.Running + || sp.LifecycleState == ScenePresenceState.PreRemove)) + { + m_log.DebugFormat( + "[SCENE]: Reusing existing child scene presence for {0}, state {1} in {2}", + sp.Name, sp.LifecycleState, Name); + + // In the case where, for example, an A B C D region layout, an avatar may + // teleport from A -> D, but then -> C before A has asked B to close its old child agent. When C + // renews the lease on the child agent at B, we must make sure that the close from A does not succeed. + // + // XXX: In the end, this should not be necessary if child agents are closed without delay on + // teleport, since realistically, the close request should always be processed before any other + // region tried to re-establish a child agent. This is much simpler since the logic below is + // vulnerable to an issue when a viewer quits a region without sending a proper logout but then + // re-establishes the connection on a relogin. This could wrongly set the DoNotCloseAfterTeleport + // flag when no teleport had taken place (and hence no close was going to come). +// if (!acd.ChildrenCapSeeds.ContainsKey(RegionInfo.RegionHandle)) +// { +// m_log.DebugFormat( +// "[SCENE]: Setting DoNotCloseAfterTeleport for child scene presence {0} in {1} because source will attempt close.", +// sp.Name, Name); +// +// sp.DoNotCloseAfterTeleport = true; +// } +// else if (EntityTransferModule.IsInTransit(sp.UUID)) + + sp.LifecycleState = ScenePresenceState.Running; + + if (EntityTransferModule.IsInTransit(sp.UUID)) + { + sp.DoNotCloseAfterTeleport = true; + + m_log.DebugFormat( + "[SCENE]: Set DoNotCloseAfterTeleport for child scene presence {0} in {1} because this region will attempt end-of-teleport close from a previous close.", + sp.Name, Name); + } + } + } + + // Need to poll here in case we are currently deleting an sp. Letting threads run over each other will + // allow unpredictable things to happen. + if (sp != null) + { + const int polls = 10; + const int pollInterval = 1000; + int pollsLeft = polls; + + while (sp.LifecycleState == ScenePresenceState.Removing && pollsLeft-- > 0) + Thread.Sleep(pollInterval); + + if (sp.LifecycleState == ScenePresenceState.Removing) { - // We have a zombie from a crashed session. - // Or the same user is trying to be root twice here, won't work. - // Kill it. m_log.WarnFormat( - "[SCENE]: Existing root scene presence detected for {0} {1} in {2} when connecting. Removing existing presence.", - sp.Name, sp.UUID, RegionInfo.RegionName); - - sp.ControllingClient.Close(true); - sp = null; - } - - land = LandChannel.GetLandObject(agent.startpos.X, agent.startpos.Y); - - //On login test land permisions + "[SCENE]: Agent {0} in {1} was still being removed after {2}s. Aborting NewUserConnection.", + sp.Name, Name, polls * pollInterval / 1000); + + return false; + } + else if (polls != pollsLeft) + { + m_log.DebugFormat( + "[SCENE]: NewUserConnection for agent {0} in {1} had to wait {2}s for in-progress removal to complete on an old presence.", + sp.Name, Name, polls * pollInterval / 1000); + } + } + + // TODO: can we remove this lock? + lock (acd) + { + if (sp != null && !sp.IsChildAgent) + { + // We have a root agent. Is it in transit? + if (!EntityTransferModule.IsInTransit(sp.UUID)) + { + // We have a zombie from a crashed session. + // Or the same user is trying to be root twice here, won't work. + // Kill it. + m_log.WarnFormat( + "[SCENE]: Existing root scene presence detected for {0} {1} in {2} when connecting. Removing existing presence.", + sp.Name, sp.UUID, RegionInfo.RegionName); + + if (sp.ControllingClient != null) + CloseAgent(sp.UUID, true); + + sp = null; + } + //else + // m_log.WarnFormat("[SCENE]: Existing root scene presence for {0} {1} in {2}, but agent is in trasit", sp.Name, sp.UUID, RegionInfo.RegionName); + } + + // Optimistic: add or update the circuit data with the new agent circuit data and teleport flags. + // We need the circuit data here for some of the subsequent checks. (groups, for example) + // If the checks fail, we remove the circuit. + acd.teleportFlags = teleportFlags; + m_authenticateHandler.AddNewCircuit(acd.circuitcode, acd); + + land = LandChannel.GetLandObject(acd.startpos.X, acd.startpos.Y); + + // On login test land permisions if (vialogin) { - if (land != null && !TestLandRestrictions(agent, land, out reason)) + if (land != null && !TestLandRestrictions(acd.AgentID, out reason, ref acd.startpos.X, ref acd.startpos.Y)) { + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } } - + if (sp == null) // We don't have an [child] agent here already { if (requirePresenceLookup) { try { - if (!VerifyUserPresence(agent, out reason)) + if (!VerifyUserPresence(acd, out reason)) + { + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; + } } catch (Exception e) { m_log.ErrorFormat( "[SCENE]: Exception verifying presence {0}{1}", e.Message, e.StackTrace); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } } - + try { - if (!AuthorizeUser(agent, out reason)) + if (!AuthorizeUser(acd, (vialogin ? false : SeeIntoRegion), out reason)) + { + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; + } } catch (Exception e) { m_log.ErrorFormat( "[SCENE]: Exception authorizing user {0}{1}", e.Message, e.StackTrace); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } - + m_log.InfoFormat( "[SCENE]: Region {0} authenticated and authorized incoming {1} agent {2} {3} {4} (circuit code {5})", - RegionInfo.RegionName, (agent.child ? "child" : "root"), agent.firstname, agent.lastname, - agent.AgentID, agent.circuitcode); - + Name, (acd.child ? "child" : "root"), acd.firstname, acd.lastname, + acd.AgentID, acd.circuitcode); + if (CapsModule != null) { - CapsModule.SetAgentCapsSeeds(agent); - CapsModule.CreateCaps(agent.AgentID); + CapsModule.SetAgentCapsSeeds(acd); + CapsModule.CreateCaps(acd.AgentID); } } else @@ -3736,83 +4032,42 @@ namespace OpenSim.Region.Framework.Scenes // Let the SP know how we got here. This has a lot of interesting // uses down the line. sp.TeleportFlags = (TPFlags)teleportFlags; - + if (sp.IsChildAgent) { m_log.DebugFormat( "[SCENE]: Adjusting known seeds for existing agent {0} in {1}", - agent.AgentID, RegionInfo.RegionName); - + acd.AgentID, RegionInfo.RegionName); + sp.AdjustKnownSeeds(); - + if (CapsModule != null) - CapsModule.SetAgentCapsSeeds(agent); + { + CapsModule.SetAgentCapsSeeds(acd); + CapsModule.CreateCaps(acd.AgentID); + } } } - } - // In all cases, add or update the circuit data with the new agent circuit data and teleport flags - agent.teleportFlags = teleportFlags; - m_authenticateHandler.AddNewCircuit(agent.circuitcode, agent); + // Try caching an incoming user name much earlier on to see if this helps with an issue + // where HG users are occasionally seen by others as "Unknown User" because their UUIDName + // request for the HG avatar appears to trigger before the user name is cached. + CacheUserName(null, acd); + } if (vialogin) { // CleanDroppedAttachments(); - if (TestBorderCross(agent.startpos, Cardinals.E)) - { - Border crossedBorder = GetCrossedBorder(agent.startpos, Cardinals.E); - agent.startpos.X = crossedBorder.BorderLine.Z - 1; - } + // Make sure avatar position is in the region (why it wouldn't be is a mystery but do sanity checking) + if (acd.startpos.X < 0) acd.startpos.X = 1f; + if (acd.startpos.X >= RegionInfo.RegionSizeX) acd.startpos.X = RegionInfo.RegionSizeX - 1f; + if (acd.startpos.Y < 0) acd.startpos.Y = 1f; + if (acd.startpos.Y >= RegionInfo.RegionSizeY) acd.startpos.Y = RegionInfo.RegionSizeY - 1f; - if (TestBorderCross(agent.startpos, Cardinals.N)) - { - Border crossedBorder = GetCrossedBorder(agent.startpos, Cardinals.N); - agent.startpos.Y = crossedBorder.BorderLine.Z - 1; - } - - //Mitigate http://opensimulator.org/mantis/view.php?id=3522 - // Check if start position is outside of region - // If it is, check the Z start position also.. if not, leave it alone. - if (BordersLocked) - { - lock (EastBorders) - { - if (agent.startpos.X > EastBorders[0].BorderLine.Z) - { - m_log.Warn("FIX AGENT POSITION"); - agent.startpos.X = EastBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; - } - } - lock (NorthBorders) - { - if (agent.startpos.Y > NorthBorders[0].BorderLine.Z) - { - m_log.Warn("FIX Agent POSITION"); - agent.startpos.Y = NorthBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; - } - } - } else - { - if (agent.startpos.X > EastBorders[0].BorderLine.Z) - { - m_log.Warn("FIX AGENT POSITION"); - agent.startpos.X = EastBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; - } - if (agent.startpos.Y > NorthBorders[0].BorderLine.Z) - { - m_log.Warn("FIX Agent POSITION"); - agent.startpos.Y = NorthBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; - } - } +// m_log.DebugFormat( +// "[SCENE]: Found telehub object {0} for new user connection {1} to {2}", +// RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); // Honor Estate teleport routing via Telehubs excluding ViaHome and GodLike TeleportFlags if (RegionInfo.RegionSettings.TelehubObject != UUID.Zero && @@ -3820,26 +4075,49 @@ namespace OpenSim.Region.Framework.Scenes !viahome && !godlike) { SceneObjectGroup telehub = GetSceneObjectGroup(RegionInfo.RegionSettings.TelehubObject); - // Can have multiple SpawnPoints - List spawnpoints = RegionInfo.RegionSettings.SpawnPoints(); - if (spawnpoints.Count > 1) + + if (telehub != null) { - // We have multiple SpawnPoints, Route the agent to a random or sequential one - if (SpawnPointRouting == "random") - agent.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( - telehub.AbsolutePosition, - telehub.GroupRotation - ); + // Can have multiple SpawnPoints + List spawnpoints = RegionInfo.RegionSettings.SpawnPoints(); + if (spawnpoints.Count > 1) + { + // We have multiple SpawnPoints, Route the agent to a random or sequential one + if (SpawnPointRouting == "random") + acd.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( + telehub.AbsolutePosition, + telehub.GroupRotation + ); + else + acd.startpos = spawnpoints[SpawnPoint()].GetLocation( + telehub.AbsolutePosition, + telehub.GroupRotation + ); + } + else if (spawnpoints.Count == 1) + { + // We have a single SpawnPoint and will route the agent to it + acd.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + } else - agent.startpos = spawnpoints[SpawnPoint()].GetLocation( - telehub.AbsolutePosition, - telehub.GroupRotation - ); + { + m_log.DebugFormat( + "[SCENE]: No spawnpoints defined for telehub {0} for {1} in {2}. Continuing.", + RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); + } } else { - // We have a single SpawnPoint and will route the agent to it - agent.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + m_log.DebugFormat( + "[SCENE]: No telehub {0} found to direct {1} in {2}. Continuing.", + RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); + } + + // Final permissions check; this time we don't allow changing the position + if (!IsPositionAllowed(acd.AgentID, acd.startpos, ref reason)) + { + m_authenticateHandler.RemoveCircuit(acd.circuitcode); + return false; } return true; @@ -3850,7 +4128,14 @@ namespace OpenSim.Region.Framework.Scenes { if (land.LandData.LandingType == (byte)1 && land.LandData.UserLocation != Vector3.Zero) { - agent.startpos = land.LandData.UserLocation; + acd.startpos = land.LandData.UserLocation; + + // Final permissions check; this time we don't allow changing the position + if (!IsPositionAllowed(acd.AgentID, acd.startpos, ref reason)) + { + m_authenticateHandler.RemoveCircuit(acd.circuitcode); + return false; + } } } } @@ -3858,20 +4143,52 @@ namespace OpenSim.Region.Framework.Scenes return true; } - private bool TestLandRestrictions(AgentCircuitData agent, ILandObject land, out string reason) + private bool IsPositionAllowed(UUID agentID, Vector3 pos, ref string reason) { - bool banned = land.IsBannedFromLand(agent.AgentID); - bool restricted = land.IsRestrictedFromLand(agent.AgentID); + ILandObject land = LandChannel.GetLandObject(pos); + if (land == null) + return true; + + if (land.IsBannedFromLand(agentID) || land.IsRestrictedFromLand(agentID)) + { + reason = "You are banned from the region."; + return false; + } + + return true; + } + + public bool TestLandRestrictions(UUID agentID, out string reason, ref float posX, ref float posY) + { + if (posX < 0) + posX = 0; + else if (posX >= (float)RegionInfo.RegionSizeX) + posX = (float)RegionInfo.RegionSizeX - 0.001f; + if (posY < 0) + posY = 0; + else if (posY >= (float)RegionInfo.RegionSizeY) + posY = (float)RegionInfo.RegionSizeY - 0.001f; + + reason = String.Empty; + if (Permissions.IsGod(agentID)) + return true; + + ILandObject land = LandChannel.GetLandObject(posX, posY); + if (land == null) + return false; + + bool banned = land.IsBannedFromLand(agentID); + bool restricted = land.IsRestrictedFromLand(agentID); if (banned || restricted) { - ILandObject nearestParcel = GetNearestAllowedParcel(agent.AgentID, agent.startpos.X, agent.startpos.Y); + ILandObject nearestParcel = GetNearestAllowedParcel(agentID, posX, posY); if (nearestParcel != null) { //Move agent to nearest allowed Vector3 newPosition = GetParcelCenterAtGround(nearestParcel); - agent.startpos.X = newPosition.X; - agent.startpos.Y = newPosition.Y; + posX = newPosition.X; + posY = newPosition.Y; } else { @@ -3882,7 +4199,7 @@ namespace OpenSim.Region.Framework.Scenes else { reason = String.Format("Denied access to private region {0}: You are not on the access list for that region.", - RegionInfo.RegionName); + RegionInfo.RegionName); } return false; } @@ -3927,86 +4244,93 @@ namespace OpenSim.Region.Framework.Scenes /// outputs the reason to this string /// True if the region accepts this agent. False if it does not. False will /// also return a reason. - protected virtual bool AuthorizeUser(AgentCircuitData agent, out string reason) + protected virtual bool AuthorizeUser(AgentCircuitData agent, bool bypassAccessControl, out string reason) { reason = String.Empty; if (!m_strictAccessControl) return true; if (Permissions.IsGod(agent.AgentID)) return true; - + if (AuthorizationService != null) { if (!AuthorizationService.IsAuthorizedForRegion( agent.AgentID.ToString(), agent.firstname, agent.lastname, RegionInfo.RegionID.ToString(), out reason)) { - m_log.WarnFormat("[CONNECTION BEGIN]: Denied access to: {0} ({1} {2}) at {3} because {4}", + m_log.WarnFormat("[CONNECTION BEGIN]: Denied access to: {0} ({1} {2}) at {3} because: {4}", agent.AgentID, agent.firstname, agent.lastname, RegionInfo.RegionName, reason); - - return false; - } - } - if (RegionInfo.EstateSettings != null) - { - if (RegionInfo.EstateSettings.IsBanned(agent.AgentID)) - { - m_log.WarnFormat("[CONNECTION BEGIN]: Denied access to: {0} ({1} {2}) at {3} because the user is on the banlist", - agent.AgentID, agent.firstname, agent.lastname, RegionInfo.RegionName); - reason = String.Format("Denied access to region {0}: You have been banned from that region.", - RegionInfo.RegionName); return false; } } - else - { - m_log.ErrorFormat("[CONNECTION BEGIN]: Estate Settings is null!"); - } - List agentGroups = new List(); - - if (m_groupsModule != null) + // We only test the things below when we want to cut off + // child agents from being present in the scene for which their root + // agent isn't allowed. Otherwise, we allow child agents. The test for + // the root is done elsewhere (QueryAccess) + if (!bypassAccessControl) { - GroupMembershipData[] GroupMembership = m_groupsModule.GetMembershipData(agent.AgentID); - - if (GroupMembership != null) + if (RegionInfo.EstateSettings != null) { - for (int i = 0; i < GroupMembership.Length; i++) - agentGroups.Add(GroupMembership[i].GroupID); + if (RegionInfo.EstateSettings.IsBanned(agent.AgentID)) + { + m_log.WarnFormat("[CONNECTION BEGIN]: Denied access to: {0} ({1} {2}) at {3} because the user is on the banlist", + agent.AgentID, agent.firstname, agent.lastname, RegionInfo.RegionName); + reason = String.Format("Denied access to region {0}: You have been banned from that region.", + RegionInfo.RegionName); + return false; + } } else { - m_log.ErrorFormat("[CONNECTION BEGIN]: GroupMembership is null!"); + m_log.ErrorFormat("[CONNECTION BEGIN]: Estate Settings is null!"); } - } - bool groupAccess = false; - UUID[] estateGroups = RegionInfo.EstateSettings.EstateGroups; + List agentGroups = new List(); - if (estateGroups != null) - { - foreach (UUID group in estateGroups) + if (m_groupsModule != null) { - if (agentGroups.Contains(group)) + GroupMembershipData[] GroupMembership = m_groupsModule.GetMembershipData(agent.AgentID); + + if (GroupMembership != null) { - groupAccess = true; - break; + for (int i = 0; i < GroupMembership.Length; i++) + agentGroups.Add(GroupMembership[i].GroupID); + } + else + { + m_log.ErrorFormat("[CONNECTION BEGIN]: GroupMembership is null!"); } } - } - else - { - m_log.ErrorFormat("[CONNECTION BEGIN]: EstateGroups is null!"); - } - if (!RegionInfo.EstateSettings.PublicAccess && - !RegionInfo.EstateSettings.HasAccess(agent.AgentID) && - !groupAccess) - { - m_log.WarnFormat("[CONNECTION BEGIN]: Denied access to: {0} ({1} {2}) at {3} because the user does not have access to the estate", - agent.AgentID, agent.firstname, agent.lastname, RegionInfo.RegionName); - reason = String.Format("Denied access to private region {0}: You are not on the access list for that region.", - RegionInfo.RegionName); - return false; + bool groupAccess = false; + UUID[] estateGroups = RegionInfo.EstateSettings.EstateGroups; + + if (estateGroups != null) + { + foreach (UUID group in estateGroups) + { + if (agentGroups.Contains(group)) + { + groupAccess = true; + break; + } + } + } + else + { + m_log.ErrorFormat("[CONNECTION BEGIN]: EstateGroups is null!"); + } + + if (!RegionInfo.EstateSettings.PublicAccess && + !RegionInfo.EstateSettings.HasAccess(agent.AgentID) && + !groupAccess) + { + m_log.WarnFormat("[CONNECTION BEGIN]: Denied access to: {0} ({1} {2}) at {3} because the user does not have access to the estate", + agent.AgentID, agent.firstname, agent.lastname, RegionInfo.RegionName); + reason = String.Format("Denied access to private region {0}: You are not on the access list for that region.", + RegionInfo.RegionName); + return false; + } } // TODO: estate/region settings are not properly hooked up @@ -4096,33 +4420,33 @@ namespace OpenSim.Region.Framework.Scenes // } // } - /// - /// Triggered when an agent crosses into this sim. Also happens on initial login. - /// - /// - /// - /// - public virtual void AgentCrossing(UUID agentID, Vector3 position, bool isFlying) - { - ScenePresence presence = GetScenePresence(agentID); - if (presence != null) - { - try - { - presence.MakeRootAgent(position, isFlying); - } - catch (Exception e) - { - m_log.ErrorFormat("[SCENE]: Unable to do agent crossing, exception {0}{1}", e.Message, e.StackTrace); - } - } - else - { - m_log.ErrorFormat( - "[SCENE]: Could not find presence for agent {0} crossing into scene {1}", - agentID, RegionInfo.RegionName); - } - } +// /// +// /// Triggered when an agent crosses into this sim. Also happens on initial login. +// /// +// /// +// /// +// /// +// public virtual void AgentCrossing(UUID agentID, Vector3 position, bool isFlying) +// { +// ScenePresence presence = GetScenePresence(agentID); +// if (presence != null) +// { +// try +// { +// presence.MakeRootAgent(position, isFlying); +// } +// catch (Exception e) +// { +// m_log.ErrorFormat("[SCENE]: Unable to do agent crossing, exception {0}{1}", e.Message, e.StackTrace); +// } +// } +// else +// { +// m_log.ErrorFormat( +// "[SCENE]: Could not find presence for agent {0} crossing into scene {1}", +// agentID, RegionInfo.RegionName); +// } +// } /// /// We've got an update about an agent that sees into this region, @@ -4131,18 +4455,16 @@ namespace OpenSim.Region.Framework.Scenes /// Agent that contains all of the relevant things about an agent. /// Appearance, animations, position, etc. /// true if we handled it. - public virtual bool IncomingChildAgentDataUpdate(AgentData cAgentData) + public virtual bool IncomingUpdateChildAgent(AgentData cAgentData) { m_log.DebugFormat( "[SCENE]: Incoming child agent update for {0} in {1}", cAgentData.AgentID, RegionInfo.RegionName); - // XPTO: if this agent is not allowed here as root, always return false - // TODO: This check should probably be in QueryAccess(). - ILandObject nearestParcel = GetNearestAllowedParcel(cAgentData.AgentID, Constants.RegionSize / 2, Constants.RegionSize / 2); + ILandObject nearestParcel = GetNearestAllowedParcel(cAgentData.AgentID, RegionInfo.RegionSizeX / 2, RegionInfo.RegionSizeY / 2); if (nearestParcel == null) { - m_log.DebugFormat( + m_log.InfoFormat( "[SCENE]: Denying root agent entry to {0} in {1}: no allowed parcel", cAgentData.AgentID, RegionInfo.RegionName); @@ -4150,13 +4472,44 @@ namespace OpenSim.Region.Framework.Scenes } // We have to wait until the viewer contacts this region - // after receiving the EnableSimulator HTTP Event Queue message. This triggers the viewer to send - // a UseCircuitCode packet which in turn calls AddNewClient which finally creates the ScenePresence. - ScenePresence childAgentUpdate = WaitGetScenePresence(cAgentData.AgentID); + // after receiving the EnableSimulator HTTP Event Queue message (for the v1 teleport protocol) + // or TeleportFinish (for the v2 teleport protocol). This triggers the viewer to send + // a UseCircuitCode packet which in turn calls AddNewAgent which finally creates the ScenePresence. + ScenePresence sp = WaitGetScenePresence(cAgentData.AgentID); - if (childAgentUpdate != null) + if (sp != null) { - childAgentUpdate.ChildAgentDataUpdate(cAgentData); + if (cAgentData.SessionID != sp.ControllingClient.SessionId) + { + m_log.WarnFormat( + "[SCENE]: Attempt to update agent {0} with invalid session id {1} (possibly from simulator in older version; tell them to update).", + sp.UUID, cAgentData.SessionID); + + Console.WriteLine(String.Format("[SCENE]: Attempt to update agent {0} ({1}) with invalid session id {2}", + sp.UUID, sp.ControllingClient.SessionId, cAgentData.SessionID)); + } + + sp.UpdateChildAgent(cAgentData); + + int ntimes = 20; + if (cAgentData.SenderWantsToWaitForRoot) + { + while (sp.IsChildAgent && ntimes-- > 0) + Thread.Sleep(1000); + + if (sp.IsChildAgent) + m_log.WarnFormat( + "[SCENE]: Found presence {0} {1} unexpectedly still child in {2}", + sp.Name, sp.UUID, Name); + else + m_log.InfoFormat( + "[SCENE]: Found presence {0} {1} as root in {2} after {3} waits", + sp.Name, sp.UUID, Name, 20 - ntimes); + + if (sp.IsChildAgent) + return false; + } + return true; } @@ -4169,12 +4522,20 @@ namespace OpenSim.Region.Framework.Scenes /// /// AgentPosition that contains agent positional data so we can know what to send /// true if we handled it. - public virtual bool IncomingChildAgentDataUpdate(AgentPosition cAgentData) + public virtual bool IncomingUpdateChildAgent(AgentPosition cAgentData) { - //m_log.Debug(" XXX Scene IncomingChildAgentDataUpdate POSITION in " + RegionInfo.RegionName); +// m_log.DebugFormat( +// "[SCENE PRESENCE]: IncomingChildAgentDataUpdate POSITION for {0} in {1}, position {2}", +// cAgentData.AgentID, Name, cAgentData.Position); + ScenePresence childAgentUpdate = GetScenePresence(cAgentData.AgentID); if (childAgentUpdate != null) { +// if (childAgentUpdate.ControllingClient.SessionId != cAgentData.SessionID) +// // Only warn for now +// m_log.WarnFormat("[SCENE]: Attempt at updating position of agent {0} with invalid session id {1}. Neighbor running older version?", +// childAgentUpdate.UUID, cAgentData.SessionID); + // I can't imagine *yet* why we would get an update if the agent is a root agent.. // however to avoid a race condition crossing borders.. if (childAgentUpdate.IsChildAgent) @@ -4184,7 +4545,7 @@ namespace OpenSim.Region.Framework.Scenes uint tRegionX = RegionInfo.RegionLocX; uint tRegionY = RegionInfo.RegionLocY; //Send Data to ScenePresence - childAgentUpdate.ChildAgentDataUpdate(cAgentData, tRegionX, tRegionY, rRegionX, rRegionY); + childAgentUpdate.UpdateChildAgent(cAgentData, tRegionX, tRegionY, rRegionX, rRegionY); // Not Implemented: //TODO: Do we need to pass the message on to one of our neighbors? } @@ -4202,7 +4563,7 @@ namespace OpenSim.Region.Framework.Scenes /// protected virtual ScenePresence WaitGetScenePresence(UUID agentID) { - int ntimes = 10; + int ntimes = 20; ScenePresence sp = null; while ((sp = GetScenePresence(agentID)) == null && (ntimes-- > 0)) Thread.Sleep(1000); @@ -4211,28 +4572,92 @@ namespace OpenSim.Region.Framework.Scenes m_log.WarnFormat( "[SCENE PRESENCE]: Did not find presence with id {0} in {1} before timeout", agentID, RegionInfo.RegionName); -// else -// m_log.DebugFormat( -// "[SCENE PRESENCE]: Found presence {0} {1} {2} in {3} after {4} waits", -// sp.Name, sp.UUID, sp.IsChildAgent ? "child" : "root", RegionInfo.RegionName, 10 - ntimes); return sp; } - public virtual bool IncomingRetrieveRootAgent(UUID id, out IAgentData agent) + /// + /// Authenticated close (via network) + /// + /// + /// + /// + /// + public bool CloseAgent(UUID agentID, bool force, string auth_token) { - agent = null; - ScenePresence sp = GetScenePresence(id); - if ((sp != null) && (!sp.IsChildAgent)) + //m_log.DebugFormat("[SCENE]: Processing incoming close agent {0} in region {1} with auth_token {2}", agentID, RegionInfo.RegionName, auth_token); + + // Check that the auth_token is valid + AgentCircuitData acd = AuthenticateHandler.GetAgentCircuitData(agentID); + + if (acd == null) + { + m_log.DebugFormat( + "[SCENE]: Request to close agent {0} but no such agent in scene {1}. May have been closed previously.", + agentID, Name); + + return false; + } + + if (acd.SessionID.ToString() == auth_token) + { + return CloseAgent(agentID, force); + } + else { - sp.IsChildAgent = true; - return sp.CopyAgent(out agent); + m_log.WarnFormat( + "[SCENE]: Request to close agent {0} with invalid authorization token {1} in {2}", + agentID, auth_token, Name); } return false; } /// + /// Tell a single client to prepare to close. + /// + /// + /// This should only be called if we may close the client but there will be some delay in so doing. Meant for + /// internal use - other callers should almost certainly called CloseClient(). + /// + /// + /// true if pre-close state notification was successful. false if the agent + /// was not in a state where it could transition to pre-close. + public bool IncomingPreCloseClient(ScenePresence sp) + { + lock (m_removeClientLock) + { + // We need to avoid a race condition where in, for example, an A B C D region layout, an avatar may + // teleport from A -> D, but then -> C before A has asked B to close its old child agent. We do not + // want to obey this close since C may have renewed the child agent lease on B. + if (sp.DoNotCloseAfterTeleport) + { + m_log.DebugFormat( + "[SCENE]: Not pre-closing {0} agent {1} in {2} since another simulator has re-established the child connection", + sp.IsChildAgent ? "child" : "root", sp.Name, Name); + + // Need to reset the flag so that a subsequent close after another teleport can succeed. + sp.DoNotCloseAfterTeleport = false; + + return false; + } + + if (sp.LifecycleState != ScenePresenceState.Running) + { + m_log.DebugFormat( + "[SCENE]: Called IncomingPreCloseAgent() for {0} in {1} but presence is already in state {2}", + sp.Name, Name, sp.LifecycleState); + + return false; + } + + sp.LifecycleState = ScenePresenceState.PreRemove; + + return true; + } + } + + /// /// Tell a single agent to disconnect from the region. /// /// @@ -4240,19 +4665,53 @@ namespace OpenSim.Region.Framework.Scenes /// Force the agent to close even if it might be in the middle of some other operation. You do not want to /// force unless you are absolutely sure that the agent is dead and a normal close is not working. /// - public bool IncomingCloseAgent(UUID agentID, bool force) + public override bool CloseAgent(UUID agentID, bool force) { - //m_log.DebugFormat("[SCENE]: Processing incoming close agent for {0}", agentID); + ScenePresence sp; - ScenePresence presence = m_sceneGraph.GetScenePresence(agentID); - if (presence != null) + lock (m_removeClientLock) { - presence.ControllingClient.Close(force); - return true; + sp = GetScenePresence(agentID); + + if (sp == null) + { + m_log.DebugFormat( + "[SCENE]: Called CloseClient() with agent ID {0} but no such presence is in {1}", + agentID, Name); + + return false; + } + + if (sp.LifecycleState != ScenePresenceState.Running && sp.LifecycleState != ScenePresenceState.PreRemove) + { + m_log.DebugFormat( + "[SCENE]: Called CloseClient() for {0} in {1} but presence is already in state {2}", + sp.Name, Name, sp.LifecycleState); + + return false; + } + + // We need to avoid a race condition where in, for example, an A B C D region layout, an avatar may + // teleport from A -> D, but then -> C before A has asked B to close its old child agent. We do not + // want to obey this close since C may have renewed the child agent lease on B. + if (sp.DoNotCloseAfterTeleport) + { + m_log.DebugFormat( + "[SCENE]: Not closing {0} agent {1} in {2} since another simulator has re-established the child connection", + sp.IsChildAgent ? "child" : "root", sp.Name, Name); + + // Need to reset the flag so that a subsequent close after another teleport can succeed. + sp.DoNotCloseAfterTeleport = false; + + return false; + } + + sp.LifecycleState = ScenePresenceState.Removing; } - // Agent not here - return false; + sp.ControllingClient.Close(force); + + return true; } /// @@ -4295,44 +4754,6 @@ namespace OpenSim.Region.Framework.Scenes ScenePresence sp = GetScenePresence(remoteClient.AgentId); if (sp != null) { - uint regionX = RegionInfo.RegionLocX; - uint regionY = RegionInfo.RegionLocY; - - Utils.LongToUInts(regionHandle, out regionX, out regionY); - - int shiftx = (int) regionX - (int) RegionInfo.RegionLocX * (int)Constants.RegionSize; - int shifty = (int) regionY - (int) RegionInfo.RegionLocY * (int)Constants.RegionSize; - - position.X += shiftx; - position.Y += shifty; - - bool result = false; - - if (TestBorderCross(position,Cardinals.N)) - result = true; - - if (TestBorderCross(position, Cardinals.S)) - result = true; - - if (TestBorderCross(position, Cardinals.E)) - result = true; - - if (TestBorderCross(position, Cardinals.W)) - result = true; - - // bordercross if position is outside of region - - if (!result) - { - regionHandle = RegionInfo.RegionHandle; - } - else - { - // not in this region, undo the shift! - position.X -= shiftx; - position.Y -= shifty; - } - if (EntityTransferModule != null) { EntityTransferModule.Teleport(sp, regionHandle, position, lookAt, teleportFlags); @@ -4473,20 +4894,7 @@ namespace OpenSim.Region.Framework.Scenes #region Script Engine - private List ScriptEngines = new List(); - public bool DumpAssetsToFile; - - /// - /// - /// - /// - public void AddScriptEngine(ScriptEngineInterface scriptEngine) - { - ScriptEngines.Add(scriptEngine); - scriptEngine.InitializeEngine(this); - } - - private bool ScriptDanger(SceneObjectPart part,Vector3 pos) + private bool ScriptDanger(SceneObjectPart part, Vector3 pos) { ILandObject parcel = LandChannel.GetLandObject(pos.X, pos.Y); if (part != null) @@ -4497,35 +4905,24 @@ namespace OpenSim.Region.Framework.Scenes { return true; } - else if ((parcel.LandData.Flags & (uint)ParcelFlags.AllowGroupScripts) != 0) + else if ((part.OwnerID == parcel.LandData.OwnerID) || Permissions.IsGod(part.OwnerID)) { - if (part.OwnerID == parcel.LandData.OwnerID - || (parcel.LandData.IsGroupOwned && part.GroupID == parcel.LandData.GroupID) - || Permissions.IsGod(part.OwnerID)) - { - return true; - } - else - { - return false; - } + return true; + } + else if (((parcel.LandData.Flags & (uint)ParcelFlags.AllowGroupScripts) != 0) + && (parcel.LandData.GroupID != UUID.Zero) && (parcel.LandData.GroupID == part.GroupID)) + { + return true; } else { - if (part.OwnerID == parcel.LandData.OwnerID) - { - return true; - } - else - { - return false; - } + return false; } } else { - if (pos.X > 0f && pos.X < Constants.RegionSize && pos.Y > 0f && pos.Y < Constants.RegionSize) + if (pos.X > 0f && pos.X < RegionInfo.RegionSizeX && pos.Y > 0f && pos.Y < RegionInfo.RegionSizeY) { // The only time parcel != null when an object is inside a region is when // there is nothing behind the landchannel. IE, no land plugin loaded. @@ -4854,21 +5251,6 @@ namespace OpenSim.Region.Framework.Scenes #endregion - public void RegionHandleRequest(IClientAPI client, UUID regionID) - { - ulong handle = 0; - if (regionID == RegionInfo.RegionID) - handle = RegionInfo.RegionHandle; - else - { - GridRegion r = GridService.GetRegionByUUID(UUID.Zero, regionID); - if (r != null) - handle = r.RegionHandle; - } - - if (handle != 0) - client.SendRegionHandle(regionID, handle); - } // Commented pending deletion since this method no longer appears to do anything at all // public bool NeedSceneCacheClear(UUID agentID) @@ -4920,7 +5302,7 @@ namespace OpenSim.Region.Framework.Scenes // 3 = We have seen a new user enter within the past 4 minutes // which can be seen as positive confirmation of sim health // - int health=1; // Start at 1, means we're up + int health = 1; // Start at 1, means we're up if ((Util.EnvironmentTickCountSubtract(m_lastFrameTick)) < 1000) health += 1; @@ -4967,7 +5349,7 @@ namespace OpenSim.Region.Framework.Scenes case PhysicsJointType.Ball: { Vector3 jointAnchor = PhysicsScene.GetJointAnchor(joint); - Vector3 proxyPos = new Vector3(jointAnchor.X, jointAnchor.Y, jointAnchor.Z); + Vector3 proxyPos = jointAnchor; jointProxyObject.ParentGroup.UpdateGroupPosition(proxyPos); // schedules the entire group for a terse update } break; @@ -4992,7 +5374,7 @@ namespace OpenSim.Region.Framework.Scenes jointErrorMessage(joint, "joint.TrackedBodyName is null, joint " + joint.ObjectNameInScene); } - Vector3 proxyPos = new Vector3(jointAnchor.X, jointAnchor.Y, jointAnchor.Z); + Vector3 proxyPos = jointAnchor; Quaternion q = trackedBody.RotationOffset * joint.LocalRotation; jointProxyObject.ParentGroup.UpdateGroupPosition(proxyPos); // schedules the entire group for a terse update @@ -5081,6 +5463,10 @@ namespace OpenSim.Region.Framework.Scenes return null; } + // Get terrain height at the specified location. + // Presumes the underlying implementation is a heightmap which is a 1m grid. + // Finds heightmap grid points before and after the point and + // does a linear approximation of the height at this intermediate point. public float GetGroundHeight(float x, float y) { if (x < 0) @@ -5093,8 +5479,8 @@ namespace OpenSim.Region.Framework.Scenes y = Heightmap.Height - 1; Vector3 p0 = new Vector3(x, y, (float)Heightmap[(int)x, (int)y]); - Vector3 p1 = new Vector3(p0); - Vector3 p2 = new Vector3(p0); + Vector3 p1 = p0; + Vector3 p2 = p0; p1.X += 1.0f; if (p1.X < Heightmap.Width) @@ -5141,9 +5527,14 @@ namespace OpenSim.Region.Framework.Scenes get { return m_allowScriptCrossings; } } - public Vector3? GetNearestAllowedPosition(ScenePresence avatar) + public Vector3 GetNearestAllowedPosition(ScenePresence avatar) + { + return GetNearestAllowedPosition(avatar, null); + } + + public Vector3 GetNearestAllowedPosition(ScenePresence avatar, ILandObject excludeParcel) { - ILandObject nearestParcel = GetNearestAllowedParcel(avatar.UUID, avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + ILandObject nearestParcel = GetNearestAllowedParcel(avatar.UUID, avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, excludeParcel); if (nearestParcel != null) { @@ -5152,10 +5543,7 @@ namespace OpenSim.Region.Framework.Scenes Vector3? nearestPoint = GetNearestPointInParcelAlongDirectionFromPoint(avatar.AbsolutePosition, dir, nearestParcel); if (nearestPoint != null) { -// m_log.DebugFormat( -// "[SCENE]: Found a sane previous position based on velocity for {0}, sending them to {1} in {2}", -// avatar.Name, nearestPoint, nearestParcel.LandData.Name); - + m_log.Debug("Found a sane previous position based on velocity, sending them to: " + nearestPoint.ToString()); return nearestPoint.Value; } @@ -5165,24 +5553,27 @@ namespace OpenSim.Region.Framework.Scenes nearestPoint = GetNearestPointInParcelAlongDirectionFromPoint(avatar.AbsolutePosition, dir, nearestParcel); if (nearestPoint != null) { -// m_log.DebugFormat( -// "[SCENE]: {0} had a zero velocity, sending them to {1}", avatar.Name, nearestPoint); - + m_log.Debug("They had a zero velocity, sending them to: " + nearestPoint.ToString()); return nearestPoint.Value; } - //Ultimate backup if we have no idea where they are -// m_log.DebugFormat( -// "[SCENE]: No idea where {0} is, sending them to {1}", avatar.Name, avatar.lastKnownAllowedPosition); + ILandObject dest = LandChannel.GetLandObject(avatar.lastKnownAllowedPosition.X, avatar.lastKnownAllowedPosition.Y); + if (dest != excludeParcel) + { + // Ultimate backup if we have no idea where they are and + // the last allowed position was in another parcel + m_log.Debug("Have no idea where they are, sending them to: " + avatar.lastKnownAllowedPosition.ToString()); + return avatar.lastKnownAllowedPosition; + } - return avatar.lastKnownAllowedPosition; + // else fall through to region edge } //Go to the edge, this happens in teleporting to a region with no available parcels Vector3 nearestRegionEdgePoint = GetNearestRegionEdgePosition(avatar); - //Debug.WriteLine("They are really in a place they don't belong, sending them to: " + nearestRegionEdgePoint.ToString()); - + //m_log.Debug("They are really in a place they don't belong, sending them to: " + nearestRegionEdgePoint.ToString()); + return nearestRegionEdgePoint; } @@ -5196,7 +5587,7 @@ namespace OpenSim.Region.Framework.Scenes { Vector3 unitDirection = Vector3.Normalize(direction); //Making distance to search go through some sane limit of distance - for (float distance = 0; distance < Constants.RegionSize * 2; distance += .5f) + for (float distance = 0; distance < Math.Max(RegionInfo.RegionSizeX, RegionInfo.RegionSizeY) * 2; distance += .5f) { Vector3 testPos = Vector3.Add(pos, Vector3.Multiply(unitDirection, distance)); if (parcel.ContainsPoint((int)testPos.X, (int)testPos.Y)) @@ -5209,13 +5600,18 @@ namespace OpenSim.Region.Framework.Scenes public ILandObject GetNearestAllowedParcel(UUID avatarId, float x, float y) { + return GetNearestAllowedParcel(avatarId, x, y, null); + } + + public ILandObject GetNearestAllowedParcel(UUID avatarId, float x, float y, ILandObject excludeParcel) + { List all = AllParcels(); float minParcelDistance = float.MaxValue; ILandObject nearestParcel = null; foreach (var parcel in all) { - if (!parcel.IsEitherBannedOrRestricted(avatarId)) + if (!parcel.IsEitherBannedOrRestricted(avatarId) && parcel != excludeParcel) { float parcelDistance = GetParcelDistancefromPoint(parcel, x, y); if (parcelDistance < minParcelDistance) @@ -5245,9 +5641,9 @@ namespace OpenSim.Region.Framework.Scenes int count = 0; int avgx = 0; int avgy = 0; - for (int x = 0; x < Constants.RegionSize; x++) + for (int x = 0; x < RegionInfo.RegionSizeX; x++) { - for (int y = 0; y < Constants.RegionSize; y++) + for (int y = 0; y < RegionInfo.RegionSizeY; y++) { //Just keep a running average as we check if all the points are inside or not if (parcel.ContainsPoint(x, y)) @@ -5271,31 +5667,33 @@ namespace OpenSim.Region.Framework.Scenes private Vector3 GetNearestRegionEdgePosition(ScenePresence avatar) { - float xdistance = avatar.AbsolutePosition.X < Constants.RegionSize / 2 ? avatar.AbsolutePosition.X : Constants.RegionSize - avatar.AbsolutePosition.X; - float ydistance = avatar.AbsolutePosition.Y < Constants.RegionSize / 2 ? avatar.AbsolutePosition.Y : Constants.RegionSize - avatar.AbsolutePosition.Y; + float xdistance = avatar.AbsolutePosition.X < RegionInfo.RegionSizeX / 2 + ? avatar.AbsolutePosition.X : RegionInfo.RegionSizeX - avatar.AbsolutePosition.X; + float ydistance = avatar.AbsolutePosition.Y < RegionInfo.RegionSizeY / 2 + ? avatar.AbsolutePosition.Y : RegionInfo.RegionSizeY - avatar.AbsolutePosition.Y; //find out what vertical edge to go to if (xdistance < ydistance) { - if (avatar.AbsolutePosition.X < Constants.RegionSize / 2) + if (avatar.AbsolutePosition.X < RegionInfo.RegionSizeX / 2) { return GetPositionAtAvatarHeightOrGroundHeight(avatar, 0.0f, avatar.AbsolutePosition.Y); } else { - return GetPositionAtAvatarHeightOrGroundHeight(avatar, Constants.RegionSize, avatar.AbsolutePosition.Y); + return GetPositionAtAvatarHeightOrGroundHeight(avatar, RegionInfo.RegionSizeY, avatar.AbsolutePosition.Y); } } //find out what horizontal edge to go to else { - if (avatar.AbsolutePosition.Y < Constants.RegionSize / 2) + if (avatar.AbsolutePosition.Y < RegionInfo.RegionSizeY / 2) { return GetPositionAtAvatarHeightOrGroundHeight(avatar, avatar.AbsolutePosition.X, 0.0f); } else { - return GetPositionAtAvatarHeightOrGroundHeight(avatar, avatar.AbsolutePosition.X, Constants.RegionSize); + return GetPositionAtAvatarHeightOrGroundHeight(avatar, avatar.AbsolutePosition.X, RegionInfo.RegionSizeY); } } } @@ -5336,33 +5734,7 @@ namespace OpenSim.Region.Framework.Scenes public void TriggerEstateSunUpdate() { - float sun; - if (RegionInfo.RegionSettings.UseEstateSun) - { - sun = (float)RegionInfo.EstateSettings.SunPosition; - if (RegionInfo.EstateSettings.UseGlobalTime) - { - sun = EventManager.GetCurrentTimeAsSunLindenHour() - 6.0f; - } - - // - EventManager.TriggerEstateToolsSunUpdate( - RegionInfo.RegionHandle, - RegionInfo.EstateSettings.FixedSun, - RegionInfo.RegionSettings.UseEstateSun, - sun); - } - else - { - // Use the Sun Position from the Region Settings - sun = (float)RegionInfo.RegionSettings.SunPosition - 6.0f; - - EventManager.TriggerEstateToolsSunUpdate( - RegionInfo.RegionHandle, - RegionInfo.RegionSettings.FixedSun, - RegionInfo.RegionSettings.UseEstateSun, - sun); - } + EventManager.TriggerEstateToolsSunUpdate(RegionInfo.RegionHandle); } private void HandleReloadEstate(string module, string[] cmd) @@ -5387,15 +5759,15 @@ namespace OpenSim.Region.Framework.Scenes /// /// public static Vector3[] GetCombinedBoundingBox( - List objects, + List objects, out float minX, out float maxX, out float minY, out float maxY, out float minZ, out float maxZ) { - minX = 256; - maxX = -256; - minY = 256; - maxY = -256; - minZ = 8192; - maxZ = -256; + minX = float.MaxValue; + maxX = float.MinValue; + minY = float.MaxValue; + maxY = float.MinValue; + minZ = float.MaxValue; + maxZ = float.MinValue; List offsets = new List(); @@ -5406,7 +5778,7 @@ namespace OpenSim.Region.Framework.Scenes Vector3 vec = g.AbsolutePosition; g.GetAxisAlignedBoundingBoxRaw(out ominX, out omaxX, out ominY, out omaxY, out ominZ, out omaxZ); - + // m_log.DebugFormat( // "[SCENE]: For {0} found AxisAlignedBoundingBoxRaw {1}, {2}", // g.Name, new Vector3(ominX, ominY, ominZ), new Vector3(omaxX, omaxY, omaxZ)); @@ -5465,7 +5837,7 @@ namespace OpenSim.Region.Framework.Scenes // so that all simulators can retrieve it string error = GridService.RegisterRegion(RegionInfo.ScopeID, new GridRegion(RegionInfo)); if (error != string.Empty) - throw new Exception(error); + throw new Exception(error); } /// @@ -5479,22 +5851,23 @@ namespace OpenSim.Region.Framework.Scenes /// or corssing the broder walking, but will NOT prevent /// child agent creation, thereby emulating the SL behavior. /// - /// + /// The visitor's User ID + /// The visitor's Home URI (may be null) /// /// /// - public bool QueryAccess(UUID agentID, Vector3 position, out string reason) + public bool QueryAccess(UUID agentID, string agentHomeURI, bool viaTeleport, Vector3 position, List features, out string reason) { - if (EntityTransferModule.IsInTransit(agentID)) - { - reason = "Agent is still in transit from this region"; + reason = string.Empty; - m_log.WarnFormat( - "[SCENE]: Denying agent {0} entry into {1} since region still has them registered as in transit", - agentID, RegionInfo.RegionName); + if (Permissions.IsGod(agentID)) + { + reason = String.Empty; + return true; + } + if (!AllowAvatarCrossing && !viaTeleport) return false; - } // FIXME: Root agent count is currently known to be inaccurate. This forces a recount before we check. // However, the long term fix is to make sure root agent count is always accurate. @@ -5516,7 +5889,43 @@ namespace OpenSim.Region.Framework.Scenes } } - if (position == Vector3.Zero) // Teleport + ScenePresence presence = GetScenePresence(agentID); + IClientAPI client = null; + AgentCircuitData aCircuit = null; + + if (presence != null) + { + client = presence.ControllingClient; + if (client != null) + aCircuit = client.RequestClientInfo(); + } + + // We may be called before there is a presence or a client. + // Fake AgentCircuitData to keep IAuthorizationModule smiling + if (client == null) + { + aCircuit = new AgentCircuitData(); + aCircuit.AgentID = agentID; + aCircuit.firstname = String.Empty; + aCircuit.lastname = String.Empty; + } + + try + { + if (!AuthorizeUser(aCircuit, false, out reason)) + { + //m_log.DebugFormat("[SCENE]: Denying access for {0}", agentID); + return false; + } + } + catch (Exception e) + { + m_log.DebugFormat("[SCENE]: Exception authorizing agent: {0} " + e.StackTrace, e.Message); + reason = "Error authorizing agent: " + e.Message; + return false; + } + + if (viaTeleport) { if (!RegionInfo.EstateSettings.AllowDirectTeleport) { @@ -5539,11 +5948,47 @@ namespace OpenSim.Region.Framework.Scenes if (banned) { - reason = "No suitable landing point found"; - return false; + if (Permissions.IsAdministrator(agentID) == false || Permissions.IsGridGod(agentID) == false) + { + reason = "No suitable landing point found"; + return false; + } + reason = "Administrative access only"; + return true; } } } + + float posX = 128.0f; + float posY = 128.0f; + + if (!TestLandRestrictions(agentID, out reason, ref posX, ref posY)) + { + // m_log.DebugFormat("[SCENE]: Denying {0} because they are banned on all parcels", agentID); + reason = "You are banned from the region on all parcels"; + return false; + } + } + else // Walking + { + ILandObject land = LandChannel.GetLandObject(position.X, position.Y); + if (land == null) + { + reason = "No parcel found"; + return false; + } + + bool banned = land.IsBannedFromLand(agentID); + bool restricted = land.IsRestrictedFromLand(agentID); + + if (banned || restricted) + { + if (banned) + reason = "You are banned from the parcel"; + else + reason = "The parcel is restricted"; + return false; + } } reason = String.Empty; diff --git a/OpenSim/Region/Framework/Scenes/SceneBase.cs b/OpenSim/Region/Framework/Scenes/SceneBase.cs index d3e968e..7ff3d40 100644 --- a/OpenSim/Region/Framework/Scenes/SceneBase.cs +++ b/OpenSim/Region/Framework/Scenes/SceneBase.cs @@ -44,6 +44,10 @@ namespace OpenSim.Region.Framework.Scenes { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +#pragma warning disable 414 + private static readonly string LogHeader = "[SCENE]"; +#pragma warning restore 414 + #region Events public event restart OnRestart; @@ -78,6 +82,13 @@ namespace OpenSim.Region.Framework.Scenes /// protected Dictionary> ModuleInterfaces = new Dictionary>(); + /// + /// These two objects hold the information about any formats used + /// by modules that hold agent specific data. + /// + protected List FormatsOffered = new List(); + protected Dictionary> FormatsWanted = new Dictionary>(); + protected Dictionary ModuleAPIMethods = new Dictionary(); /// @@ -141,10 +152,6 @@ namespace OpenSim.Region.Framework.Scenes get { return 1.0f; } } - protected ulong m_regionHandle; - protected string m_regionName; - protected RegionInfo m_regInfo; - public ITerrainChannel Heightmap; /// @@ -192,7 +199,8 @@ namespace OpenSim.Region.Framework.Scenes /// Number of frames to update. Exits on shutdown even if there are frames remaining. /// If -1 then updates until shutdown. /// - public abstract void Update(int frames); + /// true if update completed within minimum frame time, false otherwise. + public abstract bool Update(int frames); #endregion @@ -209,15 +217,21 @@ namespace OpenSim.Region.Framework.Scenes /// Client to send to public virtual void SendLayerData(IClientAPI RemoteClient) { - RemoteClient.SendLayerData(Heightmap.GetFloatsSerialised()); + // RemoteClient.SendLayerData(Heightmap.GetFloatsSerialised()); + ITerrainModule terrModule = RequestModuleInterface(); + if (terrModule != null) + { + terrModule.PushTerrain(RemoteClient); + } } #endregion #region Add/Remove Agent/Avatar - public abstract ISceneAgent AddNewClient(IClientAPI client, PresenceType type); - public abstract void RemoveClient(UUID agentID, bool closeChildAgents); + public abstract ISceneAgent AddNewAgent(IClientAPI client, PresenceType type); + + public abstract bool CloseAgent(UUID agentID, bool force); public bool TryGetScenePresence(UUID agentID, out object scenePresence) { @@ -360,6 +374,38 @@ namespace OpenSim.Region.Framework.Scenes return m_moduleCommanders; } + public List GetFormatsOffered() + { + List ret = new List(FormatsOffered); + + return ret; + } + + protected void CheckAndAddAgentDataFormats(object mod) + { + if (!(mod is IAgentStatefulModule)) + return; + + IAgentStatefulModule m = (IAgentStatefulModule)mod; + + List renderFormats = m.GetRenderStateFormats(); + List acceptFormats = m.GetAcceptStateFormats(); + + foreach (UUID render in renderFormats) + { + if (!(FormatsOffered.Contains(render))) + FormatsOffered.Add(render); + } + + if (acceptFormats.Count == 0) + return; + + if (FormatsWanted.ContainsKey(mod)) + return; + + FormatsWanted[mod] = acceptFormats; + } + /// /// Register an interface to a region module. This allows module methods to be called directly as /// well as via events. If there is already a module registered for this interface, it is not replaced @@ -382,6 +428,8 @@ namespace OpenSim.Region.Framework.Scenes l.Add(mod); + CheckAndAddAgentDataFormats(mod); + if (mod is IEntityCreator) { IEntityCreator entityCreator = (IEntityCreator)mod; @@ -394,6 +442,14 @@ namespace OpenSim.Region.Framework.Scenes public void UnregisterModuleInterface(M mod) { + // We can't unregister agent stateful modules because + // that would require much more data to be held about formats + // and would make that code slower and less efficient. + // No known modules are unregistered anyway, ever, unless + // the simulator shuts down anyway. + if (mod is IAgentStatefulModule) + return; + List l; if (ModuleInterfaces.TryGetValue(typeof(M), out l)) { @@ -424,6 +480,8 @@ namespace OpenSim.Region.Framework.Scenes l.Add(mod); + CheckAndAddAgentDataFormats(mod); + if (mod is IEntityCreator) { IEntityCreator entityCreator = (IEntityCreator)mod; @@ -561,6 +619,10 @@ namespace OpenSim.Region.Framework.Scenes get { return false; } } + public virtual void Start() + { + } + public void Restart() { // This has to be here to fire the event diff --git a/OpenSim/Region/Framework/Scenes/SceneCommunicationService.cs b/OpenSim/Region/Framework/Scenes/SceneCommunicationService.cs index 305f8a4..b9526da 100644 --- a/OpenSim/Region/Framework/Scenes/SceneCommunicationService.cs +++ b/OpenSim/Region/Framework/Scenes/SceneCommunicationService.cs @@ -35,7 +35,6 @@ using OpenMetaverse.StructuredData; using log4net; using OpenSim.Framework; using OpenSim.Framework.Client; -using OpenSim.Framework.Communications; using OpenSim.Framework.Capabilities; using OpenSim.Region.Framework.Interfaces; using OpenSim.Services.Interfaces; @@ -52,6 +51,7 @@ namespace OpenSim.Region.Framework.Scenes public class SceneCommunicationService //one instance per region { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[SCENE COMMUNICATION SERVICE]"; protected RegionInfo m_regionInfo; protected Scene m_scene; @@ -84,15 +84,12 @@ namespace OpenSim.Region.Framework.Scenes if (neighbourService != null) neighbour = neighbourService.HelloNeighbour(regionhandle, region); else - m_log.DebugFormat( - "[SCENE COMMUNICATION SERVICE]: No neighbour service provided for region {0} to inform neigbhours of status", - m_scene.Name); + m_log.DebugFormat( "{0} neighbour service provided for region {0} to inform neigbhours of status", LogHeader, m_scene.Name); if (neighbour != null) { - m_log.DebugFormat( - "[SCENE COMMUNICATION SERVICE]: Region {0} successfully informed neighbour {1} at {2}-{3} that it is up", - m_scene.Name, neighbour.RegionName, x / Constants.RegionSize, y / Constants.RegionSize); + m_log.DebugFormat( "{0} Region {1} successfully informed neighbour {2} at {3}-{4} that it is up", + LogHeader, m_scene.Name, neighbour.RegionName, Util.WorldToRegionLoc(x), Util.WorldToRegionLoc(y)); m_scene.EventManager.TriggerOnRegionUp(neighbour); } @@ -100,7 +97,7 @@ namespace OpenSim.Region.Framework.Scenes { m_log.WarnFormat( "[SCENE COMMUNICATION SERVICE]: Region {0} failed to inform neighbour at {1}-{2} that it is up.", - m_scene.Name, x / Constants.RegionSize, y / Constants.RegionSize); + m_scene.Name, Util.WorldToRegionLoc(x), Util.WorldToRegionLoc(y)); } } @@ -111,12 +108,35 @@ namespace OpenSim.Region.Framework.Scenes List neighbours = m_scene.GridService.GetNeighbours(m_scene.RegionInfo.ScopeID, m_scene.RegionInfo.RegionID); - m_log.DebugFormat( - "[SCENE COMMUNICATION SERVICE]: Informing {0} neighbours that region {1} is up", - neighbours.Count, m_scene.Name); + List onlineNeighbours = new List(); foreach (GridRegion n in neighbours) { + OpenSim.Framework.RegionFlags? regionFlags = n.RegionFlags; + +// m_log.DebugFormat( +// "{0}: Region flags for {1} as seen by {2} are {3}", +// LogHeader, n.RegionName, m_scene.Name, regionFlags != null ? regionFlags.ToString() : "not present"); + + // Robust services before 2015-01-14 do not return the regionFlags information. In this case, we could + // make a separate RegionFlags call but this would involve a network call for each neighbour. + if (regionFlags != null) + { + if ((regionFlags & OpenSim.Framework.RegionFlags.RegionOnline) != 0) + onlineNeighbours.Add(n); + } + else + { + onlineNeighbours.Add(n); + } + } + + m_log.DebugFormat( + "{0} Informing {1} neighbours that region {2} is up", + LogHeader, onlineNeighbours.Count, m_scene.Name); + + foreach (GridRegion n in onlineNeighbours) + { InformNeighbourThatRegionUpDelegate d = InformNeighboursThatRegionIsUpAsync; d.BeginInvoke(neighbourService, region, n.RegionHandle, InformNeighborsThatRegionisUpCompleted, @@ -154,6 +174,10 @@ namespace OpenSim.Region.Framework.Scenes public void SendChildAgentDataUpdate(AgentPosition cAgentData, ScenePresence presence) { +// m_log.DebugFormat( +// "[SCENE COMMUNICATION SERVICE]: Sending child agent position updates for {0} in {1}", +// presence.Name, m_scene.Name); + // This assumes that we know what our neighbors are. try { @@ -166,7 +190,7 @@ namespace OpenSim.Region.Framework.Scenes // we only want to send one update to each simulator; the simulator will // hand it off to the regions where a child agent exists, this does assume // that the region position is cached or performance will degrade - Utils.LongToUInts(regionHandle, out x, out y); + Util.RegionHandleToWorldLoc(regionHandle, out x, out y); GridRegion dest = m_scene.GridService.GetRegionByPosition(UUID.Zero, (int)x, (int)y); if (dest == null) continue; @@ -197,20 +221,20 @@ namespace OpenSim.Region.Framework.Scenes /// /// Closes a child agent on a given region /// - protected void SendCloseChildAgent(UUID agentID, ulong regionHandle) + protected void SendCloseChildAgent(UUID agentID, ulong regionHandle, string auth_token) { // let's do our best, but there's not much we can do if the neighbour doesn't accept. //m_commsProvider.InterRegion.TellRegionToCloseChildConnection(regionHandle, agentID); uint x = 0, y = 0; - Utils.LongToUInts(regionHandle, out x, out y); + Util.RegionHandleToWorldLoc(regionHandle, out x, out y); GridRegion destination = m_scene.GridService.GetRegionByPosition(m_regionInfo.ScopeID, (int)x, (int)y); m_log.DebugFormat( "[SCENE COMMUNICATION SERVICE]: Sending close agent ID {0} to {1}", agentID, destination.RegionName); - m_scene.SimulationService.CloseAgent(destination, agentID); + m_scene.SimulationService.CloseAgent(destination, agentID, auth_token); } /// @@ -219,11 +243,17 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - public void SendCloseChildAgentConnections(UUID agentID, List regionslst) + public void SendCloseChildAgentConnections(UUID agentID, string auth_code, List regionslst) { foreach (ulong handle in regionslst) { - SendCloseChildAgent(agentID, handle); + // We must take a copy here since handle is acts like a reference when used in an iterator. + // This leads to race conditions if directly passed to SendCloseChildAgent with more than one neighbour region. + ulong handleCopy = handle; + Util.FireAndForget( + o => SendCloseChildAgent(agentID, handleCopy, auth_code), + null, + "SceneCommunicationService.SendCloseChildAgentConnections"); } } diff --git a/OpenSim/Region/Framework/Scenes/SceneGraph.cs b/OpenSim/Region/Framework/Scenes/SceneGraph.cs old mode 100644 new mode 100755 index a4383fd..e0080f2 --- a/OpenSim/Region/Framework/Scenes/SceneGraph.cs +++ b/OpenSim/Region/Framework/Scenes/SceneGraph.cs @@ -34,7 +34,7 @@ using OpenMetaverse.Packets; using log4net; using OpenSim.Framework; using OpenSim.Region.Framework.Scenes.Types; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Region.Framework.Scenes @@ -67,7 +67,9 @@ namespace OpenSim.Region.Framework.Scenes protected Scene m_parentScene; protected Dictionary m_updateList = new Dictionary(); protected int m_numRootAgents = 0; + protected int m_numTotalPrim = 0; protected int m_numPrim = 0; + protected int m_numMesh = 0; protected int m_numChildAgents = 0; protected int m_physicalPrim = 0; @@ -109,7 +111,12 @@ namespace OpenSim.Region.Framework.Scenes public PhysicsScene PhysicsScene { - get { return _PhyScene; } + get + { + if (_PhyScene == null) + _PhyScene = m_parentScene.RequestModuleInterface(); + return _PhyScene; + } set { // If we're not doing the initial set @@ -155,9 +162,9 @@ namespace OpenSim.Region.Framework.Scenes // PhysX does this (runs in the background). - if (_PhyScene.IsThreaded) + if (PhysicsScene.IsThreaded) { - _PhyScene.GetResults(); + PhysicsScene.GetResults(); } } @@ -197,7 +204,7 @@ namespace OpenSim.Region.Framework.Scenes // position). // // Therefore, JointMoved and JointDeactivated events will be fired as a result of the following Simulate(). - return _PhyScene.Simulate((float)elapsed); + return PhysicsScene.Simulate((float)elapsed); } protected internal void UpdateScenePresenceMovement() @@ -368,7 +375,8 @@ namespace OpenSim.Region.Framework.Scenes SceneObjectPart[] parts = sceneObject.Parts; - // Clamp child prim sizes and add child prims to the m_numPrim count + // Clamp the sizes (scales) of the child prims and add the child prims to the count of all primitives + // (meshes and geometric primitives) in the scene; add child prims to m_numTotalPrim count if (m_parentScene.m_clampPrimSize) { foreach (SceneObjectPart part in parts) @@ -382,7 +390,19 @@ namespace OpenSim.Region.Framework.Scenes part.Shape.Scale = scale; } } - m_numPrim += parts.Length; + m_numTotalPrim += parts.Length; + + // Go through all parts (geometric primitives and meshes) of this Scene Object + foreach (SceneObjectPart part in parts) + { + // Keep track of the total number of meshes or geometric primitives now in the scene; + // determine which object this is based on its primitive type: sculpted (sculpt) prim refers to + // a mesh and all other prims (i.e. box, sphere, etc) are geometric primitives + if (part.GetPrimType() == PrimType.SCULPT) + m_numMesh++; + else + m_numPrim++; + } sceneObject.AttachToScene(m_parentScene); @@ -437,7 +457,21 @@ namespace OpenSim.Region.Framework.Scenes if (!resultOfObjectLinked) { - m_numPrim -= grp.PrimCount; + // Decrement the total number of primitives (meshes and geometric primitives) + // that are part of the Scene Object being removed + m_numTotalPrim -= grp.PrimCount; + + // Go through all parts (primitives and meshes) of this Scene Object + foreach (SceneObjectPart part in grp.Parts) + { + // Keep track of the total number of meshes or geometric primitives left in the scene; + // determine which object this is based on its primitive type: sculpted (sculpt) prim refers to + // a mesh and all other prims (i.e. box, sphere, etc) are geometric primitives + if (part.GetPrimType() == PrimType.SCULPT) + m_numMesh--; + else + m_numPrim--; + } if ((grp.RootPart.Flags & PrimFlags.Physics) == PrimFlags.Physics) RemovePhysicalPrim(grp.PrimCount); @@ -519,12 +553,12 @@ namespace OpenSim.Region.Framework.Scenes protected internal void AddPhysicalPrim(int number) { - m_physicalPrim++; + m_physicalPrim += number; } protected internal void RemovePhysicalPrim(int number) { - m_physicalPrim--; + m_physicalPrim -= number; } protected internal void AddToScriptLPS(int number) @@ -561,39 +595,15 @@ namespace OpenSim.Region.Framework.Scenes protected internal ScenePresence CreateAndAddChildScenePresence( IClientAPI client, AvatarAppearance appearance, PresenceType type) { - ScenePresence newAvatar = null; - // ScenePresence always defaults to child agent - newAvatar = new ScenePresence(client, m_parentScene, appearance, type); - - AddScenePresence(newAvatar); - - return newAvatar; - } - - /// - /// Add a presence to the scene - /// - /// - protected internal void AddScenePresence(ScenePresence presence) - { - // Always a child when added to the scene - bool child = presence.IsChildAgent; - - if (child) - { - m_numChildAgents++; - } - else - { - m_numRootAgents++; - presence.AddToPhysicalScene(false); - } + ScenePresence presence = new ScenePresence(client, m_parentScene, appearance, type); Entities[presence.UUID] = presence; lock (m_presenceLock) { + m_numChildAgents++; + Dictionary newmap = new Dictionary(m_scenePresenceMap); List newlist = new List(m_scenePresenceArray); @@ -604,7 +614,7 @@ namespace OpenSim.Region.Framework.Scenes } else { - // Remember the old presene reference from the dictionary + // Remember the old presence reference from the dictionary ScenePresence oldref = newmap[presence.UUID]; // Replace the presence reference in the dictionary with the new value newmap[presence.UUID] = presence; @@ -616,6 +626,8 @@ namespace OpenSim.Region.Framework.Scenes m_scenePresenceMap = newmap; m_scenePresenceArray = newlist; } + + return presence; } /// @@ -709,9 +721,19 @@ namespace OpenSim.Region.Framework.Scenes public int GetTotalObjectsCount() { + return m_numTotalPrim; + } + + public int GetTotalPrimObjectsCount() + { return m_numPrim; } + public int GetTotalMeshObjectsCount() + { + return m_numMesh; + } + public int GetActiveObjectsCount() { return m_physicalPrim; @@ -794,7 +816,8 @@ namespace OpenSim.Region.Framework.Scenes List presences = GetScenePresences(); foreach (ScenePresence presence in presences) { - if (presence.Firstname == firstName && presence.Lastname == lastName) + if (string.Equals(presence.Firstname, firstName, StringComparison.CurrentCultureIgnoreCase) + && string.Equals(presence.Lastname, lastName, StringComparison.CurrentCultureIgnoreCase)) return presence; } return null; @@ -1348,12 +1371,23 @@ namespace OpenSim.Region.Framework.Scenes /// /// Update the position of the given group. /// - /// + /// /// /// - public void UpdatePrimGroupPosition(uint localID, Vector3 pos, IClientAPI remoteClient) + public void UpdatePrimGroupPosition(uint localId, Vector3 pos, IClientAPI remoteClient) { - SceneObjectGroup group = GetGroupByPrim(localID); + UpdatePrimGroupPosition(localId, pos, remoteClient.AgentId); + } + + /// + /// Update the position of the given group. + /// + /// + /// + /// + public void UpdatePrimGroupPosition(uint localId, Vector3 pos, UUID updatingAgentId) + { + SceneObjectGroup group = GetGroupByPrim(localId); if (group != null) { @@ -1364,7 +1398,7 @@ namespace OpenSim.Region.Framework.Scenes } else { - if (m_parentScene.Permissions.CanMoveObject(group.UUID, remoteClient.AgentId) + if (m_parentScene.Permissions.CanMoveObject(group.UUID, updatingAgentId) && m_parentScene.Permissions.CanObjectEntry(group.UUID, false, pos)) { group.UpdateGroupPosition(pos); @@ -1408,7 +1442,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// protected internal void UpdatePrimFlags( - uint localID, bool UsePhysics, bool SetTemporary, bool SetPhantom, IClientAPI remoteClient) + uint localID, bool UsePhysics, bool SetTemporary, bool SetPhantom, ExtraPhysicsData PhysData, IClientAPI remoteClient) { SceneObjectGroup group = GetGroupByPrim(localID); if (group != null) @@ -1416,7 +1450,28 @@ namespace OpenSim.Region.Framework.Scenes if (m_parentScene.Permissions.CanEditObject(group.UUID, remoteClient.AgentId)) { // VolumeDetect can't be set via UI and will always be off when a change is made there - group.UpdatePrimFlags(localID, UsePhysics, SetTemporary, SetPhantom, false); + // now only change volume dtc if phantom off + + if (PhysData.PhysShapeType == PhysShapeType.invalid) // check for extraPhysics data + { + bool vdtc; + if (SetPhantom) // if phantom keep volumedtc + vdtc = group.RootPart.VolumeDetectActive; + else // else turn it off + vdtc = false; + + group.UpdatePrimFlags(localID, UsePhysics, SetTemporary, SetPhantom, vdtc); + } + else + { + SceneObjectPart part = GetSceneObjectPart(localID); + if (part != null) + { + part.UpdateExtraPhysics(PhysData); + if (part.UpdatePhysRequired) + remoteClient.SendPartPhysicsProprieties(part); + } + } } } } @@ -1435,8 +1490,9 @@ namespace OpenSim.Region.Framework.Scenes { if (m_parentScene.Permissions.CanMoveObject(group.UUID, remoteClient.AgentId))// && PermissionsMngr.) { - group.GrabMovement(offset, pos, remoteClient); + group.GrabMovement(objectID, offset, pos, remoteClient); } + // This is outside the above permissions condition // so that if the object is locked the client moving the object // get's it's position on the simulator even if it was the same as before @@ -1624,6 +1680,12 @@ namespace OpenSim.Region.Framework.Scenes /// protected internal void LinkObjects(SceneObjectPart root, List children) { + if (root.KeyframeMotion != null) + { + root.KeyframeMotion.Stop(); + root.KeyframeMotion = null; + } + SceneObjectGroup parentGroup = root.ParentGroup; if (parentGroup == null) return; @@ -1701,6 +1763,11 @@ namespace OpenSim.Region.Framework.Scenes { if (part != null) { + if (part.KeyframeMotion != null) + { + part.KeyframeMotion.Stop(); + part.KeyframeMotion = null; + } if (part.ParentGroup.PrimCount != 1) // Skip single { if (part.LinkNum < 2) // Root @@ -1884,7 +1951,7 @@ namespace OpenSim.Region.Framework.Scenes if (original == null) { m_log.WarnFormat( - "[SCENEGRAPH]: Attempt to duplicate nonexistant prim id {0} by {1}", originalPrimID, AgentID); + "[SCENEGRAPH]: Attempt to duplicate nonexistent prim id {0} by {1}", originalPrimID, AgentID); return null; } @@ -1947,7 +2014,19 @@ namespace OpenSim.Region.Framework.Scenes // think it's selected, so it will never send a deselect... copy.IsSelected = false; - m_numPrim += copy.Parts.Length; + m_numTotalPrim += copy.Parts.Length; + + // Go through all parts (primitives and meshes) of this Scene Object + foreach (SceneObjectPart part in copy.Parts) + { + // Keep track of the total number of meshes or geometric primitives now in the scene; + // determine which object this is based on its primitive type: sculpted (sculpt) prim refers to + // a mesh and all other prims (i.e. box, sphere, etc) are geometric primitives + if (part.GetPrimType() == PrimType.SCULPT) + m_numMesh++; + else + m_numPrim++; + } if (rot != Quaternion.Identity) { diff --git a/OpenSim/Region/Framework/Scenes/SceneManager.cs b/OpenSim/Region/Framework/Scenes/SceneManager.cs index 1e2e973..28f7896 100644 --- a/OpenSim/Region/Framework/Scenes/SceneManager.cs +++ b/OpenSim/Region/Framework/Scenes/SceneManager.cs @@ -331,35 +331,30 @@ namespace OpenSim.Region.Framework.Scenes public void SendCommandToPluginModules(string[] cmdparams) { - ForEachCurrentScene(delegate(Scene scene) { scene.SendCommandToPlugins(cmdparams); }); + ForEachSelectedScene(delegate(Scene scene) { scene.SendCommandToPlugins(cmdparams); }); } public void SetBypassPermissionsOnCurrentScene(bool bypassPermissions) { - ForEachCurrentScene(delegate(Scene scene) { scene.Permissions.SetBypassPermissions(bypassPermissions); }); + ForEachSelectedScene(delegate(Scene scene) { scene.Permissions.SetBypassPermissions(bypassPermissions); }); } - private void ForEachCurrentScene(Action func) + public void ForEachSelectedScene(Action func) { if (CurrentScene == null) - { - lock (m_localScenes) - m_localScenes.ForEach(func); - } + ForEachScene(func); else - { func(CurrentScene); - } } public void RestartCurrentScene() { - ForEachCurrentScene(delegate(Scene scene) { scene.RestartNow(); }); + ForEachSelectedScene(delegate(Scene scene) { scene.RestartNow(); }); } public void BackupCurrentScene() { - ForEachCurrentScene(delegate(Scene scene) { scene.Backup(true); }); + ForEachSelectedScene(delegate(Scene scene) { scene.Backup(true); }); } public bool TrySetCurrentScene(string regionName) @@ -482,34 +477,11 @@ namespace OpenSim.Region.Framework.Scenes return false; } - /// - /// Set the debug packet level on each current scene. This level governs which packets are printed out to the - /// console. - /// - /// - /// Name of avatar to debug - public void SetDebugPacketLevelOnCurrentScene(int newDebug, string name) - { - ForEachCurrentScene(scene => - scene.ForEachScenePresence(sp => - { - if (name == null || sp.Name == name) - { - m_log.DebugFormat( - "Packet debug for {0} ({1}) set to {2}", - sp.Name, sp.IsChildAgent ? "child" : "root", newDebug); - - sp.ControllingClient.DebugPacketLevel = newDebug; - } - }) - ); - } - public List GetCurrentSceneAvatars() { List avatars = new List(); - ForEachCurrentScene( + ForEachSelectedScene( delegate(Scene scene) { scene.ForEachRootScenePresence(delegate(ScenePresence scenePresence) @@ -526,7 +498,7 @@ namespace OpenSim.Region.Framework.Scenes { List presences = new List(); - ForEachCurrentScene(delegate(Scene scene) + ForEachSelectedScene(delegate(Scene scene) { scene.ForEachScenePresence(delegate(ScenePresence sp) { @@ -555,12 +527,12 @@ namespace OpenSim.Region.Framework.Scenes public void ForceCurrentSceneClientUpdate() { - ForEachCurrentScene(delegate(Scene scene) { scene.ForceClientUpdate(); }); + ForEachSelectedScene(delegate(Scene scene) { scene.ForceClientUpdate(); }); } public void HandleEditCommandOnCurrentScene(string[] cmdparams) { - ForEachCurrentScene(delegate(Scene scene) { scene.HandleEditCommand(cmdparams); }); + ForEachSelectedScene(delegate(Scene scene) { scene.HandleEditCommand(cmdparams); }); } public bool TryGetScenePresence(UUID avatarId, out ScenePresence avatar) diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.Inventory.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.Inventory.cs index ddf5da0..81cef5b 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.Inventory.cs @@ -34,6 +34,7 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using System.Collections.Generic; using System.Xml; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.Framework.Scenes { @@ -66,6 +67,12 @@ namespace OpenSim.Region.Framework.Scenes { int scriptsStarted = 0; + if (m_scene == null) + { + m_log.DebugFormat("[PRIM INVENTORY]: m_scene is null. Unable to create script instances"); + return 0; + } + // Don't start scripts if they're turned off in the region! if (!m_scene.RegionInfo.RegionSettings.DisableScripts) { @@ -258,6 +265,7 @@ namespace OpenSim.Region.Framework.Scenes for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; +// m_log.DebugFormat("[SCENE OBJECT GROUP INVENTORY]: Effective perms of {0} are {1}", part.Name, (OpenMetaverse.PermissionMask)part.OwnerMask); ownerMask &= part.OwnerMask; perms &= part.Inventory.MaskEffectivePermissions(); } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 15795e5..d08237e 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -38,8 +38,10 @@ using OpenMetaverse; using OpenMetaverse.Packets; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Region.Framework.Scenes.Serialization; +using PermissionMask = OpenSim.Framework.PermissionMask; +using OpenSim.Services.Interfaces; namespace OpenSim.Region.Framework.Scenes { @@ -74,6 +76,7 @@ namespace OpenSim.Region.Framework.Scenes touch = 8, touch_end = 536870912, touch_start = 2097152, + transaction_result = 33554432, object_rez = 4194304 } @@ -108,6 +111,9 @@ namespace OpenSim.Region.Framework.Scenes STATUS_ROTATE_Z = 0x008, } + // This flag has the same purpose as InventoryItemFlags.ObjectSlamPerm + public static readonly uint SLAM = 16; + // private PrimCountTaintedDelegate handlerPrimCountTainted = null; /// @@ -145,12 +151,27 @@ namespace OpenSim.Region.Framework.Scenes get { return m_hasGroupChanged; } } + + private bool m_groupContainsForeignPrims = false; /// - /// Has the group changed due to an unlink operation? We record this in order to optimize deletion, since - /// an unlinked group currently has to be persisted to the database before we can perform an unlink operation. + /// Whether the group contains prims that came from a different group. This happens when + /// linking or delinking groups. The implication is that until the group is persisted, + /// the prims in the database still use the old SceneGroupID. That's a problem if the group + /// is deleted, because we delete groups by searching for prims by their SceneGroupID. /// - public bool HasGroupChangedDueToDelink { get; private set; } + public bool GroupContainsForeignPrims + { + private set + { + m_groupContainsForeignPrims = value; + if (m_groupContainsForeignPrims) + HasGroupChanged = true; + } + + get { return m_groupContainsForeignPrims; } + } + private bool isTimeToPersist() { @@ -267,7 +288,10 @@ namespace OpenSim.Region.Framework.Scenes private Vector3 lastPhysGroupPos; private Quaternion lastPhysGroupRot; - private bool m_isBackedUp; + /// + /// Is this entity set to be saved in persistent storage? + /// + public bool Backup { get; private set; } protected MapAndArray m_parts = new MapAndArray(); @@ -329,7 +353,7 @@ namespace OpenSim.Region.Framework.Scenes { get { - Vector3 minScale = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionSize); + Vector3 minScale = new Vector3(Constants.MaximumRegionSize, Constants.MaximumRegionSize, Constants.MaximumRegionSize); Vector3 maxScale = Vector3.Zero; Vector3 finalScale = new Vector3(0.5f, 0.5f, 0.5f); @@ -424,9 +448,16 @@ namespace OpenSim.Region.Framework.Scenes /// public bool IsAttachmentCheckFull() { - return (IsAttachment || (m_rootPart.Shape.PCode == 9 && m_rootPart.Shape.State != 0)); + return (IsAttachment || + (m_rootPart.Shape.PCode == (byte)PCodeEnum.Primitive && m_rootPart.Shape.State != 0)); } + private struct avtocrossInfo + { + public ScenePresence av; + public uint ParentID; + } + /// /// The absolute position of this scene object in the scene /// @@ -440,24 +471,140 @@ namespace OpenSim.Region.Framework.Scenes if (Scene != null) { if ( - // (Scene.TestBorderCross(val - Vector3.UnitX, Cardinals.E) - // || Scene.TestBorderCross(val + Vector3.UnitX, Cardinals.W) - // || Scene.TestBorderCross(val - Vector3.UnitY, Cardinals.N) - // || Scene.TestBorderCross(val + Vector3.UnitY, Cardinals.S)) - // Experimental change for better border crossings. - // The commented out original lines above would, it seems, trigger - // a border crossing a little early or late depending on which - // direction the object was moving. - (Scene.TestBorderCross(val, Cardinals.E) - || Scene.TestBorderCross(val, Cardinals.W) - || Scene.TestBorderCross(val, Cardinals.N) - || Scene.TestBorderCross(val, Cardinals.S)) - && !IsAttachmentCheckFull() && (!Scene.LoadingPrims)) + !Scene.PositionIsInCurrentRegion(val) + && !IsAttachmentCheckFull() + && (!Scene.LoadingPrims) + ) { - m_scene.CrossPrimGroupIntoNewRegion(val, this, true); + IEntityTransferModule entityTransfer = m_scene.RequestModuleInterface(); + EntityTransferContext ctx = new EntityTransferContext(); + Vector3 newpos = Vector3.Zero; + string failureReason = String.Empty; + OpenSim.Services.Interfaces.GridRegion destination = null; + + if (m_rootPart.KeyframeMotion != null) + m_rootPart.KeyframeMotion.StartCrossingCheck(); + + bool canCross = true; + foreach (ScenePresence av in GetSittingAvatars()) + { + // We need to cross these agents. First, let's find + // out if any of them can't cross for some reason. + // We have to deny the crossing entirely if any + // of them are banned. Alternatively, we could + // unsit banned agents.... + + + // We set the avatar position as being the object + // position to get the region to send to + if ((destination = entityTransfer.GetDestination(m_scene, av.UUID, val, ctx, out newpos, out failureReason)) == null) + { + canCross = false; + break; + } + + m_log.DebugFormat("[SCENE OBJECT]: Avatar {0} needs to be crossed to {1}", av.Name, destination.RegionName); + } + + if (canCross) + { + // We unparent the SP quietly so that it won't + // be made to stand up + + List avsToCross = new List(); + + foreach (ScenePresence av in GetSittingAvatars()) + { + avtocrossInfo avinfo = new avtocrossInfo(); + SceneObjectPart parentPart = m_scene.GetSceneObjectPart(av.ParentID); + if (parentPart != null) + av.ParentUUID = parentPart.UUID; + + avinfo.av = av; + avinfo.ParentID = av.ParentID; + avsToCross.Add(avinfo); + + av.PrevSitOffset = av.OffsetPosition; + av.ParentID = 0; + } + + m_scene.CrossPrimGroupIntoNewRegion(val, this, true); + + // Normalize + if (val.X >= m_scene.RegionInfo.RegionSizeX) + val.X -= m_scene.RegionInfo.RegionSizeX; + if (val.Y >= m_scene.RegionInfo.RegionSizeY) + val.Y -= m_scene.RegionInfo.RegionSizeY; + if (val.X < 0) + val.X += m_scene.RegionInfo.RegionSizeX; + if (val.Y < 0) + val.Y += m_scene.RegionInfo.RegionSizeY; + + // If it's deleted, crossing was successful + if (IsDeleted) + { + foreach (avtocrossInfo avinfo in avsToCross) + { + ScenePresence av = avinfo.av; + if (!av.IsInTransit) // just in case... + { + m_log.DebugFormat("[SCENE OBJECT]: Crossing avatar {0} to {1}", av.Name, val); + + av.IsInTransit = true; + + // A temporary measure to allow regression tests to work. + // Quite possibly, all BeginInvoke() calls should be replaced by Util.FireAndForget + // or similar since BeginInvoke() always uses the system threadpool to launch + // threads rather than any replace threadpool that we might be using. + if (Util.FireAndForgetMethod == FireAndForgetMethod.RegressionTest) + { + entityTransfer.CrossAgentToNewRegionAsync(av, val, destination, av.Flying, ctx); + CrossAgentToNewRegionCompleted(av); + } + else + { + CrossAgentToNewRegionDelegate d = entityTransfer.CrossAgentToNewRegionAsync; + d.BeginInvoke( + av, val, destination, av.Flying, ctx, + ar => CrossAgentToNewRegionCompleted(d.EndInvoke(ar)), null); + } + } + else + { + m_log.DebugFormat("[SCENE OBJECT]: Not crossing avatar {0} to {1} because it's already in transit", av.Name, val); + } + } + + return; + } + else // cross failed, put avas back ?? + { + foreach (avtocrossInfo avinfo in avsToCross) + { + ScenePresence av = avinfo.av; + av.ParentUUID = UUID.Zero; + av.ParentID = avinfo.ParentID; + } + } + } + else + { + if (m_rootPart.KeyframeMotion != null) + m_rootPart.KeyframeMotion.CrossingFailure(); + + if (RootPart.PhysActor != null) + { + RootPart.PhysActor.CrossingFailure(); + } + } + + Vector3 oldp = AbsolutePosition; + val.X = Util.Clamp(oldp.X, 0.5f, (float)m_scene.RegionInfo.RegionSizeX - 0.5f); + val.Y = Util.Clamp(oldp.Y, 0.5f, (float)m_scene.RegionInfo.RegionSizeY - 0.5f); + val.Z = Util.Clamp(oldp.Z, 0.5f, Constants.RegionHeight); } } - + if (RootPart.GetStatusSandbox()) { if (Util.GetDistanceTo(RootPart.StatusSandboxPos, value) > 10) @@ -491,6 +638,36 @@ namespace OpenSim.Region.Framework.Scenes } } + public override Vector3 Velocity + { + get { return RootPart.Velocity; } + set { RootPart.Velocity = value; } + } + + private void CrossAgentToNewRegionCompleted(ScenePresence agent) + { + //// If the cross was successful, this agent is a child agent + if (agent.IsChildAgent) + { + if (agent.ParentUUID != UUID.Zero) + { + agent.ParentPart = null; +// agent.ParentPosition = Vector3.Zero; +// agent.ParentUUID = UUID.Zero; + } + } + + agent.ParentUUID = UUID.Zero; +// agent.Reset(); +// else // Not successful +// agent.RestoreInCurrentScene(); + + // In any case + agent.IsInTransit = false; + + m_log.DebugFormat("[SCENE OBJECT]: Crossing agent {0} {1} completed.", agent.Firstname, agent.Lastname); + } + public override uint LocalId { get { return m_rootPart.LocalId; } @@ -548,7 +725,11 @@ namespace OpenSim.Region.Framework.Scenes set { m_rootPart.Text = value; } } - protected virtual bool InSceneBackup + /// + /// If set to true then the scene object can be backed up in principle, though this will only actually occur + /// if Backup is set. If false then the scene object will never be backed up, Backup will always be false. + /// + protected virtual bool CanBeBackedUp { get { return true; } } @@ -577,6 +758,8 @@ namespace OpenSim.Region.Framework.Scenes childPa.Selected = value; } } + if (RootPart.KeyframeMotion != null) + RootPart.KeyframeMotion.Selected = value; } } @@ -648,6 +831,12 @@ namespace OpenSim.Region.Framework.Scenes public UUID FromFolderID { get; set; } /// + /// If true then grabs are blocked no matter what the individual part BlockGrab setting. + /// + /// true if block grab override; otherwise, false. + public bool BlockGrabOverride { get; set; } + + /// /// IDs of all avatars sat on this scene object. /// /// @@ -657,7 +846,7 @@ namespace OpenSim.Region.Framework.Scenes /// No avatar should appear more than once in this list. /// Do not manipulate this list directly - use the Add/Remove sitting avatar methods on SceneObjectPart. /// - protected internal List m_sittingAvatars = new List(); + protected internal List m_sittingAvatars = new List(); #endregion @@ -722,20 +911,60 @@ namespace OpenSim.Region.Framework.Scenes } } + public void LoadScriptState(XmlReader reader) + { +// m_log.DebugFormat("[SCENE OBJECT GROUP]: Looking for script state for {0}", Name); + + while (true) + { + if (reader.Name == "SavedScriptState" && reader.NodeType == XmlNodeType.Element) + { +// m_log.DebugFormat("[SCENE OBJECT GROUP]: Loading script state for {0}", Name); + + if (m_savedScriptState == null) + m_savedScriptState = new Dictionary(); + + string uuid = reader.GetAttribute("UUID"); + + // Even if there is no UUID attribute for some strange reason, we must always read the inner XML + // so we don't continually keep checking the same SavedScriptedState element. + string innerXml = reader.ReadInnerXml(); + + if (uuid != null) + { +// m_log.DebugFormat("[SCENE OBJECT GROUP]: Found state for item ID {0} in object {1}", uuid, Name); + + UUID itemid = new UUID(uuid); + if (itemid != UUID.Zero) + m_savedScriptState[itemid] = innerXml; + } + else + { + m_log.WarnFormat("[SCENE OBJECT GROUP]: SavedScriptState element had no UUID in object {0}", Name); + } + } + else + { + if (!reader.Read()) + break; + } + } + } + /// /// Hooks this object up to the backup event so that it is persisted to the database when the update thread executes. /// public virtual void AttachToBackup() { - if (InSceneBackup) + if (CanBeBackedUp) { - //m_log.DebugFormat( - // "[SCENE OBJECT GROUP]: Attaching object {0} {1} to scene presistence sweep", Name, UUID); +// m_log.DebugFormat( +// "[SCENE OBJECT GROUP]: Attaching object {0} {1} to scene presistence sweep", Name, UUID); - if (!m_isBackedUp) + if (!Backup) m_scene.EventManager.OnBackup += ProcessBackup; - m_isBackedUp = true; + Backup = true; } } @@ -757,6 +986,11 @@ namespace OpenSim.Region.Framework.Scenes for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; + if (part.KeyframeMotion != null) + { + part.KeyframeMotion.UpdateSceneObject(this); + } + if (Object.ReferenceEquals(part, m_rootPart)) continue; @@ -831,9 +1065,9 @@ namespace OpenSim.Region.Framework.Scenes maxX = -256f; maxY = -256f; maxZ = -256f; - minX = 256f; - minY = 256f; - minZ = 8192f; + minX = 10000f; + minY = 10000f; + minZ = 10000f; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) @@ -1085,6 +1319,7 @@ namespace OpenSim.Region.Framework.Scenes } } + /// /// /// @@ -1220,11 +1455,11 @@ namespace OpenSim.Region.Framework.Scenes /// /// Delete this group from its scene. /// - /// + /// /// This only handles the in-world consequences of deletion (e.g. any avatars sitting on it are forcibly stood /// up and all avatars receive notification of its removal. Removal of the scene object from database backup /// must be handled by the caller. - /// + /// /// If true then deletion is not broadcast to clients public void DeleteGroupFromScene(bool silent) { @@ -1233,10 +1468,10 @@ namespace OpenSim.Region.Framework.Scenes { SceneObjectPart part = parts[i]; - Scene.ForEachRootScenePresence(delegate(ScenePresence avatar) + Scene.ForEachScenePresence(sp => { - if (avatar.ParentID == LocalId) - avatar.StandUp(); + if (!sp.IsChildAgent && sp.ParentID == part.LocalId) + sp.StandUp(); if (!silent) { @@ -1244,9 +1479,9 @@ namespace OpenSim.Region.Framework.Scenes if (part == m_rootPart) { if (!IsAttachment - || AttachedAvatar == avatar.ControllingClient.AgentId + || AttachedAvatar == sp.UUID || !HasPrivateAttachmentPoint) - avatar.ControllingClient.SendKillObject(m_regionHandle, new List { part.LocalId }); + sp.ControllingClient.SendKillObject(new List { part.LocalId }); } } }); @@ -1359,7 +1594,7 @@ namespace OpenSim.Region.Framework.Scenes /// public virtual void ProcessBackup(ISimulationDataService datastore, bool forcedBackup) { - if (!m_isBackedUp) + if (!Backup) { // m_log.DebugFormat( // "[WATER WARS]: Ignoring backup of {0} {1} since object is not marked to be backed up", Name, UUID); @@ -1421,7 +1656,7 @@ namespace OpenSim.Region.Framework.Scenes backup_group.RootPart.AngularVelocity = RootPart.AngularVelocity; backup_group.RootPart.ParticleSystem = RootPart.ParticleSystem; HasGroupChanged = false; - HasGroupChangedDueToDelink = false; + GroupContainsForeignPrims = false; m_scene.EventManager.TriggerOnSceneObjectPreSave(backup_group, this); datastore.StoreObject(backup_group, m_scene.RegionInfo.RegionID); @@ -1480,31 +1715,14 @@ namespace OpenSim.Region.Framework.Scenes /// public SceneObjectGroup Copy(bool userExposed) { + // FIXME: This is dangerous since it's easy to forget to reset some references when necessary and end up + // with bugs that only occur in some circumstances (e.g. crossing between regions on the same simulator + // but not between regions on different simulators). Really, all copying should be done explicitly. SceneObjectGroup dupe = (SceneObjectGroup)MemberwiseClone(); - dupe.m_isBackedUp = false; - dupe.m_parts = new MapAndArray(); - - // Warning, The following code related to previousAttachmentStatus is needed so that clones of - // attachments do not bordercross while they're being duplicated. This is hacktastic! - // Normally, setting AbsolutePosition will bordercross a prim if it's outside the region! - // unless IsAttachment is true!, so to prevent border crossing, we save it's attachment state - // (which should be false anyway) set it as an Attachment and then set it's Absolute Position, - // then restore it's attachment state - - // This is only necessary when userExposed is false! - - bool previousAttachmentStatus = dupe.IsAttachment; - - if (!userExposed) - dupe.IsAttachment = true; - - dupe.AbsolutePosition = new Vector3(AbsolutePosition.X, AbsolutePosition.Y, AbsolutePosition.Z); - - if (!userExposed) - { - dupe.IsAttachment = previousAttachmentStatus; - } + dupe.Backup = false; + dupe.m_parts = new MapAndArray(); + dupe.m_sittingAvatars = new List(); dupe.CopyRootPart(m_rootPart, OwnerID, GroupID, userExposed); dupe.m_rootPart.LinkNum = m_rootPart.LinkNum; @@ -1550,6 +1768,8 @@ namespace OpenSim.Region.Framework.Scenes newPart.DoPhysicsPropertyUpdate(originalPartPa.IsPhysical, true); } + if (part.KeyframeMotion != null) + newPart.KeyframeMotion = part.KeyframeMotion.Copy(dupe); } if (userExposed) @@ -1577,6 +1797,12 @@ namespace OpenSim.Region.Framework.Scenes public void ScriptSetPhysicsStatus(bool usePhysics) { + if (usePhysics) + { + if (RootPart.KeyframeMotion != null) + RootPart.KeyframeMotion.Stop(); + RootPart.KeyframeMotion = null; + } UpdatePrimFlags(RootPart.LocalId, usePhysics, IsTemporary, IsPhantom, IsVolumeDetect); } @@ -1674,15 +1900,14 @@ namespace OpenSim.Region.Framework.Scenes return Vector3.Zero; } - public void moveToTarget(Vector3 target, float tau) + public void MoveToTarget(Vector3 target, float tau) { if (IsAttachment) { ScenePresence avatar = m_scene.GetScenePresence(AttachedAvatar); + if (avatar != null) - { avatar.MoveToTarget(target, false, false); - } } else { @@ -1697,12 +1922,26 @@ namespace OpenSim.Region.Framework.Scenes } } - public void stopMoveToTarget() + public void StopMoveToTarget() { - PhysicsActor pa = RootPart.PhysActor; + if (IsAttachment) + { + ScenePresence avatar = m_scene.GetScenePresence(AttachedAvatar); - if (pa != null) - pa.PIDActive = false; + if (avatar != null) + avatar.ResetMoveToTarget(); + } + else + { + PhysicsActor pa = RootPart.PhysActor; + + if (pa != null && pa.PIDActive) + { + pa.PIDActive = false; + + ScheduleGroupForTerseUpdate(); + } + } } /// @@ -2175,7 +2414,9 @@ namespace OpenSim.Region.Framework.Scenes // objectGroup.m_rootPart = null; // If linking prims with different permissions, fix them - AdjustChildPrimPermissions(); + AdjustChildPrimPermissions(false); + + GroupContainsForeignPrims = true; AttachToBackup(); @@ -2320,9 +2561,16 @@ namespace OpenSim.Region.Framework.Scenes linkPart.Rezzed = RootPart.Rezzed; - // When we delete a group, we currently have to force persist to the database if the object id has changed - // (since delete works by deleting all rows which have a given object id) - objectGroup.HasGroupChangedDueToDelink = true; + // We must persist the delinked group to the database immediately, for safety. The problem + // is that although in memory the new group has a new SceneGroupID, in the database it + // still has the parent group's SceneGroupID (until the next backup). This means that if the + // parent group is deleted then the delinked group will also be deleted from the database. + // This problem will disappear if the region remains alive long enough for another backup, + // since at that time the delinked group's new SceneGroupID will be written to the database. + // But if the region crashes before that then the prims will be permanently gone, and this must + // not happen. (We can't use a just-in-time trick like GroupContainsForeignPrims in this case + // because the delinked group doesn't know when the source group is deleted.) + m_scene.ForceSceneObjectBackup(objectGroup); return objectGroup; } @@ -2333,10 +2581,10 @@ namespace OpenSim.Region.Framework.Scenes /// public virtual void DetachFromBackup() { - if (m_isBackedUp && Scene != null) + if (Backup && Scene != null) m_scene.EventManager.OnBackup -= ProcessBackup; - m_isBackedUp = false; + Backup = false; } // This links an SOP from a previous linkset into my linkset. @@ -2396,20 +2644,26 @@ namespace OpenSim.Region.Framework.Scenes /// If object is physical, apply force to move it around /// If object is not physical, just put it at the resulting location /// + /// Part ID to check for grab /// Always seems to be 0,0,0, so ignoring /// New position. We do the math here to turn it into a force /// - public void GrabMovement(Vector3 offset, Vector3 pos, IClientAPI remoteClient) + public void GrabMovement(UUID partID, Vector3 offset, Vector3 pos, IClientAPI remoteClient) { if (m_scene.EventManager.TriggerGroupMove(UUID, pos)) { + SceneObjectPart part = GetPart(partID); + + if (part == null) + return; + PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) { if (pa.IsPhysical) { - if (!m_rootPart.BlockGrab) + if (!BlockGrabOverride && !part.BlockGrab) { Vector3 llmoveforce = pos - AbsolutePosition; Vector3 grabforce = llmoveforce; @@ -2420,20 +2674,27 @@ namespace OpenSim.Region.Framework.Scenes } else { - //NonPhysicalGrabMovement(pos); + NonPhysicalGrabMovement(pos); } } else { - //NonPhysicalGrabMovement(pos); + NonPhysicalGrabMovement(pos); } } } + /// + /// Apply possition for grabbing non-physical linksets (Ctrl+Drag) + /// This MUST be blocked for linksets that contain touch scripts because the viewer triggers grab on the touch + /// event (Viewer Bug?) This would allow anyone to drag a linkset with a touch script. SL behaviour is also to + /// block grab on prims with touch events. + /// + /// New Position public void NonPhysicalGrabMovement(Vector3 pos) { - AbsolutePosition = pos; - m_rootPart.SendTerseUpdateToAllClients(); + if(!IsAttachment && ScriptCount() == 0) + UpdateGroupPosition(pos); } /// @@ -2529,17 +2790,28 @@ namespace OpenSim.Region.Framework.Scenes } else { - //NonPhysicalSpinMovement(pos); + NonPhysicalSpinMovement(newOrientation); } } else { - //NonPhysicalSpinMovement(pos); + NonPhysicalSpinMovement(newOrientation); } } } /// + /// Apply rotation for spinning non-physical linksets (Ctrl+Shift+Drag) + /// As with dragging, scripted objects must be blocked from spinning + /// + /// New Rotation + private void NonPhysicalSpinMovement(Quaternion newOrientation) + { + if(!IsAttachment && ScriptCount() == 0) + UpdateGroupRotationR(newOrientation); + } + + /// /// Set the name of a prim /// /// @@ -2612,12 +2884,22 @@ namespace OpenSim.Region.Framework.Scenes { SceneObjectPart selectionPart = GetPart(localID); - if (SetTemporary && Scene != null) + if (Scene != null) { - DetachFromBackup(); - // Remove from database and parcel prim count - // - m_scene.DeleteFromStorage(UUID); + if (SetTemporary) + { + DetachFromBackup(); + // Remove from database and parcel prim count + // + m_scene.DeleteFromStorage(UUID); + } + else if (!Backup) + { + // Previously been temporary now switching back so make it + // available for persisting again + AttachToBackup(); + } + m_scene.EventManager.TriggerParcelPrimCountTainted(); } @@ -2668,13 +2950,29 @@ namespace OpenSim.Region.Framework.Scenes } } - public void AdjustChildPrimPermissions() + public void AdjustChildPrimPermissions(bool forceTaskInventoryPermissive) { + uint newOwnerMask = (uint)(PermissionMask.All | PermissionMask.Export) & 0xfffffff8; // Mask folded bits + uint foldedPerms = RootPart.OwnerMask & 3; + ForEachPart(part => { + newOwnerMask &= part.BaseMask; if (part != RootPart) part.ClonePermissions(RootPart); + if (forceTaskInventoryPermissive) + part.Inventory.ApplyGodPermissions(part.BaseMask); }); + + uint lockMask = ~(uint)(PermissionMask.Move | PermissionMask.Modify); + uint lockBit = RootPart.OwnerMask & (uint)(PermissionMask.Move | PermissionMask.Modify); + RootPart.OwnerMask = (RootPart.OwnerMask & lockBit) | ((newOwnerMask | foldedPerms) & lockMask); + +// m_log.DebugFormat( +// "[SCENE OBJECT GROUP]: RootPart.OwnerMask now {0} for {1} in {2}", +// (OpenMetaverse.PermissionMask)RootPart.OwnerMask, Name, Scene.Name); + + RootPart.ScheduleFullUpdate(); } public void UpdatePermissions(UUID AgentID, byte field, uint localID, @@ -2682,7 +2980,7 @@ namespace OpenSim.Region.Framework.Scenes { RootPart.UpdatePermissions(AgentID, field, localID, mask, addRemTF); - AdjustChildPrimPermissions(); + AdjustChildPrimPermissions(Scene.Permissions.IsGod(AgentID)); HasGroupChanged = true; @@ -3000,8 +3298,8 @@ namespace OpenSim.Region.Framework.Scenes /// /// Update just the root prim position in a linkset /// - /// - public void UpdateRootPosition(Vector3 pos) + /// + public void UpdateRootPosition(Vector3 newPos) { // m_log.DebugFormat( // "[SCENE OBJECT GROUP]: Updating root position of {0} {1} to {2}", Name, LocalId, pos); @@ -3010,16 +3308,16 @@ namespace OpenSim.Region.Framework.Scenes // for (int i = 0; i < parts.Length; i++) // parts[i].StoreUndoState(); - Vector3 newPos = new Vector3(pos.X, pos.Y, pos.Z); - Vector3 oldPos = - new Vector3(AbsolutePosition.X + m_rootPart.OffsetPosition.X, - AbsolutePosition.Y + m_rootPart.OffsetPosition.Y, - AbsolutePosition.Z + m_rootPart.OffsetPosition.Z); + Vector3 oldPos; + + if (IsAttachment) + oldPos = m_rootPart.AttachedPos + m_rootPart.OffsetPosition; // OffsetPosition should always be 0 in an attachments's root prim + else + oldPos = AbsolutePosition + m_rootPart.OffsetPosition; + Vector3 diff = oldPos - newPos; - Vector3 axDiff = new Vector3(diff.X, diff.Y, diff.Z); Quaternion partRotation = m_rootPart.RotationOffset; - axDiff *= Quaternion.Inverse(partRotation); - diff = axDiff; + diff *= Quaternion.Inverse(partRotation); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) @@ -3030,6 +3328,9 @@ namespace OpenSim.Region.Framework.Scenes } AbsolutePosition = newPos; + + if (IsAttachment) + m_rootPart.AttachedPos = newPos; HasGroupChanged = true; ScheduleGroupForTerseUpdate(); @@ -3583,10 +3884,10 @@ namespace OpenSim.Region.Framework.Scenes /// down after it move one place down the list. /// /// A list of the sitting avatars. Returns an empty list if there are no sitting avatars. - public List GetSittingAvatars() + public List GetSittingAvatars() { lock (m_sittingAvatars) - return new List(m_sittingAvatars); + return new List(m_sittingAvatars); } /// diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 44e8fdf..d1c5f72 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -37,11 +37,13 @@ using System.Xml.Serialization; using log4net; using OpenMetaverse; using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes.Scripting; using OpenSim.Region.Framework.Scenes.Serialization; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.Framework.Scenes { @@ -115,7 +117,7 @@ namespace OpenSim.Region.Framework.Scenes #endregion Enumerations - public class SceneObjectPart : IScriptHost, ISceneEntity + public class SceneObjectPart : ISceneEntity { /// /// Denote all sides of the prim @@ -124,6 +126,32 @@ namespace OpenSim.Region.Framework.Scenes private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// + /// Dynamic attributes can be created and deleted as required. + /// + public DAMap DynAttrs { get; set; } + + private DOMap m_dynObjs; + + /// + /// Dynamic objects that can be created and deleted as required. + /// + public DOMap DynObjs + { + get + { + if (m_dynObjs == null) + m_dynObjs = new DOMap(); + + return m_dynObjs; + } + + set + { + m_dynObjs = value; + } + } + /// /// Is this a root part? /// @@ -158,7 +186,7 @@ namespace OpenSim.Region.Framework.Scenes public bool RETURN_AT_EDGE; - public bool BlockGrab; + public bool BlockGrab { get; set; } public bool StatusSandbox; @@ -191,6 +219,14 @@ namespace OpenSim.Region.Framework.Scenes public double SoundRadius; + /// + /// Should sounds played from this prim be queued? + /// + /// + /// This should only be changed by sound modules. It is up to sound modules as to how they interpret this setting. + /// + public bool SoundQueueing { get; set; } + public uint TimeStampFull; public uint TimeStampLastActivity; // Will be used for AutoReturn @@ -230,7 +266,7 @@ namespace OpenSim.Region.Framework.Scenes public Quaternion SpinOldOrientation = Quaternion.Identity; - protected int m_APIDIterations = 0; + protected bool m_APIDActive = false; protected Quaternion m_APIDTarget = Quaternion.Identity; protected float m_APIDDamp = 0; protected float m_APIDStrength = 0; @@ -296,6 +332,12 @@ namespace OpenSim.Region.Framework.Scenes protected Vector3 m_lastAcceleration; protected Vector3 m_lastAngularVelocity; protected int m_lastTerseSent; + + protected byte m_physicsShapeType = (byte)PhysShapeType.prim; + protected float m_density = 1000.0f; // in kg/m^3 + protected float m_gravitymod = 1.0f; + protected float m_friction = 0.6f; // wood + protected float m_bounce = 0.5f; // wood /// /// Stores media texture data @@ -312,6 +354,11 @@ namespace OpenSim.Region.Framework.Scenes private UUID m_collisionSound; private float m_collisionSoundVolume; + public KeyframeMotion KeyframeMotion + { + get; set; + } + #endregion Fields // ~SceneObjectPart() @@ -335,6 +382,7 @@ namespace OpenSim.Region.Framework.Scenes m_particleSystem = Utils.EmptyBytes; Rezzed = DateTime.UtcNow; Description = String.Empty; + DynAttrs = new DAMap(); // Prims currently only contain a single folder (Contents). From looking at the Second Life protocol, // this appears to have the same UUID (!) as the prim. If this isn't the case, one can't drag items from @@ -389,8 +437,8 @@ namespace OpenSim.Region.Framework.Scenes private uint _category; private Int32 _creationDate; private uint _parentID = 0; - private uint _baseMask = (uint)PermissionMask.All; - private uint _ownerMask = (uint)PermissionMask.All; + private uint _baseMask = (uint)(PermissionMask.All | PermissionMask.Export); + private uint _ownerMask = (uint)(PermissionMask.All | PermissionMask.Export); private uint _groupMask = (uint)PermissionMask.None; private uint _everyoneMask = (uint)PermissionMask.None; private uint _nextOwnerMask = (uint)PermissionMask.All; @@ -425,7 +473,7 @@ namespace OpenSim.Region.Framework.Scenes { get { - if (CreatorData != null && CreatorData != string.Empty) + if (!string.IsNullOrEmpty(CreatorData)) return CreatorID.ToString() + ';' + CreatorData; else return CreatorID.ToString(); @@ -455,7 +503,11 @@ namespace OpenSim.Region.Framework.Scenes CreatorID = uuid; } if (parts.Length >= 2) + { CreatorData = parts[1]; + if (!CreatorData.EndsWith("/")) + CreatorData += "/"; + } if (parts.Length >= 3) name = parts[2]; @@ -590,6 +642,12 @@ namespace OpenSim.Region.Framework.Scenes } } + protected bool APIDActive + { + get { return m_APIDActive; } + set { m_APIDActive = value; } + } + protected Quaternion APIDTarget { get { return m_APIDTarget; } @@ -729,23 +787,14 @@ namespace OpenSim.Region.Framework.Scenes } // Tell the physics engines that this prim changed. - ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); + if (ParentGroup != null && ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null) + ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); } catch (Exception e) { m_log.ErrorFormat("[SCENEOBJECTPART]: GROUP POSITION. {0}", e); } } - - // TODO if we decide to do sitting in a more SL compatible way (multiple avatars per prim), this has to be fixed, too - if (SitTargetAvatar != UUID.Zero) - { - ScenePresence avatar; - if (ParentGroup.Scene.TryGetScenePresence(SitTargetAvatar, out avatar)) - { - avatar.ParentPosition = GetWorldPosition(); - } - } } } @@ -842,7 +891,7 @@ namespace OpenSim.Region.Framework.Scenes //m_log.Info("[PART]: RO2:" + actor.Orientation.ToString()); } - if (ParentGroup != null) + if (ParentGroup != null && ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null) ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); //} } @@ -880,14 +929,17 @@ namespace OpenSim.Region.Framework.Scenes set { - m_velocity = value; + if (Util.IsNanOrInfinity(value)) + m_velocity = Vector3.Zero; + else + m_velocity = value; PhysicsActor actor = PhysActor; if (actor != null) { if (actor.IsPhysical) { - actor.Velocity = value; + actor.Velocity = m_velocity; ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); } } @@ -914,14 +966,30 @@ namespace OpenSim.Region.Framework.Scenes } return m_angularVelocity; } - set { m_angularVelocity = value; } + set + { + if (Util.IsNanOrInfinity(value)) + m_angularVelocity = Vector3.Zero; + else + m_angularVelocity = value; + + PhysicsActor actor = PhysActor; + if ((actor != null) && actor.IsPhysical) + actor.RotationalVelocity = m_angularVelocity; + } } /// public Vector3 Acceleration { get { return m_acceleration; } - set { m_acceleration = value; } + set + { + if (Util.IsNanOrInfinity(value)) + m_acceleration = Vector3.Zero; + else + m_acceleration = value; + } } public string Description { get; set; } @@ -1028,6 +1096,7 @@ namespace OpenSim.Region.Framework.Scenes } public UpdateRequired UpdateFlag { get; set; } + public bool UpdatePhysRequired { get; set; } /// /// Used for media on a prim. @@ -1110,23 +1179,14 @@ namespace OpenSim.Region.Framework.Scenes // the mappings more consistant. public Vector3 SitTargetPositionLL { - get { return new Vector3(m_sitTargetPosition.X, m_sitTargetPosition.Y,m_sitTargetPosition.Z); } + get { return m_sitTargetPosition; } set { m_sitTargetPosition = value; } } public Quaternion SitTargetOrientationLL { - get - { - return new Quaternion( - m_sitTargetOrientation.X, - m_sitTargetOrientation.Y, - m_sitTargetOrientation.Z, - m_sitTargetOrientation.W - ); - } - - set { m_sitTargetOrientation = new Quaternion(value.X, value.Y, value.Z, value.W); } + get { return m_sitTargetOrientation; } + set { m_sitTargetOrientation = value; } } public bool Stopped @@ -1264,7 +1324,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// null if there are no sitting avatars. This is to save us create a hashset for every prim in a scene. /// - private HashSet m_sittingAvatars; + private HashSet m_sittingAvatars; public virtual UUID RegionID { @@ -1315,6 +1375,157 @@ namespace OpenSim.Region.Framework.Scenes set { m_collisionSoundVolume = value; } } + public byte DefaultPhysicsShapeType() + { + byte type; + + if (Shape != null && (Shape.SculptType == (byte)SculptType.Mesh)) + type = (byte)PhysShapeType.convex; + else + type = (byte)PhysShapeType.prim; + + return type; + } + + public byte PhysicsShapeType + { + get { return m_physicsShapeType; } + set + { + byte oldv = m_physicsShapeType; + + if (value >= 0 && value <= (byte)PhysShapeType.convex) + { + if (value == (byte)PhysShapeType.none && ParentGroup != null && ParentGroup.RootPart == this) + m_physicsShapeType = DefaultPhysicsShapeType(); + else + m_physicsShapeType = value; + } + else + m_physicsShapeType = DefaultPhysicsShapeType(); + + if (m_physicsShapeType != oldv && ParentGroup != null) + { + if (m_physicsShapeType == (byte)PhysShapeType.none) + { + if (PhysActor != null) + { + Velocity = new Vector3(0, 0, 0); + Acceleration = new Vector3(0, 0, 0); + if (ParentGroup.RootPart == this) + AngularVelocity = new Vector3(0, 0, 0); + ParentGroup.Scene.RemovePhysicalPrim(1); + RemoveFromPhysics(); + } + } + else if (PhysActor == null) + { + ApplyPhysics((uint)Flags, VolumeDetectActive); + } + else + { + PhysActor.PhysicsShapeType = m_physicsShapeType; + } + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + } + + if (m_physicsShapeType != value) + { + UpdatePhysRequired = true; + } + } + } + + public float Density // in kg/m^3 + { + get { return m_density; } + set + { + if (value >=1 && value <= 22587.0) + { + m_density = value; + UpdatePhysRequired = true; + } + + ScheduleFullUpdateIfNone(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + + PhysicsActor pa = PhysActor; + if (pa != null) + pa.Density = Density; + } + } + + public float GravityModifier + { + get { return m_gravitymod; } + set + { + if( value >= -1 && value <=28.0f) + { + m_gravitymod = value; + UpdatePhysRequired = true; + } + + ScheduleFullUpdateIfNone(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + + PhysicsActor pa = PhysActor; + if (pa != null) + pa.GravModifier = GravityModifier; + } + } + + public float Friction + { + get { return m_friction; } + set + { + if (value >= 0 && value <= 255.0f) + { + m_friction = value; + UpdatePhysRequired = true; + } + + ScheduleFullUpdateIfNone(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + + PhysicsActor pa = PhysActor; + if (pa != null) + pa.Friction = Friction; + } + } + + public float Restitution + { + get { return m_bounce; } + set + { + if (value >= 0 && value <= 1.0f) + { + m_bounce = value; + UpdatePhysRequired = true; + } + + ScheduleFullUpdateIfNone(); + + if (ParentGroup != null) + ParentGroup.HasGroupChanged = true; + + PhysicsActor pa = PhysActor; + if (pa != null) + pa.Restitution = Restitution; + } + } + #endregion Public Properties with only Get private uint ApplyMask(uint val, bool set, uint mask) @@ -1403,20 +1614,29 @@ namespace OpenSim.Region.Framework.Scenes public void AddTextureAnimation(Primitive.TextureAnimation pTexAnim) { - byte[] data = new byte[16]; - int pos = 0; + byte[] data; - // The flags don't like conversion from uint to byte, so we have to do - // it the crappy way. See the above function :( + if (pTexAnim.Flags == Primitive.TextureAnimMode.ANIM_OFF) + { + data = Utils.EmptyBytes; + } + else + { + data = new byte[16]; + int pos = 0; - data[pos] = ConvertScriptUintToByte((uint)pTexAnim.Flags); pos++; - data[pos] = (byte)pTexAnim.Face; pos++; - data[pos] = (byte)pTexAnim.SizeX; pos++; - data[pos] = (byte)pTexAnim.SizeY; pos++; + // The flags don't like conversion from uint to byte, so we have to do + // it the crappy way. See the above function :( - Utils.FloatToBytes(pTexAnim.Start).CopyTo(data, pos); - Utils.FloatToBytes(pTexAnim.Length).CopyTo(data, pos + 4); - Utils.FloatToBytes(pTexAnim.Rate).CopyTo(data, pos + 8); + data[pos] = ConvertScriptUintToByte((uint)pTexAnim.Flags); pos++; + data[pos] = (byte)pTexAnim.Face; pos++; + data[pos] = (byte)pTexAnim.SizeX; pos++; + data[pos] = (byte)pTexAnim.SizeY; pos++; + + Utils.FloatToBytes(pTexAnim.Start).CopyTo(data, pos); + Utils.FloatToBytes(pTexAnim.Length).CopyTo(data, pos + 4); + Utils.FloatToBytes(pTexAnim.Rate).CopyTo(data, pos + 8); + } m_TextureAnimation = data; } @@ -1511,40 +1731,36 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - public void ApplyPhysics(uint rootObjectFlags, bool VolumeDetectActive) + public void ApplyPhysics(uint rootObjectFlags, bool _VolumeDetectActive) { + VolumeDetectActive = _VolumeDetectActive; + if (!ParentGroup.Scene.CollidablePrims) return; -// m_log.DebugFormat( -// "[SCENE OBJECT PART]: Applying physics to {0} {1}, m_physicalPrim {2}", -// Name, LocalId, UUID, m_physicalPrim); + if (PhysicsShapeType == (byte)PhysShapeType.none) + return; bool isPhysical = (rootObjectFlags & (uint) PrimFlags.Physics) != 0; bool isPhantom = (rootObjectFlags & (uint) PrimFlags.Phantom) != 0; + if (_VolumeDetectActive) + isPhantom = true; + if (IsJoint()) { DoPhysicsPropertyUpdate(isPhysical, true); } else { - // Special case for VolumeDetection: If VolumeDetection is set, the phantom flag is locally ignored - if (VolumeDetectActive) - isPhantom = false; - - // The only time the physics scene shouldn't know about the prim is if it's phantom or an attachment, which is phantom by definition - // or flexible - if (!isPhantom && !ParentGroup.IsAttachment && !(Shape.PathCurve == (byte)Extrusion.Flexible)) + if ((!isPhantom || isPhysical || _VolumeDetectActive) + && !ParentGroup.IsAttachmentCheckFull() + && !(Shape.PathCurve == (byte)Extrusion.Flexible)) { - // Added clarification.. since A rigid body is an object that you can kick around, etc. - bool rigidBody = isPhysical && !isPhantom; - - PhysicsActor pa = AddToPhysics(rigidBody); - - if (pa != null) - pa.SetVolumeDetect(VolumeDetectActive ? 1 : 0); + AddToPhysics(isPhysical, isPhantom, isPhysical); } + else + PhysActor = null; // just to be sure } } @@ -1572,7 +1788,11 @@ namespace OpenSim.Region.Framework.Scenes /// public SceneObjectPart Copy(uint localID, UUID AgentID, UUID GroupID, int linkNum, bool userExposed) { + // FIXME: This is dangerous since it's easy to forget to reset some references when necessary and end up + // with bugs that only occur in some circumstances (e.g. crossing between regions on the same simulator + // but not between regions on different simulators). Really, all copying should be done explicitly. SceneObjectPart dupe = (SceneObjectPart)MemberwiseClone(); + dupe.m_shape = m_shape.Copy(); dupe.m_regionHandle = m_regionHandle; if (userExposed) @@ -1618,6 +1838,14 @@ namespace OpenSim.Region.Framework.Scenes Array.Copy(Shape.ExtraParams, extraP, extraP.Length); dupe.Shape.ExtraParams = extraP; + dupe.m_sittingAvatars = new HashSet(); + + // safeguard actual copy is done in sog.copy + dupe.KeyframeMotion = null; + dupe.PayPrice = (int[])PayPrice.Clone(); + + dupe.DynAttrs.CopyFrom(DynAttrs); + if (userExposed) { /* @@ -1816,6 +2044,9 @@ namespace OpenSim.Region.Framework.Scenes { if (UsePhysics) { + if (ParentGroup.RootPart.KeyframeMotion != null) + ParentGroup.RootPart.KeyframeMotion.Stop(); + ParentGroup.RootPart.KeyframeMotion = null; ParentGroup.Scene.AddPhysicalPrim(1); pa.OnRequestTerseUpdate += PhysicsRequestingTerseUpdate; @@ -1848,7 +2079,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - public static SceneObjectPart FromXml(XmlTextReader xmlReader) + public static SceneObjectPart FromXml(XmlReader xmlReader) { SceneObjectPart part = SceneObjectSerializer.Xml2ToSOP(xmlReader); @@ -1883,22 +2114,6 @@ namespace OpenSim.Region.Framework.Scenes ParentGroup.RootPart.RETURN_AT_EDGE = p; } - public bool GetBlockGrab() - { - if (ParentGroup.IsDeleted) - return false; - - return ParentGroup.RootPart.BlockGrab; - } - - public void SetBlockGrab(bool p) - { - if (ParentGroup.IsDeleted) - return; - - ParentGroup.RootPart.BlockGrab = p; - } - public void SetStatusSandbox(bool p) { if (ParentGroup.IsDeleted) @@ -1952,9 +2167,19 @@ namespace OpenSim.Region.Framework.Scenes PhysicsActor pa = PhysActor; if (pa != null) - return new Vector3(pa.CenterOfMass.X, pa.CenterOfMass.Y, pa.CenterOfMass.Z); + return pa.GeometricCenter; + else + return Vector3.Zero; + } + + public Vector3 GetCenterOfMass() + { + PhysicsActor pa = PhysActor; + + if (pa != null) + return pa.CenterOfMass; else - return new Vector3(0, 0, 0); + return Vector3.Zero; } public float GetMass() @@ -2030,7 +2255,7 @@ namespace OpenSim.Region.Framework.Scenes { if (tau > 0) { - ParentGroup.moveToTarget(target, tau); + ParentGroup.MoveToTarget(target, tau); } else { @@ -2168,7 +2393,7 @@ namespace OpenSim.Region.Framework.Scenes CollidingMessage = CreateColliderArgs(this, colliders); if (CollidingMessage.Colliders.Count > 0) - notify(LocalId, CollidingMessage); + DoNotify(notify, LocalId, CollidingMessage); if (PassCollisions) sendToRoot = true; @@ -2182,7 +2407,7 @@ namespace OpenSim.Region.Framework.Scenes { CollidingMessage = CreateColliderArgs(ParentGroup.RootPart, colliders); if (CollidingMessage.Colliders.Count > 0) - notify(ParentGroup.RootPart.LocalId, CollidingMessage); + DoNotify(notify, ParentGroup.RootPart.LocalId, CollidingMessage); } } } @@ -2197,7 +2422,33 @@ namespace OpenSim.Region.Framework.Scenes colliding.Add(CreateDetObjectForGround()); LandCollidingMessage.Colliders = colliding; - notify(LocalId, LandCollidingMessage); + DoNotify(notify, LocalId, LandCollidingMessage); + } + } + + private void DoNotify(ScriptCollidingNotification notify, uint id, ColliderArgs collargs) + { + if (m_parentGroup != null && ParentGroup.Scene != null && ParentGroup.Scene.ShouldUseFireAndForgetForCollisions) + { + // For those learning C#, FireAndForget takes a function, an object to pass + // to that function and an ID string. The "oo => {}" construct is a lambda expression + // for a function with one arguement ('oo'). The 'new Object[] {}" construct creates an Object + // that is an object array and initializes it with three items (the parameters + // being passed). The parameters passed are the function to call ('notify') and + // its two arguements. Finally, once in the function (called later by the FireAndForget + // thread scheduler), the passed object is cast to an object array and then each + // of its items (aoo[0] to aoo[2]) are individually cast to what they are and + // then used in a call of the passed ScriptCollidingNotification function. + Util.FireAndForget(oo => + { + Object[] aoo = (Object[])oo; + ((ScriptCollidingNotification)aoo[0])((uint)aoo[1], (ColliderArgs)aoo[2]); + + }, new Object[] { notify, id, collargs }, "SOP.Collision"); + } + else + { + notify(id, collargs); } } @@ -2244,7 +2495,7 @@ namespace OpenSim.Region.Framework.Scenes if (soundModule != null) { soundModule.SendSound(UUID, CollisionSound, - CollisionSoundVolume, true, (byte)0, 0, false, + CollisionSoundVolume, true, 0, 0, false, false); } } @@ -2254,12 +2505,9 @@ namespace OpenSim.Region.Framework.Scenes SendCollisionEvent(scriptEvents.collision_end , endedColliders , ParentGroup.Scene.EventManager.TriggerScriptCollidingEnd); if (startedColliders.Contains(0)) - { - if (m_lastColliders.Contains(0)) - SendLandCollisionEvent(scriptEvents.land_collision, ParentGroup.Scene.EventManager.TriggerScriptLandColliding); - else - SendLandCollisionEvent(scriptEvents.land_collision_start, ParentGroup.Scene.EventManager.TriggerScriptLandCollidingStart); - } + SendLandCollisionEvent(scriptEvents.land_collision_start, ParentGroup.Scene.EventManager.TriggerScriptLandCollidingStart); + if (m_lastColliders.Contains(0)) + SendLandCollisionEvent(scriptEvents.land_collision, ParentGroup.Scene.EventManager.TriggerScriptLandColliding); if (endedColliders.Contains(0)) SendLandCollisionEvent(scriptEvents.land_collision_end, ParentGroup.Scene.EventManager.TriggerScriptLandCollidingEnd); } @@ -2282,19 +2530,36 @@ namespace OpenSim.Region.Framework.Scenes if (pa != null) { - Vector3 newpos = new Vector3(pa.Position.GetBytes(), 0); - - if (ParentGroup.Scene.TestBorderCross(newpos, Cardinals.N) - | ParentGroup.Scene.TestBorderCross(newpos, Cardinals.S) - | ParentGroup.Scene.TestBorderCross(newpos, Cardinals.E) - | ParentGroup.Scene.TestBorderCross(newpos, Cardinals.W)) + Vector3 newpos = pa.Position; + if (!ParentGroup.Scene.PositionIsInCurrentRegion(newpos)) { + // Setting position outside current region will start region crossing ParentGroup.AbsolutePosition = newpos; return; } //ParentGroup.RootPart.m_groupPosition = newpos; } + if (pa != null && ParentID != 0 && ParentGroup != null) + { + // Special case where a child object is requesting property updates. + // This happens when linksets are modified to use flexible links rather than + // the default links. + // The simulator code presumes that child parts are only modified by scripts + // so the logic for changing position/rotation/etc does not take into + // account the physical object actually moving. + // This code updates the offset position and rotation of the child and then + // lets the update code push the update to the viewer. + // Since physics engines do not normally generate this event for linkset children, + // this code will not be active unless you have a specially configured + // physics engine. + Quaternion invRootRotation = Quaternion.Normalize(Quaternion.Inverse(ParentGroup.RootPart.RotationOffset)); + m_offsetPosition = pa.Position - m_groupPosition; + RotationOffset = pa.Orientation * invRootRotation; + // m_log.DebugFormat("{0} PhysicsRequestingTerseUpdate child: pos={1}, rot={2}, offPos={3}, offRot={4}", + // "[SCENE OBJECT PART]", pa.Position, pa.Orientation, m_offsetPosition, RotationOffset); + } + ScheduleTerseUpdate(); } @@ -2397,7 +2662,7 @@ namespace OpenSim.Region.Framework.Scenes return; } - m_APIDIterations = 1 + (int)(Math.PI * APIDStrength); + APIDActive = true; } // Necessary to get the lookat deltas applied @@ -2411,7 +2676,20 @@ namespace OpenSim.Region.Framework.Scenes public void StopLookAt() { - APIDTarget = Quaternion.Identity; + APIDActive = false; + } + + + + public void ScheduleFullUpdateIfNone() + { + if (ParentGroup == null) + return; + +// ??? ParentGroup.HasGroupChanged = true; + + if (UpdateFlag != UpdateRequired.FULL) + ScheduleFullUpdate(); } /// @@ -2460,7 +2738,8 @@ namespace OpenSim.Region.Framework.Scenes return; // This was pulled from SceneViewer. Attachments always receive full updates. - // I could not verify if this is a requirement but this maintains existing behavior + // This is needed because otherwise if only the root prim changes position, then + // it looks as if the entire object has moved (including the other prims). if (ParentGroup.IsAttachment) { ScheduleFullUpdate(); @@ -2996,6 +3275,10 @@ namespace OpenSim.Region.Framework.Scenes /// public void SetScriptEvents(UUID scriptid, int events) { +// m_log.DebugFormat( +// "[SCENE OBJECT PART]: Set script events for script with id {0} on {1}/{2} to {3} in {4}", +// scriptid, Name, ParentGroup.Name, events, ParentGroup.Scene.Name); + // scriptEvents oldparts; lock (m_scriptEvents) { @@ -3048,10 +3331,7 @@ namespace OpenSim.Region.Framework.Scenes public void StopMoveToTarget() { - ParentGroup.stopMoveToTarget(); - - ParentGroup.ScheduleGroupForTerseUpdate(); - //ParentGroup.ScheduleGroupForFullUpdate(); + ParentGroup.StopMoveToTarget(); } public void StoreUndoState() @@ -3618,6 +3898,7 @@ namespace OpenSim.Region.Framework.Scenes result.distance = distance2; result.HitTF = true; result.ipoint = q; + result.face = i; //m_log.Info("[FACE]:" + i.ToString()); //m_log.Info("[POINT]: " + q.ToString()); //m_log.Info("[DIST]: " + distance2.ToString()); @@ -3664,10 +3945,10 @@ namespace OpenSim.Region.Framework.Scenes public void TrimPermissions() { - BaseMask &= (uint)PermissionMask.All; - OwnerMask &= (uint)PermissionMask.All; + BaseMask &= (uint)(PermissionMask.All | PermissionMask.Export); + OwnerMask &= (uint)(PermissionMask.All | PermissionMask.Export); GroupMask &= (uint)PermissionMask.All; - EveryoneMask &= (uint)PermissionMask.All; + EveryoneMask &= (uint)(PermissionMask.All | PermissionMask.Export); NextOwnerMask &= (uint)PermissionMask.All; } @@ -3690,30 +3971,31 @@ namespace OpenSim.Region.Framework.Scenes } } - public void UpdateGroupPosition(Vector3 pos) + public void UpdateGroupPosition(Vector3 newPos) { - if ((pos.X != GroupPosition.X) || - (pos.Y != GroupPosition.Y) || - (pos.Z != GroupPosition.Z)) + Vector3 oldPos = GroupPosition; + + if ((newPos.X != oldPos.X) || + (newPos.Y != oldPos.Y) || + (newPos.Z != oldPos.Z)) { - Vector3 newPos = new Vector3(pos.X, pos.Y, pos.Z); GroupPosition = newPos; ScheduleTerseUpdate(); } } /// - /// + /// Update this part's offset position. /// /// - public void UpdateOffSet(Vector3 pos) + public void UpdateOffSet(Vector3 newPos) { - if ((pos.X != OffsetPosition.X) || - (pos.Y != OffsetPosition.Y) || - (pos.Z != OffsetPosition.Z)) - { - Vector3 newPos = new Vector3(pos.X, pos.Y, pos.Z); + Vector3 oldPos = OffsetPosition; + if ((newPos.X != oldPos.X) || + (newPos.Y != oldPos.Y) || + (newPos.Z != oldPos.Z)) + { if (ParentGroup.RootPart.GetStatusSandbox()) { if (Util.GetDistanceTo(ParentGroup.RootPart.StatusSandboxPos, newPos) > 10) @@ -3770,10 +4052,22 @@ namespace OpenSim.Region.Framework.Scenes baseMask; break; case 8: + // Trying to set export permissions - extra checks + if (set && (mask & (uint)PermissionMask.Export) != 0) + { + if ((OwnerMask & (uint)PermissionMask.Export) == 0 || (BaseMask & (uint)PermissionMask.Export) == 0 || (NextOwnerMask & (uint)PermissionMask.All) != (uint)PermissionMask.All) + mask &= ~(uint)PermissionMask.Export; + } EveryoneMask = ApplyMask(EveryoneMask, set, mask) & baseMask; break; case 16: + // Force full perm if export + if ((EveryoneMask & (uint)PermissionMask.Export) != 0) + { + NextOwnerMask = (uint)PermissionMask.All; + break; + } NextOwnerMask = ApplyMask(NextOwnerMask, set, mask) & baseMask; // Prevent the client from creating no mod, no copy @@ -3848,7 +4142,7 @@ namespace OpenSim.Region.Framework.Scenes // For now, we use the NINJA naming scheme for identifying joints. // In the future, we can support other joint specification schemes such as a // custom checkbox in the viewer GUI. - if (ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene.SupportsNINJAJoints) + if (ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null && ParentGroup.Scene.PhysicsScene.SupportsNINJAJoints) { return IsHingeJoint() || IsBallJoint(); } @@ -3858,6 +4152,26 @@ namespace OpenSim.Region.Framework.Scenes } } + public void UpdateExtraPhysics(ExtraPhysicsData physdata) + { + if (physdata.PhysShapeType == PhysShapeType.invalid || ParentGroup == null) + return; + + if (PhysicsShapeType != (byte)physdata.PhysShapeType) + { + PhysicsShapeType = (byte)physdata.PhysShapeType; + + } + + if(Density != physdata.Density) + Density = physdata.Density; + if(GravityModifier != physdata.GravitationModifier) + GravityModifier = physdata.GravitationModifier; + if(Friction != physdata.Friction) + Friction = physdata.Friction; + if(Restitution != physdata.Bounce) + Restitution = physdata.Bounce; + } /// /// Update the flags on this prim. This covers properties such as phantom, physics and temporary. /// @@ -3928,7 +4242,8 @@ namespace OpenSim.Region.Framework.Scenes } if (SetPhantom - || ParentGroup.IsAttachment + || ParentGroup.IsAttachmentCheckFull() + || PhysicsShapeType == (byte)PhysShapeType.none || (Shape.PathCurve == (byte)Extrusion.Flexible)) // note: this may have been changed above in the case of joints { AddFlag(PrimFlags.Phantom); @@ -3948,32 +4263,17 @@ namespace OpenSim.Region.Framework.Scenes if (ParentGroup.Scene.CollidablePrims && pa == null) { - pa = AddToPhysics(UsePhysics); + AddToPhysics(UsePhysics, SetPhantom, false); + pa = PhysActor; if (pa != null) { pa.SetMaterial(Material); + pa.Position = GetWorldPosition(); + pa.Orientation = GetWorldRotation(); DoPhysicsPropertyUpdate(UsePhysics, true); - - if ( - ((AggregateScriptEvents & scriptEvents.collision) != 0) || - ((AggregateScriptEvents & scriptEvents.collision_end) != 0) || - ((AggregateScriptEvents & scriptEvents.collision_start) != 0) || - ((AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || - ((AggregateScriptEvents & scriptEvents.land_collision) != 0) || - ((AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision_end) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision_start) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || - (CollisionSound != UUID.Zero) - ) - { - pa.OnCollisionUpdate += PhysicsCollision; - pa.SubscribeEvents(1000); - } + + SubscribeForCollisionEvents(); } } else // it already has a physical representation @@ -4028,6 +4328,50 @@ namespace OpenSim.Region.Framework.Scenes } /// + /// Subscribe for physics collision events if needed for scripts and sounds + /// + public void SubscribeForCollisionEvents() + { + PhysicsActor pa = PhysActor; + + if (pa != null) + { + if ( + ((AggregateScriptEvents & scriptEvents.collision) != 0) || + ((AggregateScriptEvents & scriptEvents.collision_end) != 0) || + ((AggregateScriptEvents & scriptEvents.collision_start) != 0) || + ((AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || + ((AggregateScriptEvents & scriptEvents.land_collision) != 0) || + ((AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || + ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision) != 0) || + ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision_end) != 0) || + ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision_start) != 0) || + ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || + ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision) != 0) || + ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || + (CollisionSound != UUID.Zero) + ) + { + if (!pa.SubscribedEvents()) + { + // If not already subscribed for event, set up for a collision event. + pa.OnCollisionUpdate += PhysicsCollision; + pa.SubscribeEvents(1000); + } + } + else + { + // There is no need to be subscribed to collisions so, if subscribed, remove subscription + if (pa.SubscribedEvents()) + { + pa.OnCollisionUpdate -= PhysicsCollision; + pa.UnSubscribeEvents(); + } + } + } + } + + /// /// Adds this part to the physics scene. /// /// This method also sets the PhysActor property. @@ -4035,10 +4379,13 @@ namespace OpenSim.Region.Framework.Scenes /// /// The physics actor. null if there was a failure. /// - private PhysicsActor AddToPhysics(bool rigidBody) + private void AddToPhysics(bool isPhysical, bool isPhantom, bool applyDynamics) { PhysicsActor pa; + Vector3 velocity = Velocity; + Vector3 rotationalVelocity = AngularVelocity;; + try { pa = ParentGroup.Scene.PhysicsScene.AddPrimShape( @@ -4046,8 +4393,10 @@ namespace OpenSim.Region.Framework.Scenes Shape, AbsolutePosition, Scale, - RotationOffset, - rigidBody, + GetWorldRotation(), + isPhysical, + isPhantom, + PhysicsShapeType, m_localId); } catch (Exception e) @@ -4056,20 +4405,56 @@ namespace OpenSim.Region.Framework.Scenes pa = null; } - // FIXME: Ideally we wouldn't set the property here to reduce situations where threads changing physical - // properties can stop on each other. However, DoPhysicsPropertyUpdate() currently relies on PhysActor - // being set. - PhysActor = pa; - - // Basic Physics can also return null as well as an exception catch. if (pa != null) { pa.SOPName = this.Name; // save object into the PhysActor so ODE internals know the joint/body info pa.SetMaterial(Material); - DoPhysicsPropertyUpdate(rigidBody, true); + + pa.Density = Density; + pa.GravModifier = GravityModifier; + pa.Friction = Friction; + pa.Restitution = Restitution; + + if (VolumeDetectActive) // change if not the default only + pa.SetVolumeDetect(1); + // we are going to tell rest of code about physics so better have this here + PhysActor = pa; + + if (isPhysical) + { + if (ParentGroup.RootPart.KeyframeMotion != null) + ParentGroup.RootPart.KeyframeMotion.Stop(); + ParentGroup.RootPart.KeyframeMotion = null; + ParentGroup.Scene.AddPhysicalPrim(1); + + pa.OnRequestTerseUpdate += PhysicsRequestingTerseUpdate; + pa.OnOutOfBounds += PhysicsOutOfBounds; + + if (ParentID != 0 && ParentID != LocalId) + { + PhysicsActor parentPa = ParentGroup.RootPart.PhysActor; + + if (parentPa != null) + { + pa.link(parentPa); + } + } + } + + if (applyDynamics) + // do independent of isphysical so parameters get setted (at least some) + { + Velocity = velocity; + AngularVelocity = rotationalVelocity; +// pa.Velocity = velocity; + pa.RotationalVelocity = rotationalVelocity; + } + + ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(pa); } - return pa; + PhysActor = pa; + ParentGroup.Scene.EventManager.TriggerObjectAddedToPhysicalScene(this); } /// @@ -4082,7 +4467,9 @@ namespace OpenSim.Region.Framework.Scenes /// public void RemoveFromPhysics() { - ParentGroup.Scene.PhysicsScene.RemovePrim(PhysActor); + ParentGroup.Scene.EventManager.TriggerObjectRemovedFromPhysicalScene(this); + if (ParentGroup.Scene.PhysicsScene != null) + ParentGroup.Scene.PhysicsScene.RemovePrim(PhysActor); PhysActor = null; } @@ -4255,8 +4642,33 @@ namespace OpenSim.Region.Framework.Scenes Changed changeFlags = 0; + Primitive.TextureEntryFace fallbackNewFace = newTex.DefaultTexture; + Primitive.TextureEntryFace fallbackOldFace = oldTex.DefaultTexture; + + // On Incoming packets, sometimes newText.DefaultTexture is null. The assumption is that all + // other prim-sides are set, but apparently that's not always the case. Lets assume packet/data corruption at this point. + if (fallbackNewFace == null) + { + fallbackNewFace = new Primitive.TextureEntry(Util.BLANK_TEXTURE_UUID).CreateFace(0); + newTex.DefaultTexture = fallbackNewFace; + } + if (fallbackOldFace == null) + { + fallbackOldFace = new Primitive.TextureEntry(Util.BLANK_TEXTURE_UUID).CreateFace(0); + oldTex.DefaultTexture = fallbackOldFace; + } + + // Materials capable viewers can send a ObjectImage packet + // when nothing in TE has changed. MaterialID should be updated + // by the RenderMaterials CAP handler, so updating it here may cause a + // race condtion. Therefore, if no non-materials TE fields have changed, + // we should ignore any changes and not update Shape.TextureEntry + + bool otherFieldsChanged = false; + for (int i = 0 ; i < GetNumberOfSides(); i++) { + Primitive.TextureEntryFace newFace = newTex.DefaultTexture; Primitive.TextureEntryFace oldFace = oldTex.DefaultTexture; @@ -4280,18 +4692,36 @@ namespace OpenSim.Region.Framework.Scenes // Max change, skip the rest of testing if (changeFlags == (Changed.TEXTURE | Changed.COLOR)) break; + + if (!otherFieldsChanged) + { + if (oldFace.Bump != newFace.Bump) otherFieldsChanged = true; + if (oldFace.Fullbright != newFace.Fullbright) otherFieldsChanged = true; + if (oldFace.Glow != newFace.Glow) otherFieldsChanged = true; + if (oldFace.MediaFlags != newFace.MediaFlags) otherFieldsChanged = true; + if (oldFace.OffsetU != newFace.OffsetU) otherFieldsChanged = true; + if (oldFace.OffsetV != newFace.OffsetV) otherFieldsChanged = true; + if (oldFace.RepeatU != newFace.RepeatU) otherFieldsChanged = true; + if (oldFace.RepeatV != newFace.RepeatV) otherFieldsChanged = true; + if (oldFace.Rotation != newFace.Rotation) otherFieldsChanged = true; + if (oldFace.Shiny != newFace.Shiny) otherFieldsChanged = true; + if (oldFace.TexMapType != newFace.TexMapType) otherFieldsChanged = true; + } } - m_shape.TextureEntry = newTex.GetBytes(); - if (changeFlags != 0) - TriggerScriptChangedEvent(changeFlags); - UpdateFlag = UpdateRequired.FULL; - ParentGroup.HasGroupChanged = true; + if (changeFlags != 0 || otherFieldsChanged) + { + m_shape.TextureEntry = newTex.GetBytes(); + if (changeFlags != 0) + TriggerScriptChangedEvent(changeFlags); + UpdateFlag = UpdateRequired.FULL; + ParentGroup.HasGroupChanged = true; - //This is madness.. - //ParentGroup.ScheduleGroupForFullUpdate(); - //This is sparta - ScheduleFullUpdate(); + //This is madness.. + //ParentGroup.ScheduleGroupForFullUpdate(); + //This is sparta + ScheduleFullUpdate(); + } } public void aggregateScriptEvents() @@ -4331,39 +4761,7 @@ namespace OpenSim.Region.Framework.Scenes objectflagupdate |= (uint) PrimFlags.AllowInventoryDrop; } - PhysicsActor pa = PhysActor; - - if ( - ((AggregateScriptEvents & scriptEvents.collision) != 0) || - ((AggregateScriptEvents & scriptEvents.collision_end) != 0) || - ((AggregateScriptEvents & scriptEvents.collision_start) != 0) || - ((AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || - ((AggregateScriptEvents & scriptEvents.land_collision) != 0) || - ((AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision_end) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.collision_start) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision) != 0) || - ((ParentGroup.RootPart.AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || - (CollisionSound != UUID.Zero) - ) - { - // subscribe to physics updates. - if (pa != null) - { - pa.OnCollisionUpdate += PhysicsCollision; - pa.SubscribeEvents(1000); - } - } - else - { - if (pa != null) - { - pa.UnSubscribeEvents(); - pa.OnCollisionUpdate -= PhysicsCollision; - } - } + SubscribeForCollisionEvents(); //if ((GetEffectiveObjectFlags() & (uint)PrimFlags.Scripted) != 0) //{ @@ -4449,12 +4847,76 @@ namespace OpenSim.Region.Framework.Scenes { ParentGroup.AddScriptLPS(count); } + + /// + /// Sets a prim's owner and permissions when it's rezzed. + /// + /// The inventory item from which the item was rezzed + /// True: the item is being rezzed from the user's inventory. False: from a prim's inventory. + /// The scene the prim is being rezzed into + public void ApplyPermissionsOnRez(InventoryItemBase item, bool userInventory, Scene scene) + { + if ((OwnerID != item.Owner) || ((item.CurrentPermissions & SceneObjectGroup.SLAM) != 0) || ((item.Flags & (uint)InventoryItemFlags.ObjectSlamPerm) != 0)) + { + if (scene.Permissions.PropagatePermissions()) + { + if ((item.Flags & (uint)InventoryItemFlags.ObjectHasMultipleItems) == 0) + { + // Apply the item's permissions to the object + //LogPermissions("Before applying item permissions"); + if (userInventory) + { + EveryoneMask = item.EveryOnePermissions; + NextOwnerMask = item.NextPermissions; + } + else + { + if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteEveryone) != 0) + EveryoneMask = item.EveryOnePermissions; + if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteNextOwner) != 0) + NextOwnerMask = item.NextPermissions; + if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteGroup) != 0) + GroupMask = item.GroupPermissions; + } + //LogPermissions("After applying item permissions"); + } + } + + GroupMask = 0; // DO NOT propagate here + } + + if (OwnerID != item.Owner) + { + //LogPermissions("Before ApplyNextOwnerPermissions"); + + if (scene.Permissions.PropagatePermissions()) + ApplyNextOwnerPermissions(); + + //LogPermissions("After ApplyNextOwnerPermissions"); + + LastOwnerID = OwnerID; + OwnerID = item.Owner; + Inventory.ChangeInventoryOwner(item.Owner); + } + } + + /// + /// Logs the prim's permissions. Useful when debugging permission problems. + /// + /// + private void LogPermissions(String message) + { + PermissionsUtil.LogPermissions(Name, message, BaseMask, OwnerMask, NextOwnerMask); + } public void ApplyNextOwnerPermissions() { - BaseMask &= NextOwnerMask; + // Export needs to be preserved in the base and everyone + // mask, but removed in the owner mask as a next owner + // can never change the export status + BaseMask &= NextOwnerMask | (uint)PermissionMask.Export; OwnerMask &= NextOwnerMask; - EveryoneMask &= NextOwnerMask; + EveryoneMask &= NextOwnerMask | (uint)PermissionMask.Export; Inventory.ApplyNextOwnerPermissions(); } @@ -4463,20 +4925,34 @@ namespace OpenSim.Region.Framework.Scenes { try { - if (APIDTarget != Quaternion.Identity) + if (APIDActive) { - if (m_APIDIterations <= 1) + PhysicsActor pa = ParentGroup.RootPart.PhysActor; + if (pa == null || !pa.IsPhysical || APIDStrength < 0.04) { - UpdateRotation(APIDTarget); - APIDTarget = Quaternion.Identity; + StopLookAt(); return; } - Quaternion rot = Quaternion.Slerp(RotationOffset,APIDTarget,1.0f/(float)m_APIDIterations); - rot.Normalize(); - UpdateRotation(rot); + Quaternion currRot = GetWorldRotation(); + currRot.Normalize(); + + // difference between current orientation and desired orientation + Quaternion dR = currRot / APIDTarget; + + // find axis and angle of rotation to rotate to desired orientation + Vector3 axis = Vector3.UnitX; + float angle; + dR.GetAxisAngle(out axis, out angle); + axis = axis * currRot; + + // clamp strength to avoid overshoot + float strength = 1.0f / APIDStrength; + if (strength > 1.0) strength = 1.0f; - m_APIDIterations--; + // set angular velocity to rotate to desired orientation + // with velocity proportional to strength and angle + AngularVelocity = axis * angle * strength * (float)Math.PI; // This ensures that we'll check this object on the next iteration ParentGroup.QueueForUpdateCheck(); @@ -4502,19 +4978,19 @@ namespace OpenSim.Region.Framework.Scenes /// true if the avatar was not already recorded, false otherwise. /// /// - protected internal bool AddSittingAvatar(UUID avatarId) + protected internal bool AddSittingAvatar(ScenePresence sp) { lock (ParentGroup.m_sittingAvatars) { if (IsSitTargetSet && SitTargetAvatar == UUID.Zero) - SitTargetAvatar = avatarId; + SitTargetAvatar = sp.UUID; if (m_sittingAvatars == null) - m_sittingAvatars = new HashSet(); + m_sittingAvatars = new HashSet(); - if (m_sittingAvatars.Add(avatarId)) + if (m_sittingAvatars.Add(sp)) { - ParentGroup.m_sittingAvatars.Add(avatarId); + ParentGroup.m_sittingAvatars.Add(sp); return true; } @@ -4531,22 +5007,22 @@ namespace OpenSim.Region.Framework.Scenes /// true if the avatar was present and removed, false if it was not present. /// /// - protected internal bool RemoveSittingAvatar(UUID avatarId) + protected internal bool RemoveSittingAvatar(ScenePresence sp) { lock (ParentGroup.m_sittingAvatars) { - if (SitTargetAvatar == avatarId) + if (SitTargetAvatar == sp.UUID) SitTargetAvatar = UUID.Zero; if (m_sittingAvatars == null) return false; - if (m_sittingAvatars.Remove(avatarId)) + if (m_sittingAvatars.Remove(sp)) { if (m_sittingAvatars.Count == 0) m_sittingAvatars = null; - ParentGroup.m_sittingAvatars.Remove(avatarId); + ParentGroup.m_sittingAvatars.Remove(sp); return true; } @@ -4560,14 +5036,14 @@ namespace OpenSim.Region.Framework.Scenes /// /// This applies to all sitting avatars whether there is a sit target set or not. /// A hashset of the sitting avatars. Returns null if there are no sitting avatars. - public HashSet GetSittingAvatars() + public HashSet GetSittingAvatars() { lock (ParentGroup.m_sittingAvatars) { if (m_sittingAvatars == null) return null; else - return new HashSet(m_sittingAvatars); + return new HashSet(m_sittingAvatars); } } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPartInventory.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPartInventory.cs index db723fa..ec39726 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPartInventory.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPartInventory.cs @@ -38,6 +38,7 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes.Scripting; using OpenSim.Region.Framework.Scenes.Serialization; +using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.Framework.Scenes { @@ -399,6 +400,10 @@ namespace OpenSim.Region.Framework.Scenes private UUID RestoreSavedScriptState(UUID loadedID, UUID oldID, UUID newID) { +// m_log.DebugFormat( +// "[PRIM INVENTORY]: Restoring scripted state for item {0}, oldID {1}, loadedID {2}", +// newID, oldID, loadedID); + IScriptModule[] engines = m_part.ParentGroup.Scene.RequestModuleInterfaces(); if (engines.Length == 0) // No engine at all return oldID; @@ -411,7 +416,7 @@ namespace OpenSim.Region.Framework.Scenes XmlDocument doc = new XmlDocument(); doc.LoadXml(m_part.ParentGroup.m_savedScriptState[stateID]); - + ////////// CRUFT WARNING /////////////////////////////////// // // Old objects will have ... @@ -441,6 +446,8 @@ namespace OpenSim.Region.Framework.Scenes // This created document has only the minimun data // necessary for XEngine to parse it successfully +// m_log.DebugFormat("[PRIM INVENTORY]: Adding legacy state {0} in {1}", stateID, newID); + m_part.ParentGroup.m_savedScriptState[stateID] = newDoc.OuterXml; } @@ -552,7 +559,8 @@ namespace OpenSim.Region.Framework.Scenes /// public void StopScriptInstance(TaskInventoryItem item) { - m_part.ParentGroup.Scene.EventManager.TriggerStopScript(m_part.LocalId, item.ItemID); + if (m_part.ParentGroup.Scene != null) + m_part.ParentGroup.Scene.EventManager.TriggerStopScript(m_part.LocalId, item.ItemID); // At the moment, even stopped scripts are counted as active, which is probably wrong. // m_part.ParentGroup.AddActiveScriptCount(-1); @@ -731,8 +739,8 @@ namespace OpenSim.Region.Framework.Scenes return items; } - - public SceneObjectGroup GetRezReadySceneObject(TaskInventoryItem item) + + public bool GetRezReadySceneObjects(TaskInventoryItem item, out List objlist, out List veclist) { AssetBase rezAsset = m_part.ParentGroup.Scene.AssetService.Get(item.AssetID.ToString()); @@ -741,66 +749,54 @@ namespace OpenSim.Region.Framework.Scenes m_log.WarnFormat( "[PRIM INVENTORY]: Could not find asset {0} for inventory item {1} in {2}", item.AssetID, item.Name, m_part.Name); - return null; + objlist = null; + veclist = null; + return false; } - string xmlData = Utils.BytesToString(rezAsset.Data); - SceneObjectGroup group = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); - - group.ResetIDs(); + Vector3 bbox; + float offsetHeight; - SceneObjectPart rootPart = group.GetPart(group.UUID); + m_part.ParentGroup.Scene.GetObjectsToRez(rezAsset.Data, false, out objlist, out veclist, out bbox, out offsetHeight); - // Since renaming the item in the inventory does not affect the name stored - // in the serialization, transfer the correct name from the inventory to the - // object itself before we rez. - rootPart.Name = item.Name; - rootPart.Description = item.Description; + for (int i = 0; i < objlist.Count; i++) + { + SceneObjectGroup group = objlist[i]; - SceneObjectPart[] partList = group.Parts; + group.ResetIDs(); - group.SetGroup(m_part.GroupID, null); + SceneObjectPart rootPart = group.GetPart(group.UUID); - // TODO: Remove magic number badness - if ((rootPart.OwnerID != item.OwnerID) || (item.CurrentPermissions & 16) != 0 || (item.Flags & (uint)InventoryItemFlags.ObjectSlamPerm) != 0) // Magic number - { - if (m_part.ParentGroup.Scene.Permissions.PropagatePermissions()) + // Since renaming the item in the inventory does not affect the name stored + // in the serialization, transfer the correct name from the inventory to the + // object itself before we rez. + // Only do these for the first object if we are rezzing a coalescence. + if (i == 0) { - foreach (SceneObjectPart part in partList) - { - if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteEveryone) != 0) - part.EveryoneMask = item.EveryonePermissions; - if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteNextOwner) != 0) - part.NextOwnerMask = item.NextPermissions; - if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteGroup) != 0) - part.GroupMask = item.GroupPermissions; - } - - group.ApplyNextOwnerPermissions(); + rootPart.Name = item.Name; + rootPart.Description = item.Description; } - } - foreach (SceneObjectPart part in partList) - { - // TODO: Remove magic number badness - if ((part.OwnerID != item.OwnerID) || (item.CurrentPermissions & 16) != 0 || (item.Flags & (uint)InventoryItemFlags.ObjectSlamPerm) != 0) // Magic number + group.SetGroup(m_part.GroupID, null); + + foreach (SceneObjectPart part in group.Parts) { - part.LastOwnerID = part.OwnerID; - part.OwnerID = item.OwnerID; - part.Inventory.ChangeInventoryOwner(item.OwnerID); + // Convert between InventoryItem classes. You can never have too many similar but slightly different classes :) + InventoryItemBase dest = new InventoryItemBase(item.ItemID, item.OwnerID); + dest.BasePermissions = item.BasePermissions; + dest.CurrentPermissions = item.CurrentPermissions; + dest.EveryOnePermissions = item.EveryonePermissions; + dest.GroupPermissions = item.GroupPermissions; + dest.NextPermissions = item.NextPermissions; + dest.Flags = item.Flags; + + part.ApplyPermissionsOnRez(dest, false, m_part.ParentGroup.Scene); } - - if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteEveryone) != 0) - part.EveryoneMask = item.EveryonePermissions; - if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteNextOwner) != 0) - part.NextOwnerMask = item.NextPermissions; - if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteGroup) != 0) - part.GroupMask = item.GroupPermissions; + + rootPart.TrimPermissions(); } - - rootPart.TrimPermissions(); - - return group; + + return true; } /// @@ -880,8 +876,8 @@ namespace OpenSim.Region.Framework.Scenes int type = m_items[itemID].InvType; if (type == 10) // Script { - m_part.RemoveScriptEvents(itemID); - m_part.ParentGroup.Scene.EventManager.TriggerRemoveScript(m_part.LocalId, itemID); + // route it through here, to handle script cleanup tasks + RemoveScriptInstance(itemID, false); } m_items.Remove(itemID); m_inventorySerial++; @@ -1119,25 +1115,6 @@ namespace OpenSim.Region.Framework.Scenes mask &= ~((uint)PermissionMask.Transfer >> 13); if ((item.CurrentPermissions & item.NextPermissions & (uint)PermissionMask.Modify) == 0) mask &= ~((uint)PermissionMask.Modify >> 13); - - if (item.InvType != (int)InventoryType.Object) - { - if ((item.CurrentPermissions & item.NextPermissions & (uint)PermissionMask.Copy) == 0) - mask &= ~((uint)PermissionMask.Copy >> 13); - if ((item.CurrentPermissions & item.NextPermissions & (uint)PermissionMask.Transfer) == 0) - mask &= ~((uint)PermissionMask.Transfer >> 13); - if ((item.CurrentPermissions & item.NextPermissions & (uint)PermissionMask.Modify) == 0) - mask &= ~((uint)PermissionMask.Modify >> 13); - } - else - { - if ((item.CurrentPermissions & ((uint)PermissionMask.Copy >> 13)) == 0) - mask &= ~((uint)PermissionMask.Copy >> 13); - if ((item.CurrentPermissions & ((uint)PermissionMask.Transfer >> 13)) == 0) - mask &= ~((uint)PermissionMask.Transfer >> 13); - if ((item.CurrentPermissions & ((uint)PermissionMask.Modify >> 13)) == 0) - mask &= ~((uint)PermissionMask.Modify >> 13); - } if ((item.CurrentPermissions & (uint)PermissionMask.Copy) == 0) mask &= ~(uint)PermissionMask.Copy; @@ -1161,14 +1138,11 @@ namespace OpenSim.Region.Framework.Scenes // "[SCENE OBJECT PART INVENTORY]: Applying next permissions {0} to {1} in {2} with current {3}, base {4}, everyone {5}", // item.NextPermissions, item.Name, m_part.Name, item.CurrentPermissions, item.BasePermissions, item.EveryonePermissions); - if (item.InvType == (int)InventoryType.Object && (item.CurrentPermissions & 7) != 0) + if (item.InvType == (int)InventoryType.Object) { - if ((item.CurrentPermissions & ((uint)PermissionMask.Copy >> 13)) == 0) - item.CurrentPermissions &= ~(uint)PermissionMask.Copy; - if ((item.CurrentPermissions & ((uint)PermissionMask.Transfer >> 13)) == 0) - item.CurrentPermissions &= ~(uint)PermissionMask.Transfer; - if ((item.CurrentPermissions & ((uint)PermissionMask.Modify >> 13)) == 0) - item.CurrentPermissions &= ~(uint)PermissionMask.Modify; + uint perms = item.CurrentPermissions; + PermissionsUtil.ApplyFoldedPermissions(perms, ref perms); + item.CurrentPermissions = perms; } item.CurrentPermissions &= item.NextPermissions; diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index a90872e..1fddd91 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -29,16 +29,19 @@ using System; using System.Xml; using System.Collections.Generic; using System.Reflection; +using System.Threading; using System.Timers; +using Timer = System.Timers.Timer; using OpenMetaverse; using log4net; using Nini.Config; using OpenSim.Framework; using OpenSim.Framework.Client; +using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes.Animation; using OpenSim.Region.Framework.Scenes.Types; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using GridRegion = OpenSim.Services.Interfaces.GridRegion; using OpenSim.Services.Interfaces; using TeleportFlags = OpenSim.Framework.Constants.TeleportFlags; @@ -63,6 +66,7 @@ namespace OpenSim.Region.Framework.Scenes struct ScriptControllers { + public UUID objectID; public UUID itemID; public ScriptControlled ignoreControls; public ScriptControlled eventControls; @@ -72,21 +76,51 @@ namespace OpenSim.Region.Framework.Scenes public class ScenePresence : EntityBase, IScenePresence { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly String LogHeader = "[SCENE PRESENCE]"; + // ~ScenePresence() // { // m_log.DebugFormat("[SCENE PRESENCE]: Destructor called on {0}", Name); // } - private void TriggerScenePresenceUpdated() + public void TriggerScenePresenceUpdated() { if (m_scene != null) m_scene.EventManager.TriggerScenePresenceUpdated(this); } - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - public PresenceType PresenceType { get; private set; } + private ScenePresenceStateMachine m_stateMachine; + + /// + /// The current state of this presence. Governs only the existence lifecycle. See ScenePresenceStateMachine + /// for more details. + /// + public ScenePresenceState LifecycleState + { + get + { + return m_stateMachine.GetState(); + } + + set + { + m_stateMachine.SetState(value); + } + } + + /// + /// This exists to prevent race conditions between two CompleteMovement threads if the simulator is slow and + /// the viewer fires these in quick succession. + /// + /// + /// TODO: The child -> agent transition should be folded into LifecycleState and the CompleteMovement + /// regulation done there. + /// + private object m_completeMovementLock = new object(); + // private static readonly byte[] DEFAULT_TEXTURE = AvatarAppearance.GetDefaultTexture().GetBytes(); private static readonly Array DIR_CONTROL_FLAGS = Enum.GetValues(typeof(Dir_ControlFlags)); private static readonly Vector3 HEAD_ADJUSTMENT = new Vector3(0f, 0f, 0.3f); @@ -99,7 +133,7 @@ namespace OpenSim.Region.Framework.Scenes /// rotation, prim cut, prim twist, prim taper, and prim shear. See mantis /// issue #1716 /// - public static readonly Vector3 SIT_TARGET_ADJUSTMENT = new Vector3(0.0f, 0.0f, 0.418f); + public static readonly Vector3 SIT_TARGET_ADJUSTMENT = new Vector3(0.0f, 0.0f, 0.4f); /// /// Movement updates for agents in neighboring regions are sent directly to clients. @@ -139,6 +173,10 @@ namespace OpenSim.Region.Framework.Scenes private Vector3 m_lastPosition; private Quaternion m_lastRotation; private Vector3 m_lastVelocity; + private Vector3 m_lastSize = new Vector3(0.45f,0.6f,1.9f); + + private bool m_followCamAuto = false; + private Vector3? m_forceToApply; private int m_userFlags; @@ -196,10 +234,15 @@ namespace OpenSim.Region.Framework.Scenes private float m_sitAvatarHeight = 2.0f; private Vector3 m_lastChildAgentUpdatePosition; - private Vector3 m_lastChildAgentUpdateCamPosition; +// private Vector3 m_lastChildAgentUpdateCamPosition; private const int LAND_VELOCITYMAG_MAX = 12; + private const float FLY_ROLL_MAX_RADIANS = 1.1f; + + private const float FLY_ROLL_RADIANS_PER_UPDATE = 0.06f; + private const float FLY_ROLL_RESET_RADIANS_PER_UPDATE = 0.02f; + private float m_health = 100f; protected ulong crossingFromRegion; @@ -221,8 +264,6 @@ namespace OpenSim.Region.Framework.Scenes /// public bool LandAtTarget { get; private set; } - private bool m_followCamAuto; - private int m_movementUpdateCount; private const int NumMovementsBetweenRayCast = 5; @@ -230,6 +271,10 @@ namespace OpenSim.Region.Framework.Scenes //private int m_moveToPositionStateStatus; //***************************************************** + private int m_movementAnimationUpdateCounter = 0; + + public Vector3 PrevSitOffset { get; set; } + protected AvatarAppearance m_appearance; public AvatarAppearance Appearance @@ -242,6 +287,8 @@ namespace OpenSim.Region.Framework.Scenes } } + public bool SentInitialDataToClient { get; private set; } + /// /// Copy of the script states while the agent is in transit. This state may /// need to be placed back in case of transfer fail. @@ -276,9 +323,43 @@ namespace OpenSim.Region.Framework.Scenes /// private Vector3 posLastSignificantMove; - // For teleports and crossings callbacks - string m_callbackURI; - UUID m_originRegionID; + #region For teleports and crossings callbacks + + /// + /// In the V1 teleport protocol, the destination simulator sends ReleaseAgent to this address. + /// + private string m_callbackURI; + + /// + /// Records the region from which this presence originated, if not from login. + /// + /// + /// Also acts as a signal in the teleport V2 process to release UpdateAgent after a viewer has triggered + /// CompleteMovement and made the previous child agent a root agent. + /// + private UUID m_originRegionID; + + /// + /// This object is used as a lock before accessing m_originRegionID to make sure that every thread is seeing + /// the very latest value and not using some cached version. Cannot make m_originRegionID itself volatite as + /// it is a value type. + /// + private object m_originRegionIDAccessLock = new object(); + + /// + /// Triggered on entity transfer after to allow CompleteMovement() to proceed after we have received an + /// UpdateAgent from the originating region.ddkjjkj + /// + private AutoResetEvent m_updateAgentReceivedAfterTransferEvent = new AutoResetEvent(false); + + /// + /// Used by the entity transfer module to signal when the presence should not be closed because a subsequent + /// teleport is reusing the connection. + /// + /// May be refactored or move somewhere else soon. + public bool DoNotCloseAfterTeleport { get; set; } + + #endregion /// /// Script engines present in the scene @@ -295,15 +376,17 @@ namespace OpenSim.Region.Framework.Scenes /// /// Record user movement inputs. /// - public byte MovementFlag { get; private set; } + public uint MovementFlag { get; private set; } - private bool m_updateflag; + /// + /// Set this if we need to force a movement update on the next received AgentUpdate from the viewer. + /// + private const uint ForceUpdateMovementFlagValue = uint.MaxValue; - public bool Updated - { - set { m_updateflag = value; } - get { return m_updateflag; } - } + /// + /// Is the agent stop control flag currently active? + /// + public bool AgentControlStopActive { get; private set; } private bool m_invulnerable = true; @@ -344,6 +427,9 @@ namespace OpenSim.Region.Framework.Scenes /// protected Vector3 m_lastCameraPosition; + private Vector4 m_lastCameraCollisionPlane = new Vector4(0f, 0f, 0f, 1); + private bool m_doingCamRayCast = false; + public Vector3 CameraPosition { get; set; } public Quaternion CameraRotation @@ -375,7 +461,19 @@ namespace OpenSim.Region.Framework.Scenes public string Firstname { get; private set; } public string Lastname { get; private set; } - public string Grouptitle { get; set; } + public string Grouptitle + { + get { return UseFakeGroupTitle ? "(Loading)" : m_groupTitle; } + set { m_groupTitle = value; } + } + private string m_groupTitle; + + /// + /// When this is 'true', return a dummy group title instead of the real group title. This is + /// used as part of a hack to force viewers to update the displayed avatar name. + /// + public bool UseFakeGroupTitle { get; set; } + // Agent's Draw distance. public float DrawDistance { get; set; } @@ -424,7 +522,9 @@ namespace OpenSim.Region.Framework.Scenes get { return (IClientCore)ControllingClient; } } - public Vector3 ParentPosition { get; set; } + public UUID COF { get; set; } + +// public Vector3 ParentPosition { get; set; } /// /// Position of this avatar relative to the region the avatar is in @@ -437,12 +537,13 @@ namespace OpenSim.Region.Framework.Scenes { m_pos = PhysicsActor.Position; - //m_log.DebugFormat( - // "[SCENE PRESENCE]: Set position {0} for {1} in {2} via getting AbsolutePosition!", - // m_pos, Name, Scene.RegionInfo.RegionName); +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Set position of {0} in {1} to {2} via getting AbsolutePosition!", +// Name, Scene.Name, m_pos); } else { +// m_log.DebugFormat("[SCENE PRESENCE]: Fetching abs pos where PhysicsActor == null and parent part {0} for {1}", Name, Scene.Name); // Obtain the correct position of a seated avatar. // In addition to providing the correct position while // the avatar is seated, this value will also @@ -459,13 +560,16 @@ namespace OpenSim.Region.Framework.Scenes SceneObjectPart sitPart = ParentPart; if (sitPart != null) - return sitPart.AbsolutePosition + (m_pos * sitPart.GetWorldRotation()); + return sitPart.ParentGroup.AbsolutePosition + (m_pos * sitPart.GetWorldRotation()); } return m_pos; } set { +// m_log.DebugFormat("[SCENE PRESENCE]: Setting position of {0} to {1} in {2}", Name, value, Scene.Name); +// Util.PrintCallStack(); + if (PhysicsActor != null) { try @@ -480,10 +584,7 @@ namespace OpenSim.Region.Framework.Scenes // Don't update while sitting. The PhysicsActor above is null whilst sitting. if (ParentID == 0) - { m_pos = value; - ParentPosition = Vector3.Zero; - } //m_log.DebugFormat( // "[ENTITY BASE]: In {0} set AbsolutePosition of {1} to {2}", @@ -513,8 +614,11 @@ namespace OpenSim.Region.Framework.Scenes } /// - /// Current velocity of the avatar. + /// Velocity of the avatar with respect to its local reference frame. /// + /// + /// So when sat on a vehicle this will be 0. To get velocity with respect to the world use GetWorldVelocity() + /// public override Vector3 Velocity { get @@ -527,11 +631,21 @@ namespace OpenSim.Region.Framework.Scenes // "[SCENE PRESENCE]: Set velocity {0} for {1} in {2} via getting Velocity!", // m_velocity, Name, Scene.RegionInfo.RegionName); } +// else if (ParentPart != null) +// { +// return ParentPart.ParentGroup.Velocity; +// } return m_velocity; } + set { +// Util.PrintCallStack(); +// m_log.DebugFormat( +// "[SCENE PRESENCE]: In {0} set velocity of {1} to {2}", +// Scene.RegionInfo.RegionName, Name, value); + if (PhysicsActor != null) { try @@ -544,37 +658,86 @@ namespace OpenSim.Region.Framework.Scenes } } - m_velocity = value; - -// m_log.DebugFormat( -// "[SCENE PRESENCE]: In {0} set velocity of {1} to {2}", -// Scene.RegionInfo.RegionName, Name, m_velocity); + m_velocity = value; } } +/* + public override Vector3 AngularVelocity + { + get + { + if (PhysicsActor != null) + { + m_rotationalvelocity = PhysicsActor.RotationalVelocity; + + // m_log.DebugFormat( + // "[SCENE PRESENCE]: Set velocity {0} for {1} in {2} via getting Velocity!", + // m_velocity, Name, Scene.RegionInfo.RegionName); + } + return m_rotationalvelocity; + } + } +*/ private Quaternion m_bodyRot = Quaternion.Identity; + /// + /// The rotation of the avatar. + /// + /// + /// If the avatar is not sitting, this is with respect to the world + /// If the avatar is sitting, this is a with respect to the part that it's sitting upon (a local rotation). + /// If you always want the world rotation, use GetWorldRotation() + /// public Quaternion Rotation { - get { return m_bodyRot; } + get + { + return m_bodyRot; + } + set { m_bodyRot = value; + if (PhysicsActor != null) { - PhysicsActor.Orientation = m_bodyRot; + try + { + PhysicsActor.Orientation = m_bodyRot; + } + catch (Exception e) + { + m_log.Error("[SCENE PRESENCE]: Orientation " + e.Message); + } } // m_log.DebugFormat("[SCENE PRESENCE]: Body rot for {0} set to {1}", Name, m_bodyRot); } } + // Used for limited viewer 'fake' user rotations. + private Vector3 m_AngularVelocity = Vector3.Zero; + + public Vector3 AngularVelocity + { + get { return m_AngularVelocity; } + } + public bool IsChildAgent { get; set; } + public bool IsLoggingIn { get; set; } /// /// If the avatar is sitting, the local ID of the prim that it's sitting on. If not sitting then zero. /// public uint ParentID { get; set; } + public UUID ParentUUID + { + get { return m_parentUUID; } + set { m_parentUUID = value; } + } + private UUID m_parentUUID = UUID.Zero; + /// /// Are we sitting on an object? /// @@ -595,6 +758,33 @@ namespace OpenSim.Region.Framework.Scenes set { m_health = value; } } + /// + /// Get rotation relative to the world. + /// + /// + public Quaternion GetWorldRotation() + { + SceneObjectPart sitPart = ParentPart; + + if (sitPart != null) + return sitPart.GetWorldRotation() * Rotation; + + return Rotation; + } + + /// + /// Get velocity relative to the world. + /// + public Vector3 GetWorldVelocity() + { + SceneObjectPart sitPart = ParentPart; + + if (sitPart != null) + return sitPart.ParentGroup.Velocity; + + return Velocity; + } + public void AdjustKnownSeeds() { Dictionary seeds; @@ -608,9 +798,8 @@ namespace OpenSim.Region.Framework.Scenes foreach (ulong handle in seeds.Keys) { uint x, y; - Utils.LongToUInts(handle, out x, out y); - x = x / Constants.RegionSize; - y = y / Constants.RegionSize; + Util.RegionHandleToRegionLoc(handle, out x, out y); + if (Util.IsOutsideView(DrawDistance, x, Scene.RegionInfo.RegionLocX, y, Scene.RegionInfo.RegionLocY)) { old.Add(handle); @@ -632,18 +821,22 @@ namespace OpenSim.Region.Framework.Scenes foreach (KeyValuePair kvp in KnownRegions) { uint x, y; - Utils.LongToUInts(kvp.Key, out x, out y); - x = x / Constants.RegionSize; - y = y / Constants.RegionSize; + Util.RegionHandleToRegionLoc(kvp.Key, out x, out y); m_log.Info(" >> "+x+", "+y+": "+kvp.Value); } } private bool m_mouseLook; - private bool m_leftButtonDown; +// private bool m_leftButtonDown; private bool m_inTransit; + /// + /// This signals whether the presence is in transit between neighbouring regions. + /// + /// + /// It is not set when the presence is teleporting or logging in/out directly to a region. + /// public bool IsInTransit { get { return m_inTransit; } @@ -667,6 +860,14 @@ namespace OpenSim.Region.Framework.Scenes set { m_speedModifier = value; } } + /// + /// Modifier for agent movement if we get an AGENT_CONTROL_STOP whilst walking or running + /// + /// + /// AGENT_CONTRL_STOP comes about if user holds down space key on viewers. + /// + private float AgentControlStopSlowWhilstMoving = 0.5f; + private bool m_forceFly; public bool ForceFly @@ -685,23 +886,30 @@ namespace OpenSim.Region.Framework.Scenes public string Viewer { - get { return m_scene.AuthenticateHandler.GetAgentCircuitData(ControllingClient.CircuitCode).Viewer; } + get { return Util.GetViewerName(m_scene.AuthenticateHandler.GetAgentCircuitData(ControllingClient.CircuitCode)); } } + /// + /// Count of how many terse updates we have sent out. It doesn't matter if this overflows. + /// + private int m_terseUpdateCount; + #endregion #region Constructor(s) public ScenePresence( IClientAPI client, Scene world, AvatarAppearance appearance, PresenceType type) - { + { AttachmentsSyncLock = new Object(); AllowMovement = true; IsChildAgent = true; + IsLoggingIn = false; m_sendCoarseLocationsMethod = SendCoarseLocationsDefault; Animator = new ScenePresenceAnimator(this); PresenceType = type; - DrawDistance = world.DefaultDrawDistance; + // DrawDistance = world.DefaultDrawDistance; + DrawDistance = Constants.RegionSize; RegionHandle = world.RegionInfo.RegionHandle; ControllingClient = client; Firstname = ControllingClient.FirstName; @@ -739,12 +947,42 @@ namespace OpenSim.Region.Framework.Scenes SetDirectionVectors(); Appearance = appearance; + + m_stateMachine = new ScenePresenceStateMachine(this); + } + + private void RegionHeartbeatEnd(Scene scene) + { + if (IsChildAgent) + return; + + m_movementAnimationUpdateCounter ++; + if (m_movementAnimationUpdateCounter >= 2) + { + m_movementAnimationUpdateCounter = 0; + if (Animator != null) + { + // If the parentID == 0 we are not sitting + // if !SitGournd then we are not sitting on the ground + // Fairly straightforward, now here comes the twist + // if ParentUUID is NOT UUID.Zero, we are looking to + // be sat on an object that isn't there yet. Should + // be treated as if sat. + if(ParentID == 0 && !SitGround && ParentUUID == UUID.Zero) // skip it if sitting + Animator.UpdateMovementAnimations(); + } + else + { + m_scene.EventManager.OnRegionHeartbeatEnd -= RegionHeartbeatEnd; + } + } } public void RegisterToEvents() { ControllingClient.OnCompleteMovementToRegion += CompleteMovement; ControllingClient.OnAgentUpdate += HandleAgentUpdate; + ControllingClient.OnAgentCameraUpdate += HandleAgentCamerasUpdate; ControllingClient.OnAgentRequestSit += HandleAgentRequestSit; ControllingClient.OnAgentSit += HandleAgentSit; ControllingClient.OnSetAlwaysRun += HandleSetAlwaysRun; @@ -772,23 +1010,6 @@ namespace OpenSim.Region.Framework.Scenes Dir_Vectors[10] = new Vector3(0f, 0f, -0.5f); //DOWN_Nudge } - private Vector3[] GetWalkDirectionVectors() - { - Vector3[] vector = new Vector3[11]; - vector[0] = new Vector3(CameraUpAxis.Z, 0f, -CameraAtAxis.Z); //FORWARD - vector[1] = new Vector3(-CameraUpAxis.Z, 0f, CameraAtAxis.Z); //BACK - vector[2] = Vector3.UnitY; //LEFT - vector[3] = -Vector3.UnitY; //RIGHT - vector[4] = new Vector3(CameraAtAxis.Z, 0f, CameraUpAxis.Z); //UP - vector[5] = new Vector3(-CameraAtAxis.Z, 0f, -CameraUpAxis.Z); //DOWN - vector[6] = new Vector3(CameraUpAxis.Z, 0f, -CameraAtAxis.Z); //FORWARD_NUDGE - vector[7] = new Vector3(-CameraUpAxis.Z, 0f, CameraAtAxis.Z); //BACK_NUDGE - vector[8] = Vector3.UnitY; //LEFT_NUDGE - vector[9] = -Vector3.UnitY; //RIGHT_NUDGE - vector[10] = new Vector3(-CameraAtAxis.Z, 0f, -CameraUpAxis.Z); //DOWN_NUDGE - return vector; - } - #endregion #region Status Methods @@ -796,6 +1017,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// Turns a child agent into a root agent. /// + /// /// Child agents are logged into neighbouring sims largely to observe changes. Root agents exist when the /// avatar is actual in the sim. They can perform all actions. /// This change is made whenever an avatar enters a region, whether by crossing over from a neighbouring sim, @@ -803,80 +1025,188 @@ namespace OpenSim.Region.Framework.Scenes /// /// This method is on the critical path for transferring an avatar from one region to another. Delay here /// delays that crossing. - /// - public void MakeRootAgent(Vector3 pos, bool isFlying) + /// + private bool MakeRootAgent(Vector3 pos, bool isFlying) { - m_log.DebugFormat( - "[SCENE]: Upgrading child to root agent for {0} in {1}", - Name, m_scene.RegionInfo.RegionName); + lock (m_completeMovementLock) + { + if (!IsChildAgent) + return false; + + //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); + + // m_log.InfoFormat( + // "[SCENE]: Upgrading child to root agent for {0} in {1}", + // Name, m_scene.RegionInfo.RegionName); + + if (ParentUUID != UUID.Zero) + { + m_log.DebugFormat("[SCENE PRESENCE]: Sitting avatar back on prim {0}", ParentUUID); + SceneObjectPart part = m_scene.GetSceneObjectPart(ParentUUID); + if (part == null) + { + m_log.ErrorFormat("[SCENE PRESENCE]: Can't find prim {0} to sit on", ParentUUID); + } + else + { + part.AddSittingAvatar(this); + // ParentPosition = part.GetWorldPosition(); + ParentID = part.LocalId; + ParentPart = part; + m_pos = PrevSitOffset; + // pos = ParentPosition; + pos = part.GetWorldPosition(); + } + ParentUUID = UUID.Zero; + + // Animator.TrySetMovementAnimation("SIT"); + } + else + { + IsLoggingIn = false; + } - //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); + IsChildAgent = false; + } - bool wasChild = IsChildAgent; - IsChildAgent = false; + // Must reset this here so that a teleport to a region next to an existing region does not keep the flag + // set and prevent the close of the connection on a subsequent re-teleport. + // Should not be needed if we are not trying to tell this region to close +// DoNotCloseAfterTeleport = false; IGroupsModule gm = m_scene.RequestModuleInterface(); if (gm != null) Grouptitle = gm.GetGroupTitle(m_uuid); + AgentCircuitData aCircuit = m_scene.AuthenticateHandler.GetAgentCircuitData(ControllingClient.CircuitCode); + uint teleportFlags = (aCircuit == null) ? 0 : aCircuit.teleportFlags; + if ((teleportFlags & (uint)TeleportFlags.ViaHGLogin) != 0) + { + // The avatar is arriving from another grid. This means that we may have changed the + // avatar's name to or from the special Hypergrid format ("First.Last @grid.example.com"). + // Unfortunately, due to a viewer bug, viewers don't always show the new name. + // But we have a trick that can force them to update the name anyway. + ForceViewersUpdateName(); + } + RegionHandle = m_scene.RegionInfo.RegionHandle; m_scene.EventManager.TriggerSetRootAgentScene(m_uuid, m_scene); - // Moved this from SendInitialData to ensure that Appearance is initialized - // before the inventory is processed in MakeRootAgent. This fixes a race condition - // related to the handling of attachments - //m_scene.GetAvatarAppearance(ControllingClient, out Appearance); - if (m_scene.TestBorderCross(pos, Cardinals.E)) + UUID groupUUID = ControllingClient.ActiveGroupId; + string groupName = string.Empty; + ulong groupPowers = 0; + + // ---------------------------------- + // Previous Agent Difference - AGNI sends an unsolicited AgentDataUpdate upon root agent status + try { - Border crossedBorder = m_scene.GetCrossedBorder(pos, Cardinals.E); - pos.X = crossedBorder.BorderLine.Z - 1; - } + if (groupUUID != UUID.Zero && gm != null) + { + GroupRecord record = gm.GetGroupRecord(groupUUID); + if (record != null) + groupName = record.GroupName; + + GroupMembershipData groupMembershipData = gm.GetMembershipData(groupUUID, m_uuid); - if (m_scene.TestBorderCross(pos, Cardinals.N)) + if (groupMembershipData != null) + groupPowers = groupMembershipData.GroupPowers; + } + + ControllingClient.SendAgentDataUpdate( + m_uuid, groupUUID, Firstname, Lastname, groupPowers, groupName, Grouptitle); + } + catch (Exception e) { - Border crossedBorder = m_scene.GetCrossedBorder(pos, Cardinals.N); - pos.Y = crossedBorder.BorderLine.Z - 1; + m_log.Error("[AGENTUPDATE]: Error ", e); } + // ------------------------------------ - CheckAndAdjustLandingPoint(ref pos); - - if (pos.X < 0f || pos.Y < 0f || pos.Z < 0f) + if (ParentID == 0) { - m_log.WarnFormat( - "[SCENE PRESENCE]: MakeRootAgent() was given an illegal position of {0} for avatar {1}, {2}. Clamping", - pos, Name, UUID); + // Moved this from SendInitialData to ensure that Appearance is initialized + // before the inventory is processed in MakeRootAgent. This fixes a race condition + // related to the handling of attachments + //m_scene.GetAvatarAppearance(ControllingClient, out Appearance); + + /* RA 20140111: Commented out these TestBorderCross's. + * Not sure why this code is here. It is not checking all the borders + * and 'in region' sanity checking is done in CheckAndAdjustLandingPoint and below. + if (m_scene.TestBorderCross(pos, Cardinals.E)) + { + Border crossedBorder = m_scene.GetCrossedBorder(pos, Cardinals.E); + pos.X = crossedBorder.BorderLine.Z - 1; + } - if (pos.X < 0f) pos.X = 0f; - if (pos.Y < 0f) pos.Y = 0f; - if (pos.Z < 0f) pos.Z = 0f; - } + if (m_scene.TestBorderCross(pos, Cardinals.N)) + { + Border crossedBorder = m_scene.GetCrossedBorder(pos, Cardinals.N); + pos.Y = crossedBorder.BorderLine.Z - 1; + } + */ + + CheckAndAdjustLandingPoint(ref pos); + + if (pos.X < 0f || pos.Y < 0f || pos.Z < 0f) + { + m_log.WarnFormat( + "[SCENE PRESENCE]: MakeRootAgent() was given an illegal position of {0} for avatar {1}, {2}. Clamping", + pos, Name, UUID); - float localAVHeight = 1.56f; - if (Appearance.AvatarHeight > 0) - localAVHeight = Appearance.AvatarHeight; + if (pos.X < 0f) pos.X = 0f; + if (pos.Y < 0f) pos.Y = 0f; + if (pos.Z < 0f) pos.Z = 0f; + } - float posZLimit = 0; + float localAVHeight = 1.56f; + if (Appearance.AvatarHeight > 0) + localAVHeight = Appearance.AvatarHeight; - if (pos.X < Constants.RegionSize && pos.Y < Constants.RegionSize) - posZLimit = (float)m_scene.Heightmap[(int)pos.X, (int)pos.Y]; - - float newPosZ = posZLimit + localAVHeight / 2; - if (posZLimit >= (pos.Z - (localAVHeight / 2)) && !(Single.IsInfinity(newPosZ) || Single.IsNaN(newPosZ))) - { - pos.Z = newPosZ; - } - AbsolutePosition = pos; + float posZLimit = 0; - AddToPhysicalScene(isFlying); + if (pos.X < m_scene.RegionInfo.RegionSizeX && pos.Y < m_scene.RegionInfo.RegionSizeY) + posZLimit = (float)m_scene.Heightmap[(int)pos.X, (int)pos.Y]; + + float newPosZ = posZLimit + localAVHeight / 2; + if (posZLimit >= (pos.Z - (localAVHeight / 2)) && !(Single.IsInfinity(newPosZ) || Single.IsNaN(newPosZ))) + { + pos.Z = newPosZ; + } + AbsolutePosition = pos; - if (ForceFly) - { - Flying = true; - } - else if (FlyDisabled) - { - Flying = false; +// m_log.DebugFormat( +// "Set pos {0}, vel {1} in {1} to {2} from input position of {3} on MakeRootAgent", +// Name, Scene.Name, AbsolutePosition, pos); +// + if (m_teleportFlags == TeleportFlags.Default) + { + AddToPhysicalScene(isFlying); +// +// Console.WriteLine( +// "Set velocity of {0} in {1} to {2} from input velocity of {3} on MakeRootAgent", +// Name, Scene.Name, PhysicsActor.Velocity, vel); +// } + } + else + { + AddToPhysicalScene(isFlying); + } + + // XXX: This is to trigger any secondary teleport needed for a megaregion when the user has teleported to a + // location outside the 'root region' (the south-west 256x256 corner). This is the earlist we can do it + // since it requires a physics actor to be present. If it is left any later, then physics appears to reset + // the value to a negative position which does not trigger the border cross. + // This may not be the best location for this. + CheckForBorderCrossing(); + + if (ForceFly) + { + Flying = true; + } + else if (FlyDisabled) + { + Flying = false; + } } // Don't send an animation pack here, since on a region crossing this will sometimes cause a flying @@ -886,26 +1216,33 @@ namespace OpenSim.Region.Framework.Scenes m_scene.SwapRootAgentCount(false); - // The initial login scene presence is already root when it gets here - // and it has already rezzed the attachments and started their scripts. - // We do the following only for non-login agents, because their scripts - // haven't started yet. - lock (m_attachments) + if (Scene.AttachmentsModule != null) { - if (wasChild && HasAttachments()) + // The initial login scene presence is already root when it gets here + // and it has already rezzed the attachments and started their scripts. + // We do the following only for non-login agents, because their scripts + // haven't started yet. + if (PresenceType == PresenceType.Npc || IsRealLogin(m_teleportFlags)) { - m_log.DebugFormat( - "[SCENE PRESENCE]: Restarting scripts in attachments for {0} in {1}", Name, Scene.Name); - - // Resume scripts - foreach (SceneObjectGroup sog in m_attachments) - { - sog.RootPart.ParentGroup.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, GetStateSource()); - sog.ResumeScripts(); - } + WorkManager.RunJob( + "RezAttachments", + o => Scene.AttachmentsModule.RezAttachments(this), + null, + string.Format("Rez attachments for {0} in {1}", Name, Scene.Name)); + } + else + { + WorkManager.RunJob( + "StartAttachmentScripts", + o => RestartAttachmentScripts(), + null, + string.Format("Start attachment scripts for {0} in {1}", Name, Scene.Name), + true); } } + SendAvatarDataToAllClients(); + // send the animations of the other presences to me m_scene.ForEachRootScenePresence(delegate(ScenePresence presence) { @@ -916,27 +1253,93 @@ namespace OpenSim.Region.Framework.Scenes // If we don't reset the movement flag here, an avatar that crosses to a neighbouring sim and returns will // stall on the border crossing since the existing child agent will still have the last movement // recorded, which stops the input from being processed. - MovementFlag = 0; + MovementFlag = ForceUpdateMovementFlagValue; m_scene.EventManager.TriggerOnMakeRootAgent(this); + + return true; } - public int GetStateSource() + private void RestartAttachmentScripts() { - AgentCircuitData aCircuit = m_scene.AuthenticateHandler.GetAgentCircuitData(UUID); + // We need to restart scripts here so that they receive the correct changed events (CHANGED_TELEPORT + // and CHANGED_REGION) when the attachments have been rezzed in the new region. This cannot currently + // be done in AttachmentsModule.CopyAttachments(AgentData ad, IScenePresence sp) itself since we are + // not transporting the required data. + // + // We must take a copy of the attachments list here (rather than locking) to avoid a deadlock where a script in one of + // the attachments may start processing an event (which locks ScriptInstance.m_Script) that then calls a method here + // which needs to lock m_attachments. ResumeScripts() needs to take a ScriptInstance.m_Script lock to try to unset the Suspend status. + // + // FIXME: In theory, this deadlock should not arise since scripts should not be processing events until ResumeScripts(). + // But XEngine starts all scripts unsuspended. Starting them suspended will not currently work because script rezzing + // is placed in an asynchronous queue in XEngine and so the ResumeScripts() call will almost certainly execute before the + // script is rezzed. This means the ResumeScripts() does absolutely nothing when using XEngine. + List attachments = GetAttachments(); - if (aCircuit != null && (aCircuit.teleportFlags != (uint)TeleportFlags.Default)) - { - // This will get your attention - //m_log.Error("[XXX] Triggering CHANGED_TELEPORT"); + m_log.DebugFormat( + "[SCENE PRESENCE]: Restarting scripts in {0} attachments for {1} in {2}", attachments.Count, Name, Scene.Name); - return 5; // StateSource.Teleporting + // Resume scripts + foreach (SceneObjectGroup sog in attachments) + { + sog.ScheduleGroupForFullUpdate(); + sog.RootPart.ParentGroup.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, GetStateSource()); + sog.ResumeScripts(); } - return 2; // StateSource.PrimCrossing + } + + private static bool IsRealLogin(TeleportFlags teleportFlags) + { + return ((teleportFlags & TeleportFlags.ViaLogin) != 0) && ((teleportFlags & TeleportFlags.ViaHGLogin) == 0); } /// - /// This turns a root agent into a child agent + /// Force viewers to show the avatar's current name. + /// + /// + /// The avatar name that is shown above the avatar in the viewers is sent in ObjectUpdate packets, + /// and they get the name from the ScenePresence. Unfortunately, viewers have a bug (as of April 2014) + /// where they ignore changes to the avatar name. However, tey don't ignore changes to the avatar's + /// Group Title. So the following trick makes viewers update the avatar's name by briefly changing + /// the group title (to "(Loading)"), and then restoring it. + /// + public void ForceViewersUpdateName() + { + m_log.DebugFormat("[SCENE PRESENCE]: Forcing viewers to update the avatar name for " + Name); + + UseFakeGroupTitle = true; + SendAvatarDataToAllClients(false); + + Util.FireAndForget(o => + { + // Viewers only update the avatar name when idle. Therefore, we must wait long + // enough for the viewer to show the fake name that we had set above, and only + // then switch back to the true name. This delay was chosen because it has a high + // chance of succeeding (we don't want to choose a value that's too low). + Thread.Sleep(5000); + + UseFakeGroupTitle = false; + SendAvatarDataToAllClients(false); + }, null, "Scenepresence.ForceViewersUpdateName"); + } + + public int GetStateSource() + { + AgentCircuitData aCircuit = m_scene.AuthenticateHandler.GetAgentCircuitData(UUID); + + if (aCircuit != null && (aCircuit.teleportFlags != (uint)TeleportFlags.Default)) + { + // This will get your attention + //m_log.Error("[XXX] Triggering CHANGED_TELEPORT"); + + return 5; // StateSource.Teleporting + } + return 2; // StateSource.PrimCrossing + } + + /// + /// This turns a root agent into a child agent /// /// /// when an agent departs this region for a neighbor, this gets called. @@ -946,12 +1349,21 @@ namespace OpenSim.Region.Framework.Scenes /// public void MakeChildAgent() { + m_scene.EventManager.OnRegionHeartbeatEnd -= RegionHeartbeatEnd; + m_log.DebugFormat("[SCENE PRESENCE]: Making {0} a child agent in {1}", Name, Scene.RegionInfo.RegionName); + // Reset the m_originRegionID as it has dual use as a flag to signal that the UpdateAgent() call orignating + // from the source simulator has completed on a V2 teleport. + lock (m_originRegionIDAccessLock) + m_originRegionID = UUID.Zero; + // Reset these so that teleporting in and walking out isn't seen // as teleporting back TeleportFlags = TeleportFlags.Default; + MovementFlag = 0; + // It looks like Animator is set to null somewhere, and MakeChild // is called after that. Probably in aborted teleports. if (Animator == null) @@ -959,6 +1371,7 @@ namespace OpenSim.Region.Framework.Scenes else Animator.ResetAnimations(); + // m_log.DebugFormat( // "[SCENE PRESENCE]: Downgrading root agent {0}, {1} to a child agent in {2}", // Name, UUID, m_scene.RegionInfo.RegionName); @@ -970,6 +1383,7 @@ namespace OpenSim.Region.Framework.Scenes IsChildAgent = true; m_scene.SwapRootAgentCount(true); RemoveFromPhysicalScene(); + ParentID = 0; // Child agents can't be sitting // FIXME: Set RegionHandle to the region handle of the scene this agent is moving into @@ -984,10 +1398,10 @@ namespace OpenSim.Region.Framework.Scenes if (PhysicsActor != null) { // PhysicsActor.OnRequestTerseUpdate -= SendTerseUpdateToAllClients; - PhysicsActor.OnOutOfBounds -= OutOfBoundsCall; - m_scene.PhysicsScene.RemoveAvatar(PhysicsActor); PhysicsActor.UnSubscribeEvents(); + PhysicsActor.OnOutOfBounds -= OutOfBoundsCall; PhysicsActor.OnCollisionUpdate -= PhysicsCollisionUpdate; + m_scene.PhysicsScene.RemoveAvatar(PhysicsActor); PhysicsActor = null; } // else @@ -1004,7 +1418,7 @@ namespace OpenSim.Region.Framework.Scenes /// public void Teleport(Vector3 pos) { - TeleportWithMomentum(pos, null); + TeleportWithMomentum(pos, Vector3.Zero); } public void TeleportWithMomentum(Vector3 pos, Vector3? v) @@ -1024,14 +1438,141 @@ namespace OpenSim.Region.Framework.Scenes else PhysicsActor.SetMomentum(vel); } + } + + public void avnLocalTeleport(Vector3 newpos, Vector3? newvel, bool rotateToVelXY) + { + CheckLandingPoint(ref newpos); + AbsolutePosition = newpos; - SendTerseUpdateToAllClients(); + if (newvel.HasValue) + { + if ((Vector3)newvel == Vector3.Zero) + { + if (PhysicsActor != null) + PhysicsActor.SetMomentum(Vector3.Zero); + m_velocity = Vector3.Zero; + } + else + { + if (PhysicsActor != null) + PhysicsActor.SetMomentum((Vector3)newvel); + m_velocity = (Vector3)newvel; + + if (rotateToVelXY) + { + Vector3 lookAt = (Vector3)newvel; + lookAt.Z = 0; + lookAt.Normalize(); + ControllingClient.SendLocalTeleport(newpos, lookAt, (uint)TeleportFlags.ViaLocation); + return; + } + } + } } public void StopFlying() { - ControllingClient.StopFlying(this); + Vector3 pos = AbsolutePosition; + if (Appearance.AvatarHeight != 127.0f) + pos += new Vector3(0f, 0f, (Appearance.AvatarHeight / 6f)); + else + pos += new Vector3(0f, 0f, (1.56f / 6f)); + + AbsolutePosition = pos; + + // attach a suitable collision plane regardless of the actual situation to force the LLClient to land. + // Collision plane below the avatar's position a 6th of the avatar's height is suitable. + // Mind you, that this method doesn't get called if the avatar's velocity magnitude is greater then a + // certain amount.. because the LLClient wouldn't land in that situation anyway. + + // why are we still testing for this really old height value default??? + if (Appearance.AvatarHeight != 127.0f) + CollisionPlane = new Vector4(0, 0, 0, pos.Z - Appearance.AvatarHeight / 6f); + else + CollisionPlane = new Vector4(0, 0, 0, pos.Z - (1.56f / 6f)); + + ControllingClient.SendAgentTerseUpdate(this); + } + + /// + /// Applies a roll accumulator to the avatar's angular velocity for the avatar fly roll effect. + /// + /// Postive or negative roll amount in radians + private void ApplyFlyingRoll(float amount, bool PressingUp, bool PressingDown) + { + + float rollAmount = Util.Clamp(m_AngularVelocity.Z + amount, -FLY_ROLL_MAX_RADIANS, FLY_ROLL_MAX_RADIANS); + m_AngularVelocity.Z = rollAmount; + + // APPLY EXTRA consideration for flying up and flying down during this time. + // if we're turning left + if (amount > 0) + { + + // If we're at the max roll and pressing up, we want to swing BACK a bit + // Automatically adds noise + if (PressingUp) + { + if (m_AngularVelocity.Z >= FLY_ROLL_MAX_RADIANS - 0.04f) + m_AngularVelocity.Z -= 0.9f; + } + // If we're at the max roll and pressing down, we want to swing MORE a bit + if (PressingDown) + { + if (m_AngularVelocity.Z >= FLY_ROLL_MAX_RADIANS && m_AngularVelocity.Z < FLY_ROLL_MAX_RADIANS + 0.6f) + m_AngularVelocity.Z += 0.6f; + } + } + else // we're turning right. + { + // If we're at the max roll and pressing up, we want to swing BACK a bit + // Automatically adds noise + if (PressingUp) + { + if (m_AngularVelocity.Z <= (-FLY_ROLL_MAX_RADIANS)) + m_AngularVelocity.Z += 0.6f; + } + // If we're at the max roll and pressing down, we want to swing MORE a bit + if (PressingDown) + { + if (m_AngularVelocity.Z >= -FLY_ROLL_MAX_RADIANS - 0.6f) + m_AngularVelocity.Z -= 0.6f; + } + } + } + + /// + /// incrementally sets roll amount to zero + /// + /// Positive roll amount in radians + /// + private float CalculateFlyingRollResetToZero(float amount) + { + const float rollMinRadians = 0f; + + if (m_AngularVelocity.Z > 0) + { + + float leftOverToMin = m_AngularVelocity.Z - rollMinRadians; + if (amount > leftOverToMin) + return -leftOverToMin; + else + return -amount; + + } + else + { + + float leftOverToMin = -m_AngularVelocity.Z - rollMinRadians; + if (amount > leftOverToMin) + return leftOverToMin; + else + return amount; + } } + + // neighbouring regions we have enabled a child agent in // holds the seed cap for the child agent in that region @@ -1118,6 +1659,40 @@ namespace OpenSim.Region.Framework.Scenes PhysicsActor.Size = new Vector3(0.45f, 0.6f, height); } + public void SetSize(Vector3 size, float feetoffset) + { + if (PhysicsActor != null && !IsChildAgent) + { + // Eventually there will be a physics call that sets avatar size that includes offset info. + // For the moment, just set the size as passed. + PhysicsActor.Size = size; + // PhysicsActor.setAvatarSize(size, feetoffset); + } + } + + private bool WaitForUpdateAgent(IClientAPI client) + { + // Before the source region executes UpdateAgent + // (which triggers Scene.IncomingUpdateChildAgent(AgentData cAgentData) here in the destination, + // m_originRegionID is UUID.Zero; after, it's non-Zero. The CompleteMovement sequence initiated from the + // viewer (in turn triggered by the source region sending it a TeleportFinish event) waits until it's non-zero + m_updateAgentReceivedAfterTransferEvent.WaitOne(10000); + + UUID originID = UUID.Zero; + + lock (m_originRegionIDAccessLock) + originID = m_originRegionID; + + if (originID.Equals(UUID.Zero)) + { + // Movement into region will fail + m_log.WarnFormat("[SCENE PRESENCE]: Update agent {0} never arrived in {1}", client.Name, Scene.Name); + return false; + } + + return true; + } + /// /// Complete Avatar's movement into the region. /// @@ -1131,74 +1706,131 @@ namespace OpenSim.Region.Framework.Scenes { // DateTime startTime = DateTime.Now; - m_log.DebugFormat( + m_log.InfoFormat( "[SCENE PRESENCE]: Completing movement of {0} into region {1} in position {2}", - client.Name, Scene.RegionInfo.RegionName, AbsolutePosition); + client.Name, Scene.Name, AbsolutePosition); - Vector3 look = Velocity; + bool flying = ((m_AgentControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_FLY) != 0); // Get this ahead of time because IsInTransit modifies 'm_AgentControlFlags' - if ((look.X == 0) && (look.Y == 0) && (look.Z == 0)) + IsInTransit = true; + try { - look = new Vector3(0.99f, 0.042f, 0); - } + // Make sure it's not a login agent. We don't want to wait for updates during login + if (!(PresenceType == PresenceType.Npc || IsRealLogin(m_teleportFlags))) + { + // Let's wait until UpdateAgent (called by departing region) is done + if (!WaitForUpdateAgent(client)) + // The sending region never sent the UpdateAgent data, we have to refuse + return; + } - // Prevent teleporting to an underground location - // (may crash client otherwise) - // - Vector3 pos = AbsolutePosition; - float ground = m_scene.GetGroundHeight(pos.X, pos.Y); - if (pos.Z < ground + 1.5f) - { - pos.Z = ground + 1.5f; - AbsolutePosition = pos; - } + Vector3 look = Velocity; - bool flying = ((m_AgentControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_FLY) != 0); - MakeRootAgent(AbsolutePosition, flying); - ControllingClient.MoveAgentIntoRegion(m_scene.RegionInfo, AbsolutePosition, look); + // if ((look.X == 0) && (look.Y == 0) && (look.Z == 0)) + if ((Math.Abs(look.X) < 0.1) && (Math.Abs(look.Y) < 0.1) && (Math.Abs(look.Z) < 0.1)) + { + look = new Vector3(0.99f, 0.042f, 0); + } -// m_log.DebugFormat("[SCENE PRESENCE] Completed movement"); + // Prevent teleporting to an underground location + // (may crash client otherwise) + // + Vector3 pos = AbsolutePosition; + float ground = m_scene.GetGroundHeight(pos.X, pos.Y); + if (pos.Z < ground + 1.5f) + { + pos.Z = ground + 1.5f; + AbsolutePosition = pos; + } - if ((m_callbackURI != null) && !m_callbackURI.Equals("")) - { - // We cannot sleep here since this would hold up the inbound packet processing thread, as - // CompleteMovement() is executed synchronously. However, it might be better to delay the release - // here until we know for sure that the agent is active in this region. Sending AgentMovementComplete - // is not enough for Imprudence clients - there appears to be a small delay (<200ms, <500ms) until they regard this - // region as the current region, meaning that a close sent before then will fail the teleport. -// System.Threading.Thread.Sleep(2000); + if (!MakeRootAgent(AbsolutePosition, flying)) + { + m_log.DebugFormat( + "[SCENE PRESENCE]: Aborting CompleteMovement call for {0} in {1} as they are already root", + Name, Scene.Name); - m_log.DebugFormat( - "[SCENE PRESENCE]: Releasing {0} {1} with callback to {2}", - client.Name, client.AgentId, m_callbackURI); + return; + } - Scene.SimulationService.ReleaseAgent(m_originRegionID, UUID, m_callbackURI); - m_callbackURI = null; - } -// else -// { -// m_log.DebugFormat( -// "[SCENE PRESENCE]: No callback provided on CompleteMovement of {0} {1} to {2}", -// client.Name, client.AgentId, m_scene.RegionInfo.RegionName); -// } + // Tell the client that we're totally ready + ControllingClient.MoveAgentIntoRegion(m_scene.RegionInfo, AbsolutePosition, look); - ValidateAndSendAppearanceAndAgentData(); + // Child agents send initial data up in LLUDPServer.HandleUseCircuitCode() + if (!SentInitialDataToClient) + SendInitialDataToClient(); - // Create child agents in neighbouring regions - if (openChildAgents && !IsChildAgent) - { - IEntityTransferModule m_agentTransfer = m_scene.RequestModuleInterface(); - if (m_agentTransfer != null) - Util.FireAndForget(delegate { m_agentTransfer.EnableChildAgents(this); }); + // m_log.DebugFormat("[SCENE PRESENCE] Completed movement"); + + if (!string.IsNullOrEmpty(m_callbackURI)) + { + // We cannot sleep here since this would hold up the inbound packet processing thread, as + // CompleteMovement() is executed synchronously. However, it might be better to delay the release + // here until we know for sure that the agent is active in this region. Sending AgentMovementComplete + // is not enough for Imprudence clients - there appears to be a small delay (<200ms, <500ms) until they regard this + // region as the current region, meaning that a close sent before then will fail the teleport. + // System.Threading.Thread.Sleep(2000); + + m_log.DebugFormat( + "[SCENE PRESENCE]: Releasing {0} {1} with callback to {2}", + client.Name, client.AgentId, m_callbackURI); + + Scene.SimulationService.ReleaseAgent(m_originRegionID, UUID, m_callbackURI); + m_callbackURI = null; + } + // else + // { + // m_log.DebugFormat( + // "[SCENE PRESENCE]: No callback provided on CompleteMovement of {0} {1} to {2}", + // client.Name, client.AgentId, m_scene.RegionInfo.RegionName); + // } + + ValidateAndSendAppearanceAndAgentData(); + + // Create child agents in neighbouring regions + if (openChildAgents && !IsChildAgent) + { + IEntityTransferModule m_agentTransfer = m_scene.RequestModuleInterface(); + if (m_agentTransfer != null) + { + // Note: this call can take a while, because it notifies each of the simulator's neighbours. + // It's important that we don't allow the avatar to cross regions meanwhile, as that will + // cause serious errors. We've prevented that from happening by setting IsInTransit=true. + m_agentTransfer.EnableChildAgents(this); + } + + IFriendsModule friendsModule = m_scene.RequestModuleInterface(); + if (friendsModule != null) + friendsModule.SendFriendsOnlineIfNeeded(ControllingClient); + + } - IFriendsModule friendsModule = m_scene.RequestModuleInterface(); - if (friendsModule != null) - friendsModule.SendFriendsOnlineIfNeeded(ControllingClient); + // XXX: If we force an update after activity has completed, then multiple attachments do appear correctly on a destination region + // If we do it a little bit earlier (e.g. when converting the child to a root agent) then this does not work. + // This may be due to viewer code or it may be something we're not doing properly simulator side. + WorkManager.RunJob( + "ScheduleAttachmentsForFullUpdate", + o => ScheduleAttachmentsForFullUpdate(), + null, + string.Format("Schedule attachments for full update for {0} in {1}", Name, Scene.Name), + true); + + // m_log.DebugFormat( + // "[SCENE PRESENCE]: Completing movement of {0} into region {1} took {2}ms", + // client.Name, Scene.RegionInfo.RegionName, (DateTime.Now - startTime).Milliseconds); } + finally + { + IsInTransit = false; + } + } -// m_log.DebugFormat( -// "[SCENE PRESENCE]: Completing movement of {0} into region {1} took {2}ms", -// client.Name, Scene.RegionInfo.RegionName, (DateTime.Now - startTime).Milliseconds); + private void ScheduleAttachmentsForFullUpdate() + { + lock (m_attachments) + { + foreach (SceneObjectGroup sog in m_attachments) + sog.ScheduleGroupForFullUpdate(); + } } /// @@ -1209,36 +1841,69 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// + /// + + private void UpdateCameraCollisionPlane(Vector4 plane) + { + if (m_lastCameraCollisionPlane != plane) + { + m_lastCameraCollisionPlane = plane; + ControllingClient.SendCameraConstraint(plane); + } + } + public void RayCastCameraCallback(bool hitYN, Vector3 collisionPoint, uint localid, float distance, Vector3 pNormal) { const float POSITION_TOLERANCE = 0.02f; - const float VELOCITY_TOLERANCE = 0.02f; const float ROTATION_TOLERANCE = 0.02f; - if (m_followCamAuto) + m_doingCamRayCast = false; + if (hitYN && localid != LocalId) { - if (hitYN) + SceneObjectGroup group = m_scene.GetGroupByPrim(localid); + bool IsPrim = group != null; + if (IsPrim) { - CameraConstraintActive = true; - //m_log.DebugFormat("[RAYCASTRESULT]: {0}, {1}, {2}, {3}", hitYN, collisionPoint, localid, distance); - - Vector3 normal = Vector3.Normalize(new Vector3(0f, 0f, collisionPoint.Z) - collisionPoint); - ControllingClient.SendCameraConstraint(new Vector4(normal.X, normal.Y, normal.Z, -1 * Vector3.Distance(new Vector3(0,0,collisionPoint.Z),collisionPoint))); + SceneObjectPart part = group.GetPart(localid); + if (part != null && !part.VolumeDetectActive) + { + CameraConstraintActive = true; + pNormal.X = (float) Math.Round(pNormal.X, 2); + pNormal.Y = (float) Math.Round(pNormal.Y, 2); + pNormal.Z = (float) Math.Round(pNormal.Z, 2); + pNormal.Normalize(); + collisionPoint.X = (float) Math.Round(collisionPoint.X, 1); + collisionPoint.Y = (float) Math.Round(collisionPoint.Y, 1); + collisionPoint.Z = (float) Math.Round(collisionPoint.Z, 1); + + Vector4 plane = new Vector4(pNormal.X, pNormal.Y, pNormal.Z, + Vector3.Dot(collisionPoint, pNormal)); + UpdateCameraCollisionPlane(plane); + } } else { - if (!m_pos.ApproxEquals(m_lastPosition, POSITION_TOLERANCE) || - !Velocity.ApproxEquals(m_lastVelocity, VELOCITY_TOLERANCE) || - !Rotation.ApproxEquals(m_lastRotation, ROTATION_TOLERANCE)) - { - if (CameraConstraintActive) - { - ControllingClient.SendCameraConstraint(new Vector4(0f, 0.5f, 0.9f, -3000f)); - CameraConstraintActive = false; - } - } + CameraConstraintActive = true; + pNormal.X = (float) Math.Round(pNormal.X, 2); + pNormal.Y = (float) Math.Round(pNormal.Y, 2); + pNormal.Z = (float) Math.Round(pNormal.Z, 2); + pNormal.Normalize(); + collisionPoint.X = (float) Math.Round(collisionPoint.X, 1); + collisionPoint.Y = (float) Math.Round(collisionPoint.Y, 1); + collisionPoint.Z = (float) Math.Round(collisionPoint.Z, 1); + + Vector4 plane = new Vector4(pNormal.X, pNormal.Y, pNormal.Z, + Vector3.Dot(collisionPoint, pNormal)); + UpdateCameraCollisionPlane(plane); } } + else if (!m_pos.ApproxEquals(m_lastPosition, POSITION_TOLERANCE) || + !Rotation.ApproxEquals(m_lastRotation, ROTATION_TOLERANCE)) + { + Vector4 plane = new Vector4(0.9f, 0.0f, 0.361f, -9000f); // not right... + UpdateCameraCollisionPlane(plane); + CameraConstraintActive = false; + } } /// @@ -1248,18 +1913,14 @@ namespace OpenSim.Region.Framework.Scenes { // m_log.DebugFormat( // "[SCENE PRESENCE]: In {0} received agent update from {1}, flags {2}", -// Scene.RegionInfo.RegionName, remoteClient.Name, (AgentManager.ControlFlags)agentData.ControlFlags); +// Scene.Name, remoteClient.Name, (AgentManager.ControlFlags)agentData.ControlFlags); if (IsChildAgent) { - // // m_log.Debug("DEBUG: HandleAgentUpdate: child agent"); +// m_log.DebugFormat("DEBUG: HandleAgentUpdate: child agent in {0}", Scene.Name); return; } - ++m_movementUpdateCount; - if (m_movementUpdateCount < 1) - m_movementUpdateCount = 1; - #region Sanity Checking // This is irritating. Really. @@ -1290,36 +1951,20 @@ namespace OpenSim.Region.Framework.Scenes AgentManager.ControlFlags flags = (AgentManager.ControlFlags)agentData.ControlFlags; - // Camera location in world. We'll need to raytrace - // from this location from time to time. - CameraPosition = agentData.CameraCenter; - if (Vector3.Distance(m_lastCameraPosition, CameraPosition) >= Scene.RootReprioritizationDistance) - { - ReprioritizeUpdates(); - m_lastCameraPosition = CameraPosition; - } - - // Use these three vectors to figure out what the agent is looking at - // Convert it to a Matrix and/or Quaternion - CameraAtAxis = agentData.CameraAtAxis; - CameraLeftAxis = agentData.CameraLeftAxis; - CameraUpAxis = agentData.CameraUpAxis; - // The Agent's Draw distance setting // When we get to the point of re-computing neighbors everytime this // changes, then start using the agent's drawdistance rather than the // region's draw distance. - // DrawDistance = agentData.Far; - DrawDistance = Scene.DefaultDrawDistance; - - // Check if Client has camera in 'follow cam' or 'build' mode. - Vector3 camdif = (Vector3.One * Rotation - Vector3.One * CameraRotation); - - m_followCamAuto = ((CameraUpAxis.Z > 0.959f && CameraUpAxis.Z < 0.98f) - && (Math.Abs(camdif.X) < 0.4f && Math.Abs(camdif.Y) < 0.4f)) ? true : false; + DrawDistance = agentData.Far; + // DrawDistance = Scene.DefaultDrawDistance; m_mouseLook = (flags & AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK) != 0; - m_leftButtonDown = (flags & AgentManager.ControlFlags.AGENT_CONTROL_LBUTTON_DOWN) != 0; + + // FIXME: This does not work as intended because the viewer only sends the lbutton down when the button + // is first pressed, not whilst it is held down. If this is required in the future then need to look + // for an AGENT_CONTROL_LBUTTON_UP event and make sure to handle cases where an initial DOWN is not + // received (e.g. on holding LMB down on the avatar in a viewer). +// m_leftButtonDown = (flags & AgentManager.ControlFlags.AGENT_CONTROL_LBUTTON_DOWN) != 0; #endregion Inputs @@ -1337,14 +1982,38 @@ namespace OpenSim.Region.Framework.Scenes StandUp(); } - //m_log.DebugFormat("[FollowCam]: {0}", m_followCamAuto); // Raycast from the avatar's head to the camera to see if there's anything blocking the view - if ((m_movementUpdateCount % NumMovementsBetweenRayCast) == 0 && m_scene.PhysicsScene.SupportsRayCast()) + // this exclude checks may not be complete + + if (m_movementUpdateCount % NumMovementsBetweenRayCast == 0 && m_scene.PhysicsScene.SupportsRayCast()) { - if (m_followCamAuto) + if (!m_doingCamRayCast && !m_mouseLook && ParentID == 0) { - Vector3 posAdjusted = m_pos + HEAD_ADJUSTMENT; - m_scene.PhysicsScene.RaycastWorld(m_pos, Vector3.Normalize(CameraPosition - posAdjusted), Vector3.Distance(CameraPosition, posAdjusted) + 0.3f, RayCastCameraCallback); + Vector3 posAdjusted = AbsolutePosition; +// posAdjusted.Z += 0.5f * Appearance.AvatarSize.Z - 0.5f; + posAdjusted.Z += 1.0f; // viewer current camera focus point + Vector3 tocam = CameraPosition - posAdjusted; + tocam.X = (float)Math.Round(tocam.X, 1); + tocam.Y = (float)Math.Round(tocam.Y, 1); + tocam.Z = (float)Math.Round(tocam.Z, 1); + + float distTocamlen = tocam.Length(); + if (distTocamlen > 0.3f) + { + tocam *= (1.0f / distTocamlen); + posAdjusted.X = (float)Math.Round(posAdjusted.X, 1); + posAdjusted.Y = (float)Math.Round(posAdjusted.Y, 1); + posAdjusted.Z = (float)Math.Round(posAdjusted.Z, 1); + + m_doingCamRayCast = true; + m_scene.PhysicsScene.RaycastWorld(posAdjusted, tocam, distTocamlen + 1.0f, RayCastCameraCallback); + } + } + else if (CameraConstraintActive && (m_mouseLook || ParentID != 0)) + { + Vector4 plane = new Vector4(0.9f, 0.0f, 0.361f, -10000f); // not right... + UpdateCameraCollisionPlane(plane); + CameraConstraintActive = false; } } @@ -1358,9 +2027,16 @@ namespace OpenSim.Region.Framework.Scenes // Here's where you get them. m_AgentControlFlags = flags; m_headrotation = agentData.HeadRotation; + byte oldState = State; State = agentData.State; + // We need to send this back to the client in order to stop the edit beams + if ((oldState & (uint)AgentState.Editing) != 0 && State == (uint)AgentState.None) + ControllingClient.SendAgentTerseUpdate(this); + PhysicsActor actor = PhysicsActor; + + // This will be the case if the agent is sitting on the groudn or on an object. if (actor == null) { SendControlsToScripts(flagsForScripts); @@ -1369,17 +2045,26 @@ namespace OpenSim.Region.Framework.Scenes if (AllowMovement && !SitGround) { - Quaternion bodyRotation = agentData.BodyRotation; +// m_log.DebugFormat("[SCENE PRESENCE]: Initial body rotation {0} for {1}", agentData.BodyRotation, Name); + bool update_rotation = false; - if (bodyRotation != Rotation) + if (agentData.BodyRotation != Rotation) { - Rotation = bodyRotation; + Rotation = agentData.BodyRotation; update_rotation = true; } bool update_movementflag = false; + // If we were just made root agent then we must perform movement updates for the first AgentUpdate that + // we get + if (MovementFlag == ForceUpdateMovementFlagValue) + { + MovementFlag = 0; + update_movementflag = true; + } + if (agentData.UseClientAgentPosition) { MovingToTarget = (agentData.ClientAgentPosition - AbsolutePosition).Length() > 0.2f; @@ -1411,19 +2096,7 @@ namespace OpenSim.Region.Framework.Scenes { bool bAllowUpdateMoveToPosition = false; - Vector3[] dirVectors; - - // use camera up angle when in mouselook and not flying or when holding the left mouse button down and not flying - // this prevents 'jumping' in inappropriate situations. - if (!Flying && (m_mouseLook || m_leftButtonDown)) - dirVectors = GetWalkDirectionVectors(); - else - dirVectors = Dir_Vectors; - - // The fact that MovementFlag is a byte needs to be fixed - // it really should be a uint // A DIR_CONTROL_FLAG occurs when the user is trying to move in a particular direction. - uint nudgehack = 250; foreach (Dir_ControlFlags DCF in DIR_CONTROL_FLAGS) { if (((uint)flags & (uint)DCF) != 0) @@ -1432,7 +2105,9 @@ namespace OpenSim.Region.Framework.Scenes try { - agent_control_v3 += dirVectors[i]; + // Don't slide against ground when crouching if camera is panned around avatar + if (Flying || DCF != Dir_ControlFlags.DIR_CONTROL_FLAG_DOWN) + agent_control_v3 += Dir_Vectors[i]; //m_log.DebugFormat("[Motion]: {0}, {1}",i, dirVectors[i]); } catch (IndexOutOfRangeException) @@ -1440,29 +2115,19 @@ namespace OpenSim.Region.Framework.Scenes // Why did I get this? } - if ((MovementFlag & (byte)(uint)DCF) == 0) - { - if (DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_FORWARD_NUDGE || DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_BACKWARD_NUDGE || - DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_LEFT_NUDGE || DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_RIGHT_NUDGE) - { - MovementFlag |= (byte)nudgehack; - } - + if (((MovementFlag & (uint)DCF) == 0) & !AgentControlStopActive) + { //m_log.DebugFormat("[SCENE PRESENCE]: Updating MovementFlag for {0} with {1}", Name, DCF); - MovementFlag += (byte)(uint)DCF; + MovementFlag += (uint)DCF; update_movementflag = true; } } else { - if ((MovementFlag & (byte)(uint)DCF) != 0 || - ((DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_FORWARD_NUDGE || DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_BACKWARD_NUDGE || - DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_LEFT_NUDGE || DCF == Dir_ControlFlags.DIR_CONTROL_FLAG_RIGHT_NUDGE) - && ((MovementFlag & (byte)nudgehack) == nudgehack)) - ) // This or is for Nudge forward + if ((MovementFlag & (uint)DCF) != 0) { //m_log.DebugFormat("[SCENE PRESENCE]: Updating MovementFlag for {0} with lack of {1}", Name, DCF); - MovementFlag -= ((byte)(uint)DCF); + MovementFlag -= (uint)DCF; update_movementflag = true; /* @@ -1482,6 +2147,13 @@ namespace OpenSim.Region.Framework.Scenes i++; } + // Detect AGENT_CONTROL_STOP state changes + if (AgentControlStopActive != ((flags & AgentManager.ControlFlags.AGENT_CONTROL_STOP) != 0)) + { + AgentControlStopActive = !AgentControlStopActive; + update_movementflag = true; + } + if (MovingToTarget) { // If the user has pressed a key then we want to cancel any move to target. @@ -1507,30 +2179,79 @@ namespace OpenSim.Region.Framework.Scenes // Only do this if we're flying if (Flying && !ForceFly) { - // Landing detection code + // Need to stop in mid air if user holds down AGENT_CONTROL_STOP + if (AgentControlStopActive) + { + agent_control_v3 = Vector3.Zero; + } + else + { + // Landing detection code - // Are the landing controls requirements filled? - bool controlland = (((flags & AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG) != 0) || - ((flags & AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG) != 0)); + // Are the landing controls requirements filled? + bool controlland = (((flags & AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG) != 0) || + ((flags & AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG) != 0)); - if (Flying && IsColliding && controlland) - { - // nesting this check because LengthSquared() is expensive and we don't - // want to do it every step when flying. - if ((Velocity.LengthSquared() <= LAND_VELOCITYMAG_MAX)) - StopFlying(); + //m_log.Debug("[CONTROL]: " +flags); + // Applies a satisfying roll effect to the avatar when flying. + if ((flags & AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT) != 0 && (flags & AgentManager.ControlFlags.AGENT_CONTROL_YAW_POS) != 0) + { + ApplyFlyingRoll( + FLY_ROLL_RADIANS_PER_UPDATE, + (flags & AgentManager.ControlFlags.AGENT_CONTROL_UP_POS) != 0, + (flags & AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG) != 0); + } + else if ((flags & AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT) != 0 && + (flags & AgentManager.ControlFlags.AGENT_CONTROL_YAW_NEG) != 0) + { + ApplyFlyingRoll( + -FLY_ROLL_RADIANS_PER_UPDATE, + (flags & AgentManager.ControlFlags.AGENT_CONTROL_UP_POS) != 0, + (flags & AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG) != 0); + } + else + { + if (m_AngularVelocity.Z != 0) + m_AngularVelocity.Z += CalculateFlyingRollResetToZero(FLY_ROLL_RESET_RADIANS_PER_UPDATE); + } + + if (Flying && IsColliding && controlland) + { + // nesting this check because LengthSquared() is expensive and we don't + // want to do it every step when flying. + if ((Velocity.LengthSquared() <= LAND_VELOCITYMAG_MAX)) + StopFlying(); + } } } +// m_log.DebugFormat("[SCENE PRESENCE]: MovementFlag {0} for {1}", MovementFlag, Name); + // If the agent update does move the avatar, then calculate the force ready for the velocity update, // which occurs later in the main scene loop - if (update_movementflag || (update_rotation && DCFlagKeyPressed)) + // We also need to update if the user rotates their avatar whilst it is slow walking/running (if they + // held down AGENT_CONTROL_STOP whilst normal walking/running). However, we do not want to update + // if the user rotated whilst holding down AGENT_CONTROL_STOP when already still (which locks the + // avatar location in place). + if (update_movementflag + || (update_rotation && DCFlagKeyPressed && (!AgentControlStopActive || MovementFlag != 0))) { -// m_log.DebugFormat( -// "[SCENE PRESENCE]: In {0} adding velocity of {1} to {2}, umf = {3}, ur = {4}", -// m_scene.RegionInfo.RegionName, agent_control_v3, Name, update_movementflag, update_rotation); +// if (update_movementflag || !AgentControlStopActive || MovementFlag != 0) +// { +// m_log.DebugFormat( +// "[SCENE PRESENCE]: In {0} adding velocity of {1} to {2}, umf = {3}, mf = {4}, ur = {5}", +// m_scene.RegionInfo.RegionName, agent_control_v3, Name, +// update_movementflag, MovementFlag, update_rotation); + + float speedModifier; + + if (AgentControlStopActive) + speedModifier = AgentControlStopSlowWhilstMoving; + else + speedModifier = 1; - AddNewMovement(agent_control_v3); + AddNewMovement(agent_control_v3, speedModifier); +// } } // else // { @@ -1543,15 +2264,86 @@ namespace OpenSim.Region.Framework.Scenes // } if (update_movementflag && ParentID == 0) + { +// m_log.DebugFormat("[SCENE PRESENCE]: Updating movement animations for {0}", Name); Animator.UpdateMovementAnimations(); + } SendControlsToScripts(flagsForScripts); } + // We need to send this back to the client in order to see the edit beams + if ((State & (uint)AgentState.Editing) != 0) + ControllingClient.SendAgentTerseUpdate(this); + m_scene.EventManager.TriggerOnClientMovement(this); - TriggerScenePresenceUpdated(); } + + /// + /// This is the event handler for client cameras. If a client is moving, or moving the camera, this event is triggering. + /// + private void HandleAgentCamerasUpdate(IClientAPI remoteClient, AgentUpdateArgs agentData) + { + //m_log.DebugFormat( + // "[SCENE PRESENCE]: In {0} received agent camera update from {1}, flags {2}", + // Scene.RegionInfo.RegionName, remoteClient.Name, (AgentManager.ControlFlags)agentData.ControlFlags); + + if (IsChildAgent) + { + // // m_log.Debug("DEBUG: HandleAgentUpdate: child agent"); + return; + } + + ++m_movementUpdateCount; + if (m_movementUpdateCount < 1) + m_movementUpdateCount = 1; + +// AgentManager.ControlFlags flags = (AgentManager.ControlFlags)agentData.ControlFlags; + + // Camera location in world. We'll need to raytrace + // from this location from time to time. + CameraPosition = agentData.CameraCenter; + if (Vector3.Distance(m_lastCameraPosition, CameraPosition) >= Scene.RootReprioritizationDistance) + { + ReprioritizeUpdates(); + m_lastCameraPosition = CameraPosition; + } + + // Use these three vectors to figure out what the agent is looking at + // Convert it to a Matrix and/or Quaternion + CameraAtAxis = agentData.CameraAtAxis; + CameraLeftAxis = agentData.CameraLeftAxis; + CameraUpAxis = agentData.CameraUpAxis; + + // The Agent's Draw distance setting + // When we get to the point of re-computing neighbors everytime this + // changes, then start using the agent's drawdistance rather than the + // region's draw distance. + DrawDistance = agentData.Far; + // DrawDistance = Scene.DefaultDrawDistance; + + // Check if Client has camera in 'follow cam' or 'build' mode. + Vector3 camdif = (Vector3.One * Rotation - Vector3.One * CameraRotation); + + m_followCamAuto = ((CameraUpAxis.Z > 0.959f && CameraUpAxis.Z < 0.98f) + && (Math.Abs(camdif.X) < 0.4f && Math.Abs(camdif.Y) < 0.4f)) ? true : false; + + + //m_log.DebugFormat("[FollowCam]: {0}", m_followCamAuto); + // Raycast from the avatar's head to the camera to see if there's anything blocking the view + if ((m_movementUpdateCount % NumMovementsBetweenRayCast) == 0 && m_scene.PhysicsScene.SupportsRayCast()) + { + if (m_followCamAuto) + { + Vector3 posAdjusted = m_pos + HEAD_ADJUSTMENT; + m_scene.PhysicsScene.RaycastWorld(m_pos, Vector3.Normalize(CameraPosition - posAdjusted), Vector3.Distance(CameraPosition, posAdjusted) + 0.3f, RayCastCameraCallback); + } + } + + TriggerScenePresenceUpdated(); + } + /// /// Calculate an update to move the presence to the set target. /// @@ -1706,13 +2498,15 @@ namespace OpenSim.Region.Framework.Scenes if (regionCombinerModule != null) regionSize = regionCombinerModule.GetSizeOfMegaregion(m_scene.RegionInfo.RegionID); else - regionSize = new Vector2(Constants.RegionSize); + regionSize = new Vector2(m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY); if (pos.X < 0 || pos.X >= regionSize.X || pos.Y < 0 || pos.Y >= regionSize.Y || pos.Z < 0) return; + Scene targetScene = m_scene; + // Vector3 heightAdjust = new Vector3(0, 0, Appearance.AvatarHeight / 2); // pos += heightAdjust; // @@ -1724,15 +2518,23 @@ namespace OpenSim.Region.Framework.Scenes // } // Get terrain height for sub-region in a megaregion if necessary - int X = (int)((m_scene.RegionInfo.RegionLocX * Constants.RegionSize) + pos.X); - int Y = (int)((m_scene.RegionInfo.RegionLocY * Constants.RegionSize) + pos.Y); - UUID target_regionID = m_scene.GridService.GetRegionByPosition(m_scene.RegionInfo.ScopeID, X, Y).RegionID; - Scene targetScene = m_scene; - if (!SceneManager.Instance.TryGetScene(target_regionID, out targetScene)) - targetScene = m_scene; + //COMMENT: If its only nessesary in a megaregion, why do it on normal region's too? + + if (regionCombinerModule != null) + { + int x = (int)((m_scene.RegionInfo.WorldLocX) + pos.X); + int y = (int)((m_scene.RegionInfo.WorldLocY) + pos.Y); + GridRegion target_region = m_scene.GridService.GetRegionByPosition(m_scene.RegionInfo.ScopeID, x, y); + + // If X and Y is NaN, target_region will be null + if (target_region == null) + return; + + SceneManager.Instance.TryGetScene(target_region.RegionID, out targetScene); + } - float terrainHeight = (float)targetScene.Heightmap[(int)(pos.X % Constants.RegionSize), (int)(pos.Y % Constants.RegionSize)]; + float terrainHeight = (float)targetScene.Heightmap[(int)(pos.X % regionSize.X), (int)(pos.Y % regionSize.Y)]; pos.Z = Math.Max(terrainHeight, pos.Z); // Fudge factor. It appears that if one clicks "go here" on a piece of ground, the go here request is @@ -1741,15 +2543,18 @@ namespace OpenSim.Region.Framework.Scenes if (pos.Z - terrainHeight < 0.2) pos.Z = terrainHeight; -// m_log.DebugFormat( -// "[SCENE PRESENCE]: Avatar {0} set move to target {1} (terrain height {2}) in {3}", -// Name, pos, terrainHeight, m_scene.RegionInfo.RegionName); - if (noFly) Flying = false; else if (pos.Z > terrainHeight) Flying = true; +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Avatar {0} set move to target {1} (terrain height {2}) in {3}", +// Name, pos, terrainHeight, m_scene.RegionInfo.RegionName); + + if (noFly) + Flying = false; + LandAtTarget = landAtTarget; MovingToTarget = true; MoveToPositionTarget = pos; @@ -1782,7 +2587,8 @@ namespace OpenSim.Region.Framework.Scenes // m_log.DebugFormat("[SCENE PRESENCE]: Resetting move to target for {0}", Name); MovingToTarget = false; - MoveToPositionTarget = Vector3.Zero; +// MoveToPositionTarget = Vector3.Zero; + m_forceToApply = null; // cancel possible last action // We need to reset the control flag as the ScenePresenceAnimator uses this to determine the correct // resting animation (e.g. hover or stand). NPCs don't have a client that will quickly reset this flag. @@ -1799,13 +2605,15 @@ namespace OpenSim.Region.Framework.Scenes { // m_log.DebugFormat("[SCENE PRESENCE]: StandUp() for {0}", Name); + bool satOnObject = IsSatOnObject; + SceneObjectPart part = ParentPart; SitGround = false; - if (PhysicsActor == null) - AddToPhysicalScene(false); - if (ParentID != 0) + if (satOnObject) { - SceneObjectPart part = ParentPart; + PrevSitOffset = m_pos; // Save sit offset + UnRegisterSeatControls(part.ParentGroup.UUID); + TaskInventoryDictionary taskIDict = part.TaskInventory; if (taskIDict != null) { @@ -1821,24 +2629,70 @@ namespace OpenSim.Region.Framework.Scenes } } - ParentPosition = part.GetWorldPosition(); ControllingClient.SendClearFollowCamProperties(part.ParentUUID); - m_pos += ParentPosition + new Vector3(0.0f, 0.0f, 2.0f * m_sitAvatarHeight); - ParentPosition = Vector3.Zero; - ParentID = 0; ParentPart = null; - SendAvatarDataToAllAgents(); + + Quaternion standRotation; + + if (part.SitTargetAvatar == UUID) + { + standRotation = part.GetWorldRotation(); + + if (!part.IsRoot) + standRotation = standRotation * part.SitTargetOrientation; +// standRotation = part.RotationOffset * part.SitTargetOrientation; +// else +// standRotation = part.SitTargetOrientation; + + } + else + { + standRotation = Rotation; + } + + //Vector3 standPos = ParentPosition + new Vector3(0.0f, 0.0f, 2.0f * m_sitAvatarHeight); + //Vector3 standPos = ParentPosition; + +// Vector3 standPositionAdjustment +// = part.SitTargetPosition + new Vector3(0.5f, 0f, m_sitAvatarHeight / 2f); + Vector3 adjustmentForSitPosition = OffsetPosition * part.ParentGroup.GroupRotation - SIT_TARGET_ADJUSTMENT * part.GetWorldRotation(); + + // XXX: This is based on the physics capsule sizes. Need to find a better way to read this rather than + // hardcoding here. + Vector3 adjustmentForSitPose = new Vector3(0.74f, 0f, 0f) * standRotation; + + Vector3 standPos = part.ParentGroup.AbsolutePosition + adjustmentForSitPosition + adjustmentForSitPose; + +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Setting stand to pos {0}, (adjustmentForSitPosition {1}, adjustmentForSitPose {2}) rotation {3} for {4} in {5}", +// standPos, adjustmentForSitPosition, adjustmentForSitPose, standRotation, Name, Scene.Name); + + Rotation = standRotation; + AbsolutePosition = standPos; + } + + // We need to wait until we have calculated proper stand positions before sitting up the physical + // avatar to avoid race conditions. + if (PhysicsActor == null) + AddToPhysicalScene(false); + + if (satOnObject) + { + SendAvatarDataToAllClients(); m_requestedSitTargetID = 0; - part.RemoveSittingAvatar(UUID); + part.RemoveSittingAvatar(this); - if (part != null) - part.ParentGroup.TriggerScriptChangedEvent(Changed.LINK); + part.ParentGroup.TriggerScriptChangedEvent(Changed.LINK); } + else if (PhysicsActor == null) + AddToPhysicalScene(false); + Animator.TrySetMovementAnimation("STAND"); + TriggerScenePresenceUpdated(); } private SceneObjectPart FindNextAvailableSitTarget(UUID targetID) @@ -1885,14 +2739,10 @@ namespace OpenSim.Region.Framework.Scenes if (part == null) return; - // TODO: determine position to sit at based on scene geometry; don't trust offset from client - // see http://wiki.secondlife.com/wiki/User:Andrew_Linden/Office_Hours/2007_11_06 for details on how LL does it - if (PhysicsActor != null) - m_sitAvatarHeight = PhysicsActor.Size.Z; + m_sitAvatarHeight = PhysicsActor.Size.Z * 0.5f; bool canSit = false; - Vector3 pos = part.AbsolutePosition + offset; if (part.IsSitTargetSet && part.SitTargetAvatar == UUID.Zero) { @@ -1902,45 +2752,88 @@ namespace OpenSim.Region.Framework.Scenes offset = part.SitTargetPosition; sitOrientation = part.SitTargetOrientation; + + if (!part.IsRoot) + { + // m_log.DebugFormat("Old sit orient {0}", sitOrientation); + sitOrientation = part.RotationOffset * sitOrientation; + // m_log.DebugFormat("New sit orient {0}", sitOrientation); +// m_log.DebugFormat("Old sit offset {0}", offset); + offset = offset * part.RotationOffset; +// m_log.DebugFormat("New sit offset {0}", offset); + } + canSit = true; } else { + if (PhysicsSit(part,offset)) // physics engine + return; + + Vector3 pos = part.AbsolutePosition + offset; + if (Util.GetDistanceTo(AbsolutePosition, pos) <= 10) { -// m_log.DebugFormat( -// "[SCENE PRESENCE]: Sitting {0} on {1} {2} because sit target is unset and within 10m", -// Name, part.Name, part.LocalId); - AbsolutePosition = pos + new Vector3(0.0f, 0.0f, m_sitAvatarHeight); 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 (PhysicsActor != null) { // We can remove the physicsActor until they stand up. RemoveFromPhysicalScene(); } - part.AddSittingAvatar(UUID); + if (MovingToTarget) + ResetMoveToTarget(); + + Velocity = Vector3.Zero; + + part.AddSittingAvatar(this); cameraAtOffset = part.GetCameraAtOffset(); + + if (!part.IsRoot && cameraAtOffset == Vector3.Zero) + cameraAtOffset = part.ParentGroup.RootPart.GetCameraAtOffset(); + + bool cameraEyeOffsetFromRootForChild = false; cameraEyeOffset = part.GetCameraEyeOffset(); + + if (!part.IsRoot && cameraEyeOffset == Vector3.Zero) + { + cameraEyeOffset = part.ParentGroup.RootPart.GetCameraEyeOffset(); + cameraEyeOffsetFromRootForChild = true; + } + + if ((cameraEyeOffset != Vector3.Zero && !cameraEyeOffsetFromRootForChild) || cameraAtOffset != Vector3.Zero) + { + if (!part.IsRoot) + { + cameraEyeOffset = cameraEyeOffset * part.RotationOffset; + cameraAtOffset += part.OffsetPosition; + } + + cameraEyeOffset += part.OffsetPosition; + } + +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Using cameraAtOffset {0}, cameraEyeOffset {1} for sit on {2} by {3} in {4}", +// cameraAtOffset, cameraEyeOffset, part.Name, Name, Scene.Name); + forceMouselook = part.GetForceMouselook(); + // An viewer expects to specify sit positions as offsets to the root prim, even if a child prim is + // being sat upon. + offset += part.OffsetPosition; + ControllingClient.SendSitResponse( - targetID, offset, sitOrientation, false, cameraAtOffset, cameraEyeOffset, forceMouselook); + part.ParentGroup.UUID, offset, sitOrientation, false, cameraAtOffset, cameraEyeOffset, forceMouselook); - m_requestedSitTargetUUID = targetID; + m_requestedSitTargetUUID = part.UUID; HandleAgentSit(ControllingClient, UUID); @@ -1952,6 +2845,9 @@ namespace OpenSim.Region.Framework.Scenes public void HandleAgentRequestSit(IClientAPI remoteClient, UUID agentID, UUID targetID, Vector3 offset) { + if (IsChildAgent) + return; + if (ParentID != 0) { if (ParentPart.UUID == targetID) @@ -1965,16 +2861,8 @@ namespace OpenSim.Region.Framework.Scenes if (part != null) { m_requestedSitTargetID = part.LocalId; - m_requestedSitTargetUUID = targetID; + m_requestedSitTargetUUID = part.UUID; -// m_log.DebugFormat("[SIT]: Client requested Sit Position: {0}", offset); - - if (m_scene.PhysicsScene.SupportsRayCast()) - { - //m_scene.PhysicsScene.RaycastWorld(Vector3.Zero,Vector3.Zero, 0.01f,new RaycastCallback()); - //SitRayCastAvatarPosition(part); - //return; - } } else { @@ -1984,200 +2872,119 @@ namespace OpenSim.Region.Framework.Scenes SendSitResponse(targetID, offset, Quaternion.Identity); } - /* - public void SitRayCastAvatarPosition(SceneObjectPart part) - { - Vector3 EndRayCastPosition = part.AbsolutePosition + m_requestedSitOffset; - Vector3 StartRayCastPosition = AbsolutePosition; - Vector3 direction = Vector3.Normalize(EndRayCastPosition - StartRayCastPosition); - float distance = Vector3.Distance(EndRayCastPosition, StartRayCastPosition); - m_scene.PhysicsScene.RaycastWorld(StartRayCastPosition, direction, distance, SitRayCastAvatarPositionResponse); - } - - public void SitRayCastAvatarPositionResponse(bool hitYN, Vector3 collisionPoint, uint localid, float pdistance, Vector3 normal) + // returns false if does not suport so older sit can be tried + public bool PhysicsSit(SceneObjectPart part, Vector3 offset) { - SceneObjectPart part = FindNextAvailableSitTarget(m_requestedSitTargetUUID); - if (part != null) - { - if (hitYN) - { - if (collisionPoint.ApproxEquals(m_requestedSitOffset + part.AbsolutePosition, 0.2f)) - { - SitRaycastFindEdge(collisionPoint, normal); - m_log.DebugFormat("[SIT]: Raycast Avatar Position succeeded at point: {0}, normal:{1}", collisionPoint, normal); - } - else - { - SitRayCastAvatarPositionCameraZ(part); - } - } - else - { - SitRayCastAvatarPositionCameraZ(part); - } - } - else +// TODO: Pull in these bits + return false; +/* + if (part == null || part.ParentGroup.IsAttachment) { - ControllingClient.SendAlertMessage("Sit position no longer exists"); - m_requestedSitTargetUUID = UUID.Zero; - m_requestedSitTargetID = 0; - m_requestedSitOffset = Vector3.Zero; + return true; } - } + if ( m_scene.PhysicsScene == null) + return false; - public void SitRayCastAvatarPositionCameraZ(SceneObjectPart part) - { - // Next, try to raycast from the camera Z position - Vector3 EndRayCastPosition = part.AbsolutePosition + m_requestedSitOffset; - Vector3 StartRayCastPosition = AbsolutePosition; StartRayCastPosition.Z = CameraPosition.Z; - Vector3 direction = Vector3.Normalize(EndRayCastPosition - StartRayCastPosition); - float distance = Vector3.Distance(EndRayCastPosition, StartRayCastPosition); - m_scene.PhysicsScene.RaycastWorld(StartRayCastPosition, direction, distance, SitRayCastAvatarPositionCameraZResponse); - } - - public void SitRayCastAvatarPositionCameraZResponse(bool hitYN, Vector3 collisionPoint, uint localid, float pdistance, Vector3 normal) - { - SceneObjectPart part = FindNextAvailableSitTarget(m_requestedSitTargetUUID); - if (part != null) + if (part.PhysActor == null) { - if (hitYN) - { - if (collisionPoint.ApproxEquals(m_requestedSitOffset + part.AbsolutePosition, 0.2f)) - { - SitRaycastFindEdge(collisionPoint, normal); - m_log.DebugFormat("[SIT]: Raycast Avatar Position + CameraZ succeeded at point: {0}, normal:{1}", collisionPoint, normal); - } - else - { - SitRayCastCameraPosition(part); - } - } + // none physcis shape + if (part.PhysicsShapeType == (byte)PhysicsShapeType.None) + ControllingClient.SendAlertMessage(" There is no suitable surface to sit on, try another spot."); else - { - SitRayCastCameraPosition(part); + { // non physical phantom TODO + ControllingClient.SendAlertMessage(" There is no suitable surface to sit on, try another spot."); + return false; } - } - else - { - ControllingClient.SendAlertMessage("Sit position no longer exists"); - m_requestedSitTargetUUID = UUID.Zero; - m_requestedSitTargetID = 0; - m_requestedSitOffset = Vector3.Zero; + return true; } - } - public void SitRayCastCameraPosition(SceneObjectPart part) - { - // Next, try to raycast from the camera position - Vector3 EndRayCastPosition = part.AbsolutePosition + m_requestedSitOffset; - Vector3 StartRayCastPosition = CameraPosition; - Vector3 direction = Vector3.Normalize(EndRayCastPosition - StartRayCastPosition); - float distance = Vector3.Distance(EndRayCastPosition, StartRayCastPosition); - m_scene.PhysicsScene.RaycastWorld(StartRayCastPosition, direction, distance, SitRayCastCameraPositionResponse); - } + // not doing autopilot + m_requestedSitTargetID = 0; - public void SitRayCastCameraPositionResponse(bool hitYN, Vector3 collisionPoint, uint localid, float pdistance, Vector3 normal) - { - SceneObjectPart part = FindNextAvailableSitTarget(m_requestedSitTargetUUID); - if (part != null) - { - if (hitYN) - { - if (collisionPoint.ApproxEquals(m_requestedSitOffset + part.AbsolutePosition, 0.2f)) - { - SitRaycastFindEdge(collisionPoint, normal); - m_log.DebugFormat("[SIT]: Raycast Camera Position succeeded at point: {0}, normal:{1}", collisionPoint, normal); - } - else - { - SitRayHorizontal(part); - } - } - else - { - SitRayHorizontal(part); - } - } - else - { - ControllingClient.SendAlertMessage("Sit position no longer exists"); - m_requestedSitTargetUUID = UUID.Zero; - m_requestedSitTargetID = 0; - m_requestedSitOffset = Vector3.Zero; - } + if (m_scene.PhysicsScene.SitAvatar(part.PhysActor, AbsolutePosition, CameraPosition, offset, new Vector3(0.35f, 0, 0.65f), PhysicsSitResponse) != 0) + return true; + return false; +*/ } - public void SitRayHorizontal(SceneObjectPart part) + + private bool CanEnterLandPosition(Vector3 testPos) { - // Next, try to raycast from the avatar position to fwd - Vector3 EndRayCastPosition = part.AbsolutePosition + m_requestedSitOffset; - Vector3 StartRayCastPosition = CameraPosition; - Vector3 direction = Vector3.Normalize(EndRayCastPosition - StartRayCastPosition); - float distance = Vector3.Distance(EndRayCastPosition, StartRayCastPosition); - m_scene.PhysicsScene.RaycastWorld(StartRayCastPosition, direction, distance, SitRayCastHorizontalResponse); + ILandObject land = m_scene.LandChannel.GetLandObject(testPos.X, testPos.Y); + + if (land == null || land.LandData.Name == "NO_LAND") + return true; + + return land.CanBeOnThisLand(UUID,testPos.Z); } - public void SitRayCastHorizontalResponse(bool hitYN, Vector3 collisionPoint, uint localid, float pdistance, Vector3 normal) + // status + // < 0 ignore + // 0 bad sit spot + public void PhysicsSitResponse(int status, uint partID, Vector3 offset, Quaternion Orientation) { - SceneObjectPart part = FindNextAvailableSitTarget(m_requestedSitTargetUUID); - if (part != null) + if (status < 0) + return; + + if (status == 0) { - if (hitYN) - { - if (collisionPoint.ApproxEquals(m_requestedSitOffset + part.AbsolutePosition, 0.2f)) - { - SitRaycastFindEdge(collisionPoint, normal); - m_log.DebugFormat("[SIT]: Raycast Horizontal Position succeeded at point: {0}, normal:{1}", collisionPoint, normal); - // Next, try to raycast from the camera position - Vector3 EndRayCastPosition = part.AbsolutePosition + m_requestedSitOffset; - Vector3 StartRayCastPosition = CameraPosition; - Vector3 direction = Vector3.Normalize(EndRayCastPosition - StartRayCastPosition); - float distance = Vector3.Distance(EndRayCastPosition, StartRayCastPosition); - //m_scene.PhysicsScene.RaycastWorld(StartRayCastPosition, direction, distance, SitRayCastResponseAvatarPosition); - } - else - { - ControllingClient.SendAlertMessage("Sit position not accessable."); - m_requestedSitTargetUUID = UUID.Zero; - m_requestedSitTargetID = 0; - m_requestedSitOffset = Vector3.Zero; - } - } - else - { - ControllingClient.SendAlertMessage("Sit position not accessable."); - m_requestedSitTargetUUID = UUID.Zero; - m_requestedSitTargetID = 0; - m_requestedSitOffset = Vector3.Zero; - } + ControllingClient.SendAlertMessage(" There is no suitable surface to sit on, try another spot."); + return; } - else + + SceneObjectPart part = m_scene.GetSceneObjectPart(partID); + if (part == null) + return; + + Vector3 targetPos = part.GetWorldPosition() + offset * part.GetWorldRotation(); + if(!CanEnterLandPosition(targetPos)) { - ControllingClient.SendAlertMessage("Sit position no longer exists"); - m_requestedSitTargetUUID = UUID.Zero; - m_requestedSitTargetID = 0; - m_requestedSitOffset = Vector3.Zero; + ControllingClient.SendAlertMessage(" Sit position on restricted land, try another spot"); + return; } - } + RemoveFromPhysicalScene(); - private void SitRaycastFindEdge(Vector3 collisionPoint, Vector3 collisionNormal) - { - int i = 0; - //throw new NotImplementedException(); - //m_requestedSitTargetUUID = UUID.Zero; - //m_requestedSitTargetID = 0; - //m_requestedSitOffset = Vector3.Zero; + if (MovingToTarget) + ResetMoveToTarget(); + + Velocity = Vector3.Zero; + + part.AddSittingAvatar(this); + + Vector3 cameraAtOffset = part.GetCameraAtOffset(); + Vector3 cameraEyeOffset = part.GetCameraEyeOffset(); + bool forceMouselook = part.GetForceMouselook(); + + ControllingClient.SendSitResponse( + part.UUID, offset, Orientation, false, cameraAtOffset, cameraEyeOffset, forceMouselook); + + // not using autopilot + + Rotation = Orientation; + m_pos = offset; + + m_requestedSitTargetID = 0; + + ParentPart = part; + ParentID = part.LocalId; + if(status == 3) + Animator.TrySetMovementAnimation("SIT_GROUND"); + else + Animator.TrySetMovementAnimation("SIT"); + SendAvatarDataToAllClients(); - SendSitResponse(ControllingClient, m_requestedSitTargetUUID, collisionPoint - m_requestedSitOffset, Quaternion.Identity); + part.ParentGroup.TriggerScriptChangedEvent(Changed.LINK); } - */ public void HandleAgentSit(IClientAPI remoteClient, UUID agentID) { + if (IsChildAgent) + return; + SceneObjectPart part = m_scene.GetSceneObjectPart(m_requestedSitTargetID); if (part != null) @@ -2205,23 +3012,75 @@ namespace OpenSim.Region.Framework.Scenes //Quaternion result = (sitTargetOrient * vq) * nq; - m_pos = sitTargetPos + SIT_TARGET_ADJUSTMENT; - Rotation = sitTargetOrient; - ParentPosition = part.AbsolutePosition; + double x, y, z, m1, m2; + + Quaternion r = sitTargetOrient; + m1 = r.X * r.X + r.Y * r.Y; + m2 = r.Z * r.Z + r.W * r.W; + + // Rotate the vector <0, 0, 1> + x = 2 * (r.X * r.Z + r.Y * r.W); + y = 2 * (-r.X * r.W + r.Y * r.Z); + z = m2 - m1; + + // Set m to be the square of the norm of r. + double m = m1 + m2; + + // This constant is emperically determined to be what is used in SL. + // See also http://opensimulator.org/mantis/view.php?id=7096 + double offset = 0.05; + + // Normally m will be ~ 1, but if someone passed a handcrafted quaternion + // to llSitTarget with values so small that squaring them is rounded off + // to zero, then m could be zero. The result of this floating point + // round off error (causing us to skip this impossible normalization) + // is only 5 cm. + if (m > 0.000001) + { + offset /= m; + } + + Vector3 up = new Vector3((float)x, (float)y, (float)z); + Vector3 sitOffset = up * (float)offset; + + // sitOffset is in Avatar Center coordinates: from origin to 'sitTargetPos + SIT_TARGET_ADJUSTMENT'. + // So, we need to _substract_ it to get to the origin of the Avatar Center. + Vector3 newPos = sitTargetPos + SIT_TARGET_ADJUSTMENT - sitOffset; + Quaternion newRot; + + if (part.IsRoot) + { + newRot = sitTargetOrient; + } + else + { + newPos = newPos * part.RotationOffset; + newRot = part.RotationOffset * sitTargetOrient; + } + + newPos += part.OffsetPosition; + + m_pos = newPos; + Rotation = newRot; + +// ParentPosition = part.AbsolutePosition; } else { - m_pos -= part.AbsolutePosition; - ParentPosition = part.AbsolutePosition; + // An viewer expects to specify sit positions as offsets to the root prim, even if a child prim is + // being sat upon. + m_pos -= part.GroupPosition; + +// ParentPosition = part.AbsolutePosition; // m_log.DebugFormat( // "[SCENE PRESENCE]: Sitting {0} at position {1} ({2} + {3}) on part {4} {5} without sit target", // Name, part.AbsolutePosition, m_pos, ParentPosition, part.Name, part.LocalId); } - ParentPart = m_scene.GetSceneObjectPart(m_requestedSitTargetID); + ParentPart = part; ParentID = m_requestedSitTargetID; - + m_AngularVelocity = Vector3.Zero; Velocity = Vector3.Zero; RemoveFromPhysicalScene(); @@ -2231,14 +3090,20 @@ namespace OpenSim.Region.Framework.Scenes sitAnimation = part.SitAnimation; } Animator.TrySetMovementAnimation(sitAnimation); - SendAvatarDataToAllAgents(); + SendAvatarDataToAllClients(); + TriggerScenePresenceUpdated(); } } public void HandleAgentSitOnGround() { -// m_updateCount = 0; // Kill animation update burst so that the SIT_G.. will stick. + if (IsChildAgent) + return; + +// m_updateCount = 0; // Kill animation update burst so that the SIT_G.. will stick.. + m_AngularVelocity = Vector3.Zero; Animator.TrySetMovementAnimation("SIT_GROUND_CONSTRAINED"); + TriggerScenePresenceUpdated(); SitGround = true; RemoveFromPhysicalScene(); } @@ -2255,22 +3120,39 @@ namespace OpenSim.Region.Framework.Scenes public void HandleStartAnim(IClientAPI remoteClient, UUID animID) { Animator.AddAnimation(animID, UUID.Zero); + TriggerScenePresenceUpdated(); } public void HandleStopAnim(IClientAPI remoteClient, UUID animID) { Animator.RemoveAnimation(animID, false); + TriggerScenePresenceUpdated(); } /// /// Rotate the avatar to the given rotation and apply a movement in the given relative vector /// /// The vector in which to move. This is relative to the rotation argument - public void AddNewMovement(Vector3 vec) + /// + /// Optional additional speed modifier for this particular add. Default is 1 + public void AddNewMovement(Vector3 vec, float thisAddSpeedModifier = 1) { -// m_log.DebugFormat("[SCENE PRESENCE]: Adding new movement {0} for {1}", vec, Name); +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Adding new movement {0} with rotation {1}, thisAddSpeedModifier {2} for {3}", +// vec, Rotation, thisAddSpeedModifier, Name); - Vector3 direc = vec * Rotation; + Quaternion rot = Rotation; + if (!Flying && PresenceType != PresenceType.Npc) + { + // The only situation in which we care about X and Y is avatar flying. The rest of the time + // these parameters are not relevant for determining avatar movement direction and cause issues such + // as wrong walk speed if the camera is rotated. + rot.X = 0; + rot.Y = 0; + rot.Normalize(); + } + + Vector3 direc = vec * rot; direc.Normalize(); if (Flying != FlyingOld) // add for fly velocity control @@ -2286,7 +3168,9 @@ namespace OpenSim.Region.Framework.Scenes if ((vec.Z == 0f) && !Flying) direc.Z = 0f; // Prevent camera WASD up. - direc *= 0.03f * 128f * SpeedModifier; + direc *= 0.03f * 128f * SpeedModifier * thisAddSpeedModifier; + +// m_log.DebugFormat("[SCENE PRESENCE]: Force to apply before modification was {0} for {1}", direc, Name); if (PhysicsActor != null) { @@ -2315,14 +3199,17 @@ namespace OpenSim.Region.Framework.Scenes direc.Z *= 2.6f; // TODO: PreJump and jump happen too quickly. Many times prejump gets ignored. - Animator.TrySetMovementAnimation("PREJUMP"); - Animator.TrySetMovementAnimation("JUMP"); +// Animator.TrySetMovementAnimation("PREJUMP"); +// Animator.TrySetMovementAnimation("JUMP"); } } } +// m_log.DebugFormat("[SCENE PRESENCE]: Setting force to apply to {0} for {1}", direc, Name); + // TODO: Add the force instead of only setting it to support multiple forces per frame? m_forceToApply = direc; + Animator.UpdateMovementAnimations(); } #endregion @@ -2331,25 +3218,27 @@ namespace OpenSim.Region.Framework.Scenes public override void Update() { - const float ROTATION_TOLERANCE = 0.01f; - const float VELOCITY_TOLERANCE = 0.001f; - const float POSITION_TOLERANCE = 0.05f; - if (IsChildAgent == false) { // NOTE: Velocity is not the same as m_velocity. Velocity will attempt to // grab the latest PhysicsActor velocity, whereas m_velocity is often // storing a requested force instead of an actual traveling velocity - - // Throw away duplicate or insignificant updates - if ( - // If the velocity has become zero, send it no matter what. - (Velocity != m_lastVelocity && Velocity == Vector3.Zero) - // otherwise, if things have changed reasonably, send the update - || (!Rotation.ApproxEquals(m_lastRotation, ROTATION_TOLERANCE) - || !Velocity.ApproxEquals(m_lastVelocity, VELOCITY_TOLERANCE) - || !m_pos.ApproxEquals(m_lastPosition, POSITION_TOLERANCE))) - + if (Appearance.AvatarSize != m_lastSize && !IsLoggingIn) + SendAvatarDataToAllClients(); + + // Allow any updates for sitting avatars to that llSetPrimitiveLinkParams() can work for very + // small increments (e.g. sit position adjusters). An alternative may be to eliminate the tolerance + // checks on all updates but the ramifications of this would need careful consideration. + bool updateClients + = IsSatOnObject && (Rotation != m_lastRotation || Velocity != m_lastVelocity || m_pos != m_lastPosition); + + if (!updateClients) + updateClients + = !Rotation.ApproxEquals(m_lastRotation, Scene.RootRotationUpdateTolerance) + || !Velocity.ApproxEquals(m_lastVelocity, Scene.RootVelocityUpdateTolerance) + || !m_pos.ApproxEquals(m_lastPosition, Scene.RootPositionUpdateTolerance); + + if (updateClients) { SendTerseUpdateToAllClients(); @@ -2359,7 +3248,8 @@ namespace OpenSim.Region.Framework.Scenes m_lastVelocity = Velocity; } - CheckForBorderCrossing(); + if (Scene.AllowAvatarCrossing) + CheckForBorderCrossing(); CheckForSignificantMovement(); // sends update to the modules. } @@ -2369,7 +3259,6 @@ namespace OpenSim.Region.Framework.Scenes #region Update Client(s) - /// /// Sends a location update to the client connected to this scenePresence /// @@ -2380,6 +3269,29 @@ namespace OpenSim.Region.Framework.Scenes // server. if (remoteClient.IsActive) { + if (Scene.RootTerseUpdatePeriod > 1) + { +// Console.WriteLine( +// "{0} {1} {2} {3} {4} {5} for {6} to {7}", +// remoteClient.AgentId, UUID, remoteClient.SceneAgent.IsChildAgent, m_terseUpdateCount, Scene.RootTerseUpdatePeriod, Velocity.ApproxEquals(Vector3.Zero, 0.001f), Name, remoteClient.Name); + if (remoteClient.AgentId != UUID + && !remoteClient.SceneAgent.IsChildAgent + && m_terseUpdateCount % Scene.RootTerseUpdatePeriod != 0 + && !Velocity.ApproxEquals(Vector3.Zero, 0.001f)) + { +// m_log.DebugFormat("[SCENE PRESENCE]: Discarded update from {0} to {1}, args {2} {3} {4} {5} {6} {7}", +// Name, remoteClient.Name, remoteClient.AgentId, UUID, remoteClient.SceneAgent.IsChildAgent, m_terseUpdateCount, Scene.RootTerseUpdatePeriod, Velocity.ApproxEquals(Vector3.Zero, 0.001f)); + + return; + } + } + + if (Scene.ChildTerseUpdatePeriod > 1 + && remoteClient.SceneAgent.IsChildAgent + && m_terseUpdateCount % Scene.ChildTerseUpdatePeriod != 0 + && !Velocity.ApproxEquals(Vector3.Zero, 0.001f)) + return; + //m_log.DebugFormat("[SCENE PRESENCE]: " + Name + " sending TerseUpdate to " + remoteClient.Name + " : Pos={0} Rot={1} Vel={2}", m_pos, Rotation, m_velocity); remoteClient.SendEntityUpdate( @@ -2417,7 +3329,11 @@ namespace OpenSim.Region.Framework.Scenes float distanceError = Vector3.Distance(OffsetPosition, expectedPosition); float speed = Velocity.Length(); - float velocidyDiff = Vector3.Distance(lastVelocitySentToAllClients, Velocity); + float velocityDiff = Vector3.Distance(lastVelocitySentToAllClients, Velocity); + +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Delta-v {0}, lastVelocity {1}, Velocity {2} for {3} in {4}", +// velocidyDiff, lastVelocitySentToAllClients, Velocity, Name, Scene.Name); // assuming 5 ms. worst case precision for timer, use 2x that // for distance error threshold @@ -2425,12 +3341,19 @@ namespace OpenSim.Region.Framework.Scenes if (speed < 0.01f // allow rotation updates if avatar position is unchanged || Math.Abs(distanceError) > distanceErrorThreshold - || velocidyDiff > 0.01f) // did velocity change from last update? + || velocityDiff > 0.01f) // did velocity change from last update? { +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Update triggered with speed {0}, distanceError {1}, distanceThreshold {2}, delta-v {3} for {4} in {5}", +// speed, distanceError, distanceErrorThreshold, velocidyDiff, Name, Scene.Name); + lastVelocitySentToAllClients = Velocity; lastTerseUpdateToAllClientsTick = currentTick; lastPositionSentToAllClients = OffsetPosition; + m_terseUpdateCount++; + +// Console.WriteLine("Scheduled update for {0} in {1}", Name, Scene.Name); m_scene.ForEachClient(SendTerseUpdateToClient); } TriggerScenePresenceUpdated(); @@ -2456,24 +3379,30 @@ namespace OpenSim.Region.Framework.Scenes ControllingClient.SendCoarseLocationUpdate(avatarUUIDs, coarseLocations); } - public void SendInitialDataToMe() + public void SendInitialDataToClient() { + SentInitialDataToClient = true; + // Send all scene object to the new client - Util.FireAndForget(delegate + WorkManager.RunJob("SendInitialDataToClient", delegate { +// m_log.DebugFormat( +// "[SCENE PRESENCE]: Sending initial data to {0} agent {1} in {2}, tp flags {3}", +// IsChildAgent ? "child" : "root", Name, Scene.Name, m_teleportFlags); + // we created a new ScenePresence (a new child agent) in a fresh region. // Request info about all the (root) agents in this region // Note: This won't send data *to* other clients in that region (children don't send) - SendOtherAgentsAvatarDataToMe(); - SendOtherAgentsAppearanceToMe(); + SendOtherAgentsAvatarDataToClient(); + SendOtherAgentsAppearanceToClient(); EntityBase[] entities = Scene.Entities.GetEntities(); - foreach(EntityBase e in entities) + foreach (EntityBase e in entities) { if (e != null && e is SceneObjectGroup) ((SceneObjectGroup)e).SendFullUpdateToClient(ControllingClient); } - }); + }, null, string.Format("SendInitialDataToClient ({0} in {1})", Name, Scene.Name), false, true); } /// @@ -2506,26 +3435,33 @@ namespace OpenSim.Region.Framework.Scenes // getting other avatars information was initiated elsewhere immediately after the child circuit connected... don't do it // again here... this comes after the cached appearance check because the avatars // appearance goes into the avatar update packet - SendAvatarDataToAllAgents(); - SendAppearanceToAgent(this); + SendAvatarDataToAllClients(); + + // This invocation always shows up in the viewer logs as an error. Is it needed? + SendAppearanceToClient(this); // If we are using the the cached appearance then send it out to everyone if (cachedappearance) { - m_log.DebugFormat("[SCENE PRESENCE]: baked textures are in the cache for {0}", Name); + m_log.DebugFormat("[SCENE PRESENCE]: Baked textures are in the cache for {0} in {1}", Name, m_scene.Name); // If the avatars baked textures are all in the cache, then we have a // complete appearance... send it out, if not, then we'll send it when // the avatar finishes updating its appearance - SendAppearanceToAllOtherAgents(); + SendAppearanceToAllOtherClients(); } } + public void SendAvatarDataToAllClients() + { + SendAvatarDataToAllClients(true); + } + /// /// Send this agent's avatar data to all other root and child agents in the scene /// This agent must be root. This avatar will receive its own update. /// - public void SendAvatarDataToAllAgents() + public void SendAvatarDataToAllClients(bool full) { //m_log.DebugFormat("[SCENE PRESENCE] SendAvatarDataToAllAgents: {0} ({1})", Name, UUID); // only send update from root agents to other clients; children are only "listening posts" @@ -2538,12 +3474,17 @@ namespace OpenSim.Region.Framework.Scenes return; } + m_lastSize = Appearance.AvatarSize; + int count = 0; m_scene.ForEachScenePresence(delegate(ScenePresence scenePresence) - { - SendAvatarDataToAgent(scenePresence); - count++; - }); + { + if (full) + SendAvatarDataToClient(scenePresence); + else + scenePresence.ControllingClient.SendAvatarDataImmediate(this); + count++; + }); m_scene.StatsReporter.AddAgentUpdates(count); } @@ -2552,7 +3493,7 @@ namespace OpenSim.Region.Framework.Scenes /// Send avatar data for all other root agents to this agent, this agent /// can be either a child or root /// - public void SendOtherAgentsAvatarDataToMe() + public void SendOtherAgentsAvatarDataToClient() { int count = 0; m_scene.ForEachRootScenePresence(delegate(ScenePresence scenePresence) @@ -2561,7 +3502,7 @@ namespace OpenSim.Region.Framework.Scenes if (scenePresence.UUID == UUID) return; - scenePresence.SendAvatarDataToAgent(this); + scenePresence.SendAvatarDataToClient(this); count++; }); @@ -2572,9 +3513,9 @@ namespace OpenSim.Region.Framework.Scenes /// Send avatar data to an agent. /// /// - public void SendAvatarDataToAgent(ScenePresence avatar) + public void SendAvatarDataToClient(ScenePresence avatar) { - //m_log.DebugFormat("[SCENE PRESENCE] SendAvatarDataToAgent from {0} ({1}) to {2} ({3})", Name, UUID, avatar.Name, avatar.UUID); + //m_log.DebugFormat("[SCENE PRESENCE] SendAvatarDataToClient from {0} ({1}) to {2} ({3})", Name, UUID, avatar.Name, avatar.UUID); avatar.ControllingClient.SendAvatarDataImmediate(this); Animator.SendAnimPackToClient(avatar.ControllingClient); @@ -2584,9 +3525,9 @@ namespace OpenSim.Region.Framework.Scenes /// Send this agent's appearance to all other root and child agents in the scene /// This agent must be root. /// - public void SendAppearanceToAllOtherAgents() + public void SendAppearanceToAllOtherClients() { -// m_log.DebugFormat("[SCENE PRESENCE] SendAppearanceToAllOtherAgents: {0} {1}", Name, UUID); +// m_log.DebugFormat("[SCENE PRESENCE] SendAppearanceToAllOtherClients: {0} {1}", Name, UUID); // only send update from root agents to other clients; children are only "listening posts" if (IsChildAgent) @@ -2605,7 +3546,7 @@ namespace OpenSim.Region.Framework.Scenes if (scenePresence.UUID == UUID) return; - SendAppearanceToAgent(scenePresence); + SendAppearanceToClient(scenePresence); count++; }); @@ -2616,9 +3557,9 @@ namespace OpenSim.Region.Framework.Scenes /// Send appearance from all other root agents to this agent. this agent /// can be either root or child /// - public void SendOtherAgentsAppearanceToMe() + public void SendOtherAgentsAppearanceToClient() { -// m_log.DebugFormat("[SCENE PRESENCE] SendOtherAgentsAppearanceToMe: {0} {1}", Name, UUID); +// m_log.DebugFormat("[SCENE PRESENCE] SendOtherAgentsAppearanceToClient {0} {1}", Name, UUID); int count = 0; m_scene.ForEachRootScenePresence(delegate(ScenePresence scenePresence) @@ -2627,7 +3568,7 @@ namespace OpenSim.Region.Framework.Scenes if (scenePresence.UUID == UUID) return; - scenePresence.SendAppearanceToAgent(this); + scenePresence.SendAppearanceToClient(this); count++; }); @@ -2638,13 +3579,15 @@ namespace OpenSim.Region.Framework.Scenes /// Send appearance data to an agent. /// /// - public void SendAppearanceToAgent(ScenePresence avatar) + public void SendAppearanceToClient(ScenePresence avatar) { // m_log.DebugFormat( // "[SCENE PRESENCE]: Sending appearance data from {0} {1} to {2} {3}", Name, m_uuid, avatar.Name, avatar.UUID); avatar.ControllingClient.SendAppearance( UUID, Appearance.VisualParams, Appearance.Texture.GetBytes()); + + } #endregion @@ -2663,11 +3606,10 @@ namespace OpenSim.Region.Framework.Scenes } // Minimum Draw distance is 64 meters, the Radius of the draw distance sphere is 32m - if (Util.GetDistanceTo(AbsolutePosition, m_lastChildAgentUpdatePosition) >= Scene.ChildReprioritizationDistance || - Util.GetDistanceTo(CameraPosition, m_lastChildAgentUpdateCamPosition) >= Scene.ChildReprioritizationDistance) + if (Util.GetDistanceTo(AbsolutePosition, m_lastChildAgentUpdatePosition) >= Scene.ChildReprioritizationDistance) { m_lastChildAgentUpdatePosition = AbsolutePosition; - m_lastChildAgentUpdateCamPosition = CameraPosition; +// m_lastChildAgentUpdateCamPosition = CameraPosition; ChildAgentDataUpdate cadu = new ChildAgentDataUpdate(); cadu.ActiveGroupID = UUID.Zero.Guid; @@ -2694,10 +3636,11 @@ namespace OpenSim.Region.Framework.Scenes cadu.Velocity = Velocity; AgentPosition agentpos = new AgentPosition(); - agentpos.CopyFrom(cadu); + agentpos.CopyFrom(cadu, ControllingClient.SessionId); // Let's get this out of the update loop - Util.FireAndForget(delegate { m_scene.SendOutChildAgentUpdates(agentpos, this); }); + Util.FireAndForget( + o => m_scene.SendOutChildAgentUpdates(agentpos, this), null, "ScenePresence.SendOutChildAgentUpdates"); } } @@ -2719,140 +3662,84 @@ namespace OpenSim.Region.Framework.Scenes // If we don't have a PhysActor, we can't cross anyway // Also don't do this while sat, sitting avatars cross with the - // object they sit on. - if (ParentID != 0 || PhysicsActor == null) + // object they sit on. ParentUUID denoted a pending sit, don't + // interfere with it. + if (ParentID != 0 || PhysicsActor == null || ParentUUID != UUID.Zero) return; - if (!IsInTransit) - { - Vector3 pos2 = AbsolutePosition; - Vector3 vel = Velocity; - int neighbor = 0; - int[] fix = new int[2]; + if (IsInTransit) + return; - float timeStep = 0.1f; - pos2.X = pos2.X + (vel.X * timeStep); - pos2.Y = pos2.Y + (vel.Y * timeStep); - pos2.Z = pos2.Z + (vel.Z * timeStep); + Vector3 pos2 = AbsolutePosition; + Vector3 origPosition = pos2; + Vector3 vel = Velocity; - if (!IsInTransit) - { - // Checks if where it's headed exists a region - bool needsTransit = false; - if (m_scene.TestBorderCross(pos2, Cardinals.W)) - { - if (m_scene.TestBorderCross(pos2, Cardinals.S)) - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.SW, ref fix); - } - else if (m_scene.TestBorderCross(pos2, Cardinals.N)) - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.NW, ref fix); - } - else - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.W, ref fix); - } - } - else if (m_scene.TestBorderCross(pos2, Cardinals.E)) - { - if (m_scene.TestBorderCross(pos2, Cardinals.S)) - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.SE, ref fix); - } - else if (m_scene.TestBorderCross(pos2, Cardinals.N)) - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.NE, ref fix); - } - else - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.E, ref fix); - } - } - else if (m_scene.TestBorderCross(pos2, Cardinals.S)) - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.S, ref fix); - } - else if (m_scene.TestBorderCross(pos2, Cardinals.N)) - { - needsTransit = true; - neighbor = m_scene.HaveNeighbor(Cardinals.N, ref fix); - } + // Compute the future avatar position. + // If the avatar will be crossing, we force the crossing to happen now + // in the hope that this will make the avatar movement smoother when crossing. + pos2 += vel * 0.05f; - // Makes sure avatar does not end up outside region - if (neighbor <= 0) - { - if (needsTransit) - { - if (m_requestedSitTargetUUID == UUID.Zero) - { - bool isFlying = Flying; - RemoveFromPhysicalScene(); - - Vector3 pos = AbsolutePosition; - if (AbsolutePosition.X < 0) - pos.X += Velocity.X * 2; - else if (AbsolutePosition.X > Constants.RegionSize) - pos.X -= Velocity.X * 2; - if (AbsolutePosition.Y < 0) - pos.Y += Velocity.Y * 2; - else if (AbsolutePosition.Y > Constants.RegionSize) - pos.Y -= Velocity.Y * 2; - Velocity = Vector3.Zero; - AbsolutePosition = pos; - -// m_log.DebugFormat("[SCENE PRESENCE]: Prevented flyoff for {0} at {1}", Name, AbsolutePosition); - - AddToPhysicalScene(isFlying); - } - } - } - else if (neighbor > 0) - { - if (!CrossToNewRegion()) - { - if (m_requestedSitTargetUUID == UUID.Zero) - { - bool isFlying = Flying; - RemoveFromPhysicalScene(); - - Vector3 pos = AbsolutePosition; - if (AbsolutePosition.X < 0) - pos.X += Velocity.X * 2; - else if (AbsolutePosition.X > Constants.RegionSize) - pos.X -= Velocity.X * 2; - if (AbsolutePosition.Y < 0) - pos.Y += Velocity.Y * 2; - else if (AbsolutePosition.Y > Constants.RegionSize) - pos.Y -= Velocity.Y * 2; - Velocity = Vector3.Zero; - AbsolutePosition = pos; - - AddToPhysicalScene(isFlying); - } - } - } - } - else + if (m_scene.PositionIsInCurrentRegion(pos2)) + return; + + m_log.DebugFormat("{0} CheckForBorderCrossing: position outside region. {1} in {2} at pos {3}", + LogHeader, Name, Scene.Name, pos2); + + // Disconnect from the current region + bool isFlying = Flying; + RemoveFromPhysicalScene(); + + // pos2 is the forcasted position so make that the 'current' position so the crossing + // code will move us into the newly addressed region. + m_pos = pos2; + + if (CrossToNewRegion()) + { + AddToPhysicalScene(isFlying); + } + else + { + // Tried to make crossing happen but it failed. + if (m_requestedSitTargetUUID == UUID.Zero) { - // This constant has been inferred from experimentation - // I'm not sure what this value should be, so I tried a few values. - timeStep = 0.04f; - pos2 = AbsolutePosition; - pos2.X = pos2.X + (vel.X * timeStep); - pos2.Y = pos2.Y + (vel.Y * timeStep); - // Don't touch the Z - m_pos = pos2; - m_log.DebugFormat("[SCENE PRESENCE]: In transit m_pos={0}", m_pos); + m_log.DebugFormat("{0} CheckForBorderCrossing: Crossing failed. Restoring old position.", LogHeader); + + Velocity = Vector3.Zero; + AbsolutePosition = EnforceSanityOnPosition(origPosition); + + AddToPhysicalScene(isFlying); } - } + } + } + + // Given a position, make sure it is within the current region. + // If just outside some border, the returned position will be just inside the border on that side. + private Vector3 EnforceSanityOnPosition(Vector3 origPosition) + { + const float borderFudge = 0.1f; + Vector3 ret = origPosition; + + // Sanity checking on the position to make sure it is in the region we couldn't cross from + float extentX = (float)m_scene.RegionInfo.RegionSizeX; + float extentY = (float)m_scene.RegionInfo.RegionSizeY; + IRegionCombinerModule combiner = m_scene.RequestModuleInterface(); + if (combiner != null) + { + // If a mega-region, the size could be much bigger + Vector2 megaExtent = combiner.GetSizeOfMegaregion(m_scene.RegionInfo.RegionID); + extentX = megaExtent.X; + extentY = megaExtent.Y; + } + if (ret.X < 0) + ret.X = borderFudge; + else if (ret.X >= extentX) + ret.X = extentX - borderFudge; + if (ret.Y < 0) + ret.Y = borderFudge; + else if (ret.Y >= extentY) + ret.Y = extentY - borderFudge; + + return ret; } /// @@ -2873,18 +3760,13 @@ namespace OpenSim.Region.Framework.Scenes } } - public void RestoreInCurrentScene() - { - AddToPhysicalScene(false); // not exactly false - } - public void Reset() { // m_log.DebugFormat("[SCENE PRESENCE]: Resetting {0} in {1}", Name, Scene.RegionInfo.RegionName); // Put the child agent back at the center AbsolutePosition - = new Vector3(((float)Constants.RegionSize * 0.5f), ((float)Constants.RegionSize * 0.5f), 70); + = new Vector3(((float)m_scene.RegionInfo.RegionSizeX * 0.5f), ((float)m_scene.RegionInfo.RegionSizeY * 0.5f), 70); Animator.ResetAnimations(); } @@ -2911,13 +3793,13 @@ namespace OpenSim.Region.Framework.Scenes if (handle != Scene.RegionInfo.RegionHandle) { uint x, y; - Utils.LongToUInts(handle, out x, out y); - x = x / Constants.RegionSize; - y = y / Constants.RegionSize; + Util.RegionHandleToRegionLoc(handle, out x, out y); // m_log.Debug("---> x: " + x + "; newx:" + newRegionX + "; Abs:" + (int)Math.Abs((int)(x - newRegionX))); // m_log.Debug("---> y: " + y + "; newy:" + newRegionY + "; Abs:" + (int)Math.Abs((int)(y - newRegionY))); - if (Util.IsOutsideView(DrawDistance, x, newRegionX, y, newRegionY)) + float dist = (float)Math.Max(Scene.DefaultDrawDistance, + (float)Math.Max(Scene.RegionInfo.RegionSizeX, Scene.RegionInfo.RegionSizeY)); + if (Util.IsOutsideView(dist, x, newRegionX, y, newRegionY)) { byebyeRegions.Add(handle); } @@ -2927,10 +3809,12 @@ namespace OpenSim.Region.Framework.Scenes if (byebyeRegions.Count > 0) { m_log.Debug("[SCENE PRESENCE]: Closing " + byebyeRegions.Count + " child agents"); - Util.FireAndForget(delegate - { - m_scene.SceneGridService.SendCloseChildAgentConnections(ControllingClient.AgentId, byebyeRegions); - }); + + AgentCircuitData acd = Scene.AuthenticateHandler.GetAgentCircuitData(UUID); + string auth = string.Empty; + if (acd != null) + auth = acd.SessionID.ToString(); + m_scene.SceneGridService.SendCloseChildAgentConnections(ControllingClient.AgentId, auth, byebyeRegions); } foreach (ulong handle in byebyeRegions) @@ -2971,36 +3855,45 @@ namespace OpenSim.Region.Framework.Scenes #region Child Agent Updates - public void ChildAgentDataUpdate(AgentData cAgentData) + public void UpdateChildAgent(AgentData cAgentData) { // m_log.Debug(" >>> ChildAgentDataUpdate <<< " + Scene.RegionInfo.RegionName); if (!IsChildAgent) return; CopyFrom(cAgentData); + + m_updateAgentReceivedAfterTransferEvent.Set(); } private static Vector3 marker = new Vector3(-1f, -1f, -1f); + /// /// This updates important decision making data about a child agent /// The main purpose is to figure out what objects to send to a child agent that's in a neighboring region /// - public void ChildAgentDataUpdate(AgentPosition cAgentData, uint tRegionX, uint tRegionY, uint rRegionX, uint rRegionY) + public void UpdateChildAgent(AgentPosition cAgentData, uint tRegionX, uint tRegionY, uint rRegionX, uint rRegionY) { if (!IsChildAgent) return; - //m_log.Debug(" >>> ChildAgentPositionUpdate <<< " + rRegionX + "-" + rRegionY); - int shiftx = ((int)rRegionX - (int)tRegionX) * (int)Constants.RegionSize; - int shifty = ((int)rRegionY - (int)tRegionY) * (int)Constants.RegionSize; +// m_log.DebugFormat( +// "[SCENE PRESENCE]: ChildAgentPositionUpdate for {0} in {1}, tRegion {2},{3}, rRegion {4},{5}, pos {6}", +// Name, Scene.Name, tRegionX, tRegionY, rRegionX, rRegionY, cAgentData.Position); + + // Find the distance (in meters) between the two regions + // XXX: We cannot use Util.RegionLocToHandle() here because a negative value will silently overflow the + // uint + int shiftx = (int)(((int)rRegionX - (int)tRegionX) * Constants.RegionSize); + int shifty = (int)(((int)rRegionY - (int)tRegionY) * Constants.RegionSize); Vector3 offset = new Vector3(shiftx, shifty, 0f); // When we get to the point of re-computing neighbors everytime this // changes, then start using the agent's drawdistance rather than the // region's draw distance. - // DrawDistance = cAgentData.Far; - DrawDistance = Scene.DefaultDrawDistance; + DrawDistance = cAgentData.Far; + // DrawDistance = Scene.DefaultDrawDistance; if (cAgentData.Position != marker) // UGH!! m_pos = cAgentData.Position + offset; @@ -3027,6 +3920,7 @@ namespace OpenSim.Region.Framework.Scenes cAgent.AgentID = UUID; cAgent.RegionID = Scene.RegionInfo.RegionID; + cAgent.SessionID = ControllingClient.SessionId; cAgent.Position = AbsolutePosition; cAgent.Velocity = m_velocity; @@ -3061,6 +3955,9 @@ namespace OpenSim.Region.Framework.Scenes cAgent.AlwaysRun = SetAlwaysRun; cAgent.Appearance = new AvatarAppearance(Appearance); + + cAgent.ParentPart = ParentUUID; + cAgent.SitOffset = PrevSitOffset; lock (scriptedcontrols) { @@ -3069,7 +3966,7 @@ namespace OpenSim.Region.Framework.Scenes foreach (ScriptControllers c in scriptedcontrols.Values) { - controls[i++] = new ControllerData(c.itemID, (uint)c.ignoreControls, (uint)c.eventControls); + controls[i++] = new ControllerData(c.objectID, c.itemID, (uint)c.ignoreControls, (uint)c.eventControls); } cAgent.Controllers = controls; } @@ -3089,8 +3986,6 @@ namespace OpenSim.Region.Framework.Scenes private void CopyFrom(AgentData cAgent) { - m_originRegionID = cAgent.RegionID; - m_callbackURI = cAgent.CallbackURI; // m_log.DebugFormat( // "[SCENE PRESENCE]: Set callback for {0} in {1} to {2} in CopyFrom()", @@ -3102,12 +3997,14 @@ namespace OpenSim.Region.Framework.Scenes CameraAtAxis = cAgent.AtAxis; CameraLeftAxis = cAgent.LeftAxis; CameraUpAxis = cAgent.UpAxis; + ParentUUID = cAgent.ParentPart; + PrevSitOffset = cAgent.SitOffset; // When we get to the point of re-computing neighbors everytime this // changes, then start using the agent's drawdistance rather than the // region's draw distance. - // DrawDistance = cAgent.Far; - DrawDistance = Scene.DefaultDrawDistance; + DrawDistance = cAgent.Far; + // DrawDistance = Scene.DefaultDrawDistance; if ((cAgent.Throttles != null) && cAgent.Throttles.Length > 0) ControllingClient.SetChildAgentThrottle(cAgent.Throttles); @@ -3139,6 +4036,7 @@ namespace OpenSim.Region.Framework.Scenes foreach (ControllerData c in cAgent.Controllers) { ScriptControllers sc = new ScriptControllers(); + sc.objectID = c.ObjectID; sc.itemID = c.ItemID; sc.ignoreControls = (ScriptControlled)c.IgnoreControls; sc.eventControls = (ScriptControlled)c.EventControls; @@ -3159,7 +4057,27 @@ namespace OpenSim.Region.Framework.Scenes Animator.Animations.SetImplicitDefaultAnimation(cAgent.AnimState.AnimID, cAgent.AnimState.SequenceNum, UUID.Zero); if (Scene.AttachmentsModule != null) - Scene.AttachmentsModule.CopyAttachments(cAgent, this); + { + // If the JobEngine is running we can schedule this job now and continue rather than waiting for all + // attachments to copy, which might take a long time in the Hypergrid case as the entire inventory + // graph is inspected for each attachments and assets possibly fetched. + // + // We don't need to worry about a race condition as the job to later start the scripts is also + // JobEngine scheduled and so will always occur after this task. + // XXX: This will not be true if JobEngine ever gets more than one thread. + WorkManager.RunJob( + "CopyAttachments", + o => Scene.AttachmentsModule.CopyAttachments(cAgent, this), + null, + string.Format("Copy attachments for {0} entering {1}", Name, Scene.Name), + true); + } + + // This must occur after attachments are copied or scheduled to be copied, as it releases the CompleteMovement() calling thread + // originating from the client completing a teleport. Otherwise, CompleteMovement() code to restart + // script attachments can outrace this thread. + lock (m_originRegionIDAccessLock) + m_originRegionID = cAgent.RegionID; } public bool CopyAgent(out IAgentData agent) @@ -3180,8 +4098,6 @@ namespace OpenSim.Region.Framework.Scenes { Vector3 force = m_forceToApply.Value; - Updated = true; - Velocity = force; m_forceToApply = null; @@ -3206,20 +4122,23 @@ namespace OpenSim.Region.Framework.Scenes } if (Appearance.AvatarHeight == 0) - Appearance.SetHeight(); - - PhysicsScene scene = m_scene.PhysicsScene; - - Vector3 pVec = AbsolutePosition; - +// Appearance.SetHeight(); + Appearance.SetSize(new Vector3(0.45f,0.6f,1.9f)); + +/* PhysicsActor = scene.AddAvatar( LocalId, Firstname + "." + Lastname, pVec, - new Vector3(0f, 0f, Appearance.AvatarHeight), isFlying); + new Vector3(0.45f, 0.6f, Appearance.AvatarHeight), isFlying); +*/ + + PhysicsActor = m_scene.PhysicsScene.AddAvatar( + LocalId, Firstname + "." + Lastname, AbsolutePosition, Velocity, + Appearance.AvatarBoxSize, isFlying); //PhysicsActor.OnRequestTerseUpdate += SendTerseUpdateToAllClients; PhysicsActor.OnCollisionUpdate += PhysicsCollisionUpdate; PhysicsActor.OnOutOfBounds += OutOfBoundsCall; // Called for PhysicsActors when there's something wrong - PhysicsActor.SubscribeEvents(500); + PhysicsActor.SubscribeEvents(100); PhysicsActor.LocalID = LocalId; } @@ -3233,6 +4152,7 @@ namespace OpenSim.Region.Framework.Scenes ControllingClient.SendAgentAlertMessage("Physics is having a problem with your avatar. You may not be able to move until you relog.", true); } + /// /// Event called by the physics plugin to tell the avatar about a collision. /// @@ -3246,7 +4166,7 @@ namespace OpenSim.Region.Framework.Scenes /// public void PhysicsCollisionUpdate(EventArgs e) { - if (IsChildAgent) + if (IsChildAgent || Animator == null) return; //if ((Math.Abs(Velocity.X) > 0.1e-9f) || (Math.Abs(Velocity.Y) > 0.1e-9f)) @@ -3255,14 +4175,14 @@ namespace OpenSim.Region.Framework.Scenes // if (m_updateCount > 0) // { - Animator.UpdateMovementAnimations(); + if (Animator.UpdateMovementAnimations()) + TriggerScenePresenceUpdated(); // m_updateCount--; // } CollisionEventUpdate collisionData = (CollisionEventUpdate)e; Dictionary coldata = collisionData.m_objCollisionList; - CollisionPlane = Vector4.UnitW; // // No collisions at all means we may be flying. Update always // // to make falling work @@ -3272,34 +4192,7 @@ namespace OpenSim.Region.Framework.Scenes // m_lastColCount = coldata.Count; // } - if (coldata.Count != 0) - { - switch (Animator.CurrentMovementAnimation) - { - case "STAND": - case "WALK": - case "RUN": - case "CROUCH": - case "CROUCHWALK": - { - ContactPoint lowest; - lowest.SurfaceNormal = Vector3.Zero; - lowest.Position = Vector3.Zero; - lowest.Position.Z = Single.NaN; - - foreach (ContactPoint contact in coldata.Values) - { - if (Single.IsNaN(lowest.Position.Z) || contact.Position.Z < lowest.Position.Z) - { - lowest = contact; - } - } - - CollisionPlane = new Vector4(-lowest.SurfaceNormal, -Vector3.Dot(lowest.Position, lowest.SurfaceNormal)); - } - break; - } - } + CollisionPlane = Vector4.UnitW; // Gods do not take damage and Invulnerable is set depending on parcel/region flags if (Invulnerable || GodLevel > 0) @@ -3398,6 +4291,14 @@ namespace OpenSim.Region.Framework.Scenes // m_reprioritizationTimer.Dispose(); RemoveFromPhysicalScene(); + + m_scene.EventManager.OnRegionHeartbeatEnd -= RegionHeartbeatEnd; + +// if (Animator != null) +// Animator.Close(); + Animator = null; + + LifecycleState = ScenePresenceState.Removed; } public void AddAttachment(SceneObjectGroup gobj) @@ -3602,7 +4503,7 @@ namespace OpenSim.Region.Framework.Scenes } } } - }); + }, null, "ScenePresence.SendScriptEventToAttachments"); } /// @@ -3631,10 +4532,18 @@ namespace OpenSim.Region.Framework.Scenes public void RegisterControlEventsToScript(int controls, int accept, int pass_on, uint Obj_localID, UUID Script_item_UUID) { + SceneObjectPart p = m_scene.GetSceneObjectPart(Obj_localID); + if (p == null) + return; + + ControllingClient.SendTakeControls(controls, false, false); + ControllingClient.SendTakeControls(controls, true, false); + ScriptControllers obj = new ScriptControllers(); obj.ignoreControls = ScriptControlled.CONTROL_ZERO; obj.eventControls = ScriptControlled.CONTROL_ZERO; + obj.objectID = p.ParentGroup.UUID; obj.itemID = Script_item_UUID; if (pass_on == 0 && accept == 0) { @@ -3683,6 +4592,21 @@ namespace OpenSim.Region.Framework.Scenes ControllingClient.SendTakeControls(int.MaxValue, false, false); } + private void UnRegisterSeatControls(UUID obj) + { + List takers = new List(); + + foreach (ScriptControllers c in scriptedcontrols.Values) + { + if (c.objectID == obj) + takers.Add(c.itemID); + } + foreach (UUID t in takers) + { + UnRegisterControlEventsToScript(0, t); + } + } + public void UnRegisterControlEventsToScript(uint Obj_localID, UUID Script_item_UUID) { ScriptControllers takecontrols; @@ -3899,6 +4823,7 @@ namespace OpenSim.Region.Framework.Scenes (m_teleportFlags & TeleportFlags.ViaLocation) != 0 || (m_teleportFlags & Constants.TeleportFlags.ViaHGLogin) != 0) { + if (GodLevel < 200 && ((!m_scene.Permissions.IsGod(m_uuid) && !m_scene.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_uuid)) || @@ -3907,7 +4832,14 @@ namespace OpenSim.Region.Framework.Scenes { SpawnPoint[] spawnPoints = m_scene.RegionInfo.RegionSettings.SpawnPoints().ToArray(); if (spawnPoints.Length == 0) + { + if(m_scene.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_uuid)) + { + pos.X = 128.0f; + pos.Y = 128.0f; + } return; + } int index; bool selected = false; @@ -3916,6 +4848,8 @@ namespace OpenSim.Region.Framework.Scenes { case "random": + if (spawnPoints.Length == 0) + return; do { index = Util.RandomClass.Next(spawnPoints.Length - 1); @@ -3927,6 +4861,7 @@ namespace OpenSim.Region.Framework.Scenes // SpawnPoint sp = spawnPoints[index]; ILandObject land = m_scene.LandChannel.GetLandObject(spawnPosition.X, spawnPosition.Y); + if (land == null || land.IsEitherBannedOrRestricted(UUID)) selected = false; else @@ -3999,8 +4934,15 @@ namespace OpenSim.Region.Framework.Scenes } } + // Modify landing point based on possible banning, telehubs or parcel restrictions. private void CheckAndAdjustLandingPoint(ref Vector3 pos) { + string reason; + + // Honor bans + if (!m_scene.TestLandRestrictions(UUID, out reason, ref pos.X, ref pos.Y)) + return; + SceneObjectGroup telehub = null; if (m_scene.RegionInfo.RegionSettings.TelehubObject != UUID.Zero && (telehub = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject)) != null) { @@ -4040,11 +4982,119 @@ namespace OpenSim.Region.Framework.Scenes pos = land.LandData.UserLocation; } } - + land.SendLandUpdateToClient(ControllingClient); } } + private DetectedObject CreateDetObject(SceneObjectPart obj) + { + DetectedObject detobj = new DetectedObject(); + detobj.keyUUID = obj.UUID; + detobj.nameStr = obj.Name; + detobj.ownerUUID = obj.OwnerID; + detobj.posVector = obj.AbsolutePosition; + detobj.rotQuat = obj.GetWorldRotation(); + detobj.velVector = obj.Velocity; + detobj.colliderType = 0; + detobj.groupUUID = obj.GroupID; + + return detobj; + } + + private DetectedObject CreateDetObject(ScenePresence av) + { + DetectedObject detobj = new DetectedObject(); + detobj.keyUUID = av.UUID; + detobj.nameStr = av.ControllingClient.Name; + detobj.ownerUUID = av.UUID; + detobj.posVector = av.AbsolutePosition; + detobj.rotQuat = av.Rotation; + detobj.velVector = av.Velocity; + detobj.colliderType = 0; + detobj.groupUUID = av.ControllingClient.ActiveGroupId; + + return detobj; + } + + private DetectedObject CreateDetObjectForGround() + { + DetectedObject detobj = new DetectedObject(); + detobj.keyUUID = UUID.Zero; + detobj.nameStr = ""; + detobj.ownerUUID = UUID.Zero; + detobj.posVector = AbsolutePosition; + detobj.rotQuat = Quaternion.Identity; + detobj.velVector = Vector3.Zero; + detobj.colliderType = 0; + detobj.groupUUID = UUID.Zero; + + return detobj; + } + + private ColliderArgs CreateColliderArgs(SceneObjectPart dest, List colliders) + { + ColliderArgs colliderArgs = new ColliderArgs(); + List colliding = new List(); + foreach (uint localId in colliders) + { + if (localId == 0) + continue; + + SceneObjectPart obj = m_scene.GetSceneObjectPart(localId); + if (obj != null) + { + if (!dest.CollisionFilteredOut(obj.UUID, obj.Name)) + colliding.Add(CreateDetObject(obj)); + } + else + { + ScenePresence av = m_scene.GetScenePresence(localId); + if (av != null && (!av.IsChildAgent)) + { + if (!dest.CollisionFilteredOut(av.UUID, av.Name)) + colliding.Add(CreateDetObject(av)); + } + } + } + + colliderArgs.Colliders = colliding; + + return colliderArgs; + } + + private delegate void ScriptCollidingNotification(uint localID, ColliderArgs message); + + private void SendCollisionEvent(SceneObjectGroup dest, scriptEvents ev, List colliders, ScriptCollidingNotification notify) + { + ColliderArgs CollidingMessage; + + if (colliders.Count > 0) + { + if ((dest.RootPart.ScriptEvents & ev) != 0) + { + CollidingMessage = CreateColliderArgs(dest.RootPart, colliders); + + if (CollidingMessage.Colliders.Count > 0) + notify(dest.RootPart.LocalId, CollidingMessage); + } + } + } + + private void SendLandCollisionEvent(SceneObjectGroup dest, scriptEvents ev, ScriptCollidingNotification notify) + { + if ((dest.RootPart.ScriptEvents & ev) != 0) + { + ColliderArgs LandCollidingMessage = new ColliderArgs(); + List colliding = new List(); + + colliding.Add(CreateDetObjectForGround()); + LandCollidingMessage.Colliders = colliding; + + notify(dest.RootPart.LocalId, LandCollidingMessage); + } + } + private void TeleportFlagsDebug() { // Some temporary debugging help to show all the TeleportFlags we have... @@ -4069,6 +5119,5 @@ namespace OpenSim.Region.Framework.Scenes m_log.InfoFormat("[SCENE PRESENCE]: TELEPORT ******************"); } - } } diff --git a/OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs b/OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs new file mode 100644 index 0000000..cae7fe5 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs @@ -0,0 +1,113 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace OpenSim.Region.Framework.Scenes +{ + /// + /// The possible states that a scene presence can be in. This is currently orthagonal to whether a scene presence + /// is root or child. + /// + /// + /// This is a state machine. + /// + /// [Entry] => Running + /// Running => PreRemove, Removing + /// PreRemove => Running, Removing + /// Removing => Removed + /// + /// All other methods should only see the scene presence in running state - this is the normal operational state + /// Removed state occurs when the presence has been removed. This is the end state with no exit. + /// + public enum ScenePresenceState + { + Running, // Normal operation state. The scene presence is available. + PreRemove, // The presence is due to be removed but can still be returning to running. + Removing, // The presence is in the process of being removed from the scene via Scene.RemoveClient. + Removed, // The presence has been removed from the scene and is effectively dead. + // There is no exit from this state. + } + + internal class ScenePresenceStateMachine + { + private ScenePresence m_sp; + private ScenePresenceState m_state; + + internal ScenePresenceStateMachine(ScenePresence sp) + { + m_sp = sp; + m_state = ScenePresenceState.Running; + } + + internal ScenePresenceState GetState() + { + return m_state; + } + + /// + /// Updates the state of an agent that is already in transit. + /// + /// + /// + /// + /// Illegal transitions will throw an Exception + internal void SetState(ScenePresenceState newState) + { + bool transitionOkay = false; + + lock (this) + { + if (newState == m_state) + return; + else if (newState == ScenePresenceState.Running && m_state == ScenePresenceState.PreRemove) + transitionOkay = true; + else if (newState == ScenePresenceState.PreRemove && m_state == ScenePresenceState.Running) + transitionOkay = true; + else if (newState == ScenePresenceState.Removing) + { + if (m_state == ScenePresenceState.Running || m_state == ScenePresenceState.PreRemove) + transitionOkay = true; + } + else if (newState == ScenePresenceState.Removed && m_state == ScenePresenceState.Removing) + transitionOkay = true; + } + + if (!transitionOkay) + { + throw new Exception( + string.Format( + "Scene presence {0} is not allowed to move from state {1} to new state {2} in {3}", + m_sp.Name, m_state, newState, m_sp.Scene.Name)); + } + else + { + m_state = newState; + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs b/OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs deleted file mode 100644 index f3be028..0000000 --- a/OpenSim/Region/Framework/Scenes/Scripting/IScriptHost.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using OpenMetaverse; - -namespace OpenSim.Region.Framework.Scenes.Scripting -{ - public interface IScriptHost - { - string Name { get; set; } - string Description { get; set; } - - UUID UUID { get; } - UUID OwnerID { get; } - UUID CreatorID { get; } - Vector3 AbsolutePosition { get; } - - string SitName { get; set; } - string TouchName { get; set; } - void SetText(string text, Vector3 color, double alpha); - } -} diff --git a/OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs b/OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs deleted file mode 100644 index d7198f0..0000000 --- a/OpenSim/Region/Framework/Scenes/Scripting/NullScriptHost.cs +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using OpenMetaverse; -using log4net; -using System.Reflection; -using OpenSim.Framework; - -namespace OpenSim.Region.Framework.Scenes.Scripting -{ - public class NullScriptHost : IScriptHost - { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - private Vector3 m_pos = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 30); - - public string Name - { - get { return "Object"; } - set { } - } - - public string SitName - { - get { return String.Empty; } - set { } - } - - public string TouchName - { - get { return String.Empty; } - set { } - } - - public string Description - { - get { return String.Empty; } - set { } - } - - public UUID UUID - { - get { return UUID.Zero; } - } - - public UUID OwnerID - { - get { return UUID.Zero; } - } - - public UUID CreatorID - { - get { return UUID.Zero; } - } - - public Vector3 AbsolutePosition - { - get { return m_pos; } - } - - public void SetText(string text, Vector3 color, double alpha) - { - m_log.Warn("Tried to SetText "+text+" on NullScriptHost"); - } - } -} diff --git a/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs b/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs deleted file mode 100644 index 812a21c..0000000 --- a/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineInterface.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -//TODO: WHERE TO PLACE THIS? - -namespace OpenSim.Region.Framework.Scenes.Scripting -{ - public interface ScriptEngineInterface - { - void InitializeEngine(Scene Sceneworld); - void Shutdown(); -// void StartScript(string ScriptID, IScriptHost ObjectID); - } -} diff --git a/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineLoader.cs b/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineLoader.cs deleted file mode 100644 index c58ccc5..0000000 --- a/OpenSim/Region/Framework/Scenes/Scripting/ScriptEngineLoader.cs +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) Contributors, http://opensimulator.org/ - * See CONTRIBUTORS.TXT for a full list of copyright holders. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the OpenSimulator Project nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* Original code: Tedd Hansen */ -using System; -using System.IO; -using System.Reflection; -using log4net; - -namespace OpenSim.Region.Framework.Scenes.Scripting -{ - public class ScriptEngineLoader - { - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - public ScriptEngineInterface LoadScriptEngine(string EngineName) - { - ScriptEngineInterface ret = null; - try - { - ret = - LoadAndInitAssembly( - Path.Combine("ScriptEngines", "OpenSim.Region.ScriptEngine." + EngineName + ".dll"), - "OpenSim.Region.ScriptEngine." + EngineName + ".ScriptEngine"); - } - catch (Exception e) - { - m_log.Error("[ScriptEngine]: " + - "Error loading assembly \"" + EngineName + "\": " + e.Message + ", " + - e.StackTrace.ToString()); - } - return ret; - } - - /// - /// Does actual loading and initialization of script Assembly - /// - /// AppDomain to load script into - /// FileName of script assembly (.dll) - /// - private ScriptEngineInterface LoadAndInitAssembly(string FileName, string NameSpace) - { - //Common.SendToDebug("Loading ScriptEngine Assembly " + FileName); - // Load .Net Assembly (.dll) - // Initialize and return it - - // TODO: Add error handling - - Assembly a; - //try - //{ - - - // Load to default appdomain (temporary) - a = Assembly.LoadFrom(FileName); - // Load to specified appdomain - // TODO: Insert security - //a = FreeAppDomain.Load(FileName); - //} - //catch (Exception e) - //{ - // m_log.Error("[ScriptEngine]: Error loading assembly \String.Empty + FileName + "\": " + e.ToString()); - //} - - - //m_log.Debug("Loading: " + FileName); - //foreach (Type _t in a.GetTypes()) - //{ - // m_log.Debug("Type: " + _t.ToString()); - //} - - Type t; - //try - //{ - t = a.GetType(NameSpace, true); - //} - //catch (Exception e) - //{ - // m_log.Error("[ScriptEngine]: Error initializing type \String.Empty + NameSpace + "\" from \String.Empty + FileName + "\": " + e.ToString()); - //} - - ScriptEngineInterface ret; - //try - //{ - ret = (ScriptEngineInterface) Activator.CreateInstance(t); - //} - //catch (Exception e) - //{ - // m_log.Error("[ScriptEngine]: Error initializing type \String.Empty + NameSpace + "\" from \String.Empty + FileName + "\": " + e.ToString()); - //} - - return ret; - } - } -} diff --git a/OpenSim/Region/Framework/Scenes/Scripting/ScriptUtils.cs b/OpenSim/Region/Framework/Scenes/Scripting/ScriptUtils.cs new file mode 100644 index 0000000..f08ba59 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Scripting/ScriptUtils.cs @@ -0,0 +1,107 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Region.Framework.Scenes.Scripting +{ + /// + /// Utility functions for use by scripts manipulating the scene. + /// + public static class ScriptUtils + { + /// + /// Get an asset id given an item name and an item type. + /// + /// UUID.Zero if the name and type did not match any item. + /// + /// + /// + public static UUID GetAssetIdFromItemName(SceneObjectPart part, string name, int type) + { + TaskInventoryItem item = part.Inventory.GetInventoryItem(name); + + if (item != null && item.Type == type) + return item.AssetID; + else + return UUID.Zero; + } + + /// + /// accepts a valid UUID, -or- a name of an inventory item. + /// Returns a valid UUID or UUID.Zero if key invalid and item not found + /// in prim inventory. + /// + /// Scene object part to search for inventory item + /// + /// + public static UUID GetAssetIdFromKeyOrItemName(SceneObjectPart part, string identifier) + { + UUID key; + + // if we can parse the string as a key, use it. + // else try to locate the name in inventory of object. found returns key, + // not found returns UUID.Zero + if (!UUID.TryParse(identifier, out key)) + { + TaskInventoryItem item = part.Inventory.GetInventoryItem(identifier); + + if (item != null) + key = item.AssetID; + else + key = UUID.Zero; + } + + return key; + } + + /// + /// Return the UUID of the asset matching the specified key or name + /// and asset type. + /// + /// Scene object part to search for inventory item + /// + /// + /// + public static UUID GetAssetIdFromKeyOrItemName(SceneObjectPart part, string identifier, AssetType type) + { + UUID key; + + if (!UUID.TryParse(identifier, out key)) + { + TaskInventoryItem item = part.Inventory.GetInventoryItem(identifier); + if (item != null && item.Type == (int)type) + key = item.AssetID; + } + + return key; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Serialization/CoalescedSceneObjectsSerializer.cs b/OpenSim/Region/Framework/Scenes/Serialization/CoalescedSceneObjectsSerializer.cs index 5cb271d..998789d 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/CoalescedSceneObjectsSerializer.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/CoalescedSceneObjectsSerializer.cs @@ -119,21 +119,22 @@ namespace OpenSim.Region.Framework.Scenes.Serialization return output; } } - + public static bool TryFromXml(string xml, out CoalescedSceneObjects coa) { // m_log.DebugFormat("[COALESCED SCENE OBJECTS SERIALIZER]: TryFromXml() deserializing {0}", xml); coa = null; - int i = 0; - using (StringReader sr = new StringReader(xml)) - { - using (XmlTextReader reader = new XmlTextReader(sr)) - { - try + try + { + // Quickly check if this is a coalesced object, without fully parsing the XML + using (StringReader sr = new StringReader(xml)) + { + using (XmlTextReader reader = new XmlTextReader(sr)) { - reader.Read(); + reader.MoveToContent(); // skip possible xml declaration + if (reader.Name != "CoalescedObject") { // m_log.DebugFormat( @@ -142,49 +143,47 @@ namespace OpenSim.Region.Framework.Scenes.Serialization return false; } - - coa = new CoalescedSceneObjects(UUID.Zero); - reader.Read(); - - while (reader.NodeType != XmlNodeType.EndElement && reader.Name != "CoalescedObject") - { - if (reader.Name == "SceneObjectGroup") - { - string soXml = reader.ReadOuterXml(); - - SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(soXml); - - if (so != null) - { - coa.Add(so); - } - else - { - // XXX: Possibly we should fail outright here rather than continuing if a particular component of the - // coalesced object fails to load. - m_log.WarnFormat( - "[COALESCED SCENE OBJECTS SERIALIZER]: Deserialization of xml for component {0} failed. Continuing.", - i); - } + } + } - i++; - } - } + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + XmlElement e = (XmlElement)doc.SelectSingleNode("/CoalescedObject"); + if (e == null) + return false; - reader.ReadEndElement(); // CoalescedObject + coa = new CoalescedSceneObjects(UUID.Zero); + + XmlNodeList groups = e.SelectNodes("SceneObjectGroup"); + int i = 0; + + foreach (XmlNode n in groups) + { + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(n.OuterXml); + if (so != null) + { + coa.Add(so); } - catch (Exception e) + else { - m_log.ErrorFormat( - "[COALESCED SCENE OBJECTS SERIALIZER]: Deserialization of xml failed with {0} {1}", - e.Message, e.StackTrace); - - return false; - } + // XXX: Possibly we should fail outright here rather than continuing if a particular component of the + // coalesced object fails to load. + m_log.WarnFormat( + "[COALESCED SCENE OBJECTS SERIALIZER]: Deserialization of xml for component {0} failed. Continuing.", + i); + } + + i++; } } + catch (Exception e) + { + m_log.Error("[COALESCED SCENE OBJECTS SERIALIZER]: Deserialization of xml failed ", e); + Util.LogFailedXML("[COALESCED SCENE OBJECTS SERIALIZER]:", xml); + return false; + } return true; } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs index 2d4c60a..4caa9cb 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs @@ -38,6 +38,7 @@ using OpenSim.Framework; using OpenSim.Framework.Serialization.External; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; namespace OpenSim.Region.Framework.Scenes.Serialization { @@ -51,7 +52,7 @@ namespace OpenSim.Region.Framework.Scenes.Serialization private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static IUserManagement m_UserManagement; - + /// /// Deserialize a scene object from the original xml format /// @@ -59,57 +60,62 @@ namespace OpenSim.Region.Framework.Scenes.Serialization /// The scene object deserialized. Null on failure. public static SceneObjectGroup FromOriginalXmlFormat(string xmlData) { - //m_log.DebugFormat("[SOG]: Starting deserialization of SOG"); - //int time = System.Environment.TickCount; - - try + String fixedData = ExternalRepresentationUtils.SanitizeXml(xmlData); + using (XmlTextReader wrappedReader = new XmlTextReader(fixedData, XmlNodeType.Element, null)) { - StringReader sr; - XmlTextReader reader; - XmlNodeList parts; - XmlDocument doc; - int linkNum; - - doc = new XmlDocument(); - doc.LoadXml(xmlData); - parts = doc.GetElementsByTagName("RootPart"); + using (XmlReader reader = XmlReader.Create(wrappedReader, new XmlReaderSettings() { IgnoreWhitespace = true, ConformanceLevel = ConformanceLevel.Fragment })) + { + try { + return FromOriginalXmlFormat(reader); + } + catch (Exception e) + { + m_log.Error("[SERIALIZER]: Deserialization of xml failed ", e); + Util.LogFailedXML("[SERIALIZER]:", fixedData); + return null; + } + } + } + } - if (parts.Count == 0) - throw new Exception("Invalid Xml format - no root part"); + /// + /// Deserialize a scene object from the original xml format + /// + /// + /// The scene object deserialized. Null on failure. + public static SceneObjectGroup FromOriginalXmlFormat(XmlReader reader) + { + //m_log.DebugFormat("[SOG]: Starting deserialization of SOG"); + //int time = System.Environment.TickCount; - sr = new StringReader(parts[0].InnerXml); - reader = new XmlTextReader(sr); - SceneObjectGroup sceneObject = new SceneObjectGroup(SceneObjectPart.FromXml(reader)); - reader.Close(); - sr.Close(); + int linkNum; - parts = doc.GetElementsByTagName("Part"); + reader.ReadToFollowing("RootPart"); + reader.ReadToFollowing("SceneObjectPart"); + SceneObjectGroup sceneObject = new SceneObjectGroup(SceneObjectPart.FromXml(reader)); + reader.ReadToFollowing("OtherParts"); - for (int i = 0; i < parts.Count; i++) + if (reader.ReadToDescendant("Part")) + { + do { - sr = new StringReader(parts[i].InnerXml); - reader = new XmlTextReader(sr); - SceneObjectPart part = SceneObjectPart.FromXml(reader); - linkNum = part.LinkNum; - sceneObject.AddPart(part); - part.LinkNum = linkNum; - part.TrimPermissions(); - reader.Close(); - sr.Close(); - } + if (reader.ReadToDescendant("SceneObjectPart")) + { + SceneObjectPart part = SceneObjectPart.FromXml(reader); + linkNum = part.LinkNum; + sceneObject.AddPart(part); + part.LinkNum = linkNum; + part.TrimPermissions(); + } + } + while (reader.ReadToNextSibling("Part")); + } - // Script state may, or may not, exist. Not having any, is NOT - // ever a problem. - sceneObject.LoadScriptState(doc); + // Script state may, or may not, exist. Not having any, is NOT + // ever a problem. + sceneObject.LoadScriptState(reader); - return sceneObject; - } - catch (Exception e) - { - m_log.ErrorFormat( - "[SERIALIZER]: Deserialization of xml failed with {0}. xml was {1}", e, xmlData); - return null; - } + return sceneObject; } /// @@ -232,7 +238,8 @@ namespace OpenSim.Region.Framework.Scenes.Serialization if (parts.Count == 0) { - m_log.ErrorFormat("[SERIALIZER]: Deserialization of xml failed: No SceneObjectPart nodes. xml was " + xmlData); + m_log.Error("[SERIALIZER]: Deserialization of xml failed: No SceneObjectPart nodes"); + Util.LogFailedXML("[SERIALIZER]:", xmlData); return null; } @@ -262,6 +269,12 @@ namespace OpenSim.Region.Framework.Scenes.Serialization sr.Close(); } + XmlNodeList keymotion = doc.GetElementsByTagName("KeyframeMotion"); + if (keymotion.Count > 0) + sceneObject.RootPart.KeyframeMotion = KeyframeMotion.FromData(sceneObject, Convert.FromBase64String(keymotion[0].InnerText)); + else + sceneObject.RootPart.KeyframeMotion = null; + // Script state may, or may not, exist. Not having any, is NOT // ever a problem. sceneObject.LoadScriptState(doc); @@ -270,7 +283,8 @@ namespace OpenSim.Region.Framework.Scenes.Serialization } catch (Exception e) { - m_log.ErrorFormat("[SERIALIZER]: Deserialization of xml failed with {0}. xml was {1}", e, xmlData); + m_log.Error("[SERIALIZER]: Deserialization of xml failed ", e); + Util.LogFailedXML("[SERIALIZER]:", xmlData); return null; } } @@ -293,17 +307,84 @@ namespace OpenSim.Region.Framework.Scenes.Serialization } } + + /// + /// Modifies a SceneObjectGroup. + /// + /// The object + /// Whether the object was actually modified + public delegate bool SceneObjectModifier(SceneObjectGroup sog); + + /// + /// Modifies an object by deserializing it; applying 'modifier' to each SceneObjectGroup; and reserializing. + /// + /// The object's UUID + /// Serialized data + /// The function to run on each SceneObjectGroup + /// The new serialized object's data, or null if an error occurred + public static byte[] ModifySerializedObject(UUID assetId, byte[] data, SceneObjectModifier modifier) + { + List sceneObjects = new List(); + CoalescedSceneObjects coa = null; + + string xmlData = ExternalRepresentationUtils.SanitizeXml(Utils.BytesToString(data)); + + if (CoalescedSceneObjectsSerializer.TryFromXml(xmlData, out coa)) + { + // m_log.DebugFormat("[SERIALIZER]: Loaded coalescence {0} has {1} objects", assetId, coa.Count); + + if (coa.Objects.Count == 0) + { + m_log.WarnFormat("[SERIALIZER]: Aborting load of coalesced object from asset {0} as it has zero loaded components", assetId); + return null; + } + + sceneObjects.AddRange(coa.Objects); + } + else + { + SceneObjectGroup deserializedObject = FromOriginalXmlFormat(xmlData); + + if (deserializedObject != null) + { + sceneObjects.Add(deserializedObject); + } + else + { + m_log.WarnFormat("[SERIALIZER]: Aborting load of object from asset {0} as deserialization failed", assetId); + return null; + } + } + + bool modified = false; + foreach (SceneObjectGroup sog in sceneObjects) + { + if (modifier(sog)) + modified = true; + } + + if (modified) + { + if (coa != null) + data = Utils.StringToBytes(CoalescedSceneObjectsSerializer.ToXml(coa)); + else + data = Utils.StringToBytes(ToOriginalXmlFormat(sceneObjects[0])); + } + + return data; + } + #region manual serialization - private static Dictionary> m_SOPXmlProcessors - = new Dictionary>(); + private static Dictionary> m_SOPXmlProcessors + = new Dictionary>(); - private static Dictionary> m_TaskInventoryXmlProcessors - = new Dictionary>(); + private static Dictionary> m_TaskInventoryXmlProcessors + = new Dictionary>(); - private static Dictionary> m_ShapeXmlProcessors - = new Dictionary>(); + private static Dictionary> m_ShapeXmlProcessors + = new Dictionary>(); static SceneObjectSerializer() { @@ -359,6 +440,8 @@ namespace OpenSim.Region.Framework.Scenes.Serialization m_SOPXmlProcessors.Add("CollisionSound", ProcessCollisionSound); m_SOPXmlProcessors.Add("CollisionSoundVolume", ProcessCollisionSoundVolume); m_SOPXmlProcessors.Add("MediaUrl", ProcessMediaUrl); + m_SOPXmlProcessors.Add("AttachedPos", ProcessAttachedPos); + m_SOPXmlProcessors.Add("DynAttrs", ProcessDynAttrs); m_SOPXmlProcessors.Add("TextureAnimation", ProcessTextureAnimation); m_SOPXmlProcessors.Add("ParticleSystem", ProcessParticleSystem); m_SOPXmlProcessors.Add("PayPrice0", ProcessPayPrice0); @@ -366,6 +449,13 @@ namespace OpenSim.Region.Framework.Scenes.Serialization m_SOPXmlProcessors.Add("PayPrice2", ProcessPayPrice2); m_SOPXmlProcessors.Add("PayPrice3", ProcessPayPrice3); m_SOPXmlProcessors.Add("PayPrice4", ProcessPayPrice4); + + m_SOPXmlProcessors.Add("PhysicsShapeType", ProcessPhysicsShapeType); + m_SOPXmlProcessors.Add("Density", ProcessDensity); + m_SOPXmlProcessors.Add("Friction", ProcessFriction); + m_SOPXmlProcessors.Add("Bounce", ProcessBounce); + m_SOPXmlProcessors.Add("GravityModifier", ProcessGravityModifier); + #endregion #region TaskInventoryXmlProcessors initialization @@ -419,12 +509,13 @@ namespace OpenSim.Region.Framework.Scenes.Serialization m_ShapeXmlProcessors.Add("ProfileEnd", ProcessShpProfileEnd); m_ShapeXmlProcessors.Add("ProfileHollow", ProcessShpProfileHollow); m_ShapeXmlProcessors.Add("Scale", ProcessShpScale); + m_ShapeXmlProcessors.Add("LastAttachPoint", ProcessShpLastAttach); m_ShapeXmlProcessors.Add("State", ProcessShpState); m_ShapeXmlProcessors.Add("ProfileShape", ProcessShpProfileShape); m_ShapeXmlProcessors.Add("HollowShape", ProcessShpHollowShape); m_ShapeXmlProcessors.Add("SculptTexture", ProcessShpSculptTexture); m_ShapeXmlProcessors.Add("SculptType", ProcessShpSculptType); - m_ShapeXmlProcessors.Add("SculptData", ProcessShpSculptData); + // Ignore "SculptData"; this element is deprecated m_ShapeXmlProcessors.Add("FlexiSoftness", ProcessShpFlexiSoftness); m_ShapeXmlProcessors.Add("FlexiTension", ProcessShpFlexiTension); m_ShapeXmlProcessors.Add("FlexiDrag", ProcessShpFlexiDrag); @@ -449,112 +540,112 @@ namespace OpenSim.Region.Framework.Scenes.Serialization } #region SOPXmlProcessors - private static void ProcessAllowedDrop(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessAllowedDrop(SceneObjectPart obj, XmlReader reader) { obj.AllowedDrop = Util.ReadBoolean(reader); } - private static void ProcessCreatorID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessCreatorID(SceneObjectPart obj, XmlReader reader) { obj.CreatorID = Util.ReadUUID(reader, "CreatorID"); } - private static void ProcessCreatorData(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessCreatorData(SceneObjectPart obj, XmlReader reader) { obj.CreatorData = reader.ReadElementContentAsString("CreatorData", String.Empty); } - private static void ProcessFolderID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessFolderID(SceneObjectPart obj, XmlReader reader) { obj.FolderID = Util.ReadUUID(reader, "FolderID"); } - private static void ProcessInventorySerial(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessInventorySerial(SceneObjectPart obj, XmlReader reader) { obj.InventorySerial = (uint)reader.ReadElementContentAsInt("InventorySerial", String.Empty); } - private static void ProcessTaskInventory(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessTaskInventory(SceneObjectPart obj, XmlReader reader) { obj.TaskInventory = ReadTaskInventory(reader, "TaskInventory"); } - private static void ProcessUUID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessUUID(SceneObjectPart obj, XmlReader reader) { obj.UUID = Util.ReadUUID(reader, "UUID"); } - private static void ProcessLocalId(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessLocalId(SceneObjectPart obj, XmlReader reader) { obj.LocalId = (uint)reader.ReadElementContentAsLong("LocalId", String.Empty); } - private static void ProcessName(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessName(SceneObjectPart obj, XmlReader reader) { obj.Name = reader.ReadElementString("Name"); } - private static void ProcessMaterial(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessMaterial(SceneObjectPart obj, XmlReader reader) { obj.Material = (byte)reader.ReadElementContentAsInt("Material", String.Empty); } - private static void ProcessPassTouches(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPassTouches(SceneObjectPart obj, XmlReader reader) { obj.PassTouches = Util.ReadBoolean(reader); } - private static void ProcessPassCollisions(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPassCollisions(SceneObjectPart obj, XmlReader reader) { obj.PassCollisions = Util.ReadBoolean(reader); } - private static void ProcessRegionHandle(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessRegionHandle(SceneObjectPart obj, XmlReader reader) { obj.RegionHandle = (ulong)reader.ReadElementContentAsLong("RegionHandle", String.Empty); } - private static void ProcessScriptAccessPin(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessScriptAccessPin(SceneObjectPart obj, XmlReader reader) { obj.ScriptAccessPin = reader.ReadElementContentAsInt("ScriptAccessPin", String.Empty); } - private static void ProcessGroupPosition(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessGroupPosition(SceneObjectPart obj, XmlReader reader) { obj.GroupPosition = Util.ReadVector(reader, "GroupPosition"); } - private static void ProcessOffsetPosition(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessOffsetPosition(SceneObjectPart obj, XmlReader reader) { obj.OffsetPosition = Util.ReadVector(reader, "OffsetPosition"); ; } - private static void ProcessRotationOffset(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessRotationOffset(SceneObjectPart obj, XmlReader reader) { obj.RotationOffset = Util.ReadQuaternion(reader, "RotationOffset"); } - private static void ProcessVelocity(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessVelocity(SceneObjectPart obj, XmlReader reader) { obj.Velocity = Util.ReadVector(reader, "Velocity"); } - private static void ProcessAngularVelocity(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessAngularVelocity(SceneObjectPart obj, XmlReader reader) { obj.AngularVelocity = Util.ReadVector(reader, "AngularVelocity"); } - private static void ProcessAcceleration(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessAcceleration(SceneObjectPart obj, XmlReader reader) { obj.Acceleration = Util.ReadVector(reader, "Acceleration"); } - private static void ProcessDescription(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessDescription(SceneObjectPart obj, XmlReader reader) { obj.Description = reader.ReadElementString("Description"); } - private static void ProcessColor(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessColor(SceneObjectPart obj, XmlReader reader) { reader.ReadStartElement("Color"); if (reader.Name == "R") @@ -568,35 +659,60 @@ namespace OpenSim.Region.Framework.Scenes.Serialization } } - private static void ProcessText(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessText(SceneObjectPart obj, XmlReader reader) { obj.Text = reader.ReadElementString("Text", String.Empty); } - private static void ProcessSitName(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessSitName(SceneObjectPart obj, XmlReader reader) { obj.SitName = reader.ReadElementString("SitName", String.Empty); } - private static void ProcessTouchName(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessTouchName(SceneObjectPart obj, XmlReader reader) { obj.TouchName = reader.ReadElementString("TouchName", String.Empty); } - private static void ProcessLinkNum(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessLinkNum(SceneObjectPart obj, XmlReader reader) { obj.LinkNum = reader.ReadElementContentAsInt("LinkNum", String.Empty); } - private static void ProcessClickAction(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessClickAction(SceneObjectPart obj, XmlReader reader) { obj.ClickAction = (byte)reader.ReadElementContentAsInt("ClickAction", String.Empty); } - private static void ProcessShape(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPhysicsShapeType(SceneObjectPart obj, XmlReader reader) + { + obj.PhysicsShapeType = (byte)reader.ReadElementContentAsInt("PhysicsShapeType", String.Empty); + } + + private static void ProcessDensity(SceneObjectPart obj, XmlReader reader) + { + obj.Density = reader.ReadElementContentAsFloat("Density", String.Empty); + } + + private static void ProcessFriction(SceneObjectPart obj, XmlReader reader) + { + obj.Friction = reader.ReadElementContentAsFloat("Friction", String.Empty); + } + + private static void ProcessBounce(SceneObjectPart obj, XmlReader reader) + { + obj.Restitution = reader.ReadElementContentAsFloat("Bounce", String.Empty); + } + + private static void ProcessGravityModifier(SceneObjectPart obj, XmlReader reader) + { + obj.GravityModifier = reader.ReadElementContentAsFloat("GravityModifier", String.Empty); + } + + private static void ProcessShape(SceneObjectPart obj, XmlReader reader) { List errorNodeNames; - obj.Shape = ReadShape(reader, "Shape", out errorNodeNames); + obj.Shape = ReadShape(reader, "Shape", out errorNodeNames, obj); if (errorNodeNames != null) { @@ -606,153 +722,163 @@ namespace OpenSim.Region.Framework.Scenes.Serialization } } - private static void ProcessScale(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessScale(SceneObjectPart obj, XmlReader reader) { obj.Scale = Util.ReadVector(reader, "Scale"); } - private static void ProcessSitTargetOrientation(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessSitTargetOrientation(SceneObjectPart obj, XmlReader reader) { obj.SitTargetOrientation = Util.ReadQuaternion(reader, "SitTargetOrientation"); } - private static void ProcessSitTargetPosition(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessSitTargetPosition(SceneObjectPart obj, XmlReader reader) { obj.SitTargetPosition = Util.ReadVector(reader, "SitTargetPosition"); } - private static void ProcessSitTargetPositionLL(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessSitTargetPositionLL(SceneObjectPart obj, XmlReader reader) { obj.SitTargetPositionLL = Util.ReadVector(reader, "SitTargetPositionLL"); } - private static void ProcessSitTargetOrientationLL(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessSitTargetOrientationLL(SceneObjectPart obj, XmlReader reader) { obj.SitTargetOrientationLL = Util.ReadQuaternion(reader, "SitTargetOrientationLL"); } - private static void ProcessParentID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessParentID(SceneObjectPart obj, XmlReader reader) { string str = reader.ReadElementContentAsString("ParentID", String.Empty); obj.ParentID = Convert.ToUInt32(str); } - private static void ProcessCreationDate(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessCreationDate(SceneObjectPart obj, XmlReader reader) { obj.CreationDate = reader.ReadElementContentAsInt("CreationDate", String.Empty); } - private static void ProcessCategory(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessCategory(SceneObjectPart obj, XmlReader reader) { obj.Category = (uint)reader.ReadElementContentAsInt("Category", String.Empty); } - private static void ProcessSalePrice(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessSalePrice(SceneObjectPart obj, XmlReader reader) { obj.SalePrice = reader.ReadElementContentAsInt("SalePrice", String.Empty); } - private static void ProcessObjectSaleType(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessObjectSaleType(SceneObjectPart obj, XmlReader reader) { obj.ObjectSaleType = (byte)reader.ReadElementContentAsInt("ObjectSaleType", String.Empty); } - private static void ProcessOwnershipCost(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessOwnershipCost(SceneObjectPart obj, XmlReader reader) { obj.OwnershipCost = reader.ReadElementContentAsInt("OwnershipCost", String.Empty); } - private static void ProcessGroupID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessGroupID(SceneObjectPart obj, XmlReader reader) { obj.GroupID = Util.ReadUUID(reader, "GroupID"); } - private static void ProcessOwnerID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessOwnerID(SceneObjectPart obj, XmlReader reader) { obj.OwnerID = Util.ReadUUID(reader, "OwnerID"); } - private static void ProcessLastOwnerID(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessLastOwnerID(SceneObjectPart obj, XmlReader reader) { obj.LastOwnerID = Util.ReadUUID(reader, "LastOwnerID"); } - private static void ProcessBaseMask(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessBaseMask(SceneObjectPart obj, XmlReader reader) { obj.BaseMask = (uint)reader.ReadElementContentAsInt("BaseMask", String.Empty); } - private static void ProcessOwnerMask(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessOwnerMask(SceneObjectPart obj, XmlReader reader) { obj.OwnerMask = (uint)reader.ReadElementContentAsInt("OwnerMask", String.Empty); } - private static void ProcessGroupMask(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessGroupMask(SceneObjectPart obj, XmlReader reader) { obj.GroupMask = (uint)reader.ReadElementContentAsInt("GroupMask", String.Empty); } - private static void ProcessEveryoneMask(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessEveryoneMask(SceneObjectPart obj, XmlReader reader) { obj.EveryoneMask = (uint)reader.ReadElementContentAsInt("EveryoneMask", String.Empty); } - private static void ProcessNextOwnerMask(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessNextOwnerMask(SceneObjectPart obj, XmlReader reader) { obj.NextOwnerMask = (uint)reader.ReadElementContentAsInt("NextOwnerMask", String.Empty); } - private static void ProcessFlags(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessFlags(SceneObjectPart obj, XmlReader reader) { obj.Flags = Util.ReadEnum(reader, "Flags"); } - private static void ProcessCollisionSound(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessCollisionSound(SceneObjectPart obj, XmlReader reader) { obj.CollisionSound = Util.ReadUUID(reader, "CollisionSound"); } - private static void ProcessCollisionSoundVolume(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessCollisionSoundVolume(SceneObjectPart obj, XmlReader reader) { obj.CollisionSoundVolume = reader.ReadElementContentAsFloat("CollisionSoundVolume", String.Empty); } - private static void ProcessMediaUrl(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessMediaUrl(SceneObjectPart obj, XmlReader reader) { obj.MediaUrl = reader.ReadElementContentAsString("MediaUrl", String.Empty); } - private static void ProcessTextureAnimation(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessAttachedPos(SceneObjectPart obj, XmlReader reader) + { + obj.AttachedPos = Util.ReadVector(reader, "AttachedPos"); + } + + private static void ProcessDynAttrs(SceneObjectPart obj, XmlReader reader) + { + obj.DynAttrs.ReadXml(reader); + } + + private static void ProcessTextureAnimation(SceneObjectPart obj, XmlReader reader) { obj.TextureAnimation = Convert.FromBase64String(reader.ReadElementContentAsString("TextureAnimation", String.Empty)); } - private static void ProcessParticleSystem(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessParticleSystem(SceneObjectPart obj, XmlReader reader) { obj.ParticleSystem = Convert.FromBase64String(reader.ReadElementContentAsString("ParticleSystem", String.Empty)); } - private static void ProcessPayPrice0(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPayPrice0(SceneObjectPart obj, XmlReader reader) { obj.PayPrice[0] = (int)reader.ReadElementContentAsInt("PayPrice0", String.Empty); } - private static void ProcessPayPrice1(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPayPrice1(SceneObjectPart obj, XmlReader reader) { obj.PayPrice[1] = (int)reader.ReadElementContentAsInt("PayPrice1", String.Empty); } - private static void ProcessPayPrice2(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPayPrice2(SceneObjectPart obj, XmlReader reader) { obj.PayPrice[2] = (int)reader.ReadElementContentAsInt("PayPrice2", String.Empty); } - private static void ProcessPayPrice3(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPayPrice3(SceneObjectPart obj, XmlReader reader) { obj.PayPrice[3] = (int)reader.ReadElementContentAsInt("PayPrice3", String.Empty); } - private static void ProcessPayPrice4(SceneObjectPart obj, XmlTextReader reader) + private static void ProcessPayPrice4(SceneObjectPart obj, XmlReader reader) { obj.PayPrice[4] = (int)reader.ReadElementContentAsInt("PayPrice4", String.Empty); } @@ -760,122 +886,122 @@ namespace OpenSim.Region.Framework.Scenes.Serialization #endregion #region TaskInventoryXmlProcessors - private static void ProcessTIAssetID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIAssetID(TaskInventoryItem item, XmlReader reader) { item.AssetID = Util.ReadUUID(reader, "AssetID"); } - private static void ProcessTIBasePermissions(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIBasePermissions(TaskInventoryItem item, XmlReader reader) { item.BasePermissions = (uint)reader.ReadElementContentAsInt("BasePermissions", String.Empty); } - private static void ProcessTICreationDate(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTICreationDate(TaskInventoryItem item, XmlReader reader) { item.CreationDate = (uint)reader.ReadElementContentAsInt("CreationDate", String.Empty); } - private static void ProcessTICreatorID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTICreatorID(TaskInventoryItem item, XmlReader reader) { item.CreatorID = Util.ReadUUID(reader, "CreatorID"); } - private static void ProcessTICreatorData(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTICreatorData(TaskInventoryItem item, XmlReader reader) { item.CreatorData = reader.ReadElementContentAsString("CreatorData", String.Empty); } - private static void ProcessTIDescription(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIDescription(TaskInventoryItem item, XmlReader reader) { item.Description = reader.ReadElementContentAsString("Description", String.Empty); } - private static void ProcessTIEveryonePermissions(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIEveryonePermissions(TaskInventoryItem item, XmlReader reader) { item.EveryonePermissions = (uint)reader.ReadElementContentAsInt("EveryonePermissions", String.Empty); } - private static void ProcessTIFlags(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIFlags(TaskInventoryItem item, XmlReader reader) { item.Flags = (uint)reader.ReadElementContentAsInt("Flags", String.Empty); } - private static void ProcessTIGroupID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIGroupID(TaskInventoryItem item, XmlReader reader) { item.GroupID = Util.ReadUUID(reader, "GroupID"); } - private static void ProcessTIGroupPermissions(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIGroupPermissions(TaskInventoryItem item, XmlReader reader) { item.GroupPermissions = (uint)reader.ReadElementContentAsInt("GroupPermissions", String.Empty); } - private static void ProcessTIInvType(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIInvType(TaskInventoryItem item, XmlReader reader) { item.InvType = reader.ReadElementContentAsInt("InvType", String.Empty); } - private static void ProcessTIItemID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIItemID(TaskInventoryItem item, XmlReader reader) { item.ItemID = Util.ReadUUID(reader, "ItemID"); } - private static void ProcessTIOldItemID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIOldItemID(TaskInventoryItem item, XmlReader reader) { item.OldItemID = Util.ReadUUID(reader, "OldItemID"); } - private static void ProcessTILastOwnerID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTILastOwnerID(TaskInventoryItem item, XmlReader reader) { item.LastOwnerID = Util.ReadUUID(reader, "LastOwnerID"); } - private static void ProcessTIName(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIName(TaskInventoryItem item, XmlReader reader) { item.Name = reader.ReadElementContentAsString("Name", String.Empty); } - private static void ProcessTINextPermissions(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTINextPermissions(TaskInventoryItem item, XmlReader reader) { item.NextPermissions = (uint)reader.ReadElementContentAsInt("NextPermissions", String.Empty); } - private static void ProcessTIOwnerID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIOwnerID(TaskInventoryItem item, XmlReader reader) { item.OwnerID = Util.ReadUUID(reader, "OwnerID"); } - private static void ProcessTICurrentPermissions(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTICurrentPermissions(TaskInventoryItem item, XmlReader reader) { item.CurrentPermissions = (uint)reader.ReadElementContentAsInt("CurrentPermissions", String.Empty); } - private static void ProcessTIParentID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIParentID(TaskInventoryItem item, XmlReader reader) { item.ParentID = Util.ReadUUID(reader, "ParentID"); } - private static void ProcessTIParentPartID(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIParentPartID(TaskInventoryItem item, XmlReader reader) { item.ParentPartID = Util.ReadUUID(reader, "ParentPartID"); } - private static void ProcessTIPermsGranter(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIPermsGranter(TaskInventoryItem item, XmlReader reader) { item.PermsGranter = Util.ReadUUID(reader, "PermsGranter"); } - private static void ProcessTIPermsMask(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIPermsMask(TaskInventoryItem item, XmlReader reader) { item.PermsMask = reader.ReadElementContentAsInt("PermsMask", String.Empty); } - private static void ProcessTIType(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIType(TaskInventoryItem item, XmlReader reader) { item.Type = reader.ReadElementContentAsInt("Type", String.Empty); } - private static void ProcessTIOwnerChanged(TaskInventoryItem item, XmlTextReader reader) + private static void ProcessTIOwnerChanged(TaskInventoryItem item, XmlReader reader) { item.OwnerChanged = Util.ReadBoolean(reader); } @@ -883,245 +1009,243 @@ namespace OpenSim.Region.Framework.Scenes.Serialization #endregion #region ShapeXmlProcessors - private static void ProcessShpProfileCurve(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpProfileCurve(PrimitiveBaseShape shp, XmlReader reader) { shp.ProfileCurve = (byte)reader.ReadElementContentAsInt("ProfileCurve", String.Empty); } - private static void ProcessShpTextureEntry(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpTextureEntry(PrimitiveBaseShape shp, XmlReader reader) { byte[] teData = Convert.FromBase64String(reader.ReadElementString("TextureEntry")); shp.Textures = new Primitive.TextureEntry(teData, 0, teData.Length); } - private static void ProcessShpExtraParams(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpExtraParams(PrimitiveBaseShape shp, XmlReader reader) { shp.ExtraParams = Convert.FromBase64String(reader.ReadElementString("ExtraParams")); } - private static void ProcessShpPathBegin(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathBegin(PrimitiveBaseShape shp, XmlReader reader) { shp.PathBegin = (ushort)reader.ReadElementContentAsInt("PathBegin", String.Empty); } - private static void ProcessShpPathCurve(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathCurve(PrimitiveBaseShape shp, XmlReader reader) { shp.PathCurve = (byte)reader.ReadElementContentAsInt("PathCurve", String.Empty); } - private static void ProcessShpPathEnd(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathEnd(PrimitiveBaseShape shp, XmlReader reader) { shp.PathEnd = (ushort)reader.ReadElementContentAsInt("PathEnd", String.Empty); } - private static void ProcessShpPathRadiusOffset(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathRadiusOffset(PrimitiveBaseShape shp, XmlReader reader) { shp.PathRadiusOffset = (sbyte)reader.ReadElementContentAsInt("PathRadiusOffset", String.Empty); } - private static void ProcessShpPathRevolutions(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathRevolutions(PrimitiveBaseShape shp, XmlReader reader) { shp.PathRevolutions = (byte)reader.ReadElementContentAsInt("PathRevolutions", String.Empty); } - private static void ProcessShpPathScaleX(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathScaleX(PrimitiveBaseShape shp, XmlReader reader) { shp.PathScaleX = (byte)reader.ReadElementContentAsInt("PathScaleX", String.Empty); } - private static void ProcessShpPathScaleY(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathScaleY(PrimitiveBaseShape shp, XmlReader reader) { shp.PathScaleY = (byte)reader.ReadElementContentAsInt("PathScaleY", String.Empty); } - private static void ProcessShpPathShearX(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathShearX(PrimitiveBaseShape shp, XmlReader reader) { shp.PathShearX = (byte)reader.ReadElementContentAsInt("PathShearX", String.Empty); } - private static void ProcessShpPathShearY(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathShearY(PrimitiveBaseShape shp, XmlReader reader) { shp.PathShearY = (byte)reader.ReadElementContentAsInt("PathShearY", String.Empty); } - private static void ProcessShpPathSkew(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathSkew(PrimitiveBaseShape shp, XmlReader reader) { shp.PathSkew = (sbyte)reader.ReadElementContentAsInt("PathSkew", String.Empty); } - private static void ProcessShpPathTaperX(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathTaperX(PrimitiveBaseShape shp, XmlReader reader) { shp.PathTaperX = (sbyte)reader.ReadElementContentAsInt("PathTaperX", String.Empty); } - private static void ProcessShpPathTaperY(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathTaperY(PrimitiveBaseShape shp, XmlReader reader) { shp.PathTaperY = (sbyte)reader.ReadElementContentAsInt("PathTaperY", String.Empty); } - private static void ProcessShpPathTwist(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathTwist(PrimitiveBaseShape shp, XmlReader reader) { shp.PathTwist = (sbyte)reader.ReadElementContentAsInt("PathTwist", String.Empty); } - private static void ProcessShpPathTwistBegin(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPathTwistBegin(PrimitiveBaseShape shp, XmlReader reader) { shp.PathTwistBegin = (sbyte)reader.ReadElementContentAsInt("PathTwistBegin", String.Empty); } - private static void ProcessShpPCode(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpPCode(PrimitiveBaseShape shp, XmlReader reader) { shp.PCode = (byte)reader.ReadElementContentAsInt("PCode", String.Empty); } - private static void ProcessShpProfileBegin(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpProfileBegin(PrimitiveBaseShape shp, XmlReader reader) { shp.ProfileBegin = (ushort)reader.ReadElementContentAsInt("ProfileBegin", String.Empty); } - private static void ProcessShpProfileEnd(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpProfileEnd(PrimitiveBaseShape shp, XmlReader reader) { shp.ProfileEnd = (ushort)reader.ReadElementContentAsInt("ProfileEnd", String.Empty); } - private static void ProcessShpProfileHollow(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpProfileHollow(PrimitiveBaseShape shp, XmlReader reader) { shp.ProfileHollow = (ushort)reader.ReadElementContentAsInt("ProfileHollow", String.Empty); } - private static void ProcessShpScale(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpScale(PrimitiveBaseShape shp, XmlReader reader) { shp.Scale = Util.ReadVector(reader, "Scale"); } - private static void ProcessShpState(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpState(PrimitiveBaseShape shp, XmlReader reader) { shp.State = (byte)reader.ReadElementContentAsInt("State", String.Empty); } - private static void ProcessShpProfileShape(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLastAttach(PrimitiveBaseShape shp, XmlReader reader) + { + shp.LastAttachPoint = (byte)reader.ReadElementContentAsInt("LastAttachPoint", String.Empty); + } + + private static void ProcessShpProfileShape(PrimitiveBaseShape shp, XmlReader reader) { shp.ProfileShape = Util.ReadEnum(reader, "ProfileShape"); } - private static void ProcessShpHollowShape(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpHollowShape(PrimitiveBaseShape shp, XmlReader reader) { shp.HollowShape = Util.ReadEnum(reader, "HollowShape"); } - private static void ProcessShpSculptTexture(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpSculptTexture(PrimitiveBaseShape shp, XmlReader reader) { shp.SculptTexture = Util.ReadUUID(reader, "SculptTexture"); } - private static void ProcessShpSculptType(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpSculptType(PrimitiveBaseShape shp, XmlReader reader) { shp.SculptType = (byte)reader.ReadElementContentAsInt("SculptType", String.Empty); } - private static void ProcessShpSculptData(PrimitiveBaseShape shp, XmlTextReader reader) - { -// m_log.DebugFormat("[SCENE OBJECT SERIALIZER]: Setting sculpt data length {0}", shp.SculptData.Length); - - shp.SculptData = Convert.FromBase64String(reader.ReadElementString("SculptData")); - } - - private static void ProcessShpFlexiSoftness(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiSoftness(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiSoftness = reader.ReadElementContentAsInt("FlexiSoftness", String.Empty); } - private static void ProcessShpFlexiTension(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiTension(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiTension = reader.ReadElementContentAsFloat("FlexiTension", String.Empty); } - private static void ProcessShpFlexiDrag(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiDrag(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiDrag = reader.ReadElementContentAsFloat("FlexiDrag", String.Empty); } - private static void ProcessShpFlexiGravity(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiGravity(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiGravity = reader.ReadElementContentAsFloat("FlexiGravity", String.Empty); } - private static void ProcessShpFlexiWind(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiWind(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiWind = reader.ReadElementContentAsFloat("FlexiWind", String.Empty); } - private static void ProcessShpFlexiForceX(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiForceX(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiForceX = reader.ReadElementContentAsFloat("FlexiForceX", String.Empty); } - private static void ProcessShpFlexiForceY(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiForceY(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiForceY = reader.ReadElementContentAsFloat("FlexiForceY", String.Empty); } - private static void ProcessShpFlexiForceZ(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiForceZ(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiForceZ = reader.ReadElementContentAsFloat("FlexiForceZ", String.Empty); } - private static void ProcessShpLightColorR(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightColorR(PrimitiveBaseShape shp, XmlReader reader) { shp.LightColorR = reader.ReadElementContentAsFloat("LightColorR", String.Empty); } - private static void ProcessShpLightColorG(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightColorG(PrimitiveBaseShape shp, XmlReader reader) { shp.LightColorG = reader.ReadElementContentAsFloat("LightColorG", String.Empty); } - private static void ProcessShpLightColorB(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightColorB(PrimitiveBaseShape shp, XmlReader reader) { shp.LightColorB = reader.ReadElementContentAsFloat("LightColorB", String.Empty); } - private static void ProcessShpLightColorA(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightColorA(PrimitiveBaseShape shp, XmlReader reader) { shp.LightColorA = reader.ReadElementContentAsFloat("LightColorA", String.Empty); } - private static void ProcessShpLightRadius(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightRadius(PrimitiveBaseShape shp, XmlReader reader) { shp.LightRadius = reader.ReadElementContentAsFloat("LightRadius", String.Empty); } - private static void ProcessShpLightCutoff(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightCutoff(PrimitiveBaseShape shp, XmlReader reader) { shp.LightCutoff = reader.ReadElementContentAsFloat("LightCutoff", String.Empty); } - private static void ProcessShpLightFalloff(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightFalloff(PrimitiveBaseShape shp, XmlReader reader) { shp.LightFalloff = reader.ReadElementContentAsFloat("LightFalloff", String.Empty); } - private static void ProcessShpLightIntensity(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightIntensity(PrimitiveBaseShape shp, XmlReader reader) { shp.LightIntensity = reader.ReadElementContentAsFloat("LightIntensity", String.Empty); } - private static void ProcessShpFlexiEntry(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpFlexiEntry(PrimitiveBaseShape shp, XmlReader reader) { shp.FlexiEntry = Util.ReadBoolean(reader); } - private static void ProcessShpLightEntry(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpLightEntry(PrimitiveBaseShape shp, XmlReader reader) { shp.LightEntry = Util.ReadBoolean(reader); } - private static void ProcessShpSculptEntry(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpSculptEntry(PrimitiveBaseShape shp, XmlReader reader) { shp.SculptEntry = Util.ReadBoolean(reader); } - private static void ProcessShpMedia(PrimitiveBaseShape shp, XmlTextReader reader) + private static void ProcessShpMedia(PrimitiveBaseShape shp, XmlReader reader) { string value = reader.ReadElementContentAsString("Media", String.Empty); shp.Media = PrimitiveBaseShape.MediaList.FromXml(value); @@ -1144,6 +1268,16 @@ namespace OpenSim.Region.Framework.Scenes.Serialization }); writer.WriteEndElement(); + + if (sog.RootPart.KeyframeMotion != null) + { + Byte[] data = sog.RootPart.KeyframeMotion.Serialize(); + + writer.WriteStartElement(String.Empty, "KeyframeMotion", String.Empty); + writer.WriteBase64(data, 0, data.Length); + writer.WriteEndElement(); + } + writer.WriteEndElement(); } @@ -1157,14 +1291,14 @@ namespace OpenSim.Region.Framework.Scenes.Serialization WriteUUID(writer, "CreatorID", sop.CreatorID, options); - if (sop.CreatorData != null && sop.CreatorData != string.Empty) + if (!string.IsNullOrEmpty(sop.CreatorData)) writer.WriteElementString("CreatorData", sop.CreatorData); else if (options.ContainsKey("home")) { if (m_UserManagement == null) m_UserManagement = sop.ParentGroup.Scene.RequestModuleInterface(); string name = m_UserManagement.GetUserName(sop.CreatorID); - writer.WriteElementString("CreatorData", (string)options["home"] + ";" + name); + writer.WriteElementString("CreatorData", ExternalRepresentationUtils.CalcCreatorData((string)options["home"], name)); } WriteUUID(writer, "FolderID", sop.FolderID, options); @@ -1217,7 +1351,9 @@ namespace OpenSim.Region.Framework.Scenes.Serialization writer.WriteElementString("SalePrice", sop.SalePrice.ToString()); writer.WriteElementString("ObjectSaleType", sop.ObjectSaleType.ToString()); writer.WriteElementString("OwnershipCost", sop.OwnershipCost.ToString()); - WriteUUID(writer, "GroupID", sop.GroupID, options); + + UUID groupID = options.ContainsKey("wipe-owners") ? UUID.Zero : sop.GroupID; + WriteUUID(writer, "GroupID", groupID, options); UUID ownerID = options.ContainsKey("wipe-owners") ? UUID.Zero : sop.OwnerID; WriteUUID(writer, "OwnerID", ownerID, options); @@ -1235,6 +1371,15 @@ namespace OpenSim.Region.Framework.Scenes.Serialization writer.WriteElementString("CollisionSoundVolume", sop.CollisionSoundVolume.ToString()); if (sop.MediaUrl != null) writer.WriteElementString("MediaUrl", sop.MediaUrl.ToString()); + WriteVector(writer, "AttachedPos", sop.AttachedPos); + + if (sop.DynAttrs.CountNamespaces > 0) + { + writer.WriteStartElement("DynAttrs"); + sop.DynAttrs.WriteXml(writer); + writer.WriteEndElement(); + } + WriteBytes(writer, "TextureAnimation", sop.TextureAnimation); WriteBytes(writer, "ParticleSystem", sop.ParticleSystem); writer.WriteElementString("PayPrice0", sop.PayPrice[0].ToString()); @@ -1243,6 +1388,17 @@ namespace OpenSim.Region.Framework.Scenes.Serialization writer.WriteElementString("PayPrice3", sop.PayPrice[3].ToString()); writer.WriteElementString("PayPrice4", sop.PayPrice[4].ToString()); + if(sop.PhysicsShapeType != sop.DefaultPhysicsShapeType()) + writer.WriteElementString("PhysicsShapeType", sop.PhysicsShapeType.ToString().ToLower()); + if (sop.Density != 1000.0f) + writer.WriteElementString("Density", sop.Density.ToString().ToLower()); + if (sop.Friction != 0.6f) + writer.WriteElementString("Friction", sop.Friction.ToString().ToLower()); + if (sop.Restitution != 0.5f) + writer.WriteElementString("Bounce", sop.Restitution.ToString().ToLower()); + if (sop.GravityModifier != 1.0f) + writer.WriteElementString("GravityModifier", sop.GravityModifier.ToString().ToLower()); + writer.WriteEndElement(); } @@ -1310,20 +1466,23 @@ namespace OpenSim.Region.Framework.Scenes.Serialization WriteUUID(writer, "CreatorID", item.CreatorID, options); - if (item.CreatorData != null && item.CreatorData != string.Empty) + if (!string.IsNullOrEmpty(item.CreatorData)) writer.WriteElementString("CreatorData", item.CreatorData); else if (options.ContainsKey("home")) { if (m_UserManagement == null) m_UserManagement = scene.RequestModuleInterface(); string name = m_UserManagement.GetUserName(item.CreatorID); - writer.WriteElementString("CreatorData", (string)options["home"] + ";" + name); + writer.WriteElementString("CreatorData", ExternalRepresentationUtils.CalcCreatorData((string)options["home"], name)); } writer.WriteElementString("Description", item.Description); writer.WriteElementString("EveryonePermissions", item.EveryonePermissions.ToString()); writer.WriteElementString("Flags", item.Flags.ToString()); - WriteUUID(writer, "GroupID", item.GroupID, options); + + UUID groupID = options.ContainsKey("wipe-owners") ? UUID.Zero : item.GroupID; + WriteUUID(writer, "GroupID", groupID, options); + writer.WriteElementString("GroupPermissions", item.GroupPermissions.ToString()); writer.WriteElementString("InvType", item.InvType.ToString()); WriteUUID(writer, "ItemID", item.ItemID, options); @@ -1344,7 +1503,9 @@ namespace OpenSim.Region.Framework.Scenes.Serialization WriteUUID(writer, "PermsGranter", item.PermsGranter, options); writer.WriteElementString("PermsMask", item.PermsMask.ToString()); writer.WriteElementString("Type", item.Type.ToString()); - writer.WriteElementString("OwnerChanged", item.OwnerChanged.ToString().ToLower()); + + bool ownerChanged = options.ContainsKey("wipe-owners") ? false : item.OwnerChanged; + writer.WriteElementString("OwnerChanged", ownerChanged.ToString().ToLower()); writer.WriteEndElement(); // TaskInventoryItem } @@ -1398,20 +1559,14 @@ namespace OpenSim.Region.Framework.Scenes.Serialization writer.WriteElementString("ProfileEnd", shp.ProfileEnd.ToString()); writer.WriteElementString("ProfileHollow", shp.ProfileHollow.ToString()); writer.WriteElementString("State", shp.State.ToString()); + writer.WriteElementString("LastAttachPoint", shp.LastAttachPoint.ToString()); WriteFlags(writer, "ProfileShape", shp.ProfileShape.ToString(), options); WriteFlags(writer, "HollowShape", shp.HollowShape.ToString(), options); WriteUUID(writer, "SculptTexture", shp.SculptTexture, options); writer.WriteElementString("SculptType", shp.SculptType.ToString()); - writer.WriteStartElement("SculptData"); - byte[] sd; - if (shp.SculptData != null) - sd = shp.SculptData; - else - sd = Utils.EmptyBytes; - writer.WriteBase64(sd, 0, sd.Length); - writer.WriteEndElement(); // SculptData + // Don't serialize SculptData. It's just a copy of the asset, which can be loaded separately using 'SculptTexture'. writer.WriteElementString("FlexiSoftness", shp.FlexiSoftness.ToString()); writer.WriteElementString("FlexiTension", shp.FlexiTension.ToString()); @@ -1442,28 +1597,31 @@ namespace OpenSim.Region.Framework.Scenes.Serialization } } - public static SceneObjectPart Xml2ToSOP(XmlTextReader reader) + public static SceneObjectPart Xml2ToSOP(XmlReader reader) { SceneObjectPart obj = new SceneObjectPart(); reader.ReadStartElement("SceneObjectPart"); - ExternalRepresentationUtils.ExecuteReadProcessors( + bool errors = ExternalRepresentationUtils.ExecuteReadProcessors( obj, m_SOPXmlProcessors, reader, - (o, nodeName, e) - => m_log.DebugFormat( - "[SceneObjectSerializer]: Exception while parsing {0} in object {1} {2}: {3}{4}", - ((SceneObjectPart)o).Name, ((SceneObjectPart)o).UUID, nodeName, e.Message, e.StackTrace)); + (o, nodeName, e) => { + m_log.Debug(string.Format("[SceneObjectSerializer]: Error while parsing element {0} in object {1} {2} ", + nodeName, ((SceneObjectPart)o).Name, ((SceneObjectPart)o).UUID), e); + }); + + if (errors) + throw new XmlException(string.Format("Error parsing object {0} {1}", obj.Name, obj.UUID)); reader.ReadEndElement(); // SceneObjectPart - //m_log.DebugFormat("[XXX]: parsed SOP {0} - {1}", obj.Name, obj.UUID); + // m_log.DebugFormat("[SceneObjectSerializer]: parsed SOP {0} {1}", obj.Name, obj.UUID); return obj; } - public static TaskInventoryDictionary ReadTaskInventory(XmlTextReader reader, string name) + public static TaskInventoryDictionary ReadTaskInventory(XmlReader reader, string name) { TaskInventoryDictionary tinv = new TaskInventoryDictionary(); @@ -1504,7 +1662,7 @@ namespace OpenSim.Region.Framework.Scenes.Serialization /// The name of the xml element containing the shape /// a list containing the failing node names. If no failures then null. /// The shape parsed - public static PrimitiveBaseShape ReadShape(XmlTextReader reader, string name, out List errorNodeNames) + public static PrimitiveBaseShape ReadShape(XmlReader reader, string name, out List errorNodeNames, SceneObjectPart obj) { List internalErrorNodeNames = null; @@ -1523,18 +1681,14 @@ namespace OpenSim.Region.Framework.Scenes.Serialization shape, m_ShapeXmlProcessors, reader, - (o, nodeName, e) - => - { -// m_log.DebugFormat( -// "[SceneObjectSerializer]: Exception while parsing Shape property {0}: {1}{2}", -// nodeName, e.Message, e.StackTrace); - if (internalErrorNodeNames == null) - internalErrorNodeNames = new List(); + (o, nodeName, e) => { + m_log.Debug(string.Format("[SceneObjectSerializer]: Error while parsing element {0} in Shape property of object {1} {2} ", + nodeName, obj.Name, obj.UUID), e); - internalErrorNodeNames.Add(nodeName); - } - ); + if (internalErrorNodeNames == null) + internalErrorNodeNames = new List(); + internalErrorNodeNames.Add(nodeName); + }); reader.ReadEndElement(); // Shape diff --git a/OpenSim/Region/Framework/Scenes/Serialization/SceneXmlLoader.cs b/OpenSim/Region/Framework/Scenes/Serialization/SceneXmlLoader.cs index a3485d2..3c03130 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/SceneXmlLoader.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/SceneXmlLoader.cs @@ -34,7 +34,7 @@ using OpenMetaverse; using log4net; using OpenSim.Framework; using OpenSim.Region.Framework.Scenes; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; namespace OpenSim.Region.Framework.Scenes.Serialization { diff --git a/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs b/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs old mode 100644 new mode 100755 index b9d615e..3effee7 --- a/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs +++ b/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs @@ -61,6 +61,10 @@ namespace OpenSim.Region.Framework.Scenes private YourStatsAreWrong handlerStatsIncorrect; + // Determines the size of the array that is used to collect StatBlocks + // for sending to the SimStats and SimExtraStatsCollector + private const int m_statisticArraySize = 28; + /// /// These are the IDs of stats sent in the StatsPacket to the viewer. /// @@ -104,7 +108,12 @@ namespace OpenSim.Region.Framework.Scenes ScriptEps = 31, SimSpareMs = 32, SimSleepMs = 33, - SimIoPumpTime = 34 + SimIoPumpTime = 34, + FrameDilation = 35, + UsersLoggingIn = 36, + TotalGeoPrim = 37, + TotalMesh = 38, + ThreadCount = 39 } /// @@ -167,15 +176,20 @@ namespace OpenSim.Region.Framework.Scenes /// Parameter to adjust reported scene fps /// /// - /// Our scene loop runs slower than other server implementations, apparantly because we work somewhat differently. - /// However, we will still report an FPS that's closer to what people are used to seeing. A lower FPS might - /// affect clients and monitoring scripts/software. + /// The close we have to a frame rate as expected by viewers, users and scripts + /// is heartbeat rate. + /// heartbeat rate default value is very diferent from the expected one + /// and can be changed from region to region acording to its specific simulation needs + /// since this creates incompatibility with expected values, + /// this scale factor can be used to normalize values to a Virtual FPS. + /// original decision was to use a value of 55fps for all opensim + /// corresponding, with default heartbeat rate, to a value of 5. /// - private float m_reportedFpsCorrectionFactor = 5; + private float m_statisticsFPSfactor = 5.0f; // saved last reported value so there is something available for llGetRegionFPS private float lastReportedSimFPS; - private float[] lastReportedSimStats = new float[22]; + private float[] lastReportedSimStats = new float[m_statisticArraySize]; private float m_pfps; /// @@ -195,13 +209,13 @@ namespace OpenSim.Region.Framework.Scenes private int m_physicsMS; private int m_imageMS; private int m_otherMS; - -//Ckrinke: (3-21-08) Comment out to remove a compiler warning. Bring back into play when needed. -//Ckrinke private int m_scriptMS = 0; + private int m_scriptMS; private int m_rootAgents; private int m_childAgents; private int m_numPrim; + private int m_numGeoPrim; + private int m_numMesh; private int m_inPacketsPerSecond; private int m_outPacketsPerSecond; private int m_activePrim; @@ -213,6 +227,34 @@ namespace OpenSim.Region.Framework.Scenes private int m_objectCapacity = 45000; + // This is the number of frames that will be stored and then averaged for + // the Total, Simulation, Physics, and Network Frame Time; It is set to + // 10 by default but can be changed by the OpenSim.ini configuration file + // NumberOfFrames parameter + private int m_numberFramesStored; + + // The arrays that will hold the time it took to run the past N frames, + // where N is the num_frames_to_average given by the configuration file + private double[] m_totalFrameTimeMilliseconds; + private double[] m_simulationFrameTimeMilliseconds; + private double[] m_physicsFrameTimeMilliseconds; + private double[] m_networkFrameTimeMilliseconds; + + // The location of the next time in milliseconds that will be + // (over)written when the next frame completes + private int m_nextLocation = 0; + + // The correct number of frames that have completed since the last stats + // update for physics + private int m_numberPhysicsFrames; + + // The current number of users attempting to login to the region + private int m_usersLoggingIn; + + // The last reported value of threads from the SmartThreadPool inside of + // XEngine + private int m_inUseThreads; + private Scene m_scene; private RegionInfo ReportingRegion; @@ -222,12 +264,34 @@ namespace OpenSim.Region.Framework.Scenes private IEstateModule estateModule; public SimStatsReporter(Scene scene) + : this(scene, Scene.m_defaultNumberFramesStored) + { + } + + public SimStatsReporter(Scene scene, int numberOfFrames) { + // Store the number of frames from the OpenSim.ini configuration file + m_numberFramesStored = numberOfFrames; + + // Initialize the different frame time arrays to the correct sizes + m_totalFrameTimeMilliseconds = new double[m_numberFramesStored]; + m_simulationFrameTimeMilliseconds = new double[m_numberFramesStored]; + m_physicsFrameTimeMilliseconds = new double[m_numberFramesStored]; + m_networkFrameTimeMilliseconds = new double[m_numberFramesStored]; + + // Initialize the current number of users logging into the region + m_usersLoggingIn = 0; + m_scene = scene; - m_reportedFpsCorrectionFactor = scene.MinFrameTime * m_nominalReportedFps; + m_statsUpdateFactor = (float)(m_statsUpdatesEveryMS / 1000); ReportingRegion = scene.RegionInfo; + if(scene.Normalized55FPS) + m_statisticsFPSfactor = 55.0f * m_scene.MinFrameTicks / 1000.0f; + else + m_statisticsFPSfactor = 1.0f; + m_objectCapacity = scene.RegionInfo.ObjectCapacity; m_report.AutoReset = true; m_report.Interval = m_statsUpdatesEveryMS; @@ -239,7 +303,7 @@ namespace OpenSim.Region.Framework.Scenes /// At the moment, we'll only report if a frame is over 120% of target, since commonly frames are a bit /// longer than ideal (which in itself is a concern). - SlowFramesStatReportThreshold = (int)Math.Ceiling(m_scene.MinFrameTime * 1000 * 1.2); + SlowFramesStatReportThreshold = (int)Math.Ceiling(scene.MinFrameTicks * 1.2); SlowFramesStat = new Stat( @@ -254,8 +318,10 @@ namespace OpenSim.Region.Framework.Scenes StatVerbosity.Info); StatsManager.RegisterStat(SlowFramesStat); + } + public void Close() { m_report.Elapsed -= TriggerStatsHeartbeat; @@ -289,7 +355,21 @@ namespace OpenSim.Region.Framework.Scenes private void statsHeartBeat(object sender, EventArgs e) { - SimStatsPacket.StatBlock[] sb = new SimStatsPacket.StatBlock[22]; + double totalSumFrameTime; + double simulationSumFrameTime; + double physicsSumFrameTime; + double networkSumFrameTime; + float frameDilation; + int currentFrame; + + if (!m_scene.Active) + return; + + // Create arrays to hold the statistics for this current scene, + // these will be passed to the SimExtraStatsCollector, they are also + // sent to the SimStats class + SimStatsPacket.StatBlock[] sb = new + SimStatsPacket.StatBlock[m_statisticArraySize]; SimStatsPacket.RegionBlock rb = new SimStatsPacket.RegionBlock(); // Know what's not thread safe in Mono... modifying timers. @@ -311,14 +391,15 @@ namespace OpenSim.Region.Framework.Scenes #region various statistic googly moogly - // We're going to lie about the FPS because we've been lying since 2008. The actual FPS is currently - // locked at a maximum of 11. Maybe at some point this can change so that we're not lying. - int reportedFPS = (int)(m_fps * m_reportedFpsCorrectionFactor); + int reportedFPS = (int)(m_fps * m_statisticsFPSfactor); // save the reported value so there is something available for llGetRegionFPS lastReportedSimFPS = reportedFPS / m_statsUpdateFactor; - float physfps = ((m_pfps / 1000)); + // ORIGINAL code commented out until we have time to add our own + // statistics to the statistics window + //float physfps = ((m_pfps / 1000)); + float physfps = m_numberPhysicsFrames * m_statisticsFPSfactor; //if (physfps > 600) //physfps = physfps - (physfps - 600); @@ -331,6 +412,8 @@ namespace OpenSim.Region.Framework.Scenes m_rootAgents = m_scene.SceneGraph.GetRootAgentCount(); m_childAgents = m_scene.SceneGraph.GetChildAgentCount(); m_numPrim = m_scene.SceneGraph.GetTotalObjectsCount(); + m_numGeoPrim = m_scene.SceneGraph.GetTotalPrimObjectsCount(); + m_numMesh = m_scene.SceneGraph.GetTotalMeshObjectsCount(); m_activePrim = m_scene.SceneGraph.GetActiveObjectsCount(); m_activeScripts = m_scene.SceneGraph.GetActiveScriptsCount(); @@ -349,18 +432,52 @@ namespace OpenSim.Region.Framework.Scenes // values to X-per-second values. uint thisFrame = m_scene.Frame; - float framesUpdated = (float)(thisFrame - m_lastUpdateFrame) * m_reportedFpsCorrectionFactor; + uint numFrames = thisFrame - m_lastUpdateFrame; + float framesUpdated = (float)numFrames * m_statisticsFPSfactor; m_lastUpdateFrame = thisFrame; // Avoid div-by-zero if somehow we've not updated any frames. if (framesUpdated == 0) framesUpdated = 1; - for (int i = 0; i < 22; i++) + for (int i = 0; i < m_statisticArraySize; i++) { sb[i] = new SimStatsPacket.StatBlock(); } - + + // Resetting the sums of the frame times to prevent any errors + // in calculating the moving average for frame time + totalSumFrameTime = 0; + simulationSumFrameTime = 0; + physicsSumFrameTime = 0; + networkSumFrameTime = 0; + + // Loop through all the frames that were stored for the current + // heartbeat to process the moving average of frame times + for (int i = 0; i < m_numberFramesStored; i++) + { + // Sum up each frame time in order to calculate the moving + // average of frame time + totalSumFrameTime += m_totalFrameTimeMilliseconds[i]; + simulationSumFrameTime += + m_simulationFrameTimeMilliseconds[i]; + physicsSumFrameTime += m_physicsFrameTimeMilliseconds[i]; + networkSumFrameTime += m_networkFrameTimeMilliseconds[i]; + } + + // Get the index that represents the current frame based on the next one known; go back + // to the last index if next one is stated to restart at 0 + if (m_nextLocation == 0) + currentFrame = m_numberFramesStored - 1; + else + currentFrame = m_nextLocation - 1; + + // Calculate the frame dilation; which is currently based on the ratio between the sum of the + // physics and simulation rate, and the set minimum time to run a scene's frame + frameDilation = (float)(m_simulationFrameTimeMilliseconds[currentFrame] + + m_physicsFrameTimeMilliseconds[currentFrame]) / m_scene.MinFrameTicks; + + // ORIGINAL code commented out until we have time to add our own sb[0].StatID = (uint) Stats.TimeDilation; sb[0].StatValue = (Single.IsNaN(m_timeDilation)) ? 0.1f : m_timeDilation ; //((((m_timeDilation + (0.10f * statsUpdateFactor)) /10) / statsUpdateFactor)); @@ -385,20 +502,26 @@ namespace OpenSim.Region.Framework.Scenes sb[7].StatID = (uint) Stats.ActivePrim; sb[7].StatValue = m_activePrim; + // ORIGINAL code commented out until we have time to add our own + // statistics to the statistics window sb[8].StatID = (uint)Stats.FrameMS; - sb[8].StatValue = m_frameMS / framesUpdated; + //sb[8].StatValue = m_frameMS / framesUpdated; + sb[8].StatValue = (float) totalSumFrameTime / m_numberFramesStored / m_statisticsFPSfactor; sb[9].StatID = (uint)Stats.NetMS; - sb[9].StatValue = m_netMS / framesUpdated; + //sb[9].StatValue = m_netMS / framesUpdated; + sb[9].StatValue = (float) networkSumFrameTime / m_numberFramesStored / m_statisticsFPSfactor; sb[10].StatID = (uint)Stats.PhysicsMS; - sb[10].StatValue = m_physicsMS / framesUpdated; + //sb[10].StatValue = m_physicsMS / framesUpdated; + sb[10].StatValue = (float) physicsSumFrameTime / m_numberFramesStored / m_statisticsFPSfactor; sb[11].StatID = (uint)Stats.ImageMS ; sb[11].StatValue = m_imageMS / framesUpdated; sb[12].StatID = (uint)Stats.OtherMS; - sb[12].StatValue = m_otherMS / framesUpdated; + //sb[12].StatValue = m_otherMS / framesUpdated; + sb[12].StatValue = (float) simulationSumFrameTime / m_numberFramesStored / m_statisticsFPSfactor; sb[13].StatID = (uint)Stats.InPacketsPerSecond; sb[13].StatValue = (m_inPacketsPerSecond / m_statsUpdateFactor); @@ -427,7 +550,31 @@ namespace OpenSim.Region.Framework.Scenes sb[21].StatID = (uint)Stats.SimSpareMs; sb[21].StatValue = m_spareMS / framesUpdated; - for (int i = 0; i < 22; i++) + // Current ratio between the sum of physics and sim rate, and the + // minimum time to run a scene's frame + sb[22].StatID = (uint)Stats.FrameDilation; + sb[22].StatValue = frameDilation; + + // Current number of users currently attemptint to login to region + sb[23].StatID = (uint)Stats.UsersLoggingIn; + sb[23].StatValue = m_usersLoggingIn; + + // Total number of geometric primitives in the scene + sb[24].StatID = (uint)Stats.TotalGeoPrim; + sb[24].StatValue = m_numGeoPrim; + + // Total number of mesh objects in the scene + sb[25].StatID = (uint)Stats.TotalMesh; + sb[25].StatValue = m_numMesh; + + // Current number of threads that XEngine is using + sb[26].StatID = (uint)Stats.ThreadCount; + sb[26].StatValue = m_inUseThreads; + + sb[27].StatID = (uint)Stats.ScriptMS; + sb[27].StatValue = (numFrames <= 0) ? 0 : ((float)m_scriptMS / numFrames); + + for (int i = 0; i < m_statisticArraySize; i++) { lastReportedSimStats[i] = sb[i].StatValue; } @@ -472,6 +619,10 @@ namespace OpenSim.Region.Framework.Scenes private void ResetValues() { + // Reset the number of frames that the physics library has + // processed since the last stats report + m_numberPhysicsFrames = 0; + m_timeDilation = 0; m_fps = 0; m_pfps = 0; @@ -488,10 +639,8 @@ namespace OpenSim.Region.Framework.Scenes m_physicsMS = 0; m_imageMS = 0; m_otherMS = 0; + m_scriptMS = 0; m_spareMS = 0; - -//Ckrinke This variable is not used, so comment to remove compiler warning until it is used. -//Ckrinke m_scriptMS = 0; } # region methods called from Scene @@ -602,6 +751,37 @@ namespace OpenSim.Region.Framework.Scenes m_otherMS += ms; } + public void AddScriptMS(int ms) + { + m_scriptMS += ms; + } + + public void addPhysicsFrame(int frames) + { + // Add the number of physics frames to the correct total physics + // frames + m_numberPhysicsFrames += frames; + } + + public void addFrameTimeMilliseconds(double total, double simulation, + double physics, double network) + { + // Save the frame times from the current frame into the appropriate + // arrays + m_totalFrameTimeMilliseconds[m_nextLocation] = total; + m_simulationFrameTimeMilliseconds[m_nextLocation] = simulation; + m_physicsFrameTimeMilliseconds[m_nextLocation] = physics; + m_networkFrameTimeMilliseconds[m_nextLocation] = network; + + // Update to the next location in the list + m_nextLocation++; + + // Since the list will begin to overwrite the oldest frame values + // first, the next location needs to loop back to the beginning of the + // list whenever it reaches the end + m_nextLocation = m_nextLocation % m_numberFramesStored; + } + public void AddPendingDownloads(int count) { m_pendingDownloads += count; @@ -624,6 +804,31 @@ namespace OpenSim.Region.Framework.Scenes AddunAckedBytes(unAckedBytes); } + public void UpdateUsersLoggingIn(bool isLoggingIn) + { + // Determine whether the user has started logging in or has completed + // logging into the region + if (isLoggingIn) + { + // The user is starting to login to the region so increment the + // number of users attempting to login to the region + m_usersLoggingIn++; + } + else + { + // The user has finished logging into the region so decrement the + // number of users logging into the region + m_usersLoggingIn--; + } + } + + public void SetThreadCount(int inUseThreads) + { + // Save the new number of threads to our member variable to send to + // the extra stats collector + m_inUseThreads = inUseThreads; + } + #endregion public Dictionary GetExtraSimStats() diff --git a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs index c0ca48e..3d563a6 100644 --- a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs +++ b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs @@ -25,14 +25,21 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -using OpenSim.Framework; -using OpenSim.Region.Framework.Interfaces; using System; +using System.IO; using System.Text; +using System.Reflection; using System.Xml; -using System.IO; using System.Xml.Serialization; +using OpenSim.Data; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +using OpenMetaverse; + +using log4net; + namespace OpenSim.Region.Framework.Scenes { /// @@ -40,132 +47,136 @@ namespace OpenSim.Region.Framework.Scenes /// public class TerrainChannel : ITerrainChannel { - private readonly bool[,] taint; - private double[,] map; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[TERRAIN CHANNEL]"; + + protected TerrainData m_terrainData; + + public int Width { get { return m_terrainData.SizeX; } } // X dimension + // Unfortunately, for historical reasons, in this module 'Width' is X and 'Height' is Y + public int Height { get { return m_terrainData.SizeY; } } // Y dimension + public int Altitude { get { return m_terrainData.SizeZ; } } // Y dimension + // Default, not-often-used builder public TerrainChannel() { - map = new double[Constants.RegionSize, Constants.RegionSize]; - taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16]; - - PinHeadIsland(); + m_terrainData = new HeightmapTerrainData((int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight); + FlatLand(); + // PinHeadIsland(); } - public TerrainChannel(String type) + // Create terrain of given size + public TerrainChannel(int pX, int pY) { - map = new double[Constants.RegionSize, Constants.RegionSize]; - taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16]; + m_terrainData = new HeightmapTerrainData(pX, pY, (int)Constants.RegionHeight); + } + // Create terrain of specified size and initialize with specified terrain. + // TODO: join this with the terrain initializers. + public TerrainChannel(String type, int pX, int pY, int pZ) + { + m_terrainData = new HeightmapTerrainData(pX, pY, pZ); if (type.Equals("flat")) FlatLand(); else PinHeadIsland(); } - public TerrainChannel(double[,] import) + // Create channel passed a heightmap and expected dimensions of the region. + // The heightmap might not fit the passed size so accomodations must be made. + public TerrainChannel(double[,] pM, int pSizeX, int pSizeY, int pAltitude) { - map = import; - taint = new bool[import.GetLength(0),import.GetLength(1)]; - } + int hmSizeX = pM.GetLength(0); + int hmSizeY = pM.GetLength(1); - public TerrainChannel(bool createMap) - { - if (createMap) - { - map = new double[Constants.RegionSize,Constants.RegionSize]; - taint = new bool[Constants.RegionSize / 16,Constants.RegionSize / 16]; - } + m_terrainData = new HeightmapTerrainData(pSizeX, pSizeY, pAltitude); + + for (int xx = 0; xx < pSizeX; xx++) + for (int yy = 0; yy < pSizeY; yy++) + if (xx > hmSizeX || yy > hmSizeY) + m_terrainData[xx, yy] = TerrainData.DefaultTerrainHeight; + else + m_terrainData[xx, yy] = (float)pM[xx, yy]; } - public TerrainChannel(int w, int h) + public TerrainChannel(TerrainData pTerrData) { - map = new double[w,h]; - taint = new bool[w / 16,h / 16]; + m_terrainData = pTerrData; } #region ITerrainChannel Members - public int Width + // ITerrainChannel.MakeCopy() + public ITerrainChannel MakeCopy() { - get { return map.GetLength(0); } + return this.Copy(); } - public int Height + // ITerrainChannel.GetTerrainData() + public TerrainData GetTerrainData() { - get { return map.GetLength(1); } + return m_terrainData; } - public ITerrainChannel MakeCopy() + // ITerrainChannel.GetFloatsSerialized() + // This one dimensional version is ordered so height = map[y*sizeX+x]; + // DEPRECATED: don't use this function as it does not retain the dimensions of the terrain + // and the caller will probably do the wrong thing if the terrain is not the legacy 256x256. + public float[] GetFloatsSerialised() { - TerrainChannel copy = new TerrainChannel(false); - copy.map = (double[,]) map.Clone(); - - return copy; + return m_terrainData.GetFloatsSerialized(); } - public float[] GetFloatsSerialised() + // ITerrainChannel.GetDoubles() + public double[,] GetDoubles() { - // Move the member variables into local variables, calling - // member variables 256*256 times gets expensive - int w = Width; - int h = Height; - float[] heights = new float[w * h]; + double[,] heights = new double[Width, Height]; - int i, j; // map coordinates int idx = 0; // index into serialized array - for (i = 0; i < h; i++) + for (int ii = 0; ii < Width; ii++) { - for (j = 0; j < w; j++) + for (int jj = 0; jj < Height; jj++) { - heights[idx++] = (float)map[j, i]; + heights[ii, jj] = (double)m_terrainData[ii, jj]; + idx++; } } return heights; } - public double[,] GetDoubles() - { - return map; - } - + // ITerrainChannel.this[x,y] public double this[int x, int y] { - get { return map[x, y]; } + get { + if (x < 0 || x >= Width || y < 0 || y >= Height) + return 0; + return (double)m_terrainData[x, y]; + } set { - // Will "fix" terrain hole problems. Although not fantastically. if (Double.IsNaN(value) || Double.IsInfinity(value)) return; - if (map[x, y] != value) - { - taint[x / 16, y / 16] = true; - map[x, y] = value; - } + m_terrainData[x, y] = (float)value; } } - public bool Tainted(int x, int y) + // ITerrainChannel.GetHieghtAtXYZ(x, y, z) + public float GetHeightAtXYZ(float x, float y, float z) { - if (taint[x / 16, y / 16]) - { - taint[x / 16, y / 16] = false; - return true; - } - return false; + if (x < 0 || x >= Width || y < 0 || y >= Height) + return 0; + return m_terrainData[(int)x, (int)y]; } - #endregion - - public TerrainChannel Copy() + // ITerrainChannel.Tainted() + public bool Tainted(int x, int y) { - TerrainChannel copy = new TerrainChannel(false); - copy.map = (double[,]) map.Clone(); - - return copy; + return m_terrainData.IsTaintedAt(x, y); } + // ITerrainChannel.SaveToXmlString() public string SaveToXmlString() { XmlWriterSettings settings = new XmlWriterSettings(); @@ -181,13 +192,7 @@ namespace OpenSim.Region.Framework.Scenes } } - private void WriteXml(XmlWriter writer) - { - writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty); - ToXml(writer); - writer.WriteEndElement(); - } - + // ITerrainChannel.LoadFromXmlString() public void LoadFromXmlString(string data) { StringReader sr = new StringReader(data); @@ -199,12 +204,124 @@ namespace OpenSim.Region.Framework.Scenes sr.Close(); } + // ITerrainChannel.Merge + public void Merge(ITerrainChannel newTerrain, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement) + { + m_log.DebugFormat("{0} Merge. inSize=<{1},{2}>, disp={3}, rot={4}, rotDisp={5}, outSize=<{6},{7}>", LogHeader, + newTerrain.Width, newTerrain.Height, + displacement, radianRotation, rotationDisplacement, + m_terrainData.SizeX, m_terrainData.SizeY); + for (int xx = 0; xx < newTerrain.Width; xx++) + { + for (int yy = 0; yy < newTerrain.Height; yy++) + { + int dispX = (int)displacement.X; + int dispY = (int)displacement.Y; + float newHeight = (float)newTerrain[xx, yy] + displacement.Z; + if (radianRotation == 0) + { + // If no rotation, place the new height in the specified location + dispX += xx; + dispY += yy; + if (dispX >= 0 && dispX < m_terrainData.SizeX && dispY >= 0 && dispY < m_terrainData.SizeY) + { + m_terrainData[dispX, dispY] = newHeight; + } + } + else + { + // If rotating, we have to smooth the result because the conversion + // to ints will mean heightmap entries will not get changed + // First compute the rotation location for the new height. + dispX += (int)(rotationDisplacement.X + + ((float)xx - rotationDisplacement.X) * Math.Cos(radianRotation) + - ((float)yy - rotationDisplacement.Y) * Math.Sin(radianRotation) ); + + dispY += (int)(rotationDisplacement.Y + + ((float)xx - rotationDisplacement.X) * Math.Sin(radianRotation) + + ((float)yy - rotationDisplacement.Y) * Math.Cos(radianRotation) ); + + if (dispX >= 0 && dispX < m_terrainData.SizeX && dispY >= 0 && dispY < m_terrainData.SizeY) + { + float oldHeight = m_terrainData[dispX, dispY]; + // Smooth the heights around this location if the old height is far from this one + for (int sxx = dispX - 2; sxx < dispX + 2; sxx++) + { + for (int syy = dispY - 2; syy < dispY + 2; syy++) + { + if (sxx >= 0 && sxx < m_terrainData.SizeX && syy >= 0 && syy < m_terrainData.SizeY) + { + if (sxx == dispX && syy == dispY) + { + // Set height for the exact rotated point + m_terrainData[dispX, dispY] = newHeight; + } + else + { + if (Math.Abs(m_terrainData[sxx, syy] - newHeight) > 1f) + { + // If the adjacent height is far off, force it to this height + m_terrainData[sxx, syy] = newHeight; + } + } + } + } + } + } + + if (dispX >= 0 && dispX < m_terrainData.SizeX && dispY >= 0 && dispY < m_terrainData.SizeY) + { + m_terrainData[dispX, dispY] = (float)newTerrain[xx, yy]; + } + } + } + } + } + + #endregion + + public TerrainChannel Copy() + { + TerrainChannel copy = new TerrainChannel(); + copy.m_terrainData = m_terrainData.Clone(); + return copy; + } + + private void WriteXml(XmlWriter writer) + { + if (Width == Constants.RegionSize && Height == Constants.RegionSize) + { + // Downward compatibility for legacy region terrain maps. + // If region is exactly legacy size, return the old format XML. + writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty); + ToXml(writer); + writer.WriteEndElement(); + } + else + { + // New format XML that includes width and length. + writer.WriteStartElement(String.Empty, "TerrainMap2", String.Empty); + ToXml2(writer); + writer.WriteEndElement(); + } + } + private void ReadXml(XmlReader reader) { - reader.ReadStartElement("TerrainMap"); - FromXml(reader); + // Check the first element. If legacy element, use the legacy reader. + if (reader.IsStartElement("TerrainMap")) + { + reader.ReadStartElement("TerrainMap"); + FromXml(reader); + } + else + { + reader.ReadStartElement("TerrainMap2"); + FromXml2(reader); + } } + // Write legacy terrain map. Presumed to be 256x256 of data encoded as floats in a byte array. private void ToXml(XmlWriter xmlWriter) { float[] mapData = GetFloatsSerialised(); @@ -218,12 +335,15 @@ namespace OpenSim.Region.Framework.Scenes serializer.Serialize(xmlWriter, buffer); } + // Read legacy terrain map. Presumed to be 256x256 of data encoded as floats in a byte array. private void FromXml(XmlReader xmlReader) { XmlSerializer serializer = new XmlSerializer(typeof(byte[])); byte[] dataArray = (byte[])serializer.Deserialize(xmlReader); int index = 0; + m_terrainData = new HeightmapTerrainData(Height, Width, (int)Constants.RegionHeight); + for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) @@ -236,35 +356,63 @@ namespace OpenSim.Region.Framework.Scenes } } + private class TerrainChannelXMLPackage + { + public int Version; + public int SizeX; + public int SizeY; + public int SizeZ; + public float CompressionFactor; + public int[] Map; + public TerrainChannelXMLPackage(int pX, int pY, int pZ, float pCompressionFactor, int[] pMap) + { + Version = 1; + SizeX = pX; + SizeY = pY; + SizeZ = pZ; + CompressionFactor = pCompressionFactor; + Map = pMap; + } + } + + // New terrain serialization format that includes the width and length. + private void ToXml2(XmlWriter xmlWriter) + { + TerrainChannelXMLPackage package = new TerrainChannelXMLPackage(Width, Height, Altitude, m_terrainData.CompressionFactor, + m_terrainData.GetCompressedMap()); + XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage)); + serializer.Serialize(xmlWriter, package); + } + + // New terrain serialization format that includes the width and length. + private void FromXml2(XmlReader xmlReader) + { + XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage)); + TerrainChannelXMLPackage package = (TerrainChannelXMLPackage)serializer.Deserialize(xmlReader); + m_terrainData = new HeightmapTerrainData(package.Map, package.CompressionFactor, package.SizeX, package.SizeY, package.SizeZ); + } + + // Fill the heightmap with the center bump terrain private void PinHeadIsland() { - int x; - for (x = 0; x < Constants.RegionSize; x++) + for (int x = 0; x < Width; x++) { - int y; - for (y = 0; y < Constants.RegionSize; y++) + for (int y = 0; y < Height; y++) { - map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10; - double spherFacA = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 50) * 0.01; - double spherFacB = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 100) * 0.001; - if (map[x, y] < spherFacA) - map[x, y] = spherFacA; - if (map[x, y] < spherFacB) - map[x, y] = spherFacB; + m_terrainData[x, y] = (float)TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10; + float spherFacA = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 50) * 0.01d); + float spherFacB = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 100) * 0.001d); + if (m_terrainData[x, y]< spherFacA) + m_terrainData[x, y]= spherFacA; + if (m_terrainData[x, y]< spherFacB) + m_terrainData[x, y] = spherFacB; } } } private void FlatLand() { - int x; - for (x = 0; x < Constants.RegionSize; x++) - { - int y; - for (y = 0; y < Constants.RegionSize; y++) - map[x, y] = 21; - } + m_terrainData.ClearLand(); } - } } diff --git a/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs new file mode 100644 index 0000000..fc8f8cd --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs @@ -0,0 +1,948 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* Freely adapted from the Aurora version of the terrain compressor. + * Copyright (c) Contributors, http://aurora-sim.org/, http://opensimulator.org/ + */ + +using System; +using System.Reflection; + +using log4net; + +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.Framework.Scenes; + +using OpenMetaverse; +using OpenMetaverse.Packets; + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + public static class OpenSimTerrainCompressor + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + +#pragma warning disable 414 + private static string LogHeader = "[TERRAIN COMPRESSOR]"; +#pragma warning restore 414 + + public const int END_OF_PATCHES = 97; + + private const float OO_SQRT2 = 0.7071067811865475244008443621049f; + private const int STRIDE = 264; + + private const int ZERO_CODE = 0x0; + private const int ZERO_EOB = 0x2; + private const int POSITIVE_VALUE = 0x6; + private const int NEGATIVE_VALUE = 0x7; + + private static readonly float[] DequantizeTable16 = + new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + private static readonly float[] DequantizeTable32 = + new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + private static readonly float[] CosineTable16 = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + //private static readonly float[] CosineTable32 = new float[Constants.TerrainPatchSize * Constants.TerrainPatchSize]; + private static readonly int[] CopyMatrix16 = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + private static readonly int[] CopyMatrix32 = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + private static readonly float[] QuantizeTable16 = + new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + static OpenSimTerrainCompressor() + { + // Initialize the decompression tables + BuildDequantizeTable16(); + SetupCosines16(); + BuildCopyMatrix16(); + BuildQuantizeTable16(); + } + + // Used to send cloud and wind patches + public static LayerDataPacket CreateLayerDataPacket(TerrainPatch[] patches, byte type, int pRegionSizeX, + int pRegionSizeY) + { + LayerDataPacket layer = new LayerDataPacket {LayerID = {Type = type}}; + + TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader + {Stride = STRIDE, PatchSize = Constants.TerrainPatchSize}; + + // Should be enough to fit even the most poorly packed data + byte[] data = new byte[patches.Length*Constants.TerrainPatchSize*Constants.TerrainPatchSize*2]; + BitPack bitpack = new BitPack(data, 0); + bitpack.PackBits(header.Stride, 16); + bitpack.PackBits(header.PatchSize, 8); + bitpack.PackBits(type, 8); + + foreach (TerrainPatch t in patches) + CreatePatch(bitpack, t.Data, t.X, t.Y, pRegionSizeX, pRegionSizeY); + + bitpack.PackBits(END_OF_PATCHES, 8); + + layer.LayerData.Data = new byte[bitpack.BytePos + 1]; + Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1); + + return layer; + } + + // Create a land packet for a single patch. + public static LayerDataPacket CreateLandPacket(TerrainData terrData, int patchX, int patchY) + { + int[] xPieces = new int[1]; + int[] yPieces = new int[1]; + xPieces[0] = patchX; // patch X dimension + yPieces[0] = patchY; + + return CreateLandPacket(terrData, xPieces, yPieces); + } + + public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] xPieces, int[] yPieces) + { + byte landPacketType = (byte)TerrainPatch.LayerType.Land; + if (terrData.SizeX > Constants.RegionSize || terrData.SizeY > Constants.RegionSize) + { + landPacketType = (byte)TerrainPatch.LayerType.LandExtended; + } + + return CreateLandPacket(terrData, xPieces, yPieces, landPacketType); + } + + /// + /// Creates a LayerData packet for compressed land data given a full + /// simulator heightmap and an array of indices of patches to compress + /// + /// + /// Terrain data that can result in a meter square heightmap. + /// + /// + /// Array of indexes in the grid of patches + /// for this simulator. + /// If creating a packet for multiple patches, there will be entries in + /// both the X and Y arrays for each of the patches. + /// For example if patches 1 and 17 are to be sent, + /// x[] = {1,1} and y[] = {0,1} which specifies the patches at + /// indexes <1,0> and <1,1> (presuming the terrain size is 16x16 patches). + /// + /// + /// Array of indexes in the grid of patches. + /// + /// + /// + public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] x, int[] y, byte type) + { + LayerDataPacket layer = new LayerDataPacket {LayerID = {Type = type}}; + + TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader + {Stride = STRIDE, PatchSize = Constants.TerrainPatchSize}; + + byte[] data = new byte[x.Length * Constants.TerrainPatchSize * Constants.TerrainPatchSize * 2]; + BitPack bitpack = new BitPack(data, 0); + bitpack.PackBits(header.Stride, 16); + bitpack.PackBits(header.PatchSize, 8); + bitpack.PackBits(type, 8); + + for (int i = 0; i < x.Length; i++) + CreatePatchFromHeightmap(bitpack, terrData, x[i], y[i]); + + bitpack.PackBits(END_OF_PATCHES, 8); + + layer.LayerData.Data = new byte[bitpack.BytePos + 1]; + Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1); + + return layer; + } + + // Unused: left for historical reference. + public static void CreatePatch(BitPack output, float[] patchData, int x, int y, int pRegionSizeX, int pRegionSizeY) + { + TerrainPatch.Header header = PrescanPatch(patchData); + header.QuantWBits = 136; + if (pRegionSizeX > Constants.RegionSize || pRegionSizeY > Constants.RegionSize) + { + header.PatchIDs = (y & 0xFFFF); + header.PatchIDs += (x << 16); + } + else + { + header.PatchIDs = (y & 0x1F); + header.PatchIDs += (x << 5); + } + + // NOTE: No idea what prequant and postquant should be or what they do + + int wbits; + int[] patch = CompressPatch(patchData, header, 10, out wbits); + wbits = EncodePatchHeader(output, header, patch, Constants.RegionSize, Constants.RegionSize, wbits); + EncodePatch(output, patch, 0, wbits); + } + + /// + /// Add a patch of terrain to a BitPacker + /// + /// BitPacker to write the patch to + /// + /// Heightmap of the simulator. Presumed to be an sizeX*sizeY array. + /// + /// + /// X offset of the patch to create. + /// + /// + /// Y offset of the patch to create. + /// + /// + /// + public static void CreatePatchFromHeightmap(BitPack output, TerrainData terrData, int patchX, int patchY) + { + TerrainPatch.Header header = PrescanPatch(terrData, patchX, patchY); + header.QuantWBits = 136; + + // If larger than legacy region size, pack patch X and Y info differently. + if (terrData.SizeX > Constants.RegionSize || terrData.SizeY > Constants.RegionSize) + { + header.PatchIDs = (patchY & 0xFFFF); + header.PatchIDs += (patchX << 16); + } + else + { + header.PatchIDs = (patchY & 0x1F); + header.PatchIDs += (patchX << 5); + } + + // m_log.DebugFormat("{0} CreatePatchFromHeightmap. patchX={1}, patchY={2}, DCOffset={3}, range={4}", + // LogHeader, patchX, patchY, header.DCOffset, header.Range); + + // NOTE: No idea what prequant and postquant should be or what they do + int wbits; + int[] patch = CompressPatch(terrData, patchX, patchY, header, 10, out wbits); + wbits = EncodePatchHeader(output, header, patch, (uint)terrData.SizeX, (uint)terrData.SizeY, wbits); + EncodePatch(output, patch, 0, wbits); + } + + private static TerrainPatch.Header PrescanPatch(float[] patch) + { + TerrainPatch.Header header = new TerrainPatch.Header(); + float zmax = -99999999.0f; + float zmin = 99999999.0f; + + for (int i = 0; i < Constants.TerrainPatchSize*Constants.TerrainPatchSize; i++) + { + float val = patch[i]; + if (val > zmax) zmax = val; + if (val < zmin) zmin = val; + } + + header.DCOffset = zmin; + header.Range = (int) ((zmax - zmin) + 1.0f); + + return header; + } + + // Scan the height info we're returning and return a patch packet header for this patch. + private static TerrainPatch.Header PrescanPatch(TerrainData terrData, int patchX, int patchY) + { + TerrainPatch.Header header = new TerrainPatch.Header(); + float zmax = -99999999.0f; + float zmin = 99999999.0f; + + for (int j = patchY*Constants.TerrainPatchSize; j < (patchY + 1)*Constants.TerrainPatchSize; j++) + { + for (int i = patchX*Constants.TerrainPatchSize; i < (patchX + 1)*Constants.TerrainPatchSize; i++) + { + float val = terrData[i, j]; + if (val > zmax) zmax = val; + if (val < zmin) zmin = val; + } + } + + header.DCOffset = zmin; + header.Range = (int)((zmax - zmin) + 1.0f); + + return header; + } + + public static TerrainPatch.Header DecodePatchHeader(BitPack bitpack) + { + TerrainPatch.Header header = new TerrainPatch.Header {QuantWBits = bitpack.UnpackBits(8)}; + + // Quantized word bits + if (header.QuantWBits == END_OF_PATCHES) + return header; + + // DC offset + header.DCOffset = bitpack.UnpackFloat(); + + // Range + header.Range = bitpack.UnpackBits(16); + + // Patch IDs (10 bits) + header.PatchIDs = bitpack.UnpackBits(10); + + // Word bits + header.WordBits = (uint) ((header.QuantWBits & 0x0f) + 2); + + return header; + } + + private static int EncodePatchHeader(BitPack output, TerrainPatch.Header header, int[] patch, uint pRegionSizeX, + uint pRegionSizeY, int wbits) + { + /* + int temp; + int wbits = (header.QuantWBits & 0x0f) + 2; + uint maxWbits = (uint)wbits + 5; + uint minWbits = ((uint)wbits >> 1); + int wbitsMaxValue; + */ + // goal is to determ minimum number of bits to use so all data fits + /* + wbits = (int)minWbits; + wbitsMaxValue = (1 << wbits); + + for (int i = 0; i < patch.Length; i++) + { + temp = patch[i]; + if (temp != 0) + { + // Get the absolute value + if (temp < 0) temp *= -1; + + no coments.. + + for (int j = (int)maxWbits; j > (int)minWbits; j--) + { + if ((temp & (1 << j)) != 0) + { + if (j > wbits) wbits = j; + break; + } + } + + while (temp > wbitsMaxValue) + { + wbits++; + if (wbits == maxWbits) + goto Done; + wbitsMaxValue = 1 << wbits; + } + } + } + + Done: + + // wbits += 1; + */ + // better check + if (wbits > 17) + wbits = 16; + else if (wbits < 3) + wbits = 3; + + header.QuantWBits &= 0xf0; + + header.QuantWBits |= (wbits - 2); + + output.PackBits(header.QuantWBits, 8); + output.PackFloat(header.DCOffset); + output.PackBits(header.Range, 16); + if (pRegionSizeX > Constants.RegionSize || pRegionSizeY > Constants.RegionSize) + output.PackBits(header.PatchIDs, 32); + else + output.PackBits(header.PatchIDs, 10); + + return wbits; + } + + private static void IDCTColumn16(float[] linein, float[] lineout, int column) + { + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + float total = OO_SQRT2*linein[column]; + + for (int u = 1; u < Constants.TerrainPatchSize; u++) + { + int usize = u*Constants.TerrainPatchSize; + total += linein[usize + column]*CosineTable16[usize + n]; + } + + lineout[Constants.TerrainPatchSize*n + column] = total; + } + } + + private static void IDCTLine16(float[] linein, float[] lineout, int line) + { + const float oosob = 2.0f/Constants.TerrainPatchSize; + int lineSize = line*Constants.TerrainPatchSize; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + float total = OO_SQRT2*linein[lineSize]; + + for (int u = 1; u < Constants.TerrainPatchSize; u++) + { + total += linein[lineSize + u]*CosineTable16[u*Constants.TerrainPatchSize + n]; + } + + lineout[lineSize + n] = total*oosob; + } + } + +/* + private static void DCTLine16(float[] linein, float[] lineout, int line) + { + float total = 0.0f; + int lineSize = line * Constants.TerrainPatchSize; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[lineSize + n]; + } + + lineout[lineSize] = OO_SQRT2 * total; + + int uptr = 0; + for (int u = 1; u < Constants.TerrainPatchSize; u++) + { + total = 0.0f; + uptr += Constants.TerrainPatchSize; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[lineSize + n] * CosineTable16[uptr + n]; + } + + lineout[lineSize + u] = total; + } + } +*/ + + private static void DCTLine16(float[] linein, float[] lineout, int line) + { + // outputs transpose data (lines exchanged with coluns ) + // so to save a bit of cpu when doing coluns + float total = 0.0f; + int lineSize = line*Constants.TerrainPatchSize; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[lineSize + n]; + } + + lineout[line] = OO_SQRT2*total; + + for (int u = Constants.TerrainPatchSize; + u < Constants.TerrainPatchSize*Constants.TerrainPatchSize; + u += Constants.TerrainPatchSize) + { + total = 0.0f; + for (int ptrn = lineSize, ptru = u; ptrn < lineSize + Constants.TerrainPatchSize; ptrn++,ptru++) + { + total += linein[ptrn]*CosineTable16[ptru]; + } + + lineout[line + u] = total; + } + } + + + /* + private static void DCTColumn16(float[] linein, int[] lineout, int column) + { + float total = 0.0f; + // const float oosob = 2.0f / Constants.TerrainPatchSize; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[Constants.TerrainPatchSize * n + column]; + } + + // lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]); + lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * QuantizeTable16[column]); + + for (int uptr = Constants.TerrainPatchSize; uptr < Constants.TerrainPatchSize * Constants.TerrainPatchSize; uptr += Constants.TerrainPatchSize) + { + total = 0.0f; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[Constants.TerrainPatchSize * n + column] * CosineTable16[uptr + n]; + } + + // lineout[CopyMatrix16[Constants.TerrainPatchSize * u + column]] = (int)(total * oosob * QuantizeTable16[Constants.TerrainPatchSize * u + column]); + lineout[CopyMatrix16[uptr + column]] = (int)(total * QuantizeTable16[uptr + column]); + } + } + + private static void DCTColumn16(float[] linein, int[] lineout, int column) + { + // input columns are in fact stored in lines now + + float total = 0.0f; +// const float oosob = 2.0f / Constants.TerrainPatchSize; + int inlinesptr = Constants.TerrainPatchSize*column; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[inlinesptr + n]; + } + + // lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]); + lineout[CopyMatrix16[column]] = (int) (OO_SQRT2*total*QuantizeTable16[column]); + + for (int uptr = Constants.TerrainPatchSize; + uptr < Constants.TerrainPatchSize*Constants.TerrainPatchSize; + uptr += Constants.TerrainPatchSize) + { + total = 0.0f; + + for (int n = inlinesptr, ptru = uptr; n < inlinesptr + Constants.TerrainPatchSize; n++, ptru++) + { + total += linein[n]*CosineTable16[ptru]; + } + +// lineout[CopyMatrix16[Constants.TerrainPatchSize * u + column]] = (int)(total * oosob * QuantizeTable16[Constants.TerrainPatchSize * u + column]); + lineout[CopyMatrix16[uptr + column]] = (int) (total*QuantizeTable16[uptr + column]); + } + } + */ + + private static int DCTColumn16Wbits(float[] linein, int[] lineout, int column, int wbits, int maxwbits) + { + // input columns are in fact stored in lines now + + bool dowbits = wbits != maxwbits; + int wbitsMaxValue = 1 << wbits; + + float total = 0.0f; + // const float oosob = 2.0f / Constants.TerrainPatchSize; + int inlinesptr = Constants.TerrainPatchSize*column; + + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + total += linein[inlinesptr + n]; + } + + // lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]); + int tmp = (int) (OO_SQRT2*total*QuantizeTable16[column]); + lineout[CopyMatrix16[column]] = tmp; + + if (dowbits) + { + if (tmp < 0) tmp *= -1; + while (tmp > wbitsMaxValue) + { + wbits++; + wbitsMaxValue = 1 << wbits; + if (wbits == maxwbits) + { + dowbits = false; + break; + } + } + } + + for (int uptr = Constants.TerrainPatchSize; + uptr < Constants.TerrainPatchSize*Constants.TerrainPatchSize; + uptr += Constants.TerrainPatchSize) + { + total = 0.0f; + + for (int n = inlinesptr, ptru = uptr; n < inlinesptr + Constants.TerrainPatchSize; n++, ptru++) + { + total += linein[n]*CosineTable16[ptru]; + } + + tmp = (int) (total*QuantizeTable16[uptr + column]); + lineout[CopyMatrix16[uptr + column]] = tmp; + + if (dowbits) + { + if (tmp < 0) tmp *= -1; + while (tmp > wbitsMaxValue) + { + wbits++; + wbitsMaxValue = 1 << wbits; + if (wbits == maxwbits) + { + dowbits = false; + break; + } + } + } + } + return wbits; + } + + public static void DecodePatch(int[] patches, BitPack bitpack, TerrainPatch.Header header, int size) + { + for (int n = 0; n < size*size; n++) + { + // ? + int temp = bitpack.UnpackBits(1); + if (temp != 0) + { + // Value or EOB + temp = bitpack.UnpackBits(1); + if (temp != 0) + { + // Value + temp = bitpack.UnpackBits(1); + if (temp != 0) + { + // Negative + temp = bitpack.UnpackBits((int) header.WordBits); + patches[n] = temp*-1; + } + else + { + // Positive + temp = bitpack.UnpackBits((int) header.WordBits); + patches[n] = temp; + } + } + else + { + // Set the rest to zero + // TODO: This might not be necessary + for (int o = n; o < size*size; o++) + { + patches[o] = 0; + } + break; + } + } + else + { + patches[n] = 0; + } + } + } + + private static void EncodePatch(BitPack output, int[] patch, int postquant, int wbits) + { + int maxwbitssize = (1 << wbits) - 1; + + if (postquant > Constants.TerrainPatchSize*Constants.TerrainPatchSize || postquant < 0) + { + Logger.Log("Postquant is outside the range of allowed values in EncodePatch()", Helpers.LogLevel.Error); + return; + } + + if (postquant != 0) patch[Constants.TerrainPatchSize*Constants.TerrainPatchSize - postquant] = 0; + + for (int i = 0; i < Constants.TerrainPatchSize*Constants.TerrainPatchSize; i++) + { + int temp = patch[i]; + + if (temp == 0) + { + bool eob = true; + + for (int j = i; j < Constants.TerrainPatchSize*Constants.TerrainPatchSize - postquant; j++) + { + if (patch[j] != 0) + { + eob = false; + break; + } + } + + if (eob) + { + output.PackBits(ZERO_EOB, 2); + return; + } + output.PackBits(ZERO_CODE, 1); + } + else + { + if (temp < 0) + { + temp *= -1; + + if (temp > maxwbitssize) temp = maxwbitssize; + + output.PackBits(NEGATIVE_VALUE, 3); + output.PackBits(temp, wbits); + } + else + { + if (temp > maxwbitssize) temp = maxwbitssize; + + output.PackBits(POSITIVE_VALUE, 3); + output.PackBits(temp, wbits); + } + } + } + } + + public static float[] DecompressPatch(int[] patches, TerrainPatch.Header header, TerrainPatch.GroupHeader group) + { + float[] block = new float[group.PatchSize*group.PatchSize]; + float[] output = new float[group.PatchSize*group.PatchSize]; + int prequant = (header.QuantWBits >> 4) + 2; + int quantize = 1 << prequant; + float ooq = 1.0f/quantize; + float mult = ooq*header.Range; + float addval = mult*(1 << (prequant - 1)) + header.DCOffset; + + if (group.PatchSize == Constants.TerrainPatchSize) + { + for (int n = 0; n < Constants.TerrainPatchSize*Constants.TerrainPatchSize; n++) + { + block[n] = patches[CopyMatrix16[n]]*DequantizeTable16[n]; + } + + float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + for (int o = 0; o < Constants.TerrainPatchSize; o++) + IDCTColumn16(block, ftemp, o); + for (int o = 0; o < Constants.TerrainPatchSize; o++) + IDCTLine16(ftemp, block, o); + } + else + { + for (int n = 0; n < Constants.TerrainPatchSize*2*Constants.TerrainPatchSize*2; n++) + { + block[n] = patches[CopyMatrix32[n]]*DequantizeTable32[n]; + } + + Logger.Log("Implement IDCTPatchLarge", Helpers.LogLevel.Error); + } + + for (int j = 0; j < block.Length; j++) + { + output[j] = block[j]*mult + addval; + } + + return output; + } + + private static int[] CompressPatch(float[] patchData, TerrainPatch.Header header, int prequant, out int wbits) + { + float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + int wordsize = (prequant - 2) & 0x0f; + float oozrange = 1.0f/header.Range; + float range = (1 << prequant); + float premult = oozrange*range; + float sub = (1 << (prequant - 1)) + header.DCOffset*premult; + + header.QuantWBits = wordsize; + header.QuantWBits |= wordsize << 4; + + int k = 0; + for (int j = 0; j < Constants.TerrainPatchSize; j++) + { + for (int i = 0; i < Constants.TerrainPatchSize; i++) + block[k++] = patchData[j*Constants.TerrainPatchSize + i]*premult - sub; + } + + float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + int[] itemp = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + + int maxWbits = prequant + 5; + wbits = (prequant >> 1); + + for (int o = 0; o < Constants.TerrainPatchSize; o++) + DCTLine16(block, ftemp, o); + for (int o = 0; o < Constants.TerrainPatchSize; o++) + wbits = DCTColumn16Wbits(ftemp, itemp, o, wbits, maxWbits); + + return itemp; + } + + private static int[] CompressPatch(float[,] patchData, TerrainPatch.Header header, int prequant, out int wbits) + { + float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + float oozrange = 1.0f/header.Range; + float range = (1 << prequant); + float premult = oozrange*range; + float sub = (1 << (prequant - 1)) + header.DCOffset*premult; + int wordsize = (prequant - 2) & 0x0f; + + header.QuantWBits = wordsize; + header.QuantWBits |= wordsize << 4; + + int k = 0; + for (int j = 0; j < Constants.TerrainPatchSize; j++) + { + for (int i = 0; i < Constants.TerrainPatchSize; i++) + block[k++] = patchData[j, i]*premult - sub; + } + + float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + int[] itemp = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + int maxWbits = prequant + 5; + wbits = (prequant >> 1); + + for (int o = 0; o < Constants.TerrainPatchSize; o++) + DCTLine16(block, ftemp, o); + for (int o = 0; o < Constants.TerrainPatchSize; o++) + wbits = DCTColumn16Wbits(ftemp, itemp, o, wbits, maxWbits); + + return itemp; + } + + private static int[] CompressPatch(TerrainData terrData, int patchX, int patchY, TerrainPatch.Header header, + int prequant, out int wbits) + { + float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + int wordsize = prequant; + float oozrange = 1.0f/header.Range; + float range = (1 << prequant); + float premult = oozrange*range; + float sub = (1 << (prequant - 1)) + header.DCOffset*premult; + + header.QuantWBits = wordsize - 2; + header.QuantWBits |= (prequant - 2) << 4; + + int k = 0; + + int yPatchLimit = patchY >= (terrData.SizeY / Constants.TerrainPatchSize) ? + (terrData.SizeY - Constants.TerrainPatchSize) / Constants.TerrainPatchSize : patchY; + yPatchLimit = (yPatchLimit + 1) * Constants.TerrainPatchSize; + + int xPatchLimit = patchX >= (terrData.SizeX / Constants.TerrainPatchSize) ? + (terrData.SizeX - Constants.TerrainPatchSize) / Constants.TerrainPatchSize : patchX; + xPatchLimit = (xPatchLimit + 1) * Constants.TerrainPatchSize; + + for (int yy = patchY * Constants.TerrainPatchSize; yy < yPatchLimit; yy++) + { + for (int xx = patchX * Constants.TerrainPatchSize; xx < xPatchLimit; xx++) + { + block[k++] = terrData[xx, yy] * premult - sub; + } + } + + float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + int[] itemp = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; + + int maxWbits = prequant + 5; + wbits = (prequant >> 1); + + for (int o = 0; o < Constants.TerrainPatchSize; o++) + DCTLine16(block, ftemp, o); + for (int o = 0; o < Constants.TerrainPatchSize; o++) + wbits = DCTColumn16Wbits(ftemp, itemp, o, wbits, maxWbits); + + return itemp; + } + + #region Initialization + + private static void BuildDequantizeTable16() + { + for (int j = 0; j < Constants.TerrainPatchSize; j++) + { + for (int i = 0; i < Constants.TerrainPatchSize; i++) + { + DequantizeTable16[j*Constants.TerrainPatchSize + i] = 1.0f + 2.0f*(i + j); + } + } + } + + private static void BuildQuantizeTable16() + { + const float oosob = 2.0f/Constants.TerrainPatchSize; + for (int j = 0; j < Constants.TerrainPatchSize; j++) + { + for (int i = 0; i < Constants.TerrainPatchSize; i++) + { +// QuantizeTable16[j * Constants.TerrainPatchSize + i] = 1.0f / (1.0f + 2.0f * ((float)i + (float)j)); + QuantizeTable16[j*Constants.TerrainPatchSize + i] = oosob/(1.0f + 2.0f*(i + (float) j)); + } + } + } + + private static void SetupCosines16() + { + const float hposz = (float) Math.PI*0.5f/Constants.TerrainPatchSize; + + for (int u = 0; u < Constants.TerrainPatchSize; u++) + { + for (int n = 0; n < Constants.TerrainPatchSize; n++) + { + CosineTable16[u*Constants.TerrainPatchSize + n] = (float) Math.Cos((2.0f*n + 1.0f)*u*hposz); + } + } + } + + private static void BuildCopyMatrix16() + { + bool diag = false; + bool right = true; + int i = 0; + int j = 0; + int count = 0; + + while (i < Constants.TerrainPatchSize && j < Constants.TerrainPatchSize) + { + CopyMatrix16[j*Constants.TerrainPatchSize + i] = count++; + + if (!diag) + { + if (right) + { + if (i < Constants.TerrainPatchSize - 1) i++; + else j++; + + right = false; + diag = true; + } + else + { + if (j < Constants.TerrainPatchSize - 1) j++; + else i++; + + right = true; + diag = true; + } + } + else + { + if (right) + { + i++; + j--; + if (i == Constants.TerrainPatchSize - 1 || j == 0) diag = false; + } + else + { + i--; + j++; + if (j == Constants.TerrainPatchSize - 1 || i == 0) diag = false; + } + } + } + } + + #endregion Initialization + } +} diff --git a/OpenSim/Region/Framework/Scenes/Tests/EntityManagerTests.cs b/OpenSim/Region/Framework/Scenes/Tests/EntityManagerTests.cs index 766ce83..da18941 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/EntityManagerTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/EntityManagerTests.cs @@ -34,7 +34,6 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneGraphTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneGraphTests.cs index 575a081..ee7c8a9 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneGraphTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneGraphTests.cs @@ -32,10 +32,8 @@ using System.Text; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneManagerTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneManagerTests.cs index 2d831fa..3172227 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneManagerTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneManagerTests.cs @@ -32,11 +32,9 @@ using System.Threading; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectBasicTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectBasicTests.cs index a07d64c..098f1b4 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectBasicTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectBasicTests.cs @@ -33,11 +33,9 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectCopyTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectCopyTests.cs new file mode 100644 index 0000000..0b196c1 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectCopyTests.cs @@ -0,0 +1,346 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Test copying of scene objects. + /// + /// + /// This is at a level above the SceneObjectBasicTests, which act on the scene directly. + /// + [TestFixture] + public class SceneObjectCopyTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + // This facility was added after the original async delete tests were written, so it may be possible now + // to not bother explicitly disabling their async (since everything will be running sync). + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [Test] + public void TestTakeCopyWhenCopierIsOwnerWithPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", ua.PrincipalID); + uint soLocalId = so.LocalId; +// so.UpdatePermissions( +// ua.PrincipalID, (byte)PermissionWho.Owner, so.LocalId, (uint)OpenMetaverse.PermissionMask.Copy, 1); +// so.UpdatePermissions( +// ua.PrincipalID, (byte)PermissionWho.Owner, so.LocalId, (uint)OpenMetaverse.PermissionMask.Transfer, 0); +// so.UpdatePermissions( +// ua.PrincipalID, (byte)PermissionWho.Base, so.LocalId, (uint)OpenMetaverse.PermissionMask.Transfer, 0); +// scene.HandleObjectPermissionsUpdate(client, client.AgentId, client.SessionId, (byte)PermissionWho.Owner, so.LocalId, (uint)OpenMetaverse.PermissionMask.Transfer, 0); + + // Ideally we might change these via client-focussed method calls as commented out above. However, this + // becomes very convoluted so we will set only the copy perm directly. + so.RootPart.BaseMask = (uint)OpenMetaverse.PermissionMask.Copy; +// so.RootPart.OwnerMask = (uint)OpenMetaverse.PermissionMask.Copy; + + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will currently plop it in Lost and Found + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Lost And Found/so1"); + Assert.That(item, Is.Not.Null); + } + + [Test] + public void TestTakeCopyWhenCopierIsOwnerWithoutPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", ua.PrincipalID); + uint soLocalId = so.LocalId; + + so.RootPart.BaseMask = (uint)(OpenMetaverse.PermissionMask.All & ~OpenMetaverse.PermissionMask.Copy); + //so.RootPart.OwnerMask = (uint)(OpenMetaverse.PermissionMask.Copy & ~OpenMetaverse.PermissionMask.Copy); + + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will currently plop it in Lost and Found + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we do not have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Lost And Found/so1"); + Assert.That(item, Is.Null); + } + + [Test] + public void TestTakeCopyWhenCopierIsNotOwnerWithPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", TestHelpers.ParseTail(0x2)); + uint soLocalId = so.LocalId; + + // Base must allow transfer and copy + so.RootPart.BaseMask = (uint)(OpenMetaverse.PermissionMask.Copy | OpenMetaverse.PermissionMask.Transfer); + // Must be set so anyone can copy + so.RootPart.EveryoneMask = (uint)OpenMetaverse.PermissionMask.Copy; + + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Not.Null); + } + + [Test] + public void TestTakeCopyWhenCopierIsNotOwnerWithoutPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", TestHelpers.ParseTail(0x2)); + uint soLocalId = so.LocalId; + + { + // Check that object is not copied if copy base perms is missing. + // Should not allow copy if base does not have this. + so.RootPart.BaseMask = (uint)OpenMetaverse.PermissionMask.Transfer; + // Must be set so anyone can copy + so.RootPart.EveryoneMask = (uint)OpenMetaverse.PermissionMask.Copy; + + // Check that object is not copied + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder if we have perms + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Null); + } + + { + // Check that object is not copied if copy trans perms is missing. + // Should not allow copy if base does not have this. + so.RootPart.BaseMask = (uint)OpenMetaverse.PermissionMask.Copy; + // Must be set so anyone can copy + so.RootPart.EveryoneMask = (uint)OpenMetaverse.PermissionMask.Copy; + + // Check that object is not copied + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder if we have perms + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Null); + } + + { + // Check that object is not copied if everyone copy perms is missing. + // Should not allow copy if base does not have this. + so.RootPart.BaseMask = (uint)(OpenMetaverse.PermissionMask.Copy | OpenMetaverse.PermissionMask.Transfer); + // Make sure everyone perm does not allow copy + so.RootPart.EveryoneMask = (uint)(OpenMetaverse.PermissionMask.All & ~OpenMetaverse.PermissionMask.Copy); + + // Check that object is not copied + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder if we have perms + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Null); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectCrossingTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectCrossingTests.cs new file mode 100644 index 0000000..5635c20 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectCrossingTests.cs @@ -0,0 +1,259 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Land; +using OpenSim.Region.OptionalModules; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + public class SceneObjectCrossingTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + /// + /// Test cross with no prim limit module. + /// + [Test] + public void TestCrossOnSameSimulator() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, etmA); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + + SceneObjectGroup so1 = SceneHelpers.AddSceneObject(sceneA, 1, userId, "", sceneObjectIdTail); + UUID so1Id = so1.UUID; + so1.AbsolutePosition = new Vector3(128, 10, 20); + + // Cross with a negative value + so1.AbsolutePosition = new Vector3(128, -10, 20); + + Assert.IsNull(sceneA.GetSceneObjectGroup(so1Id)); + Assert.NotNull(sceneB.GetSceneObjectGroup(so1Id)); + } + + /// + /// Test cross with no prim limit module. + /// + /// + /// Possibly this should belong in ScenePresenceCrossingTests, though here it is the object that is being moved + /// where the avatar is just a passenger. + /// + [Test] + public void TestCrossOnSameSimulatorWithSittingAvatar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + Vector3 so1StartPos = new Vector3(128, 10, 20); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + SceneObjectGroup so1 = SceneHelpers.AddSceneObject(sceneA, 1, userId, "", sceneObjectIdTail); + UUID so1Id = so1.UUID; + so1.AbsolutePosition = so1StartPos; + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence sp1SceneA = SceneHelpers.AddScenePresence(sceneA, tc, acd); + sp1SceneA.AbsolutePosition = so1StartPos; + sp1SceneA.HandleAgentRequestSit(sp1SceneA.ControllingClient, sp1SceneA.UUID, so1.UUID, Vector3.Zero); + + // Cross + sceneA.SceneGraph.UpdatePrimGroupPosition( + so1.LocalId, new Vector3(so1StartPos.X, so1StartPos.Y - 20, so1StartPos.Z), userId); + + SceneObjectGroup so1PostCross; + + { + ScenePresence sp1SceneAPostCross = sceneA.GetScenePresence(userId); + Assert.IsTrue(sp1SceneAPostCross.IsChildAgent, "sp1SceneAPostCross.IsChildAgent unexpectedly false"); + + ScenePresence sp1SceneBPostCross = sceneB.GetScenePresence(userId); + TestClient sceneBTc = ((TestClient)sp1SceneBPostCross.ControllingClient); + sceneBTc.CompleteMovement(); + + Assert.IsFalse(sp1SceneBPostCross.IsChildAgent, "sp1SceneAPostCross.IsChildAgent unexpectedly true"); + Assert.IsTrue(sp1SceneBPostCross.IsSatOnObject); + + Assert.IsNull(sceneA.GetSceneObjectGroup(so1Id), "uck"); + so1PostCross = sceneB.GetSceneObjectGroup(so1Id); + Assert.NotNull(so1PostCross); + Assert.AreEqual(1, so1PostCross.GetSittingAvatarsCount()); + } + + Vector3 so1PostCrossPos = so1PostCross.AbsolutePosition; + +// Console.WriteLine("CRISSCROSS"); + + // Recross + sceneB.SceneGraph.UpdatePrimGroupPosition( + so1PostCross.LocalId, new Vector3(so1PostCrossPos.X, so1PostCrossPos.Y + 20, so1PostCrossPos.Z), userId); + + { + ScenePresence sp1SceneBPostReCross = sceneB.GetScenePresence(userId); + Assert.IsTrue(sp1SceneBPostReCross.IsChildAgent, "sp1SceneBPostReCross.IsChildAgent unexpectedly false"); + + ScenePresence sp1SceneAPostReCross = sceneA.GetScenePresence(userId); + TestClient sceneATc = ((TestClient)sp1SceneAPostReCross.ControllingClient); + sceneATc.CompleteMovement(); + + Assert.IsFalse(sp1SceneAPostReCross.IsChildAgent, "sp1SceneAPostCross.IsChildAgent unexpectedly true"); + Assert.IsTrue(sp1SceneAPostReCross.IsSatOnObject); + + Assert.IsNull(sceneB.GetSceneObjectGroup(so1Id), "uck2"); + SceneObjectGroup so1PostReCross = sceneA.GetSceneObjectGroup(so1Id); + Assert.NotNull(so1PostReCross); + Assert.AreEqual(1, so1PostReCross.GetSittingAvatarsCount()); + } + } + + /// + /// Test cross with no prim limit module. + /// + /// + /// XXX: This test may FCbe better off in a specific PrimLimitsModuleTest class in optional module tests in the + /// future (though it is configured as active by default, so not really optional). + /// + [Test] + public void TestCrossOnSameSimulatorPrimLimitsOkay() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + LandManagementModule lmmA = new LandManagementModule(); + LandManagementModule lmmB = new LandManagementModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + IConfig permissionsConfig = config.AddConfig("Permissions"); + permissionsConfig.Set("permissionmodules", "PrimLimitsModule"); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules( + sceneA, config, etmA, lmmA, new PrimLimitsModule(), new PrimCountModule()); + SceneHelpers.SetupSceneModules( + sceneB, config, etmB, lmmB, new PrimLimitsModule(), new PrimCountModule()); + + // We must set up the parcel for this to work. Normally this is taken care of by OpenSimulator startup + // code which is not yet easily invoked by tests. + lmmA.EventManagerOnNoLandDataFromStorage(); + lmmB.EventManagerOnNoLandDataFromStorage(); + + SceneObjectGroup so1 = SceneHelpers.AddSceneObject(sceneA, 1, userId, "", sceneObjectIdTail); + UUID so1Id = so1.UUID; + so1.AbsolutePosition = new Vector3(128, 10, 20); + + // Cross with a negative value. We must make this call rather than setting AbsolutePosition directly + // because only this will execute permission checks in the source region. + sceneA.SceneGraph.UpdatePrimGroupPosition(so1.LocalId, new Vector3(128, -10, 20), userId); + + Assert.IsNull(sceneA.GetSceneObjectGroup(so1Id)); + Assert.NotNull(sceneB.GetSceneObjectGroup(so1Id)); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectDeRezTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectDeRezTests.cs index c1522e7..1c396ac 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectDeRezTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectDeRezTests.cs @@ -32,13 +32,13 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Region.CoreModules.World.Permissions; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -52,6 +52,24 @@ namespace OpenSim.Region.Framework.Scenes.Tests [TestFixture] public class SceneObjectDeRezTests : OpenSimTestCase { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + // This facility was added after the original async delete tests were written, so it may be possible now + // to not bother explicitly disabling their async (since everything will be running sync). + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + /// /// Test deleting an object from a scene. /// @@ -59,46 +77,96 @@ namespace OpenSim.Region.Framework.Scenes.Tests public void TestDeRezSceneObject() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); UUID userId = UUID.Parse("10000000-0000-0000-0000-000000000001"); TestScene scene = new SceneHelpers().SetupScene(); - IConfigSource configSource = new IniConfigSource(); - IConfig config = configSource.AddConfig("Startup"); - config.Set("serverside_object_permissions", true); - SceneHelpers.SetupSceneModules(scene, configSource, new object[] { new PermissionsModule() }); - IClientAPI client = SceneHelpers.AddScenePresence(scene, userId).ControllingClient; + SceneHelpers.SetupSceneModules(scene, new PermissionsModule()); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, userId).ControllingClient; // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; sogd.Enabled = false; - - SceneObjectPart part - = new SceneObjectPart(userId, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero); - part.Name = "obj1"; - scene.AddNewSceneObject(new SceneObjectGroup(part), false); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", userId); + uint soLocalId = so.LocalId; List localIds = new List(); - localIds.Add(part.LocalId); + localIds.Add(so.LocalId); scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.Delete, UUID.Zero); // Check that object isn't deleted until we crank the sogd handle. - SceneObjectPart retrievedPart = scene.GetSceneObjectPart(part.LocalId); + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); Assert.That(retrievedPart, Is.Not.Null); Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); sogd.InventoryDeQueueAndDelete(); - SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(part.LocalId); - Assert.That(retrievedPart2, Is.Null); + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Null); + + Assert.That(client.ReceivedKills.Count, Is.EqualTo(1)); + Assert.That(client.ReceivedKills[0], Is.EqualTo(soLocalId)); + } + + /// + /// Test that child and root agents correctly receive KillObject notifications. + /// + [Test] + public void TestDeRezSceneObjectToAgents() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + // We need this so that the creation of the root client for userB in sceneB can trigger the creation of a child client in sceneA + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + EntityTransferModule etmB = new EntityTransferModule(); + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmB.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + + // We need this for derez + SceneHelpers.SetupSceneModules(sceneA, new PermissionsModule()); + + UserAccount uaA = UserAccountHelpers.CreateUserWithInventory(sceneA, "Andy", "AAA", 0x1, ""); + UserAccount uaB = UserAccountHelpers.CreateUserWithInventory(sceneA, "Brian", "BBB", 0x2, ""); + + TestClient clientA = (TestClient)SceneHelpers.AddScenePresence(sceneA, uaA).ControllingClient; + + // This is the more long-winded route we have to take to get a child client created for userB in sceneA + // rather than just calling AddScenePresence() as for userA + AgentCircuitData acd = SceneHelpers.GenerateAgentData(uaB); + TestClient clientB = new TestClient(acd, sceneB); + List childClientsB = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(clientB, childClientsB); + + SceneHelpers.AddScenePresence(sceneB, clientB, acd); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(sceneA); + uint soLocalId = so.LocalId; + + sceneA.DeleteSceneObject(so, false); + + Assert.That(clientA.ReceivedKills.Count, Is.EqualTo(1)); + Assert.That(clientA.ReceivedKills[0], Is.EqualTo(soLocalId)); + + Assert.That(childClientsB[0].ReceivedKills.Count, Is.EqualTo(1)); + Assert.That(childClientsB[0].ReceivedKills[0], Is.EqualTo(soLocalId)); } /// /// Test deleting an object from a scene where the deleter is not the owner /// - /// + /// /// This test assumes that the deleter is not a god. + /// [Test] public void TestDeRezSceneObjectNotOwner() { @@ -109,10 +177,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests UUID objectOwnerId = UUID.Parse("20000000-0000-0000-0000-000000000001"); TestScene scene = new SceneHelpers().SetupScene(); - IConfigSource configSource = new IniConfigSource(); - IConfig config = configSource.AddConfig("Startup"); - config.Set("serverside_object_permissions", true); - SceneHelpers.SetupSceneModules(scene, configSource, new object[] { new PermissionsModule() }); + SceneHelpers.SetupSceneModules(scene, new PermissionsModule()); IClientAPI client = SceneHelpers.AddScenePresence(scene, userId).ControllingClient; // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. @@ -164,7 +229,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, agentId); InventoryFolderBase folder1 - = UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, ua.PrincipalID, "folder1"); + = UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, ua.PrincipalID, "folder1", false); IClientAPI client = SceneHelpers.AddScenePresence(scene, agentId).ControllingClient; scene.DeRezObjects(client, new List() { so.LocalId }, UUID.Zero, DeRezAction.Take, folder1.ID); diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectLinkingTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectLinkingTests.cs index 9378e20..e6d5a2f 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectLinkingTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectLinkingTests.cs @@ -31,10 +31,8 @@ using System.Reflection; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; using log4net; namespace OpenSim.Region.Framework.Scenes.Tests @@ -91,7 +89,12 @@ namespace OpenSim.Region.Framework.Scenes.Tests grp2.RootPart.ClearUpdateSchedule(); // Link grp2 to grp1. part2 becomes child prim to grp1. grp2 is eliminated. + Assert.IsFalse(grp1.GroupContainsForeignPrims); grp1.LinkToGroup(grp2); + Assert.IsTrue(grp1.GroupContainsForeignPrims); + + scene.Backup(true); + Assert.IsFalse(grp1.GroupContainsForeignPrims); // FIXME: Can't do this test yet since group 2 still has its root part! We can't yet null this since // it might cause SOG.ProcessBackup() to fail due to the race condition. This really needs to be fixed. @@ -143,7 +146,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(grp1.Parts.Length, Is.EqualTo(1), "Group 1 still contained part2 after delink."); Assert.That(part2.AbsolutePosition == Vector3.Zero, "The absolute position should be zero"); - Assert.That(grp3.HasGroupChangedDueToDelink, Is.True); + Assert.NotNull(grp3); } [Test] @@ -335,30 +338,34 @@ namespace OpenSim.Region.Framework.Scenes.Tests SceneObjectPart rootPart = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) { Name = rootPartName, UUID = rootPartUuid }; + SceneObjectPart linkPart = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) { Name = linkPartName, UUID = linkPartUuid }; + SceneObjectGroup linkGroup = new SceneObjectGroup(linkPart); + scene.AddNewSceneObject(linkGroup, true); SceneObjectGroup sog = new SceneObjectGroup(rootPart); - sog.AddPart(linkPart); - scene.AddNewSceneObject(sog, true); - - // In a test, we have to crank the backup handle manually. Normally this would be done by the timer invoked - // scene backup thread. + scene.AddNewSceneObject(sog, true); + + Assert.IsFalse(sog.GroupContainsForeignPrims); + sog.LinkToGroup(linkGroup); + Assert.IsTrue(sog.GroupContainsForeignPrims); + scene.Backup(true); - + Assert.AreEqual(1, scene.SimulationDataService.LoadObjects(scene.RegionInfo.RegionID).Count); + // These changes should occur immediately without waiting for a backup pass SceneObjectGroup groupToDelete = sog.DelinkFromGroup(linkPart, false); - - Assert.That(groupToDelete.HasGroupChangedDueToDelink, Is.True); + Assert.IsFalse(groupToDelete.GroupContainsForeignPrims); + scene.DeleteSceneObject(groupToDelete, false); - Assert.That(groupToDelete.HasGroupChangedDueToDelink, Is.False); List storedObjects = scene.SimulationDataService.LoadObjects(scene.RegionInfo.RegionID); - - Assert.That(storedObjects.Count, Is.EqualTo(1)); - Assert.That(storedObjects[0].Parts.Length, Is.EqualTo(1)); - Assert.That(storedObjects[0].ContainsPart(rootPartUuid)); + + Assert.AreEqual(1, storedObjects.Count); + Assert.AreEqual(1, storedObjects[0].Parts.Length); + Assert.IsTrue(storedObjects[0].ContainsPart(rootPartUuid)); } } } diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectResizeTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectResizeTests.cs index c264433..975c4d9 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectResizeTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectResizeTests.cs @@ -30,10 +30,8 @@ using System.Reflection; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectScriptTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectScriptTests.cs index a58e735..8a2d2af 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectScriptTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectScriptTests.cs @@ -31,11 +31,9 @@ using System.Reflection; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSerializationTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSerializationTests.cs new file mode 100644 index 0000000..e3ceb04 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSerializationTests.cs @@ -0,0 +1,135 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Xml; +using System.Linq; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Serialization.External; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Basic scene object serialization tests. + /// + [TestFixture] + public class SceneObjectSerializationTests : OpenSimTestCase + { + + /// + /// Serialize and deserialize. + /// + [Test] + public void TestSerialDeserial() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + int partsToTestCount = 3; + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(partsToTestCount, TestHelpers.ParseTail(0x1), "obj1", 0x10); + SceneObjectPart[] parts = so.Parts; + so.Name = "obj1"; + so.Description = "xpto"; + + string xml = SceneObjectSerializer.ToXml2Format(so); + Assert.That(!string.IsNullOrEmpty(xml), "SOG serialization resulted in empty or null string"); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + XmlNodeList nodes = doc.GetElementsByTagName("SceneObjectPart"); + Assert.That(nodes.Count, Is.EqualTo(3), "SOG serialization resulted in wrong number of SOPs"); + + SceneObjectGroup so2 = SceneObjectSerializer.FromXml2Format(xml); + Assert.IsNotNull(so2, "SOG deserialization resulted in null object"); + Assert.That(so2.Name == so.Name, "Name of deserialized object does not match original name"); + Assert.That(so2.Description == so.Description, "Description of deserialized object does not match original name"); + } + + /// + /// This checks for a bug reported in mantis #7514 + /// + [Test] + public void TestNamespaceAttribute() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount account = new UserAccount(UUID.Zero, UUID.Random(), "Test", "User", string.Empty); + scene.UserAccountService.StoreUserAccount(account); + int partsToTestCount = 1; + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(partsToTestCount, TestHelpers.ParseTail(0x1), "obj1", 0x10); + SceneObjectPart[] parts = so.Parts; + so.Name = "obj1"; + so.Description = "xpto"; + so.OwnerID = account.PrincipalID; + so.RootPart.CreatorID = so.OwnerID; + + string xml = SceneObjectSerializer.ToXml2Format(so); + Assert.That(!string.IsNullOrEmpty(xml), "SOG serialization resulted in empty or null string"); + + xml = ExternalRepresentationUtils.RewriteSOP(xml, "Test Scene", "http://localhost", scene.UserAccountService, UUID.Zero); + //Console.WriteLine(xml); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + XmlNodeList nodes = doc.GetElementsByTagName("SceneObjectPart"); + Assert.That(nodes.Count, Is.GreaterThan(0), "SOG serialization resulted in no SOPs"); + foreach (XmlAttribute a in nodes[0].Attributes) + { + int count = a.Name.Count(c => c == ':'); + Assert.That(count, Is.EqualTo(1), "Cannot have multiple ':' in attribute name in SOP"); + } + nodes = doc.GetElementsByTagName("CreatorData"); + Assert.That(nodes.Count, Is.GreaterThan(0), "SOG serialization resulted in no CreatorData"); + foreach (XmlAttribute a in nodes[0].Attributes) + { + int count = a.Name.Count(c => c == ':'); + Assert.That(count, Is.EqualTo(1), "Cannot have multiple ':' in attribute name in CreatorData"); + } + + SceneObjectGroup so2 = SceneObjectSerializer.FromXml2Format(xml); + Assert.IsNotNull(so2, "SOG deserialization resulted in null object"); + Assert.AreNotEqual(so.RootPart.CreatorIdentification, so2.RootPart.CreatorIdentification, "RewriteSOP failed to transform CreatorData."); + Assert.That(so2.RootPart.CreatorIdentification.Contains("http://"), "RewriteSOP failed to add the homeURL to CreatorData"); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs index abaa1d1..e00defd 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectSpatialTests.cs @@ -31,10 +31,8 @@ using System.Threading; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectStatusTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectStatusTests.cs index 8eb3191..1737e3c 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectStatusTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectStatusTests.cs @@ -31,10 +31,8 @@ using System.Reflection; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -58,6 +56,25 @@ namespace OpenSim.Region.Framework.Scenes.Tests } [Test] + public void TestSetTemporary() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + m_so1.ScriptSetTemporaryStatus(true); + + // Is this really the correct flag? + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.TemporaryOnRez)); + Assert.That(m_so1.Backup, Is.False); + + // Test setting back to non-temporary + m_so1.ScriptSetTemporaryStatus(false); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.None)); + Assert.That(m_so1.Backup, Is.True); + } + + [Test] public void TestSetPhantomSinglePrim() { TestHelpers.InMethod(); diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUndoRedoTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUndoRedoTests.cs index 96973de..af3ce8e 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUndoRedoTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUndoRedoTests.cs @@ -30,10 +30,8 @@ using System.Reflection; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -110,8 +108,8 @@ namespace OpenSim.Region.Framework.Scenes.Tests Vector3 firstSize = new Vector3(2, 3, 4); Vector3 secondSize = new Vector3(5, 6, 7); - Vector3 thirdSize = new Vector3(8, 9, 10); - Vector3 fourthSize = new Vector3(11, 12, 13); +// Vector3 thirdSize = new Vector3(8, 9, 10); +// Vector3 fourthSize = new Vector3(11, 12, 13); Scene scene = new SceneHelpers().SetupScene(); scene.MaxUndoCount = 20; diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUserGroupTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUserGroupTests.cs index 2b79271..aadf7c6 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUserGroupTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneObjectUserGroupTests.cs @@ -32,14 +32,12 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.CoreModules.Avatar.InstantMessage; using OpenSim.Region.CoreModules.World.Permissions; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs index 5faf131..96d112d 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs @@ -36,7 +36,6 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.ClientStack.Linden; @@ -44,7 +43,6 @@ using OpenSim.Region.CoreModules.Framework.EntityTransfer; using OpenSim.Region.CoreModules.World.Serialiser; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; using GridRegion = OpenSim.Services.Interfaces.GridRegion; namespace OpenSim.Region.Framework.Scenes.Tests @@ -111,6 +109,45 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); } + /// + /// Test that duplicate complete movement calls are ignored. + /// + /// + /// If duplicate calls are not ignored then there is a risk of race conditions or other unexpected effects. + /// + [Test] + public void TestDupeCompleteMovementCalls() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + TestScene scene = new SceneHelpers().SetupScene(); + + int makeRootAgentEvents = 0; + scene.EventManager.OnMakeRootAgent += spi => makeRootAgentEvents++; + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, spUuid); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Normally these would be invoked by a CompleteMovement message coming in to the UDP stack. But for + // convenience, here we will invoke it manually. + sp.CompleteMovement(sp.ControllingClient, true); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Check rest of exepcted parameters. + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(spUuid), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + Assert.That(sp.IsChildAgent, Is.False); + Assert.That(sp.UUID, Is.EqualTo(spUuid)); + + Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); + } + [Test] public void TestCreateDuplicateRootScenePresence() { @@ -119,7 +156,20 @@ namespace OpenSim.Region.Framework.Scenes.Tests UUID spUuid = TestHelpers.ParseTail(0x1); + // The etm is only invoked by this test to check whether an agent is still in transit if there is a dupe + EntityTransferModule etm = new EntityTransferModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + TestScene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, config, etm); SceneHelpers.AddScenePresence(scene, spUuid); SceneHelpers.AddScenePresence(scene, spUuid); @@ -133,7 +183,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests } [Test] - public void TestCloseAgent() + public void TestCloseClient() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -141,7 +191,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests TestScene scene = new SceneHelpers().SetupScene(); ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); - scene.IncomingCloseAgent(sp.UUID, false); + scene.CloseAgent(sp.UUID, false); Assert.That(scene.GetScenePresence(sp.UUID), Is.Null); Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(sp.UUID), Is.Null); @@ -176,7 +226,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests // *** This is the first stage, when a neighbouring region is told that a viewer is about to try and // establish a child scene presence. We pass in the circuit code that the client has to connect with *** // XXX: ViaLogin may not be correct here. - scene.SimulationService.CreateAgent(region, acd, (uint)TeleportFlags.ViaLogin, out reason); + scene.SimulationService.CreateAgent(null, region, acd, (uint)TeleportFlags.ViaLogin, out reason); Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(agentId), Is.Not.Null); Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); @@ -187,7 +237,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests // *** This is the second stage, where the client established a child agent/scene presence using the // circuit code given to the scene in stage 1 *** TestClient client = new TestClient(acd, scene); - scene.AddNewClient(client, PresenceType.User); + scene.AddNewAgent(client, PresenceType.User); Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(agentId), Is.Not.Null); Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); @@ -236,161 +286,5 @@ namespace OpenSim.Region.Framework.Scenes.Tests // Assert.That(childPresence, Is.Not.Null); // Assert.That(childPresence.IsChildAgent, Is.True); } - -// /// -// /// Test adding a root agent to a scene. Doesn't yet actually complete crossing the agent into the scene. -// /// -// [Test] -// public void T010_TestAddRootAgent() -// { -// TestHelpers.InMethod(); -// -// string firstName = "testfirstname"; -// -// AgentCircuitData agent = new AgentCircuitData(); -// agent.AgentID = agent1; -// agent.firstname = firstName; -// agent.lastname = "testlastname"; -// agent.SessionID = UUID.Random(); -// agent.SecureSessionID = UUID.Random(); -// agent.circuitcode = 123; -// agent.BaseFolder = UUID.Zero; -// agent.InventoryFolder = UUID.Zero; -// agent.startpos = Vector3.Zero; -// agent.CapsPath = GetRandomCapsObjectPath(); -// agent.ChildrenCapSeeds = new Dictionary(); -// agent.child = true; -// -// scene.PresenceService.LoginAgent(agent.AgentID.ToString(), agent.SessionID, agent.SecureSessionID); -// -// string reason; -// scene.NewUserConnection(agent, (uint)TeleportFlags.ViaLogin, out reason); -// testclient = new TestClient(agent, scene); -// scene.AddNewClient(testclient); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// -// Assert.That(presence, Is.Not.Null, "presence is null"); -// Assert.That(presence.Firstname, Is.EqualTo(firstName), "First name not same"); -// acd1 = agent; -// } -// -// /// -// /// Test removing an uncrossed root agent from a scene. -// /// -// [Test] -// public void T011_TestRemoveRootAgent() -// { -// TestHelpers.InMethod(); -// -// scene.RemoveClient(agent1); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// -// Assert.That(presence, Is.Null, "presence is not null"); -// } - - // I'm commenting this test because it does not represent - // crossings. The Thread.Sleep's in here are not meaningful mocks, - // and they sometimes fail in panda. - // We need to talk in order to develop a test - // that really tests region crossings. There are 3 async components, - // but things are synchronous among them. So there should be - // 3 threads in here. - //[Test] -// public void T021_TestCrossToNewRegion() -// { -// TestHelpers.InMethod(); -// -// scene.RegisterRegionWithGrid(); -// scene2.RegisterRegionWithGrid(); -// -// // Adding child agent to region 1001 -// string reason; -// scene2.NewUserConnection(acd1,0, out reason); -// scene2.AddNewClient(testclient, PresenceType.User); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// presence.MakeRootAgent(new Vector3(0,unchecked(Constants.RegionSize-1),0), true); -// -// ScenePresence presence2 = scene2.GetScenePresence(agent1); -// -// // Adding neighbour region caps info to presence2 -// -// string cap = presence.ControllingClient.RequestClientInfo().CapsPath; -// presence2.AddNeighbourRegion(region1, cap); -// -// Assert.That(presence.IsChildAgent, Is.False, "Did not start root in origin region."); -// Assert.That(presence2.IsChildAgent, Is.True, "Is not a child on destination region."); -// -// // Cross to x+1 -// presence.AbsolutePosition = new Vector3(Constants.RegionSize+1,3,100); -// presence.Update(); -// -// EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.AutoReset, "Crossing"); -// -// // Mimicking communication between client and server, by waiting OK from client -// // sent by TestClient.CrossRegion call. Originally, this is network comm. -// if (!wh.WaitOne(5000,false)) -// { -// presence.Update(); -// if (!wh.WaitOne(8000,false)) -// throw new ArgumentException("1 - Timeout waiting for signal/variable."); -// } -// -// // This is a TestClient specific method that fires OnCompleteMovementToRegion event, which -// // would normally be fired after receiving the reply packet from comm. done on the last line. -// testclient.CompleteMovement(); -// -// // Crossings are asynchronous -// int timer = 10; -// -// // Make sure cross hasn't already finished -// if (!presence.IsInTransit && !presence.IsChildAgent) -// { -// // If not and not in transit yet, give it some more time -// Thread.Sleep(5000); -// } -// -// // Enough time, should at least be in transit by now. -// while (presence.IsInTransit && timer > 0) -// { -// Thread.Sleep(1000); -// timer-=1; -// } -// -// Assert.That(timer,Is.GreaterThan(0),"Timed out waiting to cross 2->1."); -// Assert.That(presence.IsChildAgent, Is.True, "Did not complete region cross as expected."); -// Assert.That(presence2.IsChildAgent, Is.False, "Did not receive root status after receiving agent."); -// -// // Cross Back -// presence2.AbsolutePosition = new Vector3(-10, 3, 100); -// presence2.Update(); -// -// if (!wh.WaitOne(5000,false)) -// { -// presence2.Update(); -// if (!wh.WaitOne(8000,false)) -// throw new ArgumentException("2 - Timeout waiting for signal/variable."); -// } -// testclient.CompleteMovement(); -// -// if (!presence2.IsInTransit && !presence2.IsChildAgent) -// { -// // If not and not in transit yet, give it some more time -// Thread.Sleep(5000); -// } -// -// // Enough time, should at least be in transit by now. -// while (presence2.IsInTransit && timer > 0) -// { -// Thread.Sleep(1000); -// timer-=1; -// } -// -// Assert.That(timer,Is.GreaterThan(0),"Timed out waiting to cross 1->2."); -// Assert.That(presence2.IsChildAgent, Is.True, "Did not return from region as expected."); -// Assert.That(presence.IsChildAgent, Is.False, "Presence was not made root in old region again."); -// } } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAnimationTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAnimationTests.cs index 1cd8ae9..42d91b9 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAnimationTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAnimationTests.cs @@ -35,15 +35,13 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.Framework.EntityTransfer; using OpenSim.Region.CoreModules.World.Serialiser; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; -using OpenSim.Region.Physics.Manager; +using OpenSim.Region.PhysicsModules.SharedBase; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAutopilotTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAutopilotTests.cs index d80afd3..e5c847e 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAutopilotTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAutopilotTests.cs @@ -33,11 +33,9 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCapabilityTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCapabilityTests.cs new file mode 100644 index 0000000..2e6dc70 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCapabilityTests.cs @@ -0,0 +1,86 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Timers; +using Timer = System.Timers.Timer; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Tests.Common; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class ScenePresenceCapabilityTests : OpenSimTestCase + { + [Test] + public void TestChildAgentSingleRegionCapabilities() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + // XXX: This is not great since the use of statics will mean that this has to be manually cleaned up for + // any subsequent test. + // XXX: May replace with a mock IHttpServer later. + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + + CapabilitiesModule capsMod = new CapabilitiesModule(); + TestScene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, capsMod); + + ScenePresence sp = SceneHelpers.AddChildScenePresence(scene, spUuid); + Assert.That(capsMod.GetCapsForUser(spUuid), Is.Not.Null); + + // TODO: Need to add tests for other ICapabiltiesModule methods. + + scene.CloseAgent(sp.UUID, false); + Assert.That(capsMod.GetCapsForUser(spUuid), Is.Null); + + // TODO: Need to add tests for other ICapabiltiesModule methods. + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs new file mode 100644 index 0000000..7127644 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs @@ -0,0 +1,247 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class ScenePresenceCrossingTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [Test] + public void TestCrossOnSameSimulator() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + +// TestEventQueueGetModule eqmA = new TestEventQueueGetModule(); + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); +// IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. +// entityTransferConfig.Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); +// SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA, eqmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + originalSp.AbsolutePosition = new Vector3(128, 32, 10); + +// originalSp.Flying = true; + +// Console.WriteLine("First pos {0}", originalSp.AbsolutePosition); + +// eqmA.ClearEvents(); + + AgentUpdateArgs moveArgs = new AgentUpdateArgs(); + //moveArgs.BodyRotation = Quaternion.CreateFromEulers(Vector3.Zero); + moveArgs.BodyRotation = Quaternion.CreateFromEulers(new Vector3(0, 0, (float)-(Math.PI / 2))); + moveArgs.ControlFlags = (uint)AgentManager.ControlFlags.AGENT_CONTROL_AT_POS; + moveArgs.SessionID = acd.SessionID; + + originalSp.HandleAgentUpdate(originalSp.ControllingClient, moveArgs); + + sceneA.Update(1); + +// Console.WriteLine("Second pos {0}", originalSp.AbsolutePosition); + + // FIXME: This is a sufficient number of updates to for the presence to reach the northern border. + // But really we want to do this in a more robust way. + for (int i = 0; i < 100; i++) + { + sceneA.Update(1); +// Console.WriteLine("Pos {0}", originalSp.AbsolutePosition); + } + + // Need to sort processing of EnableSimulator message on adding scene presences before we can test eqm + // messages +// Dictionary> eqmEvents = eqmA.Events; +// +// Assert.That(eqmEvents.Count, Is.EqualTo(1)); +// Assert.That(eqmEvents.ContainsKey(originalSp.UUID), Is.True); +// +// List spEqmEvents = eqmEvents[originalSp.UUID]; +// +// Assert.That(spEqmEvents.Count, Is.EqualTo(1)); +// Assert.That(spEqmEvents[0].Name, Is.EqualTo("CrossRegion")); + + // sceneA should now only have a child agent + ScenePresence spAfterCrossSceneA = sceneA.GetScenePresence(originalSp.UUID); + Assert.That(spAfterCrossSceneA.IsChildAgent, Is.True); + + ScenePresence spAfterCrossSceneB = sceneB.GetScenePresence(originalSp.UUID); + + // Agent remains a child until the client triggers complete movement + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.True); + + TestClient sceneBTc = ((TestClient)spAfterCrossSceneB.ControllingClient); + + int agentMovementCompleteReceived = 0; + sceneBTc.OnReceivedMoveAgentIntoRegion += (ri, pos, look) => agentMovementCompleteReceived++; + + sceneBTc.CompleteMovement(); + + Assert.That(agentMovementCompleteReceived, Is.EqualTo(1)); + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.False); + } + + /// + /// Test a cross attempt where the user can see into the neighbour but does not have permission to become + /// root there. + /// + [Test] + public void TestCrossOnSameSimulatorNoRootDestPerm() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + + // We need to set up the permisions module on scene B so that our later use of agent limit to deny + // QueryAccess won't succeed anyway because administrators are always allowed in and the default + // IsAdministrator if no permissions module is present is true. + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), new PermissionsModule(), etmB); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + // Make sure sceneB will not accept this avatar. + sceneB.RegionInfo.EstateSettings.PublicAccess = false; + + ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + originalSp.AbsolutePosition = new Vector3(128, 32, 10); + + AgentUpdateArgs moveArgs = new AgentUpdateArgs(); + //moveArgs.BodyRotation = Quaternion.CreateFromEulers(Vector3.Zero); + moveArgs.BodyRotation = Quaternion.CreateFromEulers(new Vector3(0, 0, (float)-(Math.PI / 2))); + moveArgs.ControlFlags = (uint)AgentManager.ControlFlags.AGENT_CONTROL_AT_POS; + moveArgs.SessionID = acd.SessionID; + + originalSp.HandleAgentUpdate(originalSp.ControllingClient, moveArgs); + + sceneA.Update(1); + +// Console.WriteLine("Second pos {0}", originalSp.AbsolutePosition); + + // FIXME: This is a sufficient number of updates to for the presence to reach the northern border. + // But really we want to do this in a more robust way. + for (int i = 0; i < 100; i++) + { + sceneA.Update(1); +// Console.WriteLine("Pos {0}", originalSp.AbsolutePosition); + } + + // sceneA agent should still be root + ScenePresence spAfterCrossSceneA = sceneA.GetScenePresence(originalSp.UUID); + Assert.That(spAfterCrossSceneA.IsChildAgent, Is.False); + + ScenePresence spAfterCrossSceneB = sceneB.GetScenePresence(originalSp.UUID); + + // sceneB agent should also still be root + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.True); + + // sceneB should ignore unauthorized attempt to upgrade agent to root + TestClient sceneBTc = ((TestClient)spAfterCrossSceneB.ControllingClient); + + int agentMovementCompleteReceived = 0; + sceneBTc.OnReceivedMoveAgentIntoRegion += (ri, pos, look) => agentMovementCompleteReceived++; + + sceneBTc.CompleteMovement(); + + Assert.That(agentMovementCompleteReceived, Is.EqualTo(0)); + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.True); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceSitTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceSitTests.cs index acaeb90..b232a44 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceSitTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceSitTests.cs @@ -32,12 +32,10 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; using System.Threading; namespace OpenSim.Region.Framework.Scenes.Tests @@ -73,6 +71,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(0)); Assert.That(part.GetSittingAvatars(), Is.Null); Assert.That(m_sp.ParentID, Is.EqualTo(0)); + Assert.AreEqual(startPos, m_sp.AbsolutePosition); } [Test] @@ -87,15 +86,21 @@ namespace OpenSim.Region.Framework.Scenes.Tests SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + // We need to preserve this here because phys actor is removed by the sit. + Vector3 spPhysActorSize = m_sp.PhysicsActor.Size; m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); Assert.That(m_sp.PhysicsActor, Is.Null); + Assert.That( + m_sp.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, spPhysActorSize.Z / 2))); + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(1)); - HashSet sittingAvatars = part.GetSittingAvatars(); + HashSet sittingAvatars = part.GetSittingAvatars(); Assert.That(sittingAvatars.Count, Is.EqualTo(1)); - Assert.That(sittingAvatars.Contains(m_sp.UUID)); + Assert.That(sittingAvatars.Contains(m_sp)); Assert.That(m_sp.ParentID, Is.EqualTo(part.LocalId)); } @@ -111,15 +116,43 @@ namespace OpenSim.Region.Framework.Scenes.Tests SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + // We need to preserve this here because phys actor is removed by the sit. + Vector3 spPhysActorSize = m_sp.PhysicsActor.Size; + m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); + + Assert.That( + m_sp.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, spPhysActorSize.Z / 2))); + + m_sp.StandUp(); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(0)); + Assert.That(part.GetSittingAvatars(), Is.Null); + Assert.That(m_sp.ParentID, Is.EqualTo(0)); + Assert.That(m_sp.PhysicsActor, Is.Not.Null); + } + + [Test] + public void TestSitAndStandWithNoSitTargetChildPrim() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + // Make sure we're within range to sit + Vector3 startPos = new Vector3(1, 1, 1); + m_sp.AbsolutePosition = startPos; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene, 2, m_sp.UUID, "part", 0x10).Parts[1]; + part.OffsetPosition = new Vector3(2, 3, 4); + + // We need to preserve this here because phys actor is removed by the sit. + Vector3 spPhysActorSize = m_sp.PhysicsActor.Size; m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); - // FIXME: This is different for live avatars - z position is adjusted. This is half the height of the - // default avatar. - // Curiously, Vector3.ToString() will not display the last two places of the float. For example, - // printing out npc.AbsolutePosition will give <0, 0, 0.8454993> not <0, 0, 0.845499337> Assert.That( m_sp.AbsolutePosition, - Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, 0.845499337f))); + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, spPhysActorSize.Z / 2))); m_sp.StandUp(); @@ -147,15 +180,39 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(part.SitTargetAvatar, Is.EqualTo(m_sp.UUID)); Assert.That(m_sp.ParentID, Is.EqualTo(part.LocalId)); + + // This section is copied from ScenePresence.HandleAgentSit(). Correctness is not guaranteed. + double x, y, z, m1, m2; + + Quaternion r = part.SitTargetOrientation;; + m1 = r.X * r.X + r.Y * r.Y; + m2 = r.Z * r.Z + r.W * r.W; + + // Rotate the vector <0, 0, 1> + x = 2 * (r.X * r.Z + r.Y * r.W); + y = 2 * (-r.X * r.W + r.Y * r.Z); + z = m2 - m1; + + // Set m to be the square of the norm of r. + double m = m1 + m2; + + // This constant is emperically determined to be what is used in SL. + // See also http://opensimulator.org/mantis/view.php?id=7096 + double offset = 0.05; + + Vector3 up = new Vector3((float)x, (float)y, (float)z); + Vector3 sitOffset = up * (float)offset; + // End of copied section. + Assert.That( m_sp.AbsolutePosition, - Is.EqualTo(part.AbsolutePosition + part.SitTargetPosition + ScenePresence.SIT_TARGET_ADJUSTMENT)); + Is.EqualTo(part.AbsolutePosition + part.SitTargetPosition - sitOffset + ScenePresence.SIT_TARGET_ADJUSTMENT)); Assert.That(m_sp.PhysicsActor, Is.Null); Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(1)); - HashSet sittingAvatars = part.GetSittingAvatars(); + HashSet sittingAvatars = part.GetSittingAvatars(); Assert.That(sittingAvatars.Count, Is.EqualTo(1)); - Assert.That(sittingAvatars.Contains(m_sp.UUID)); + Assert.That(sittingAvatars.Contains(m_sp)); m_sp.StandUp(); diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs index 8dd1f3d..443ec51 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs @@ -26,12 +26,15 @@ */ using System; -using System.Reflection; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Framework.Servers; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.Framework; @@ -39,9 +42,6 @@ using OpenSim.Region.CoreModules.Framework.EntityTransfer; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Region.CoreModules.World.Permissions; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; -using System.IO; -using System.Text; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -68,7 +68,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests } [Test] - public void TestSameRegionTeleport() + public void TestSameRegion() { TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); @@ -105,11 +105,12 @@ namespace OpenSim.Region.Framework.Scenes.Tests // Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); } +/* [Test] - public void TestSameSimulatorSeparatedRegionsTeleport() + public void TestSameSimulatorIsolatedRegionsV1() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); UUID userId = TestHelpers.ParseTail(0x1); @@ -135,15 +136,18 @@ namespace OpenSim.Region.Framework.Scenes.Tests SceneHelpers.SetupSceneModules(sceneB, config, etmB); SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + // FIXME: Hack - this is here temporarily to revert back to older entity transfer behaviour + lscm.ServiceVersion = 0.1f; + Vector3 teleportPosition = new Vector3(10, 11, 12); Vector3 teleportLookAt = new Vector3(20, 21, 22); - ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); sp.AbsolutePosition = new Vector3(30, 31, 32); - // XXX: A very nasty hack to tell the client about the destination scene without having to crank the whole - // UDP stack (?) -// ((TestClient)sp.ControllingClient).TeleportTargetScene = sceneB; + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate( + (TestClient)sp.ControllingClient, destinationTestClients); sceneA.RequestTeleportLocation( sp.ControllingClient, @@ -152,7 +156,72 @@ namespace OpenSim.Region.Framework.Scenes.Tests teleportLookAt, (uint)TeleportFlags.ViaLocation); - ((TestClient)sp.ControllingClient).CompleteTeleportClientSide(); + // SetupInformClientOfNeighbour() will have handled the callback into the target scene to setup the child + // agent. This call will now complete the movement of the user into the destination and upgrade the agent + // from child to root. + destinationTestClients[0].CompleteMovement(); + + Assert.That(sceneA.GetScenePresence(userId), Is.Null); + + ScenePresence sceneBSp = sceneB.GetScenePresence(userId); + Assert.That(sceneBSp, Is.Not.Null); + Assert.That(sceneBSp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneB.RegionInfo.RegionName)); + Assert.That(sceneBSp.AbsolutePosition, Is.EqualTo(teleportPosition)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + } +*/ + + [Test] + public void TestSameSimulatorIsolatedRegionsV2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = new Vector3(30, 31, 32); + + List destinationTestClients = new List(); + EntityTransferHelpers.SetupSendRegionTeleportTriggersDestinationClientCreateAndCompleteMovement( + (TestClient)sp.ControllingClient, destinationTestClients); + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); Assert.That(sceneA.GetScenePresence(userId), Is.Null); @@ -177,7 +246,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests /// Test teleport procedures when the target simulator returns false when queried about access. /// [Test] - public void TestSameSimulatorSeparatedRegionsQueryAccessFails() + public void TestSameSimulatorIsolatedRegions_DeniedOnQueryAccess() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -221,7 +290,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests Vector3 teleportPosition = new Vector3(10, 11, 12); Vector3 teleportLookAt = new Vector3(20, 21, 22); - ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); sp.AbsolutePosition = preTeleportPosition; // Make sceneB return false on query access @@ -261,7 +330,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests /// Test teleport procedures when the target simulator create agent step is refused. /// [Test] - public void TestSameSimulatorSeparatedRegionsCreateAgentFails() + public void TestSameSimulatorIsolatedRegions_DeniedOnCreateAgent() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -297,7 +366,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests Vector3 teleportPosition = new Vector3(10, 11, 12); Vector3 teleportLookAt = new Vector3(20, 21, 22); - ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); sp.AbsolutePosition = preTeleportPosition; // Make sceneB refuse CreateAgent @@ -333,8 +402,97 @@ namespace OpenSim.Region.Framework.Scenes.Tests // TestHelpers.DisableLogging(); } + /// + /// Test teleport when the destination region does not process (or does not receive) the connection attempt + /// from the viewer. + /// + /// + /// This could be quite a common case where the source region can connect to a remove destination region + /// (for CreateAgent) but the viewer cannot reach the destination region due to network issues. + /// [Test] - public void TestSameSimulatorNeighbouringRegionsTeleport() + public void TestSameSimulatorIsolatedRegions_DestinationDidNotProcessViewerConnection() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + Vector3 preTeleportPosition = new Vector3(30, 31, 32); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("EntityTransferModule", etmA.Name); + config.Configs["Modules"].Set("SimulationServices", lscm.Name); + + config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + config.Configs["EntityTransfer"].Set("wait_for_callback", false); + +// config.AddConfig("Startup"); +// config.Configs["Startup"].Set("serverside_object_permissions", true); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA ); + + // We need to set up the permisions module on scene B so that our later use of agent limit to deny + // QueryAccess won't succeed anyway because administrators are always allowed in and the default + // IsAdministrator if no permissions module is present is true. + SceneHelpers.SetupSceneModules(sceneB, config, new object[] { new PermissionsModule(), etmB }); + + // Shared scene modules + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = preTeleportPosition; + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // FIXME: Not setting up InformClientOfNeighbour on the TestClient means that it does not initiate + // communication with the destination region. But this is a very non-obvious way of doing it - really we + // should be forced to expicitly set this up. + + Assert.That(sceneB.GetScenePresence(userId), Is.Null); + + ScenePresence sceneASp = sceneA.GetScenePresence(userId); + Assert.That(sceneASp, Is.Not.Null); + Assert.That(sceneASp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneA.RegionInfo.RegionName)); + Assert.That(sceneASp.AbsolutePosition, Is.EqualTo(preTeleportPosition)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } + +/* + [Test] + public void TestSameSimulatorNeighbouringRegionsV1() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -363,13 +521,20 @@ namespace OpenSim.Region.Framework.Scenes.Tests SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + // FIXME: Hack - this is here temporarily to revert back to older entity transfer behaviour + lscm.ServiceVersion = 0.1f; + Vector3 teleportPosition = new Vector3(10, 11, 12); Vector3 teleportLookAt = new Vector3(20, 21, 22); - ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); - originalSp.AbsolutePosition = new Vector3(30, 31, 32); + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeSceneASp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeSceneASp.AbsolutePosition = new Vector3(30, 31, 32); - ScenePresence beforeSceneASp = sceneA.GetScenePresence(userId); Assert.That(beforeSceneASp, Is.Not.Null); Assert.That(beforeSceneASp.IsChildAgent, Is.False); @@ -377,10 +542,8 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(beforeSceneBSp, Is.Not.Null); Assert.That(beforeSceneBSp.IsChildAgent, Is.True); - // XXX: A very nasty hack to tell the client about the destination scene without having to crank the whole - // UDP stack (?) -// ((TestClient)beforeSceneASp.ControllingClient).TeleportTargetScene = sceneB; - + // In this case, we will not receieve a second InformClientOfNeighbour since the viewer already knows + // about the neighbour region it is teleporting to. sceneA.RequestTeleportLocation( beforeSceneASp.ControllingClient, sceneB.RegionInfo.RegionHandle, @@ -388,7 +551,92 @@ namespace OpenSim.Region.Framework.Scenes.Tests teleportLookAt, (uint)TeleportFlags.ViaLocation); - ((TestClient)beforeSceneASp.ControllingClient).CompleteTeleportClientSide(); + destinationTestClients[0].CompleteMovement(); + + ScenePresence afterSceneASp = sceneA.GetScenePresence(userId); + Assert.That(afterSceneASp, Is.Not.Null); + Assert.That(afterSceneASp.IsChildAgent, Is.True); + + ScenePresence afterSceneBSp = sceneB.GetScenePresence(userId); + Assert.That(afterSceneBSp, Is.Not.Null); + Assert.That(afterSceneBSp.IsChildAgent, Is.False); + Assert.That(afterSceneBSp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneB.RegionInfo.RegionName)); + Assert.That(afterSceneBSp.AbsolutePosition, Is.EqualTo(teleportPosition)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } +*/ + + [Test] + public void TestSameSimulatorNeighbouringRegionsV2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1001, 1000); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeSceneASp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeSceneASp.AbsolutePosition = new Vector3(30, 31, 32); + + Assert.That(beforeSceneASp, Is.Not.Null); + Assert.That(beforeSceneASp.IsChildAgent, Is.False); + + ScenePresence beforeSceneBSp = sceneB.GetScenePresence(userId); + Assert.That(beforeSceneBSp, Is.Not.Null); + Assert.That(beforeSceneBSp.IsChildAgent, Is.True); + + // Here, we need to make clientA's receipt of SendRegionTeleport trigger clientB's CompleteMovement(). This + // is to operate the teleport V2 mechanism where the EntityTransferModule will first request the client to + // CompleteMovement to the region and then call UpdateAgent to the destination region to confirm the receipt + // Both these operations will occur on different threads and will wait for each other. + // We have to do this via ThreadPool directly since FireAndForget has been switched to sync for the V1 + // test protocol, where we are trying to avoid unpredictable async operations in regression tests. + tc.OnTestClientSendRegionTeleport + += (regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL) + => ThreadPool.UnsafeQueueUserWorkItem(o => destinationTestClients[0].CompleteMovement(), null); + + sceneA.RequestTeleportLocation( + beforeSceneASp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); ScenePresence afterSceneASp = sceneA.GetScenePresence(userId); Assert.That(afterSceneASp, Is.Not.Null); @@ -414,4 +662,4 @@ namespace OpenSim.Region.Framework.Scenes.Tests // TestHelpers.DisableLogging(); } } -} \ No newline at end of file +} diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneStatisticsTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneStatisticsTests.cs new file mode 100644 index 0000000..045fd3c --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneStatisticsTests.cs @@ -0,0 +1,69 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class SceneStatisticsTests : OpenSimTestCase + { + private TestScene m_scene; + + [SetUp] + public void Init() + { + m_scene = new SceneHelpers().SetupScene(); + } + + [Test] + public void TestAddRemovePhysicalLinkset() + { + Assert.That(m_scene.SceneGraph.GetActiveObjectsCount(), Is.EqualTo(0)); + + UUID ownerId = TestHelpers.ParseTail(0x1); + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(3, ownerId, "so1", 0x10); + so1.ScriptSetPhysicsStatus(true); + m_scene.AddSceneObject(so1); + + Assert.That(m_scene.SceneGraph.GetTotalObjectsCount(), Is.EqualTo(3)); + Assert.That(m_scene.SceneGraph.GetActiveObjectsCount(), Is.EqualTo(3)); + + m_scene.DeleteSceneObject(so1, false); + + Assert.That(m_scene.SceneGraph.GetTotalObjectsCount(), Is.EqualTo(0)); + Assert.That(m_scene.SceneGraph.GetActiveObjectsCount(), Is.EqualTo(0)); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs new file mode 100644 index 0000000..584a03c --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs @@ -0,0 +1,118 @@ +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Estate; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene telehub tests + /// + /// + /// TODO: Tests which run through normal functionality. Currently, the only test is one that checks behaviour + /// in the case of an error condition + /// + [TestFixture] + public class SceneTelehubTests : OpenSimTestCase + { + /// + /// Test for desired behaviour when a telehub has no spawn points + /// + [Test] + public void TestNoTelehubSpawnPoints() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + + /// + /// Test for desired behaviour when the scene object nominated as a telehub object does not exist. + /// + [Test] + public void TestNoTelehubSceneObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + SceneObjectGroup spawnPointSo = SceneHelpers.AddSceneObject(scene, "spawnpointObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "spawnpoint add", spawnPointSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + scene.DeleteSceneObject(telehubSo, false); + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneTests.cs index 9d8eb0b..517faf1 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/SceneTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneTests.cs @@ -36,13 +36,11 @@ using Nini.Config; using NUnit.Framework; using OpenMetaverse; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.World.Serialiser; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -52,6 +50,29 @@ namespace OpenSim.Region.Framework.Scenes.Tests [TestFixture] public class SceneTests : OpenSimTestCase { + [Test] + public void TestCreateScene() + { + TestHelpers.InMethod(); + + new SceneHelpers().SetupScene(); + } + + [Test] + public void TestCreateVarScene() + { + TestHelpers.InMethod(); + UUID regionUuid = TestHelpers.ParseTail(0x1); + uint sizeX = 512; + uint sizeY = 512; + + Scene scene + = new SceneHelpers().SetupScene("scene", regionUuid, 1000, 1000, sizeX, sizeY, new IniConfigSource()); + + Assert.AreEqual(sizeX, scene.RegionInfo.RegionSizeX); + Assert.AreEqual(sizeY, scene.RegionInfo.RegionSizeY); + } + /// /// Very basic scene update test. Should become more elaborate with time. /// diff --git a/OpenSim/Region/Framework/Scenes/Tests/SharedRegionModuleTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SharedRegionModuleTests.cs new file mode 100644 index 0000000..eeda84f --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SharedRegionModuleTests.cs @@ -0,0 +1,249 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using Mono.Addins; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim; +using OpenSim.ApplicationPlugins.RegionModulesController; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + public class SharedRegionModuleTests : OpenSimTestCase + { +// [Test] + public void TestLifecycle() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + UUID estateOwnerId = TestHelpers.ParseTail(0x1); + UUID regionId = TestHelpers.ParseTail(0x10); + + IConfigSource configSource = new IniConfigSource(); + configSource.AddConfig("Startup"); + configSource.AddConfig("Modules"); + +// // We use this to skip estate questions + // Turns out not to be needed is estate owner id is pre-set in region information. +// IConfig estateConfig = configSource.AddConfig(OpenSimBase.ESTATE_SECTION_NAME); +// estateConfig.Set("DefaultEstateOwnerName", "Zaphod Beeblebrox"); +// estateConfig.Set("DefaultEstateOwnerUUID", estateOwnerId); +// estateConfig.Set("DefaultEstateOwnerEMail", "zaphod@galaxy.com"); +// estateConfig.Set("DefaultEstateOwnerPassword", "two heads"); + + // For grid servic + configSource.AddConfig("GridService"); + configSource.Configs["Modules"].Set("GridServices", "LocalGridServicesConnector"); + configSource.Configs["GridService"].Set("StorageProvider", "OpenSim.Data.Null.dll:NullRegionData"); + configSource.Configs["GridService"].Set("LocalServiceModule", "OpenSim.Services.GridService.dll:GridService"); + configSource.Configs["GridService"].Set("ConnectionString", "!static"); + + LocalGridServicesConnector gridService = new LocalGridServicesConnector(); +// + OpenSim sim = new OpenSim(configSource); + + sim.SuppressExit = true; + sim.EnableInitialPluginLoad = false; + sim.LoadEstateDataService = false; + sim.NetServersInfo.HttpListenerPort = 0; + + IRegistryCore reg = sim.ApplicationRegistry; + + RegionInfo ri = new RegionInfo(); + ri.RegionID = regionId; + ri.EstateSettings.EstateOwner = estateOwnerId; + ri.InternalEndPoint = new IPEndPoint(0, 0); + + MockRegionModulesControllerPlugin rmcp = new MockRegionModulesControllerPlugin(); + sim.m_plugins = new List() { rmcp }; + reg.RegisterInterface(rmcp); + + // XXX: Have to initialize directly for now + rmcp.Initialise(sim); + + rmcp.AddNode(gridService); + + TestSharedRegion tsr = new TestSharedRegion(); + rmcp.AddNode(tsr); + + // FIXME: Want to use the real one eventually but this is currently directly tied into Mono.Addins + // which has been written in such a way that makes it impossible to use for regression tests. +// RegionModulesControllerPlugin rmcp = new RegionModulesControllerPlugin(); +// rmcp.LoadModulesFromAddins = false; +//// reg.RegisterInterface(rmcp); +// rmcp.Initialise(sim); +// rmcp.PostInitialise(); +// TypeExtensionNode node = new TypeExtensionNode(); +// node. +// rmcp.AddNode(node, configSource.Configs["Modules"], new Dictionary>()); + + sim.Startup(); + IScene scene; + sim.CreateRegion(ri, out scene); + + sim.Shutdown(); + + List co = tsr.CallOrder; + int expectedEventCount = 6; + + Assert.AreEqual( + expectedEventCount, + co.Count, + "Expected {0} events but only got {1} ({2})", + expectedEventCount, co.Count, string.Join(",", co)); + Assert.AreEqual("Initialise", co[0]); + Assert.AreEqual("PostInitialise", co[1]); + Assert.AreEqual("AddRegion", co[2]); + Assert.AreEqual("RegionLoaded", co[3]); + Assert.AreEqual("RemoveRegion", co[4]); + Assert.AreEqual("Close", co[5]); + } + } + + class TestSharedRegion : ISharedRegionModule + { + // FIXME: Should really use MethodInfo + public List CallOrder = new List(); + + public string Name { get { return "TestSharedRegion"; } } + + public Type ReplaceableInterface { get { return null; } } + + public void PostInitialise() + { + CallOrder.Add("PostInitialise"); + } + + public void Initialise(IConfigSource source) + { + CallOrder.Add("Initialise"); + } + + public void Close() + { + CallOrder.Add("Close"); + } + + public void AddRegion(Scene scene) + { + CallOrder.Add("AddRegion"); + } + + public void RemoveRegion(Scene scene) + { + CallOrder.Add("RemoveRegion"); + } + + public void RegionLoaded(Scene scene) + { + CallOrder.Add("RegionLoaded"); + } + } + + class MockRegionModulesControllerPlugin : IRegionModulesController, IApplicationPlugin + { + // List of shared module instances, for adding to Scenes + private List m_sharedInstances = new List(); + + // Config access + private OpenSimBase m_openSim; + + public string Version { get { return "0"; } } + public string Name { get { return "MockRegionModulesControllerPlugin"; } } + + public void Initialise() {} + + public void Initialise(OpenSimBase sim) + { + m_openSim = sim; + } + + /// + /// Called when the application loading is completed + /// + public void PostInitialise() + { + foreach (ISharedRegionModule module in m_sharedInstances) + module.PostInitialise(); + } + + public void AddRegionToModules(Scene scene) + { + List sharedlist = new List(); + + foreach (ISharedRegionModule module in m_sharedInstances) + { + module.AddRegion(scene); + scene.AddRegionModule(module.Name, module); + + sharedlist.Add(module); + } + + foreach (ISharedRegionModule module in sharedlist) + { + module.RegionLoaded(scene); + } + } + + public void RemoveRegionFromModules(Scene scene) + { + foreach (IRegionModuleBase module in scene.RegionModules.Values) + { +// m_log.DebugFormat("[REGIONMODULE]: Removing scene {0} from module {1}", +// scene.RegionInfo.RegionName, module.Name); + module.RemoveRegion(scene); + } + + scene.RegionModules.Clear(); + } + + public void AddNode(ISharedRegionModule module) + { + m_sharedInstances.Add(module); + module.Initialise(m_openSim.ConfigSource.Source); + } + + public void Dispose() + { + // We expect that all regions have been removed already + while (m_sharedInstances.Count > 0) + { + m_sharedInstances[0].Close(); + m_sharedInstances.RemoveAt(0); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Tests/TaskInventoryTests.cs b/OpenSim/Region/Framework/Scenes/Tests/TaskInventoryTests.cs index 0b461f5..b01088d 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/TaskInventoryTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/TaskInventoryTests.cs @@ -37,7 +37,6 @@ using NUnit.Framework; using OpenMetaverse; using OpenMetaverse.Assets; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; @@ -45,7 +44,6 @@ using OpenSim.Region.CoreModules.World.Serialiser; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Tests { @@ -65,7 +63,7 @@ namespace OpenSim.Region.Framework.Tests // Create an object embedded inside the first UUID taskSceneObjectItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); - TaskInventoryHelpers.AddSceneObject(scene, sop1, "tso", taskSceneObjectItemId, user1.PrincipalID); + TaskInventoryHelpers.AddSceneObject(scene.AssetService, sop1, "tso", taskSceneObjectItemId, user1.PrincipalID); TaskInventoryItem addedItem = sop1.Inventory.GetInventoryItem(taskSceneObjectItemId); Assert.That(addedItem.ItemID, Is.EqualTo(taskSceneObjectItemId)); @@ -89,7 +87,7 @@ namespace OpenSim.Region.Framework.Tests // Create an object embedded inside the first UUID taskSceneObjectItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); TaskInventoryItem taskSceneObjectItem - = TaskInventoryHelpers.AddSceneObject(scene, sop1, "tso", taskSceneObjectItemId, user1.PrincipalID); + = TaskInventoryHelpers.AddSceneObject(scene.AssetService, sop1, "tso", taskSceneObjectItemId, user1.PrincipalID); scene.AddSceneObject(sog1); @@ -130,13 +128,14 @@ namespace OpenSim.Region.Framework.Tests SceneObjectPart sop1 = sog1.RootPart; TaskInventoryItem sopItem1 = TaskInventoryHelpers.AddNotecard( - scene, sop1, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900)); + scene.AssetService, sop1, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); InventoryFolderBase folder - = InventoryArchiveUtils.FindFolderByPath(scene.InventoryService, user1.PrincipalID, "Objects")[0]; + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, user1.PrincipalID, "Objects")[0]; // Perform test - scene.MoveTaskInventoryItem(user1.PrincipalID, folder.ID, sop1, sopItem1.ItemID); + string message; + scene.MoveTaskInventoryItem(user1.PrincipalID, folder.ID, sop1, sopItem1.ItemID, out message); InventoryItemBase ncUserItem = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, user1.PrincipalID, "Objects/ncItem"); @@ -162,10 +161,11 @@ namespace OpenSim.Region.Framework.Tests SceneObjectPart sop1 = sog1.RootPart; TaskInventoryItem sopItem1 = TaskInventoryHelpers.AddNotecard( - scene, sop1, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900)); + scene.AssetService, sop1, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); // Perform test - scene.MoveTaskInventoryItem(user1.PrincipalID, UUID.Zero, sop1, sopItem1.ItemID); + string message; + scene.MoveTaskInventoryItem(user1.PrincipalID, UUID.Zero, sop1, sopItem1.ItemID, out message); InventoryItemBase ncUserItem = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, user1.PrincipalID, "Notecards/ncItem"); diff --git a/OpenSim/Region/Framework/Scenes/Tests/UserInventoryTests.cs b/OpenSim/Region/Framework/Scenes/Tests/UserInventoryTests.cs index 9457ebb..8250e6c 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/UserInventoryTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/UserInventoryTests.cs @@ -37,7 +37,6 @@ using NUnit.Framework; using OpenMetaverse; using OpenMetaverse.Assets; using OpenSim.Framework; -using OpenSim.Framework.Communications; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; @@ -45,7 +44,6 @@ using OpenSim.Region.CoreModules.World.Serialiser; using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Tests { @@ -64,7 +62,7 @@ namespace OpenSim.Region.Framework.Tests Scene scene = new SceneHelpers().SetupScene(); UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1001)); - UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, foldersName); + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, foldersName, false); List oneFolder = UserInventoryHelpers.GetInventoryFolders(scene.InventoryService, user1.PrincipalID, foldersName); @@ -73,7 +71,7 @@ namespace OpenSim.Region.Framework.Tests InventoryFolderBase firstRetrievedFolder = oneFolder[0]; Assert.That(firstRetrievedFolder.Name, Is.EqualTo(foldersName)); - UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, foldersName); + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, foldersName, false); List twoFolders = UserInventoryHelpers.GetInventoryFolders(scene.InventoryService, user1.PrincipalID, foldersName); @@ -95,7 +93,9 @@ namespace OpenSim.Region.Framework.Tests UserAccount user2 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1002)); InventoryItemBase item1 = UserInventoryHelpers.CreateInventoryItem(scene, "item1", user1.PrincipalID); - scene.GiveInventoryItem(user2.PrincipalID, user1.PrincipalID, item1.ID); + string message; + + scene.GiveInventoryItem(user2.PrincipalID, user1.PrincipalID, item1.ID, out message); InventoryItemBase retrievedItem1 = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, user2.PrincipalID, "Notecards/item1"); @@ -103,7 +103,7 @@ namespace OpenSim.Region.Framework.Tests Assert.That(retrievedItem1, Is.Not.Null); // Try giving back the freshly received item - scene.GiveInventoryItem(user1.PrincipalID, user2.PrincipalID, retrievedItem1.ID); + scene.GiveInventoryItem(user1.PrincipalID, user2.PrincipalID, retrievedItem1.ID, out message); List reretrievedItems = UserInventoryHelpers.GetInventoryItems(scene.InventoryService, user1.PrincipalID, "Notecards/item1"); @@ -121,9 +121,9 @@ namespace OpenSim.Region.Framework.Tests UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1001)); UserAccount user2 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1002)); InventoryFolderBase folder1 - = UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, "folder1"); + = UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, "folder1", false); - scene.GiveInventoryFolder(user2.PrincipalID, user1.PrincipalID, folder1.ID, UUID.Zero); + scene.GiveInventoryFolder(null, user2.PrincipalID, user1.PrincipalID, folder1.ID, UUID.Zero); InventoryFolderBase retrievedFolder1 = UserInventoryHelpers.GetInventoryFolder(scene.InventoryService, user2.PrincipalID, "folder1"); @@ -131,7 +131,7 @@ namespace OpenSim.Region.Framework.Tests Assert.That(retrievedFolder1, Is.Not.Null); // Try giving back the freshly received folder - scene.GiveInventoryFolder(user1.PrincipalID, user2.PrincipalID, retrievedFolder1.ID, UUID.Zero); + scene.GiveInventoryFolder(null, user1.PrincipalID, user2.PrincipalID, retrievedFolder1.ID, UUID.Zero); List reretrievedFolders = UserInventoryHelpers.GetInventoryFolders(scene.InventoryService, user1.PrincipalID, "folder1"); diff --git a/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs b/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs index dd27294..937c414 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs @@ -33,7 +33,6 @@ using OpenSim.Framework; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using OpenSim.Tests.Common; -using OpenSim.Tests.Common.Mock; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -62,11 +61,11 @@ namespace OpenSim.Region.Framework.Scenes.Tests = AssetHelpers.CreateAsset(corruptAssetUuid, AssetType.Notecard, "CORRUPT ASSET", UUID.Zero); m_assetService.Store(corruptAsset); - IDictionary foundAssetUuids = new Dictionary(); - m_uuidGatherer.GatherAssetUuids(corruptAssetUuid, AssetType.Object, foundAssetUuids); + m_uuidGatherer.AddForInspection(corruptAssetUuid); + m_uuidGatherer.GatherAll(); // We count the uuid as gathered even if the asset itself is corrupt. - Assert.That(foundAssetUuids.Count, Is.EqualTo(1)); + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(1)); } /// @@ -78,38 +77,82 @@ namespace OpenSim.Region.Framework.Scenes.Tests TestHelpers.InMethod(); UUID missingAssetUuid = UUID.Parse("00000000-0000-0000-0000-000000000666"); - IDictionary foundAssetUuids = new Dictionary(); - - m_uuidGatherer.GatherAssetUuids(missingAssetUuid, AssetType.Object, foundAssetUuids); - // We count the uuid as gathered even if the asset itself is missing. - Assert.That(foundAssetUuids.Count, Is.EqualTo(1)); + m_uuidGatherer.AddForInspection(missingAssetUuid); + m_uuidGatherer.GatherAll(); + + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(0)); } [Test] public void TestNotecardAsset() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); - +// TestHelpers.EnableLogging(); + UUID ownerId = TestHelpers.ParseTail(0x10); - UUID soAssetId = TestHelpers.ParseTail(0x20); + UUID embeddedId = TestHelpers.ParseTail(0x20); + UUID secondLevelEmbeddedId = TestHelpers.ParseTail(0x21); + UUID missingEmbeddedId = TestHelpers.ParseTail(0x22); UUID ncAssetId = TestHelpers.ParseTail(0x30); - SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, ownerId); - AssetBase soAsset = AssetHelpers.CreateAsset(soAssetId, so); - m_assetService.Store(soAsset); - - AssetBase ncAsset = AssetHelpers.CreateNotecardAsset(ncAssetId, soAssetId.ToString()); + AssetBase ncAsset + = AssetHelpers.CreateNotecardAsset( + ncAssetId, string.Format("Hello{0}World{1}", embeddedId, missingEmbeddedId)); m_assetService.Store(ncAsset); - IDictionary foundAssetUuids = new Dictionary(); - m_uuidGatherer.GatherAssetUuids(ncAssetId, AssetType.Notecard, foundAssetUuids); + AssetBase embeddedAsset + = AssetHelpers.CreateNotecardAsset(embeddedId, string.Format("{0} We'll meet again.", secondLevelEmbeddedId)); + m_assetService.Store(embeddedAsset); - // We count the uuid as gathered even if the asset itself is corrupt. - Assert.That(foundAssetUuids.Count, Is.EqualTo(2)); - Assert.That(foundAssetUuids.ContainsKey(ncAssetId)); - Assert.That(foundAssetUuids.ContainsKey(soAssetId)); + AssetBase secondLevelEmbeddedAsset + = AssetHelpers.CreateNotecardAsset(secondLevelEmbeddedId, "Don't know where, don't know when."); + m_assetService.Store(secondLevelEmbeddedAsset); + + m_uuidGatherer.AddForInspection(ncAssetId); + m_uuidGatherer.GatherAll(); + +// foreach (UUID key in m_uuidGatherer.GatheredUuids.Keys) +// System.Console.WriteLine("key : {0}", key); + + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(3)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(ncAssetId)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(embeddedId)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(secondLevelEmbeddedId)); + } + + [Test] + public void TestTaskItems() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID ownerId = TestHelpers.ParseTail(0x10); + + SceneObjectGroup soL0 = SceneHelpers.CreateSceneObject(1, ownerId, "l0", 0x20); + SceneObjectGroup soL1 = SceneHelpers.CreateSceneObject(1, ownerId, "l1", 0x21); + SceneObjectGroup soL2 = SceneHelpers.CreateSceneObject(1, ownerId, "l2", 0x22); + + TaskInventoryHelpers.AddScript( + m_assetService, soL2.RootPart, TestHelpers.ParseTail(0x33), TestHelpers.ParseTail(0x43), "l3-script", "gibberish"); + + TaskInventoryHelpers.AddSceneObject( + m_assetService, soL1.RootPart, "l2-item", TestHelpers.ParseTail(0x32), soL2, TestHelpers.ParseTail(0x42)); + TaskInventoryHelpers.AddSceneObject( + m_assetService, soL0.RootPart, "l1-item", TestHelpers.ParseTail(0x31), soL1, TestHelpers.ParseTail(0x41)); + + m_uuidGatherer.AddForInspection(soL0); + m_uuidGatherer.GatherAll(); + +// foreach (UUID key in m_uuidGatherer.GatheredUuids.Keys) +// System.Console.WriteLine("key : {0}", key); + + // We expect to see the default prim texture and the assets of the contained task items + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(4)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(new UUID(Constants.DefaultTexture))); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(TestHelpers.ParseTail(0x41))); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(TestHelpers.ParseTail(0x42))); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(TestHelpers.ParseTail(0x43))); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs index e238d01..9ec4e1d 100644 --- a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs +++ b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs @@ -34,9 +34,11 @@ using System.Threading; using log4net; using OpenMetaverse; using OpenMetaverse.Assets; +using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Services.Interfaces; +using OpenSimAssetType = OpenSim.Framework.SLUtil.OpenSimAssetType; namespace OpenSim.Region.Framework.Scenes { @@ -53,75 +55,81 @@ namespace OpenSim.Region.Framework.Scenes { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// + /// Is gathering complete? + /// + public bool Complete { get { return m_assetUuidsToInspect.Count <= 0; } } + + /// + /// The dictionary of UUIDs gathered so far. If Complete == true then this is all the reachable UUIDs. + /// + /// The gathered uuids. + public IDictionary GatheredUuids { get; private set; } + + /// + /// Gets the next UUID to inspect. + /// + /// If there is no next UUID then returns null + public UUID? NextUuidToInspect + { + get + { + if (Complete) + return null; + else + return m_assetUuidsToInspect.Peek(); + } + } + protected IAssetService m_assetService; -// /// -// /// Used as a temporary store of an asset which represents an object. This can be a null if no appropriate -// /// asset was found by the asset service. -// /// -// private AssetBase m_requestedObjectAsset; -// -// /// -// /// Signal whether we are currently waiting for the asset service to deliver an asset. -// /// -// private bool m_waitingForObjectAsset; - - public UuidGatherer(IAssetService assetService) + protected Queue m_assetUuidsToInspect; + + /// + /// Initializes a new instance of the class. + /// + /// In this case the collection of gathered assets will start out blank. + /// + /// Asset service. + /// + public UuidGatherer(IAssetService assetService) : this(assetService, new Dictionary()) {} + + /// + /// Initializes a new instance of the class. + /// + /// + /// Asset service. + /// + /// + /// Gathered UUIDs will be collected in this dictinaory. + /// It can be pre-populated if you want to stop the gatherer from analyzing assets that have already been fetched and inspected. + /// + public UuidGatherer(IAssetService assetService, IDictionary collector) { m_assetService = assetService; + GatheredUuids = collector; + + // FIXME: Not efficient for searching, can improve. + m_assetUuidsToInspect = new Queue(); } - + /// - /// Gather all the asset uuids associated with the asset referenced by a given uuid + /// Adds the asset uuid for inspection during the gathering process. /// - /// - /// This includes both those directly associated with - /// it (e.g. face textures) and recursively, those of items within it's inventory (e.g. objects contained - /// within this object). - /// - /// The uuid of the asset for which to gather referenced assets - /// The type of the asset for the uuid given - /// The assets gathered - public void GatherAssetUuids(UUID assetUuid, AssetType assetType, IDictionary assetUuids) + /// true, if for inspection was added, false otherwise. + /// UUID. + public bool AddForInspection(UUID uuid) { - // avoid infinite loops - if (assetUuids.ContainsKey(assetUuid)) - return; + if (m_assetUuidsToInspect.Contains(uuid)) + return false; - try - { - assetUuids[assetUuid] = assetType; - - if (AssetType.Bodypart == assetType || AssetType.Clothing == assetType) - { - GetWearableAssetUuids(assetUuid, assetUuids); - } - else if (AssetType.Gesture == assetType) - { - GetGestureAssetUuids(assetUuid, assetUuids); - } - else if (AssetType.Notecard == assetType) - { - GetTextEmbeddedAssetUuids(assetUuid, assetUuids); - } - else if (AssetType.LSLText == assetType) - { - GetTextEmbeddedAssetUuids(assetUuid, assetUuids); - } - else if (AssetType.Object == assetType) - { - GetSceneObjectAssetUuids(assetUuid, assetUuids); - } - } - catch (Exception) - { - m_log.ErrorFormat( - "[UUID GATHERER]: Failed to gather uuids for asset id {0}, type {1}", - assetUuid, assetType); - throw; - } - } +// m_log.DebugFormat("[UUID GATHERER]: Adding asset {0} for inspection", uuid); + + m_assetUuidsToInspect.Enqueue(uuid); + return true; + } + /// /// Gather all the asset uuids associated with a given object. /// @@ -131,19 +139,18 @@ namespace OpenSim.Region.Framework.Scenes /// within this object). /// /// The scene object for which to gather assets - /// The assets gathered - public void GatherAssetUuids(SceneObjectGroup sceneObject, IDictionary assetUuids) + public void AddForInspection(SceneObjectGroup sceneObject) { -// m_log.DebugFormat( -// "[ASSET GATHERER]: Getting assets for object {0}, {1}", sceneObject.Name, sceneObject.UUID); + // m_log.DebugFormat( + // "[ASSET GATHERER]: Getting assets for object {0}, {1}", sceneObject.Name, sceneObject.UUID); SceneObjectPart[] parts = sceneObject.Parts; for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; -// m_log.DebugFormat( -// "[ARCHIVER]: Getting part {0}, {1} for object {2}", part.Name, part.UUID, sceneObject.UUID); + // m_log.DebugFormat( + // "[ARCHIVER]: Getting part {0}, {1} for object {2}", part.Name, part.UUID, sceneObject.UUID); try { @@ -152,7 +159,7 @@ namespace OpenSim.Region.Framework.Scenes { // Get the prim's default texture. This will be used for faces which don't have their own texture if (textureEntry.DefaultTexture != null) - assetUuids[textureEntry.DefaultTexture.TextureID] = AssetType.Texture; + RecordTextureEntryAssetUuids(textureEntry.DefaultTexture); if (textureEntry.FaceTextures != null) { @@ -160,27 +167,59 @@ namespace OpenSim.Region.Framework.Scenes foreach (Primitive.TextureEntryFace texture in textureEntry.FaceTextures) { if (texture != null) - assetUuids[texture.TextureID] = AssetType.Texture; + RecordTextureEntryAssetUuids(texture); } } } - + // If the prim is a sculpt then preserve this information too if (part.Shape.SculptTexture != UUID.Zero) - assetUuids[part.Shape.SculptTexture] = AssetType.Texture; - + GatheredUuids[part.Shape.SculptTexture] = (sbyte)AssetType.Texture; + + if (part.Shape.ProjectionTextureUUID != UUID.Zero) + GatheredUuids[part.Shape.ProjectionTextureUUID] = (sbyte)AssetType.Texture; + + if (part.CollisionSound != UUID.Zero) + GatheredUuids[part.CollisionSound] = (sbyte)AssetType.Sound; + + if (part.ParticleSystem.Length > 0) + { + try + { + Primitive.ParticleSystem ps = new Primitive.ParticleSystem(part.ParticleSystem, 0); + if (ps.Texture != UUID.Zero) + GatheredUuids[ps.Texture] = (sbyte)AssetType.Texture; + } + catch (Exception) + { + m_log.WarnFormat( + "[UUID GATHERER]: Could not check particle system for part {0} {1} in object {2} {3} since it is corrupt. Continuing.", + part.Name, part.UUID, sceneObject.Name, sceneObject.UUID); + } + } + TaskInventoryDictionary taskDictionary = (TaskInventoryDictionary)part.TaskInventory.Clone(); - + // Now analyze this prim's inventory items to preserve all the uuids that they reference foreach (TaskInventoryItem tii in taskDictionary.Values) { -// m_log.DebugFormat( -// "[ARCHIVER]: Analysing item {0} asset type {1} in {2} {3}", -// tii.Name, tii.Type, part.Name, part.UUID); + // m_log.DebugFormat( + // "[ARCHIVER]: Analysing item {0} asset type {1} in {2} {3}", + // tii.Name, tii.Type, part.Name, part.UUID); - if (!assetUuids.ContainsKey(tii.AssetID)) - GatherAssetUuids(tii.AssetID, (AssetType)tii.Type, assetUuids); + if (!GatheredUuids.ContainsKey(tii.AssetID)) + AddForInspection(tii.AssetID, (sbyte)tii.Type); } + + // FIXME: We need to make gathering modular but we cannot yet, since gatherers are not guaranteed + // to be called with scene objects that are in a scene (e.g. in the case of hg asset mapping and + // inventory transfer. There needs to be a way for a module to register a method without assuming a + // Scene.EventManager is present. + // part.ParentGroup.Scene.EventManager.TriggerGatherUuids(part, assetUuids); + + + // still needed to retrieve textures used as materials for any parts containing legacy materials stored in DynAttrs + RecordMaterialsUuids(part); } catch (Exception e) { @@ -191,179 +230,334 @@ namespace OpenSim.Region.Framework.Scenes } } } - -// /// -// /// The callback made when we request the asset for an object from the asset service. -// /// -// private void AssetReceived(string id, Object sender, AssetBase asset) -// { -// lock (this) -// { -// m_requestedObjectAsset = asset; -// m_waitingForObjectAsset = false; -// Monitor.Pulse(this); -// } -// } /// - /// Get an asset synchronously, potentially using an asynchronous callback. If the - /// asynchronous callback is used, we will wait for it to complete. + /// Gathers the next set of assets returned by the next uuid to get from the asset service. /// - /// - /// - protected virtual AssetBase GetAsset(UUID uuid) + /// false if gathering is already complete, true otherwise + public bool GatherNext() { - return m_assetService.Get(uuid.ToString()); + if (Complete) + return false; + + UUID nextToInspect = m_assetUuidsToInspect.Dequeue(); + +// m_log.DebugFormat("[UUID GATHERER]: Inspecting asset {0}", nextToInspect); + + GetAssetUuids(nextToInspect); - // XXX: Switching to do this synchronously where the call was async before but we always waited for it - // to complete anyway! -// m_waitingForObjectAsset = true; -// m_assetCache.Get(uuid.ToString(), this, AssetReceived); -// -// // The asset cache callback can either -// // -// // 1. Complete on the same thread (if the asset is already in the cache) or -// // 2. Come in via a different thread (if we need to go fetch it). -// // -// // The code below handles both these alternatives. -// lock (this) -// { -// if (m_waitingForObjectAsset) -// { -// Monitor.Wait(this); -// m_waitingForObjectAsset = false; -// } -// } -// -// return m_requestedObjectAsset; + return true; } /// - /// Record the asset uuids embedded within the given script. + /// Gathers all remaining asset UUIDS no matter how many calls are required to the asset service. /// - /// - /// Dictionary in which to record the references - private void GetTextEmbeddedAssetUuids(UUID embeddingAssetId, IDictionary assetUuids) + /// false if gathering is already complete, true otherwise + public bool GatherAll() { -// m_log.DebugFormat("[ASSET GATHERER]: Getting assets for uuid references in asset {0}", embeddingAssetId); + if (Complete) + return false; - AssetBase embeddingAsset = GetAsset(embeddingAssetId); + while (GatherNext()); - if (null != embeddingAsset) - { - string script = Utils.BytesToString(embeddingAsset.Data); -// m_log.DebugFormat("[ARCHIVER]: Script {0}", script); - MatchCollection uuidMatches = Util.PermissiveUUIDPattern.Matches(script); -// m_log.DebugFormat("[ARCHIVER]: Found {0} matches in text", uuidMatches.Count); + return true; + } - foreach (Match uuidMatch in uuidMatches) + /// + /// Gather all the asset uuids associated with the asset referenced by a given uuid + /// + /// + /// This includes both those directly associated with + /// it (e.g. face textures) and recursively, those of items within it's inventory (e.g. objects contained + /// within this object). + /// This method assumes that the asset type associated with this asset in persistent storage is correct (which + /// should always be the case). So with this method we always need to retrieve asset data even if the asset + /// is of a type which is known not to reference any other assets + /// + /// The uuid of the asset for which to gather referenced assets + private void GetAssetUuids(UUID assetUuid) + { + // avoid infinite loops + if (GatheredUuids.ContainsKey(assetUuid)) + return; + + try + { + AssetBase assetBase = GetAsset(assetUuid); + + if (null != assetBase) { - UUID uuid = new UUID(uuidMatch.Value); -// m_log.DebugFormat("[ARCHIVER]: Recording {0} in text", uuid); + sbyte assetType = assetBase.Type; + GatheredUuids[assetUuid] = assetType; + + if ((sbyte)AssetType.Bodypart == assetType || (sbyte)AssetType.Clothing == assetType) + { + RecordWearableAssetUuids(assetBase); + } + else if ((sbyte)AssetType.Gesture == assetType) + { + RecordGestureAssetUuids(assetBase); + } + else if ((sbyte)AssetType.Notecard == assetType) + { + RecordTextEmbeddedAssetUuids(assetBase); + } + else if ((sbyte)AssetType.LSLText == assetType) + { + RecordTextEmbeddedAssetUuids(assetBase); + } + else if ((sbyte)OpenSimAssetType.Material == assetType) + { + RecordMaterialAssetUuids(assetBase); + } + else if ((sbyte)AssetType.Object == assetType) + { + RecordSceneObjectAssetUuids(assetBase); + } + } + } + catch (Exception) + { + m_log.ErrorFormat("[UUID GATHERER]: Failed to gather uuids for asset id {0}", assetUuid); + throw; + } + } - // Assume AssetIDs embedded are textures. - assetUuids[uuid] = AssetType.Texture; + private void AddForInspection(UUID assetUuid, sbyte assetType) + { + // Here, we want to collect uuids which require further asset fetches but mark the others as gathered + try + { + if ((sbyte)AssetType.Bodypart == assetType + || (sbyte)AssetType.Clothing == assetType + || (sbyte)AssetType.Gesture == assetType + || (sbyte)AssetType.Notecard == assetType + || (sbyte)AssetType.LSLText == assetType + || (sbyte)OpenSimAssetType.Material == assetType + || (sbyte)AssetType.Object == assetType) + { + AddForInspection(assetUuid); + } + else + { + GatheredUuids[assetUuid] = assetType; } } + catch (Exception) + { + m_log.ErrorFormat( + "[UUID GATHERER]: Failed to gather uuids for asset id {0}, type {1}", + assetUuid, assetType); + throw; + } } /// - /// Record the uuids referenced by the given wearable asset + /// Collect all the asset uuids found in one face of a Texture Entry. /// - /// - /// Dictionary in which to record the references - private void GetWearableAssetUuids(UUID wearableAssetUuid, IDictionary assetUuids) + private void RecordTextureEntryAssetUuids(Primitive.TextureEntryFace texture) { - AssetBase assetBase = GetAsset(wearableAssetUuid); + GatheredUuids[texture.TextureID] = (sbyte)AssetType.Texture; - if (null != assetBase) + if (texture.MaterialID != UUID.Zero) + AddForInspection(texture.MaterialID); + } + + /// + /// Gather all of the texture asset UUIDs used to reference "Materials" such as normal and specular maps + /// stored in legacy format in part.DynAttrs + /// + /// + private void RecordMaterialsUuids(SceneObjectPart part) + { + // scan thru the dynAttrs map of this part for any textures used as materials + OSD osdMaterials = null; + + lock (part.DynAttrs) { - //m_log.Debug(new System.Text.ASCIIEncoding().GetString(bodypartAsset.Data)); - AssetWearable wearableAsset = new AssetBodypart(wearableAssetUuid, assetBase.Data); - wearableAsset.Decode(); - - //m_log.DebugFormat( - // "[ARCHIVER]: Wearable asset {0} references {1} assets", wearableAssetUuid, wearableAsset.Textures.Count); - - foreach (UUID uuid in wearableAsset.Textures.Values) + if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) { - assetUuids[uuid] = AssetType.Texture; + OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); + + if (materialsStore == null) + return; + + materialsStore.TryGetValue("Materials", out osdMaterials); + } + + if (osdMaterials != null) + { + //m_log.Info("[UUID Gatherer]: found Materials: " + OSDParser.SerializeJsonString(osd)); + + if (osdMaterials is OSDArray) + { + OSDArray matsArr = osdMaterials as OSDArray; + foreach (OSDMap matMap in matsArr) + { + try + { + if (matMap.ContainsKey("Material")) + { + OSDMap mat = matMap["Material"] as OSDMap; + if (mat.ContainsKey("NormMap")) + { + UUID normalMapId = mat["NormMap"].AsUUID(); + if (normalMapId != UUID.Zero) + { + GatheredUuids[normalMapId] = (sbyte)AssetType.Texture; + //m_log.Info("[UUID Gatherer]: found normal map ID: " + normalMapId.ToString()); + } + } + if (mat.ContainsKey("SpecMap")) + { + UUID specularMapId = mat["SpecMap"].AsUUID(); + if (specularMapId != UUID.Zero) + { + GatheredUuids[specularMapId] = (sbyte)AssetType.Texture; + //m_log.Info("[UUID Gatherer]: found specular map ID: " + specularMapId.ToString()); + } + } + } + + } + catch (Exception e) + { + m_log.Warn("[UUID Gatherer]: exception getting materials: " + e.Message); + } + } + } } } } /// + /// Get an asset synchronously, potentially using an asynchronous callback. If the + /// asynchronous callback is used, we will wait for it to complete. + /// + /// + /// + protected virtual AssetBase GetAsset(UUID uuid) + { + return m_assetService.Get(uuid.ToString()); + } + + /// + /// Record the asset uuids embedded within the given text (e.g. a script). + /// + /// + private void RecordTextEmbeddedAssetUuids(AssetBase textAsset) + { + // m_log.DebugFormat("[ASSET GATHERER]: Getting assets for uuid references in asset {0}", embeddingAssetId); + + string text = Utils.BytesToString(textAsset.Data); +// m_log.DebugFormat("[UUID GATHERER]: Text {0}", text); + MatchCollection uuidMatches = Util.PermissiveUUIDPattern.Matches(text); +// m_log.DebugFormat("[UUID GATHERER]: Found {0} matches in text", uuidMatches.Count); + + foreach (Match uuidMatch in uuidMatches) + { + UUID uuid = new UUID(uuidMatch.Value); +// m_log.DebugFormat("[UUID GATHERER]: Recording {0} in text", uuid); + + AddForInspection(uuid); + } + } + + /// + /// Record the uuids referenced by the given wearable asset + /// + /// + private void RecordWearableAssetUuids(AssetBase assetBase) + { + //m_log.Debug(new System.Text.ASCIIEncoding().GetString(bodypartAsset.Data)); + AssetWearable wearableAsset = new AssetBodypart(assetBase.FullID, assetBase.Data); + wearableAsset.Decode(); + + //m_log.DebugFormat( + // "[ARCHIVER]: Wearable asset {0} references {1} assets", wearableAssetUuid, wearableAsset.Textures.Count); + + foreach (UUID uuid in wearableAsset.Textures.Values) + GatheredUuids[uuid] = (sbyte)AssetType.Texture; + } + + /// /// Get all the asset uuids associated with a given object. This includes both those directly associated with /// it (e.g. face textures) and recursively, those of items within it's inventory (e.g. objects contained /// within this object). /// - /// - /// - private void GetSceneObjectAssetUuids(UUID sceneObjectUuid, IDictionary assetUuids) + /// + private void RecordSceneObjectAssetUuids(AssetBase sceneObjectAsset) { - AssetBase objectAsset = GetAsset(sceneObjectUuid); + string xml = Utils.BytesToString(sceneObjectAsset.Data); - if (null != objectAsset) + CoalescedSceneObjects coa; + if (CoalescedSceneObjectsSerializer.TryFromXml(xml, out coa)) { - string xml = Utils.BytesToString(objectAsset.Data); - - CoalescedSceneObjects coa; - if (CoalescedSceneObjectsSerializer.TryFromXml(xml, out coa)) - { - foreach (SceneObjectGroup sog in coa.Objects) - GatherAssetUuids(sog, assetUuids); - } - else + foreach (SceneObjectGroup sog in coa.Objects) + AddForInspection(sog); + } + else + { + SceneObjectGroup sog = SceneObjectSerializer.FromOriginalXmlFormat(xml); + + if (null != sog) + AddForInspection(sog); + } + } + + /// + /// Get the asset uuid associated with a gesture + /// + /// + private void RecordGestureAssetUuids(AssetBase gestureAsset) + { + using (MemoryStream ms = new MemoryStream(gestureAsset.Data)) + using (StreamReader sr = new StreamReader(ms)) + { + sr.ReadLine(); // Unknown (Version?) + sr.ReadLine(); // Unknown + sr.ReadLine(); // Unknown + sr.ReadLine(); // Name + sr.ReadLine(); // Comment ? + int count = Convert.ToInt32(sr.ReadLine()); // Item count + + for (int i = 0 ; i < count ; i++) { - SceneObjectGroup sog = SceneObjectSerializer.FromOriginalXmlFormat(xml); - - if (null != sog) - GatherAssetUuids(sog, assetUuids); + string type = sr.ReadLine(); + if (type == null) + break; + string name = sr.ReadLine(); + if (name == null) + break; + string id = sr.ReadLine(); + if (id == null) + break; + string unknown = sr.ReadLine(); + if (unknown == null) + break; + + // If it can be parsed as a UUID, it is an asset ID + UUID uuid; + if (UUID.TryParse(id, out uuid)) + GatheredUuids[uuid] = (sbyte)AssetType.Animation; // the asset is either an Animation or a Sound, but this distinction isn't important } } } /// - /// Get the asset uuid associated with a gesture + /// Get the asset uuid's referenced in a material. /// - /// - /// - private void GetGestureAssetUuids(UUID gestureUuid, IDictionary assetUuids) + private void RecordMaterialAssetUuids(AssetBase materialAsset) { - AssetBase assetBase = GetAsset(gestureUuid); - if (null == assetBase) - return; - - MemoryStream ms = new MemoryStream(assetBase.Data); - StreamReader sr = new StreamReader(ms); + OSDMap mat = (OSDMap)OSDParser.DeserializeLLSDXml(materialAsset.Data); - sr.ReadLine(); // Unknown (Version?) - sr.ReadLine(); // Unknown - sr.ReadLine(); // Unknown - sr.ReadLine(); // Name - sr.ReadLine(); // Comment ? - int count = Convert.ToInt32(sr.ReadLine()); // Item count + UUID normMap = mat["NormMap"].AsUUID(); + if (normMap != UUID.Zero) + GatheredUuids[normMap] = (sbyte)AssetType.Texture; - for (int i = 0 ; i < count ; i++) - { - string type = sr.ReadLine(); - if (type == null) - break; - string name = sr.ReadLine(); - if (name == null) - break; - string id = sr.ReadLine(); - if (id == null) - break; - string unknown = sr.ReadLine(); - if (unknown == null) - break; - - // If it can be parsed as a UUID, it is an asset ID - UUID uuid; - if (UUID.TryParse(id, out uuid)) - assetUuids[uuid] = AssetType.Animation; - } + UUID specMap = mat["SpecMap"].AsUUID(); + if (specMap != UUID.Zero) + GatheredUuids[specMap] = (sbyte)AssetType.Texture; } } @@ -374,7 +568,10 @@ namespace OpenSim.Region.Framework.Scenes protected string m_assetServerURL; public HGUuidGatherer(IAssetService assetService, string assetServerURL) - : base(assetService) + : this(assetService, assetServerURL, new Dictionary()) {} + + public HGUuidGatherer(IAssetService assetService, string assetServerURL, IDictionary collector) + : base(assetService, collector) { m_assetServerURL = assetServerURL; if (!m_assetServerURL.EndsWith("/") && !m_assetServerURL.EndsWith("=")) @@ -391,7 +588,6 @@ namespace OpenSim.Region.Framework.Scenes public AssetBase FetchAsset(UUID assetID) { - // Test if it's already here AssetBase asset = m_assetService.Get(assetID.ToString()); if (asset == null) -- cgit v1.1