/* * Copyright (c) 2006-2009, openmetaverse.org * All rights reserved. * * - 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. * - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.Threading; using OpenMetaverse.Packets; using OpenMetaverse.Http; using OpenMetaverse.StructuredData; using OpenMetaverse.Interfaces; using OpenMetaverse.Messages.Linden; namespace OpenMetaverse { #region Enums /// /// /// public enum ReportType : uint { /// No report None = 0, /// Unknown report type Unknown = 1, /// Bug report Bug = 2, /// Complaint report Complaint = 3, /// Customer service report CustomerServiceRequest = 4 } /// /// Bitflag field for ObjectUpdateCompressed data blocks, describing /// which options are present for each object /// [Flags] public enum CompressedFlags : uint { None = 0x00, /// Unknown ScratchPad = 0x01, /// Whether the object has a TreeSpecies Tree = 0x02, /// Whether the object has floating text ala llSetText HasText = 0x04, /// Whether the object has an active particle system HasParticles = 0x08, /// Whether the object has sound attached to it HasSound = 0x10, /// Whether the object is attached to a root object or not HasParent = 0x20, /// Whether the object has texture animation settings TextureAnimation = 0x40, /// Whether the object has an angular velocity HasAngularVelocity = 0x80, /// Whether the object has a name value pairs string HasNameValues = 0x100, /// Whether the object has a Media URL set MediaURL = 0x200 } /// /// Specific Flags for MultipleObjectUpdate requests /// [Flags] public enum UpdateType : uint { /// None None = 0x00, /// Change position of prims Position = 0x01, /// Change rotation of prims Rotation = 0x02, /// Change size of prims Scale = 0x04, /// Perform operation on link set Linked = 0x08, /// Scale prims uniformly, same as selecing ctrl+shift in the /// viewer. Used in conjunction with Scale Uniform = 0x10 } /// /// Special values in PayPriceReply. If the price is not one of these /// literal value of the price should be use /// public enum PayPriceType : int { /// /// Indicates that this pay option should be hidden /// Hide = -1, /// /// Indicates that this pay option should have the default value /// Default = -2 } #endregion Enums #region Structs /// /// Contains the variables sent in an object update packet for objects. /// Used to track position and movement of prims and avatars /// public struct ObjectMovementUpdate { /// public bool Avatar; /// public Vector4 CollisionPlane; /// public byte State; /// public uint LocalID; /// public Vector3 Position; /// public Vector3 Velocity; /// public Vector3 Acceleration; /// public Quaternion Rotation; /// public Vector3 AngularVelocity; /// public Primitive.TextureEntry Textures; } #endregion Structs /// /// Handles all network traffic related to prims and avatar positions and /// movement. /// public class ObjectManager { public const float HAVOK_TIMESTEP = 1.0f / 45.0f; #region Delegates /// The event subscribers, null of no subscribers private EventHandler m_ObjectUpdate; /// Thread sync lock object private readonly object m_ObjectUpdateLock = new object(); /// Raised when the simulator sends us data containing /// A , Foliage or Attachment /// /// public event EventHandler ObjectUpdate { add { lock (m_ObjectUpdateLock) { m_ObjectUpdate += value; } } remove { lock (m_ObjectUpdateLock) { m_ObjectUpdate -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_ObjectProperties; ///Raises the ObjectProperties Event /// A ObjectPropertiesEventArgs object containing /// the data sent from the simulator protected virtual void OnObjectProperties(ObjectPropertiesEventArgs e) { EventHandler handler = m_ObjectProperties; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ObjectPropertiesLock = new object(); /// Raised when the simulator sends us data containing /// additional information /// /// public event EventHandler ObjectProperties { add { lock (m_ObjectPropertiesLock) { m_ObjectProperties += value; } } remove { lock (m_ObjectPropertiesLock) { m_ObjectProperties -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_ObjectPropertiesUpdated; ///Raises the ObjectPropertiesUpdated Event /// A ObjectPropertiesUpdatedEventArgs object containing /// the data sent from the simulator protected virtual void OnObjectPropertiesUpdated(ObjectPropertiesUpdatedEventArgs e) { EventHandler handler = m_ObjectPropertiesUpdated; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ObjectPropertiesUpdatedLock = new object(); /// Raised when the simulator sends us data containing /// Primitive.ObjectProperties for an object we are currently tracking public event EventHandler ObjectPropertiesUpdated { add { lock (m_ObjectPropertiesUpdatedLock) { m_ObjectPropertiesUpdated += value; } } remove { lock (m_ObjectPropertiesUpdatedLock) { m_ObjectPropertiesUpdated -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_ObjectPropertiesFamily; ///Raises the ObjectPropertiesFamily Event /// A ObjectPropertiesFamilyEventArgs object containing /// the data sent from the simulator protected virtual void OnObjectPropertiesFamily(ObjectPropertiesFamilyEventArgs e) { EventHandler handler = m_ObjectPropertiesFamily; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ObjectPropertiesFamilyLock = new object(); /// Raised when the simulator sends us data containing /// additional and details /// public event EventHandler ObjectPropertiesFamily { add { lock (m_ObjectPropertiesFamilyLock) { m_ObjectPropertiesFamily += value; } } remove { lock (m_ObjectPropertiesFamilyLock) { m_ObjectPropertiesFamily -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_AvatarUpdate; ///Raises the AvatarUpdate Event /// A AvatarUpdateEventArgs object containing /// the data sent from the simulator protected virtual void OnAvatarUpdate(AvatarUpdateEventArgs e) { EventHandler handler = m_AvatarUpdate; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_AvatarUpdateLock = new object(); /// Raised when the simulator sends us data containing /// updated information for an public event EventHandler AvatarUpdate { add { lock (m_AvatarUpdateLock) { m_AvatarUpdate += value; } } remove { lock (m_AvatarUpdateLock) { m_AvatarUpdate -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_TerseObjectUpdate; /// Thread sync lock object private readonly object m_TerseObjectUpdateLock = new object(); /// Raised when the simulator sends us data containing /// and movement changes public event EventHandler TerseObjectUpdate { add { lock (m_TerseObjectUpdateLock) { m_TerseObjectUpdate += value; } } remove { lock (m_TerseObjectUpdateLock) { m_TerseObjectUpdate -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_ObjectDataBlockUpdate; ///Raises the ObjectDataBlockUpdate Event /// A ObjectDataBlockUpdateEventArgs object containing /// the data sent from the simulator protected virtual void OnObjectDataBlockUpdate(ObjectDataBlockUpdateEventArgs e) { EventHandler handler = m_ObjectDataBlockUpdate; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ObjectDataBlockUpdateLock = new object(); /// Raised when the simulator sends us data containing /// updates to an Objects DataBlock public event EventHandler ObjectDataBlockUpdate { add { lock (m_ObjectDataBlockUpdateLock) { m_ObjectDataBlockUpdate += value; } } remove { lock (m_ObjectDataBlockUpdateLock) { m_ObjectDataBlockUpdate -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_KillObject; ///Raises the KillObject Event /// A KillObjectEventArgs object containing /// the data sent from the simulator protected virtual void OnKillObject(KillObjectEventArgs e) { EventHandler handler = m_KillObject; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_KillObjectLock = new object(); /// Raised when the simulator informs us an /// or is no longer within view public event EventHandler KillObject { add { lock (m_KillObjectLock) { m_KillObject += value; } } remove { lock (m_KillObjectLock) { m_KillObject -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_AvatarSitChanged; ///Raises the AvatarSitChanged Event /// A AvatarSitChangedEventArgs object containing /// the data sent from the simulator protected virtual void OnAvatarSitChanged(AvatarSitChangedEventArgs e) { EventHandler handler = m_AvatarSitChanged; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_AvatarSitChangedLock = new object(); /// Raised when the simulator sends us data containing /// updated sit information for our public event EventHandler AvatarSitChanged { add { lock (m_AvatarSitChangedLock) { m_AvatarSitChanged += value; } } remove { lock (m_AvatarSitChangedLock) { m_AvatarSitChanged -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_PayPriceReply; ///Raises the PayPriceReply Event /// A PayPriceReplyEventArgs object containing /// the data sent from the simulator protected virtual void OnPayPriceReply(PayPriceReplyEventArgs e) { EventHandler handler = m_PayPriceReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_PayPriceReplyLock = new object(); /// Raised when the simulator sends us data containing /// purchase price information for a public event EventHandler PayPriceReply { add { lock (m_PayPriceReplyLock) { m_PayPriceReply += value; } } remove { lock (m_PayPriceReplyLock) { m_PayPriceReply -= value; } } } /// /// Callback for getting object media data via CAP /// /// Indicates if the operation was succesfull /// Object media version string /// Array indexed on prim face of media entry data public delegate void ObjectMediaCallback(bool success, string version, MediaEntry[] faceMedia); /// The event subscribers, null of no subscribers private EventHandler m_PhysicsProperties; ///Raises the PhysicsProperties Event /// A PhysicsPropertiesEventArgs object containing /// the data sent from the simulator protected virtual void OnPhysicsProperties(PhysicsPropertiesEventArgs e) { EventHandler handler = m_PhysicsProperties; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_PhysicsPropertiesLock = new object(); /// Raised when the simulator sends us data containing /// additional information /// /// public event EventHandler PhysicsProperties { add { lock (m_PhysicsPropertiesLock) { m_PhysicsProperties += value; } } remove { lock (m_PhysicsPropertiesLock) { m_PhysicsProperties -= value; } } } #endregion Delegates /// Reference to the GridClient object protected GridClient Client; /// Does periodic dead reckoning calculation to convert /// velocity and acceleration to new positions for objects private Timer InterpolationTimer; /// /// Construct a new instance of the ObjectManager class /// /// A reference to the instance public ObjectManager(GridClient client) { Client = client; Client.Network.RegisterCallback(PacketType.ObjectUpdate, ObjectUpdateHandler, false); Client.Network.RegisterCallback(PacketType.ImprovedTerseObjectUpdate, ImprovedTerseObjectUpdateHandler, false); Client.Network.RegisterCallback(PacketType.ObjectUpdateCompressed, ObjectUpdateCompressedHandler); Client.Network.RegisterCallback(PacketType.ObjectUpdateCached, ObjectUpdateCachedHandler); Client.Network.RegisterCallback(PacketType.KillObject, KillObjectHandler); Client.Network.RegisterCallback(PacketType.ObjectPropertiesFamily, ObjectPropertiesFamilyHandler); Client.Network.RegisterCallback(PacketType.ObjectProperties, ObjectPropertiesHandler); Client.Network.RegisterCallback(PacketType.PayPriceReply, PayPriceReplyHandler); Client.Network.RegisterEventCallback("ObjectPhysicsProperties", ObjectPhysicsPropertiesHandler); } #region Internal event handlers private void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message) { if (InterpolationTimer != null) { InterpolationTimer.Dispose(); InterpolationTimer = null; } } private void Network_OnConnected(object sender) { if (Client.Settings.USE_INTERPOLATION_TIMER) { InterpolationTimer = new Timer(InterpolationTimer_Elapsed, null, Settings.INTERPOLATION_INTERVAL, Timeout.Infinite); } } #endregion Internal event handlers #region Public Methods /// /// Request information for a single object from a /// you are currently connected to /// /// The the object is located /// The Local ID of the object public void RequestObject(Simulator simulator, uint localID) { RequestMultipleObjectsPacket request = new RequestMultipleObjectsPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[1]; request.ObjectData[0] = new RequestMultipleObjectsPacket.ObjectDataBlock(); request.ObjectData[0].ID = localID; request.ObjectData[0].CacheMissType = 0; Client.Network.SendPacket(request, simulator); } /// /// Request information for multiple objects contained in /// the same simulator /// /// The the objects are located /// An array containing the Local IDs of the objects public void RequestObjects(Simulator simulator, List localIDs) { RequestMultipleObjectsPacket request = new RequestMultipleObjectsPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[localIDs.Count]; for (int i = 0; i < localIDs.Count; i++) { request.ObjectData[i] = new RequestMultipleObjectsPacket.ObjectDataBlock(); request.ObjectData[i].ID = localIDs[i]; request.ObjectData[i].CacheMissType = 0; } Client.Network.SendPacket(request, simulator); } /// /// Attempt to purchase an original object, a copy, or the contents of /// an object /// /// The the object is located /// The Local ID of the object /// Whether the original, a copy, or the object /// contents are on sale. This is used for verification, if the this /// sale type is not valid for the object the purchase will fail /// Price of the object. This is used for /// verification, if it does not match the actual price the purchase /// will fail /// Group ID that will be associated with the new /// purchase /// Inventory folder UUID where the object or objects /// purchased should be placed /// /// /// BuyObject(Client.Network.CurrentSim, 500, SaleType.Copy, /// 100, UUID.Zero, Client.Self.InventoryRootFolderUUID); /// /// public void BuyObject(Simulator simulator, uint localID, SaleType saleType, int price, UUID groupID, UUID categoryID) { ObjectBuyPacket buy = new ObjectBuyPacket(); buy.AgentData.AgentID = Client.Self.AgentID; buy.AgentData.SessionID = Client.Self.SessionID; buy.AgentData.GroupID = groupID; buy.AgentData.CategoryID = categoryID; buy.ObjectData = new ObjectBuyPacket.ObjectDataBlock[1]; buy.ObjectData[0] = new ObjectBuyPacket.ObjectDataBlock(); buy.ObjectData[0].ObjectLocalID = localID; buy.ObjectData[0].SaleType = (byte)saleType; buy.ObjectData[0].SalePrice = price; Client.Network.SendPacket(buy, simulator); } /// /// Request prices that should be displayed in pay dialog. This will triggger the simulator /// to send us back a PayPriceReply which can be handled by OnPayPriceReply event /// /// The the object is located /// The ID of the object /// The result is raised in the event public void RequestPayPrice(Simulator simulator, UUID objectID) { RequestPayPricePacket payPriceRequest = new RequestPayPricePacket(); payPriceRequest.ObjectData = new RequestPayPricePacket.ObjectDataBlock(); payPriceRequest.ObjectData.ObjectID = objectID; Client.Network.SendPacket(payPriceRequest, simulator); } /// /// Select a single object. This will cause the to send us /// an which will raise the event /// /// The the object is located /// The Local ID of the object /// public void SelectObject(Simulator simulator, uint localID) { SelectObject(simulator, localID, true); } /// /// Select a single object. This will cause the to send us /// an which will raise the event /// /// The the object is located /// The Local ID of the object /// if true, a call to is /// made immediately following the request /// public void SelectObject(Simulator simulator, uint localID, bool automaticDeselect) { ObjectSelectPacket select = new ObjectSelectPacket(); select.AgentData.AgentID = Client.Self.AgentID; select.AgentData.SessionID = Client.Self.SessionID; select.ObjectData = new ObjectSelectPacket.ObjectDataBlock[1]; select.ObjectData[0] = new ObjectSelectPacket.ObjectDataBlock(); select.ObjectData[0].ObjectLocalID = localID; Client.Network.SendPacket(select, simulator); if (automaticDeselect) { DeselectObject(simulator, localID); } } /// /// Select multiple objects. This will cause the to send us /// an which will raise the event /// /// The the objects are located /// An array containing the Local IDs of the objects /// Should objects be deselected immediately after selection /// public void SelectObjects(Simulator simulator, uint[] localIDs, bool automaticDeselect) { ObjectSelectPacket select = new ObjectSelectPacket(); select.AgentData.AgentID = Client.Self.AgentID; select.AgentData.SessionID = Client.Self.SessionID; select.ObjectData = new ObjectSelectPacket.ObjectDataBlock[localIDs.Length]; for (int i = 0; i < localIDs.Length; i++) { select.ObjectData[i] = new ObjectSelectPacket.ObjectDataBlock(); select.ObjectData[i].ObjectLocalID = localIDs[i]; } Client.Network.SendPacket(select, simulator); if (automaticDeselect) { DeselectObjects(simulator, localIDs); } } /// /// Select multiple objects. This will cause the to send us /// an which will raise the event /// /// The the objects are located /// An array containing the Local IDs of the objects /// public void SelectObjects(Simulator simulator, uint[] localIDs) { SelectObjects(simulator, localIDs, true); } /// /// Update the properties of an object /// /// The the object is located /// The Local ID of the object /// true to turn the objects physical property on /// true to turn the objects temporary property on /// true to turn the objects phantom property on /// true to turn the objects cast shadows property on public void SetFlags(Simulator simulator, uint localID, bool physical, bool temporary, bool phantom, bool castsShadow) { SetFlags(simulator, localID, physical, temporary, phantom, castsShadow, PhysicsShapeType.Prim, 1000f, 0.6f, 0.5f, 1f); } /// /// Update the properties of an object /// /// The the object is located /// The Local ID of the object /// true to turn the objects physical property on /// true to turn the objects temporary property on /// true to turn the objects phantom property on /// true to turn the objects cast shadows property on /// Type of the represetnation prim will have in the physics engine /// Density - normal value 1000 /// Friction - normal value 0.6 /// Restitution - standard value 0.5 /// Gravity multiplier - standar value 1.0 public void SetFlags(Simulator simulator, uint localID, bool physical, bool temporary, bool phantom, bool castsShadow, PhysicsShapeType physicsType, float density, float friction, float restitution, float gravityMultiplier) { ObjectFlagUpdatePacket flags = new ObjectFlagUpdatePacket(); flags.AgentData.AgentID = Client.Self.AgentID; flags.AgentData.SessionID = Client.Self.SessionID; flags.AgentData.ObjectLocalID = localID; flags.AgentData.UsePhysics = physical; flags.AgentData.IsTemporary = temporary; flags.AgentData.IsPhantom = phantom; flags.AgentData.CastsShadows = castsShadow; flags.ExtraPhysics = new ObjectFlagUpdatePacket.ExtraPhysicsBlock[1]; flags.ExtraPhysics[0] = new ObjectFlagUpdatePacket.ExtraPhysicsBlock(); flags.ExtraPhysics[0].PhysicsShapeType = (byte)physicsType; flags.ExtraPhysics[0].Density = density; flags.ExtraPhysics[0].Friction = friction; flags.ExtraPhysics[0].Restitution = restitution; flags.ExtraPhysics[0].GravityMultiplier = gravityMultiplier; Client.Network.SendPacket(flags, simulator); } /// /// Sets the sale properties of a single object /// /// The the object is located /// The Local ID of the object /// One of the options from the enum /// The price of the object public void SetSaleInfo(Simulator simulator, uint localID, SaleType saleType, int price) { ObjectSaleInfoPacket sale = new ObjectSaleInfoPacket(); sale.AgentData.AgentID = Client.Self.AgentID; sale.AgentData.SessionID = Client.Self.SessionID; sale.ObjectData = new ObjectSaleInfoPacket.ObjectDataBlock[1]; sale.ObjectData[0] = new ObjectSaleInfoPacket.ObjectDataBlock(); sale.ObjectData[0].LocalID = localID; sale.ObjectData[0].SalePrice = price; sale.ObjectData[0].SaleType = (byte)saleType; Client.Network.SendPacket(sale, simulator); } /// /// Sets the sale properties of multiple objects /// /// The the objects are located /// An array containing the Local IDs of the objects /// One of the options from the enum /// The price of the object public void SetSaleInfo(Simulator simulator, List localIDs, SaleType saleType, int price) { ObjectSaleInfoPacket sale = new ObjectSaleInfoPacket(); sale.AgentData.AgentID = Client.Self.AgentID; sale.AgentData.SessionID = Client.Self.SessionID; sale.ObjectData = new ObjectSaleInfoPacket.ObjectDataBlock[localIDs.Count]; for (int i = 0; i < localIDs.Count; i++) { sale.ObjectData[i] = new ObjectSaleInfoPacket.ObjectDataBlock(); sale.ObjectData[i].LocalID = localIDs[i]; sale.ObjectData[i].SalePrice = price; sale.ObjectData[i].SaleType = (byte)saleType; } Client.Network.SendPacket(sale, simulator); } /// /// Deselect a single object /// /// The the object is located /// The Local ID of the object public void DeselectObject(Simulator simulator, uint localID) { ObjectDeselectPacket deselect = new ObjectDeselectPacket(); deselect.AgentData.AgentID = Client.Self.AgentID; deselect.AgentData.SessionID = Client.Self.SessionID; deselect.ObjectData = new ObjectDeselectPacket.ObjectDataBlock[1]; deselect.ObjectData[0] = new ObjectDeselectPacket.ObjectDataBlock(); deselect.ObjectData[0].ObjectLocalID = localID; Client.Network.SendPacket(deselect, simulator); } /// /// Deselect multiple objects. /// /// The the objects are located /// An array containing the Local IDs of the objects public void DeselectObjects(Simulator simulator, uint[] localIDs) { ObjectDeselectPacket deselect = new ObjectDeselectPacket(); deselect.AgentData.AgentID = Client.Self.AgentID; deselect.AgentData.SessionID = Client.Self.SessionID; deselect.ObjectData = new ObjectDeselectPacket.ObjectDataBlock[localIDs.Length]; for (int i = 0; i < localIDs.Length; i++) { deselect.ObjectData[i] = new ObjectDeselectPacket.ObjectDataBlock(); deselect.ObjectData[i].ObjectLocalID = localIDs[i]; } Client.Network.SendPacket(deselect, simulator); } /// /// Perform a click action on an object /// /// The the object is located /// The Local ID of the object public void ClickObject(Simulator simulator, uint localID) { ClickObject(simulator, localID, Vector3.Zero, Vector3.Zero, 0, Vector3.Zero, Vector3.Zero, Vector3.Zero); } /// /// Perform a click action (Grab) on a single object /// /// The the object is located /// The Local ID of the object /// The texture coordinates to touch /// The surface coordinates to touch /// The face of the position to touch /// The region coordinates of the position to touch /// The surface normal of the position to touch (A normal is a vector perpindicular to the surface) /// The surface binormal of the position to touch (A binormal is a vector tangen to the surface /// pointing along the U direction of the tangent space public void ClickObject(Simulator simulator, uint localID, Vector3 uvCoord, Vector3 stCoord, int faceIndex, Vector3 position, Vector3 normal, Vector3 binormal) { ObjectGrabPacket grab = new ObjectGrabPacket(); grab.AgentData.AgentID = Client.Self.AgentID; grab.AgentData.SessionID = Client.Self.SessionID; grab.ObjectData.GrabOffset = Vector3.Zero; grab.ObjectData.LocalID = localID; grab.SurfaceInfo = new ObjectGrabPacket.SurfaceInfoBlock[1]; grab.SurfaceInfo[0] = new ObjectGrabPacket.SurfaceInfoBlock(); grab.SurfaceInfo[0].UVCoord = uvCoord; grab.SurfaceInfo[0].STCoord = stCoord; grab.SurfaceInfo[0].FaceIndex = faceIndex; grab.SurfaceInfo[0].Position = position; grab.SurfaceInfo[0].Normal = normal; grab.SurfaceInfo[0].Binormal = binormal; Client.Network.SendPacket(grab, simulator); // TODO: If these hit the server out of order the click will fail // and we'll be grabbing the object Thread.Sleep(50); ObjectDeGrabPacket degrab = new ObjectDeGrabPacket(); degrab.AgentData.AgentID = Client.Self.AgentID; degrab.AgentData.SessionID = Client.Self.SessionID; degrab.ObjectData.LocalID = localID; degrab.SurfaceInfo = new ObjectDeGrabPacket.SurfaceInfoBlock[1]; degrab.SurfaceInfo[0] = new ObjectDeGrabPacket.SurfaceInfoBlock(); degrab.SurfaceInfo[0].UVCoord = uvCoord; degrab.SurfaceInfo[0].STCoord = stCoord; degrab.SurfaceInfo[0].FaceIndex = faceIndex; degrab.SurfaceInfo[0].Position = position; degrab.SurfaceInfo[0].Normal = normal; degrab.SurfaceInfo[0].Binormal = binormal; Client.Network.SendPacket(degrab, simulator); } /// /// Create (rez) a new prim object in a simulator /// /// A reference to the object to place the object in /// Data describing the prim object to rez /// Group ID that this prim will be set to, or UUID.Zero if you /// do not want the object to be associated with a specific group /// An approximation of the position at which to rez the prim /// Scale vector to size this prim /// Rotation quaternion to rotate this prim /// Due to the way client prim rezzing is done on the server, /// the requested position for an object is only close to where the prim /// actually ends up. If you desire exact placement you'll need to /// follow up by moving the object after it has been created. This /// function will not set textures, light and flexible data, or other /// extended primitive properties public void AddPrim(Simulator simulator, Primitive.ConstructionData prim, UUID groupID, Vector3 position, Vector3 scale, Quaternion rotation) { AddPrim(simulator, prim, groupID, position, scale, rotation, PrimFlags.CreateSelected); } /// /// Create (rez) a new prim object in a simulator /// /// A reference to the object to place the object in /// Data describing the prim object to rez /// Group ID that this prim will be set to, or UUID.Zero if you /// do not want the object to be associated with a specific group /// An approximation of the position at which to rez the prim /// Scale vector to size this prim /// Rotation quaternion to rotate this prim /// Specify the /// Due to the way client prim rezzing is done on the server, /// the requested position for an object is only close to where the prim /// actually ends up. If you desire exact placement you'll need to /// follow up by moving the object after it has been created. This /// function will not set textures, light and flexible data, or other /// extended primitive properties public void AddPrim(Simulator simulator, Primitive.ConstructionData prim, UUID groupID, Vector3 position, Vector3 scale, Quaternion rotation, PrimFlags createFlags) { ObjectAddPacket packet = new ObjectAddPacket(); packet.AgentData.AgentID = Client.Self.AgentID; packet.AgentData.SessionID = Client.Self.SessionID; packet.AgentData.GroupID = groupID; packet.ObjectData.State = prim.State; packet.ObjectData.AddFlags = (uint)createFlags; packet.ObjectData.PCode = (byte)PCode.Prim; packet.ObjectData.Material = (byte)prim.Material; packet.ObjectData.Scale = scale; packet.ObjectData.Rotation = rotation; packet.ObjectData.PathCurve = (byte)prim.PathCurve; packet.ObjectData.PathBegin = Primitive.PackBeginCut(prim.PathBegin); packet.ObjectData.PathEnd = Primitive.PackEndCut(prim.PathEnd); packet.ObjectData.PathRadiusOffset = Primitive.PackPathTwist(prim.PathRadiusOffset); packet.ObjectData.PathRevolutions = Primitive.PackPathRevolutions(prim.PathRevolutions); packet.ObjectData.PathScaleX = Primitive.PackPathScale(prim.PathScaleX); packet.ObjectData.PathScaleY = Primitive.PackPathScale(prim.PathScaleY); packet.ObjectData.PathShearX = (byte)Primitive.PackPathShear(prim.PathShearX); packet.ObjectData.PathShearY = (byte)Primitive.PackPathShear(prim.PathShearY); packet.ObjectData.PathSkew = Primitive.PackPathTwist(prim.PathSkew); packet.ObjectData.PathTaperX = Primitive.PackPathTaper(prim.PathTaperX); packet.ObjectData.PathTaperY = Primitive.PackPathTaper(prim.PathTaperY); packet.ObjectData.PathTwist = Primitive.PackPathTwist(prim.PathTwist); packet.ObjectData.PathTwistBegin = Primitive.PackPathTwist(prim.PathTwistBegin); packet.ObjectData.ProfileCurve = prim.profileCurve; packet.ObjectData.ProfileBegin = Primitive.PackBeginCut(prim.ProfileBegin); packet.ObjectData.ProfileEnd = Primitive.PackEndCut(prim.ProfileEnd); packet.ObjectData.ProfileHollow = Primitive.PackProfileHollow(prim.ProfileHollow); packet.ObjectData.RayStart = position; packet.ObjectData.RayEnd = position; packet.ObjectData.RayEndIsIntersection = 0; packet.ObjectData.RayTargetID = UUID.Zero; packet.ObjectData.BypassRaycast = 1; Client.Network.SendPacket(packet, simulator); } /// /// Rez a Linden tree /// /// A reference to the object where the object resides /// The size of the tree /// The rotation of the tree /// The position of the tree /// The Type of tree /// The of the group to set the tree to, /// or UUID.Zero if no group is to be set /// true to use the "new" Linden trees, false to use the old public void AddTree(Simulator simulator, Vector3 scale, Quaternion rotation, Vector3 position, Tree treeType, UUID groupOwner, bool newTree) { ObjectAddPacket add = new ObjectAddPacket(); add.AgentData.AgentID = Client.Self.AgentID; add.AgentData.SessionID = Client.Self.SessionID; add.AgentData.GroupID = groupOwner; add.ObjectData.BypassRaycast = 1; add.ObjectData.Material = 3; add.ObjectData.PathCurve = 16; add.ObjectData.PCode = newTree ? (byte)PCode.NewTree : (byte)PCode.Tree; add.ObjectData.RayEnd = position; add.ObjectData.RayStart = position; add.ObjectData.RayTargetID = UUID.Zero; add.ObjectData.Rotation = rotation; add.ObjectData.Scale = scale; add.ObjectData.State = (byte)treeType; Client.Network.SendPacket(add, simulator); } /// /// Rez grass and ground cover /// /// A reference to the object where the object resides /// The size of the grass /// The rotation of the grass /// The position of the grass /// The type of grass from the enum /// The of the group to set the tree to, /// or UUID.Zero if no group is to be set public void AddGrass(Simulator simulator, Vector3 scale, Quaternion rotation, Vector3 position, Grass grassType, UUID groupOwner) { ObjectAddPacket add = new ObjectAddPacket(); add.AgentData.AgentID = Client.Self.AgentID; add.AgentData.SessionID = Client.Self.SessionID; add.AgentData.GroupID = groupOwner; add.ObjectData.BypassRaycast = 1; add.ObjectData.Material = 3; add.ObjectData.PathCurve = 16; add.ObjectData.PCode = (byte)PCode.Grass; add.ObjectData.RayEnd = position; add.ObjectData.RayStart = position; add.ObjectData.RayTargetID = UUID.Zero; add.ObjectData.Rotation = rotation; add.ObjectData.Scale = scale; add.ObjectData.State = (byte)grassType; Client.Network.SendPacket(add, simulator); } /// /// Set the textures to apply to the faces of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The texture data to apply public void SetTextures(Simulator simulator, uint localID, Primitive.TextureEntry textures) { SetTextures(simulator, localID, textures, String.Empty); } /// /// Set the textures to apply to the faces of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The texture data to apply /// A media URL (not used) public void SetTextures(Simulator simulator, uint localID, Primitive.TextureEntry textures, string mediaUrl) { ObjectImagePacket image = new ObjectImagePacket(); image.AgentData.AgentID = Client.Self.AgentID; image.AgentData.SessionID = Client.Self.SessionID; image.ObjectData = new ObjectImagePacket.ObjectDataBlock[1]; image.ObjectData[0] = new ObjectImagePacket.ObjectDataBlock(); image.ObjectData[0].ObjectLocalID = localID; image.ObjectData[0].TextureEntry = textures.GetBytes(); image.ObjectData[0].MediaURL = Utils.StringToBytes(mediaUrl); Client.Network.SendPacket(image, simulator); } /// /// Set the Light data on an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// A object containing the data to set public void SetLight(Simulator simulator, uint localID, Primitive.LightData light) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); extra.AgentData.AgentID = Client.Self.AgentID; extra.AgentData.SessionID = Client.Self.SessionID; extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)ExtraParamType.Light; if (light.Intensity == 0.0f) { // Disables the light if intensity is 0 extra.ObjectData[0].ParamInUse = false; } else { extra.ObjectData[0].ParamInUse = true; } extra.ObjectData[0].ParamData = light.GetBytes(); extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); } /// /// Set the flexible data on an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// A object containing the data to set public void SetFlexible(Simulator simulator, uint localID, Primitive.FlexibleData flexible) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); extra.AgentData.AgentID = Client.Self.AgentID; extra.AgentData.SessionID = Client.Self.SessionID; extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)ExtraParamType.Flexible; extra.ObjectData[0].ParamInUse = true; extra.ObjectData[0].ParamData = flexible.GetBytes(); extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); } /// /// Set the sculptie texture and data on an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// A object containing the data to set public void SetSculpt(Simulator simulator, uint localID, Primitive.SculptData sculpt) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); extra.AgentData.AgentID = Client.Self.AgentID; extra.AgentData.SessionID = Client.Self.SessionID; extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)ExtraParamType.Sculpt; extra.ObjectData[0].ParamInUse = true; extra.ObjectData[0].ParamData = sculpt.GetBytes(); extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); // Not sure why, but if you don't send this the sculpted prim disappears ObjectShapePacket shape = new ObjectShapePacket(); shape.AgentData.AgentID = Client.Self.AgentID; shape.AgentData.SessionID = Client.Self.SessionID; shape.ObjectData = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock[1]; shape.ObjectData[0] = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock(); shape.ObjectData[0].ObjectLocalID = localID; shape.ObjectData[0].PathScaleX = 100; shape.ObjectData[0].PathScaleY = 150; shape.ObjectData[0].PathCurve = 32; Client.Network.SendPacket(shape, simulator); } /// /// Unset additional primitive parameters on an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The extra parameters to set public void SetExtraParamOff(Simulator simulator, uint localID, ExtraParamType type) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); extra.AgentData.AgentID = Client.Self.AgentID; extra.AgentData.SessionID = Client.Self.SessionID; extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)type; extra.ObjectData[0].ParamInUse = false; extra.ObjectData[0].ParamData = Utils.EmptyBytes; extra.ObjectData[0].ParamSize = 0; Client.Network.SendPacket(extra, simulator); } /// /// Link multiple prims into a linkset /// /// A reference to the object where the objects reside /// An array which contains the IDs of the objects to link /// The last object in the array will be the root object of the linkset TODO: Is this true? public void LinkPrims(Simulator simulator, List localIDs) { ObjectLinkPacket packet = new ObjectLinkPacket(); packet.AgentData.AgentID = Client.Self.AgentID; packet.AgentData.SessionID = Client.Self.SessionID; packet.ObjectData = new ObjectLinkPacket.ObjectDataBlock[localIDs.Count]; for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectLinkPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localIDs[i]; } Client.Network.SendPacket(packet, simulator); } /// /// Delink/Unlink multiple prims from a linkset /// /// A reference to the object where the objects reside /// An array which contains the IDs of the objects to delink public void DelinkPrims(Simulator simulator, List localIDs) { ObjectDelinkPacket packet = new ObjectDelinkPacket(); packet.AgentData.AgentID = Client.Self.AgentID; packet.AgentData.SessionID = Client.Self.SessionID; packet.ObjectData = new ObjectDelinkPacket.ObjectDataBlock[localIDs.Count]; int i = 0; foreach (uint localID in localIDs) { packet.ObjectData[i] = new ObjectDelinkPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localID; i++; } Client.Network.SendPacket(packet, simulator); } /// /// Change the rotation of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new rotation of the object public void SetRotation(Simulator simulator, uint localID, Quaternion rotation) { ObjectRotationPacket objRotPacket = new ObjectRotationPacket(); objRotPacket.AgentData.AgentID = Client.Self.AgentID; objRotPacket.AgentData.SessionID = Client.Self.SessionID; objRotPacket.ObjectData = new ObjectRotationPacket.ObjectDataBlock[1]; objRotPacket.ObjectData[0] = new ObjectRotationPacket.ObjectDataBlock(); objRotPacket.ObjectData[0].ObjectLocalID = localID; objRotPacket.ObjectData[0].Rotation = rotation; Client.Network.SendPacket(objRotPacket, simulator); } /// /// Set the name of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// A string containing the new name of the object public void SetName(Simulator simulator, uint localID, string name) { SetNames(simulator, new uint[] { localID }, new string[] { name }); } /// /// Set the name of multiple objects /// /// A reference to the object where the objects reside /// An array which contains the IDs of the objects to change the name of /// An array which contains the new names of the objects public void SetNames(Simulator simulator, uint[] localIDs, string[] names) { ObjectNamePacket namePacket = new ObjectNamePacket(); namePacket.AgentData.AgentID = Client.Self.AgentID; namePacket.AgentData.SessionID = Client.Self.SessionID; namePacket.ObjectData = new ObjectNamePacket.ObjectDataBlock[localIDs.Length]; for (int i = 0; i < localIDs.Length; ++i) { namePacket.ObjectData[i] = new ObjectNamePacket.ObjectDataBlock(); namePacket.ObjectData[i].LocalID = localIDs[i]; namePacket.ObjectData[i].Name = Utils.StringToBytes(names[i]); } Client.Network.SendPacket(namePacket, simulator); } /// /// Set the description of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// A string containing the new description of the object public void SetDescription(Simulator simulator, uint localID, string description) { SetDescriptions(simulator, new uint[] { localID }, new string[] { description }); } /// /// Set the descriptions of multiple objects /// /// A reference to the object where the objects reside /// An array which contains the IDs of the objects to change the description of /// An array which contains the new descriptions of the objects public void SetDescriptions(Simulator simulator, uint[] localIDs, string[] descriptions) { ObjectDescriptionPacket descPacket = new ObjectDescriptionPacket(); descPacket.AgentData.AgentID = Client.Self.AgentID; descPacket.AgentData.SessionID = Client.Self.SessionID; descPacket.ObjectData = new ObjectDescriptionPacket.ObjectDataBlock[localIDs.Length]; for (int i = 0; i < localIDs.Length; ++i) { descPacket.ObjectData[i] = new ObjectDescriptionPacket.ObjectDataBlock(); descPacket.ObjectData[i].LocalID = localIDs[i]; descPacket.ObjectData[i].Description = Utils.StringToBytes(descriptions[i]); } Client.Network.SendPacket(descPacket, simulator); } /// /// Attach an object to this avatar /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The point on the avatar the object will be attached /// The rotation of the attached object public void AttachObject(Simulator simulator, uint localID, AttachmentPoint attachPoint, Quaternion rotation) { ObjectAttachPacket attach = new ObjectAttachPacket(); attach.AgentData.AgentID = Client.Self.AgentID; attach.AgentData.SessionID = Client.Self.SessionID; attach.AgentData.AttachmentPoint = (byte)attachPoint; attach.ObjectData = new ObjectAttachPacket.ObjectDataBlock[1]; attach.ObjectData[0] = new ObjectAttachPacket.ObjectDataBlock(); attach.ObjectData[0].ObjectLocalID = localID; attach.ObjectData[0].Rotation = rotation; Client.Network.SendPacket(attach, simulator); } /// /// Drop an attached object from this avatar /// /// A reference to the /// object where the objects reside. This will always be the simulator the avatar is currently in /// /// The object's ID which is local to the simulator the object is in public void DropObject(Simulator simulator, uint localID) { ObjectDropPacket dropit = new ObjectDropPacket(); dropit.AgentData.AgentID = Client.Self.AgentID; dropit.AgentData.SessionID = Client.Self.SessionID; dropit.ObjectData = new ObjectDropPacket.ObjectDataBlock[1]; dropit.ObjectData[0] = new ObjectDropPacket.ObjectDataBlock(); dropit.ObjectData[0].ObjectLocalID = localID; Client.Network.SendPacket(dropit, simulator); } /// /// Detach an object from yourself /// /// A reference to the /// object where the objects reside /// /// This will always be the simulator the avatar is currently in /// /// An array which contains the IDs of the objects to detach public void DetachObjects(Simulator simulator, List localIDs) { ObjectDetachPacket detach = new ObjectDetachPacket(); detach.AgentData.AgentID = Client.Self.AgentID; detach.AgentData.SessionID = Client.Self.SessionID; detach.ObjectData = new ObjectDetachPacket.ObjectDataBlock[localIDs.Count]; for (int i = 0; i < localIDs.Count; i++) { detach.ObjectData[i] = new ObjectDetachPacket.ObjectDataBlock(); detach.ObjectData[i].ObjectLocalID = localIDs[i]; } Client.Network.SendPacket(detach, simulator); } /// /// Change the position of an object, Will change position of entire linkset /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new position of the object public void SetPosition(Simulator simulator, uint localID, Vector3 position) { UpdateObject(simulator, localID, position, UpdateType.Position | UpdateType.Linked); } /// /// Change the position of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new position of the object /// if true, will change position of (this) child prim only, not entire linkset public void SetPosition(Simulator simulator, uint localID, Vector3 position, bool childOnly) { UpdateType type = UpdateType.Position; if (!childOnly) type |= UpdateType.Linked; UpdateObject(simulator, localID, position, type); } /// /// Change the Scale (size) of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new scale of the object /// If true, will change scale of this prim only, not entire linkset /// True to resize prims uniformly public void SetScale(Simulator simulator, uint localID, Vector3 scale, bool childOnly, bool uniform) { UpdateType type = UpdateType.Scale; if (!childOnly) type |= UpdateType.Linked; if (uniform) type |= UpdateType.Uniform; UpdateObject(simulator, localID, scale, type); } /// /// Change the Rotation of an object that is either a child or a whole linkset /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new scale of the object /// If true, will change rotation of this prim only, not entire linkset public void SetRotation(Simulator simulator, uint localID, Quaternion quat, bool childOnly) { UpdateType type = UpdateType.Rotation; if (!childOnly) type |= UpdateType.Linked; MultipleObjectUpdatePacket multiObjectUpdate = new MultipleObjectUpdatePacket(); multiObjectUpdate.AgentData.AgentID = Client.Self.AgentID; multiObjectUpdate.AgentData.SessionID = Client.Self.SessionID; multiObjectUpdate.ObjectData = new MultipleObjectUpdatePacket.ObjectDataBlock[1]; multiObjectUpdate.ObjectData[0] = new MultipleObjectUpdatePacket.ObjectDataBlock(); multiObjectUpdate.ObjectData[0].Type = (byte)type; multiObjectUpdate.ObjectData[0].ObjectLocalID = localID; multiObjectUpdate.ObjectData[0].Data = quat.GetBytes(); Client.Network.SendPacket(multiObjectUpdate, simulator); } /// /// Send a Multiple Object Update packet to change the size, scale or rotation of a primitive /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new rotation, size, or position of the target object /// The flags from the Enum public void UpdateObject(Simulator simulator, uint localID, Vector3 data, UpdateType type) { MultipleObjectUpdatePacket multiObjectUpdate = new MultipleObjectUpdatePacket(); multiObjectUpdate.AgentData.AgentID = Client.Self.AgentID; multiObjectUpdate.AgentData.SessionID = Client.Self.SessionID; multiObjectUpdate.ObjectData = new MultipleObjectUpdatePacket.ObjectDataBlock[1]; multiObjectUpdate.ObjectData[0] = new MultipleObjectUpdatePacket.ObjectDataBlock(); multiObjectUpdate.ObjectData[0].Type = (byte)type; multiObjectUpdate.ObjectData[0].ObjectLocalID = localID; multiObjectUpdate.ObjectData[0].Data = data.GetBytes(); Client.Network.SendPacket(multiObjectUpdate, simulator); } /// /// Deed an object (prim) to a group, Object must be shared with group which /// can be accomplished with SetPermissions() /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The of the group to deed the object to public void DeedObject(Simulator simulator, uint localID, UUID groupOwner) { ObjectOwnerPacket objDeedPacket = new ObjectOwnerPacket(); objDeedPacket.AgentData.AgentID = Client.Self.AgentID; objDeedPacket.AgentData.SessionID = Client.Self.SessionID; // Can only be use in God mode objDeedPacket.HeaderData.Override = false; objDeedPacket.HeaderData.OwnerID = UUID.Zero; objDeedPacket.HeaderData.GroupID = groupOwner; objDeedPacket.ObjectData = new ObjectOwnerPacket.ObjectDataBlock[1]; objDeedPacket.ObjectData[0] = new ObjectOwnerPacket.ObjectDataBlock(); objDeedPacket.ObjectData[0].ObjectLocalID = localID; Client.Network.SendPacket(objDeedPacket, simulator); } /// /// Deed multiple objects (prims) to a group, Objects must be shared with group which /// can be accomplished with SetPermissions() /// /// A reference to the object where the object resides /// An array which contains the IDs of the objects to deed /// The of the group to deed the object to public void DeedObjects(Simulator simulator, List localIDs, UUID groupOwner) { ObjectOwnerPacket packet = new ObjectOwnerPacket(); packet.AgentData.AgentID = Client.Self.AgentID; packet.AgentData.SessionID = Client.Self.SessionID; // Can only be use in God mode packet.HeaderData.Override = false; packet.HeaderData.OwnerID = UUID.Zero; packet.HeaderData.GroupID = groupOwner; packet.ObjectData = new ObjectOwnerPacket.ObjectDataBlock[localIDs.Count]; for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectOwnerPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localIDs[i]; } Client.Network.SendPacket(packet, simulator); } /// /// Set the permissions on multiple objects /// /// A reference to the object where the objects reside /// An array which contains the IDs of the objects to set the permissions on /// The new Who mask to set /// Which permission to modify /// The new state of permission public void SetPermissions(Simulator simulator, List localIDs, PermissionWho who, PermissionMask permissions, bool set) { ObjectPermissionsPacket packet = new ObjectPermissionsPacket(); packet.AgentData.AgentID = Client.Self.AgentID; packet.AgentData.SessionID = Client.Self.SessionID; // Override can only be used by gods packet.HeaderData.Override = false; packet.ObjectData = new ObjectPermissionsPacket.ObjectDataBlock[localIDs.Count]; for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectPermissionsPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localIDs[i]; packet.ObjectData[i].Field = (byte)who; packet.ObjectData[i].Mask = (uint)permissions; packet.ObjectData[i].Set = Convert.ToByte(set); } Client.Network.SendPacket(packet, simulator); } /// /// Request additional properties for an object /// /// A reference to the object where the object resides /// public void RequestObjectPropertiesFamily(Simulator simulator, UUID objectID) { RequestObjectPropertiesFamily(simulator, objectID, true); } /// /// Request additional properties for an object /// /// A reference to the object where the object resides /// Absolute UUID of the object /// Whether to require server acknowledgement of this request public void RequestObjectPropertiesFamily(Simulator simulator, UUID objectID, bool reliable) { RequestObjectPropertiesFamilyPacket properties = new RequestObjectPropertiesFamilyPacket(); properties.AgentData.AgentID = Client.Self.AgentID; properties.AgentData.SessionID = Client.Self.SessionID; properties.ObjectData.ObjectID = objectID; // TODO: RequestFlags is typically only for bug report submissions, but we might be able to // use it to pass an arbitrary uint back to the callback properties.ObjectData.RequestFlags = 0; properties.Header.Reliable = reliable; Client.Network.SendPacket(properties, simulator); } /// /// Set the ownership of a list of objects to the specified group /// /// A reference to the object where the objects reside /// An array which contains the IDs of the objects to set the group id on /// The Groups ID public void SetObjectsGroup(Simulator simulator, List localIds, UUID groupID) { ObjectGroupPacket packet = new ObjectGroupPacket(); packet.AgentData.AgentID = Client.Self.AgentID; packet.AgentData.GroupID = groupID; packet.AgentData.SessionID = Client.Self.SessionID; packet.ObjectData = new ObjectGroupPacket.ObjectDataBlock[localIds.Count]; for (int i = 0; i < localIds.Count; i++) { packet.ObjectData[i] = new ObjectGroupPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localIds[i]; } Client.Network.SendPacket(packet, simulator); } /// /// Update current URL of the previously set prim media /// /// UUID of the prim /// Set current URL to this /// Prim face number /// Simulator in which prim is located public void NavigateObjectMedia(UUID primID, int face, string newURL, Simulator sim) { Uri url; if (sim.Caps != null && null != (url = sim.Caps.CapabilityURI("ObjectMediaNavigate"))) { ObjectMediaNavigateMessage req = new ObjectMediaNavigateMessage(); req.PrimID = primID; req.URL = newURL; req.Face = face; CapsClient request = new CapsClient(url); request.OnComplete += (CapsClient client, OSD result, Exception error) => { if (error != null) { Logger.Log("ObjectMediaNavigate: " + error.Message, Helpers.LogLevel.Error, Client); } }; request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { Logger.Log("ObjectMediaNavigate capability not available", Helpers.LogLevel.Error, Client); } } /// /// Set object media /// /// UUID of the prim /// Array the length of prims number of faces. Null on face indexes where there is /// no media, on faces which contain the media /// Simulatior in which prim is located public void UpdateObjectMedia(UUID primID, MediaEntry[] faceMedia, Simulator sim) { Uri url; if (sim.Caps != null && null != (url = sim.Caps.CapabilityURI("ObjectMedia"))) { ObjectMediaUpdate req = new ObjectMediaUpdate(); req.PrimID = primID; req.FaceMedia = faceMedia; req.Verb = "UPDATE"; CapsClient request = new CapsClient(url); request.OnComplete += (CapsClient client, OSD result, Exception error) => { if (error != null) { Logger.Log("ObjectMediaUpdate: " + error.Message, Helpers.LogLevel.Error, Client); } }; request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { Logger.Log("ObjectMedia capability not available", Helpers.LogLevel.Error, Client); } } /// /// Retrieve information about object media /// /// UUID of the primitive /// Simulator where prim is located /// Call this callback when done public void RequestObjectMedia(UUID primID, Simulator sim, ObjectMediaCallback callback) { Uri url; if (sim.Caps != null && null != (url = sim.Caps.CapabilityURI("ObjectMedia"))) { ObjectMediaRequest req = new ObjectMediaRequest(); req.PrimID = primID; req.Verb = "GET"; CapsClient request = new CapsClient(url); request.OnComplete += (CapsClient client, OSD result, Exception error) => { if (result == null) { Logger.Log("Failed retrieving ObjectMedia data", Helpers.LogLevel.Error, Client); try { callback(false, string.Empty, null); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } return; } ObjectMediaMessage msg = new ObjectMediaMessage(); msg.Deserialize((OSDMap)result); if (msg.Request is ObjectMediaResponse) { ObjectMediaResponse response = (ObjectMediaResponse)msg.Request; if (Client.Settings.OBJECT_TRACKING) { Primitive prim = sim.ObjectsPrimitives.Find((Primitive p) => { return p.ID == primID; }); if (prim != null) { prim.MediaVersion = response.Version; prim.FaceMedia = response.FaceMedia; } } try { callback(true, response.Version, response.FaceMedia); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } } else { try { callback(false, string.Empty, null); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } } }; request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { Logger.Log("ObjectMedia capability not available", Helpers.LogLevel.Error, Client); try { callback(false, string.Empty, null); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } } } #endregion #region Packet Handlers /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ObjectUpdateHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ObjectUpdatePacket update = (ObjectUpdatePacket)packet; UpdateDilation(e.Simulator, update.RegionData.TimeDilation); for (int b = 0; b < update.ObjectData.Length; b++) { ObjectUpdatePacket.ObjectDataBlock block = update.ObjectData[b]; ObjectMovementUpdate objectupdate = new ObjectMovementUpdate(); //Vector4 collisionPlane = Vector4.Zero; //Vector3 position; //Vector3 velocity; //Vector3 acceleration; //Quaternion rotation; //Vector3 angularVelocity; NameValue[] nameValues; bool attachment = false; PCode pcode = (PCode)block.PCode; #region Relevance check // Check if we are interested in this object if (!Client.Settings.ALWAYS_DECODE_OBJECTS) { switch (pcode) { case PCode.Grass: case PCode.Tree: case PCode.NewTree: case PCode.Prim: if (m_ObjectUpdate == null) continue; break; case PCode.Avatar: // Make an exception for updates about our own agent if (block.FullID != Client.Self.AgentID && m_AvatarUpdate == null) continue; break; case PCode.ParticleSystem: continue; // TODO: Do something with these } } #endregion Relevance check #region NameValue parsing string nameValue = Utils.BytesToString(block.NameValue); if (nameValue.Length > 0) { string[] lines = nameValue.Split('\n'); nameValues = new NameValue[lines.Length]; for (int i = 0; i < lines.Length; i++) { if (!String.IsNullOrEmpty(lines[i])) { NameValue nv = new NameValue(lines[i]); if (nv.Name == "AttachItemID") attachment = true; nameValues[i] = nv; } } } else { nameValues = new NameValue[0]; } #endregion NameValue parsing #region Decode Object (primitive) parameters Primitive.ConstructionData data = new Primitive.ConstructionData(); data.State = block.State; data.Material = (Material)block.Material; data.PathCurve = (PathCurve)block.PathCurve; data.profileCurve = block.ProfileCurve; data.PathBegin = Primitive.UnpackBeginCut(block.PathBegin); data.PathEnd = Primitive.UnpackEndCut(block.PathEnd); data.PathScaleX = Primitive.UnpackPathScale(block.PathScaleX); data.PathScaleY = Primitive.UnpackPathScale(block.PathScaleY); data.PathShearX = Primitive.UnpackPathShear((sbyte)block.PathShearX); data.PathShearY = Primitive.UnpackPathShear((sbyte)block.PathShearY); data.PathTwist = Primitive.UnpackPathTwist(block.PathTwist); data.PathTwistBegin = Primitive.UnpackPathTwist(block.PathTwistBegin); data.PathRadiusOffset = Primitive.UnpackPathTwist(block.PathRadiusOffset); data.PathTaperX = Primitive.UnpackPathTaper(block.PathTaperX); data.PathTaperY = Primitive.UnpackPathTaper(block.PathTaperY); data.PathRevolutions = Primitive.UnpackPathRevolutions(block.PathRevolutions); data.PathSkew = Primitive.UnpackPathTwist(block.PathSkew); data.ProfileBegin = Primitive.UnpackBeginCut(block.ProfileBegin); data.ProfileEnd = Primitive.UnpackEndCut(block.ProfileEnd); data.ProfileHollow = Primitive.UnpackProfileHollow(block.ProfileHollow); data.PCode = pcode; #endregion #region Decode Additional packed parameters in ObjectData int pos = 0; switch (block.ObjectData.Length) { case 76: // Collision normal for avatar objectupdate.CollisionPlane = new Vector4(block.ObjectData, pos); pos += 16; goto case 60; case 60: // Position objectupdate.Position = new Vector3(block.ObjectData, pos); pos += 12; // Velocity objectupdate.Velocity = new Vector3(block.ObjectData, pos); pos += 12; // Acceleration objectupdate.Acceleration = new Vector3(block.ObjectData, pos); pos += 12; // Rotation (theta) objectupdate.Rotation = new Quaternion(block.ObjectData, pos, true); pos += 12; // Angular velocity (omega) objectupdate.AngularVelocity = new Vector3(block.ObjectData, pos); pos += 12; break; case 48: // Collision normal for avatar objectupdate.CollisionPlane = new Vector4(block.ObjectData, pos); pos += 16; goto case 32; case 32: // The data is an array of unsigned shorts // Position objectupdate.Position = new Vector3( Utils.UInt16ToFloat(block.ObjectData, pos, -0.5f * 256.0f, 1.5f * 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 2, -0.5f * 256.0f, 1.5f * 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 3.0f * 256.0f)); pos += 6; // Velocity objectupdate.Velocity = new Vector3( Utils.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f)); pos += 6; // Acceleration objectupdate.Acceleration = new Vector3( Utils.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f)); pos += 6; // Rotation (theta) objectupdate.Rotation = new Quaternion( Utils.UInt16ToFloat(block.ObjectData, pos, -1.0f, 1.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 2, -1.0f, 1.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 4, -1.0f, 1.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 6, -1.0f, 1.0f)); pos += 8; // Angular velocity (omega) objectupdate.AngularVelocity = new Vector3( Utils.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f), Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f)); pos += 6; break; case 16: // The data is an array of single bytes (8-bit numbers) // Position objectupdate.Position = new Vector3( Utils.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; // Velocity objectupdate.Velocity = new Vector3( Utils.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; // Accleration objectupdate.Acceleration = new Vector3( Utils.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; // Rotation objectupdate.Rotation = new Quaternion( Utils.ByteToFloat(block.ObjectData, pos, -1.0f, 1.0f), Utils.ByteToFloat(block.ObjectData, pos + 1, -1.0f, 1.0f), Utils.ByteToFloat(block.ObjectData, pos + 2, -1.0f, 1.0f), Utils.ByteToFloat(block.ObjectData, pos + 3, -1.0f, 1.0f)); pos += 4; // Angular Velocity objectupdate.AngularVelocity = new Vector3( Utils.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Utils.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; break; default: Logger.Log("Got an ObjectUpdate block with ObjectUpdate field length of " + block.ObjectData.Length, Helpers.LogLevel.Warning, Client); continue; } #endregion // Determine the object type and create the appropriate class switch (pcode) { #region Prim and Foliage case PCode.Grass: case PCode.Tree: case PCode.NewTree: case PCode.Prim: bool isNewObject; lock (simulator.ObjectsPrimitives.Dictionary) isNewObject = !simulator.ObjectsPrimitives.ContainsKey(block.ID); Primitive prim = GetPrimitive(simulator, block.ID, block.FullID); // Textures objectupdate.Textures = new Primitive.TextureEntry(block.TextureEntry, 0, block.TextureEntry.Length); OnObjectDataBlockUpdate(new ObjectDataBlockUpdateEventArgs(simulator, prim, data, block, objectupdate, nameValues)); #region Update Prim Info with decoded data prim.Flags = (PrimFlags)block.UpdateFlags; if ((prim.Flags & PrimFlags.ZlibCompressed) != 0) { Logger.Log("Got a ZlibCompressed ObjectUpdate, implement me!", Helpers.LogLevel.Warning, Client); continue; } // Automatically request ObjectProperties for prim if it was rezzed selected. if ((prim.Flags & PrimFlags.CreateSelected) != 0) { SelectObject(simulator, prim.LocalID); } prim.NameValues = nameValues; prim.LocalID = block.ID; prim.ID = block.FullID; prim.ParentID = block.ParentID; prim.RegionHandle = update.RegionData.RegionHandle; prim.Scale = block.Scale; prim.ClickAction = (ClickAction)block.ClickAction; prim.OwnerID = block.OwnerID; prim.MediaURL = Utils.BytesToString(block.MediaURL); prim.Text = Utils.BytesToString(block.Text); prim.TextColor = new Color4(block.TextColor, 0, false, true); // Sound information prim.Sound = block.Sound; prim.SoundFlags = (SoundFlags)block.Flags; prim.SoundGain = block.Gain; prim.SoundRadius = block.Radius; // Joint information prim.Joint = (JointType)block.JointType; prim.JointPivot = block.JointPivot; prim.JointAxisOrAnchor = block.JointAxisOrAnchor; // Object parameters prim.PrimData = data; // Textures, texture animations, particle system, and extra params prim.Textures = objectupdate.Textures; prim.TextureAnim = new Primitive.TextureAnimation(block.TextureAnim, 0); prim.ParticleSys = new Primitive.ParticleSystem(block.PSBlock, 0); prim.SetExtraParamsFromBytes(block.ExtraParams, 0); // PCode-specific data switch (pcode) { case PCode.Grass: case PCode.Tree: case PCode.NewTree: if (block.Data.Length == 1) prim.TreeSpecies = (Tree)block.Data[0]; else Logger.Log("Got a foliage update with an invalid TreeSpecies field", Helpers.LogLevel.Warning); // prim.ScratchPad = Utils.EmptyBytes; // break; //default: // prim.ScratchPad = new byte[block.Data.Length]; // if (block.Data.Length > 0) // Buffer.BlockCopy(block.Data, 0, prim.ScratchPad, 0, prim.ScratchPad.Length); break; } prim.ScratchPad = Utils.EmptyBytes; // Packed parameters prim.CollisionPlane = objectupdate.CollisionPlane; prim.Position = objectupdate.Position; prim.Velocity = objectupdate.Velocity; prim.Acceleration = objectupdate.Acceleration; prim.Rotation = objectupdate.Rotation; prim.AngularVelocity = objectupdate.AngularVelocity; #endregion EventHandler handler = m_ObjectUpdate; if (handler != null) { ThreadPool.QueueUserWorkItem(delegate(object o) { handler(this, new PrimEventArgs(simulator, prim, update.RegionData.TimeDilation, isNewObject, attachment)); }); } break; #endregion Prim and Foliage #region Avatar case PCode.Avatar: bool isNewAvatar; lock (simulator.ObjectsAvatars.Dictionary) isNewAvatar = !simulator.ObjectsAvatars.ContainsKey(block.ID); // Update some internals if this is our avatar if (block.FullID == Client.Self.AgentID && simulator == Client.Network.CurrentSim) { #region Update Client.Self // We need the local ID to recognize terse updates for our agent Client.Self.localID = block.ID; // Packed parameters Client.Self.collisionPlane = objectupdate.CollisionPlane; Client.Self.relativePosition = objectupdate.Position; Client.Self.velocity = objectupdate.Velocity; Client.Self.acceleration = objectupdate.Acceleration; Client.Self.relativeRotation = objectupdate.Rotation; Client.Self.angularVelocity = objectupdate.AngularVelocity; #endregion } #region Create an Avatar from the decoded data Avatar avatar = GetAvatar(simulator, block.ID, block.FullID); objectupdate.Avatar = true; // Textures objectupdate.Textures = new Primitive.TextureEntry(block.TextureEntry, 0, block.TextureEntry.Length); OnObjectDataBlockUpdate(new ObjectDataBlockUpdateEventArgs(simulator, avatar, data, block, objectupdate, nameValues)); uint oldSeatID = avatar.ParentID; avatar.ID = block.FullID; avatar.LocalID = block.ID; avatar.Scale = block.Scale; avatar.CollisionPlane = objectupdate.CollisionPlane; avatar.Position = objectupdate.Position; avatar.Velocity = objectupdate.Velocity; avatar.Acceleration = objectupdate.Acceleration; avatar.Rotation = objectupdate.Rotation; avatar.AngularVelocity = objectupdate.AngularVelocity; avatar.NameValues = nameValues; avatar.PrimData = data; if (block.Data.Length > 0) { Logger.Log("Unexpected Data field for an avatar update, length " + block.Data.Length, Helpers.LogLevel.Warning); } avatar.ParentID = block.ParentID; avatar.RegionHandle = update.RegionData.RegionHandle; SetAvatarSittingOn(simulator, avatar, block.ParentID, oldSeatID); // Textures avatar.Textures = objectupdate.Textures; #endregion Create an Avatar from the decoded data OnAvatarUpdate(new AvatarUpdateEventArgs(simulator, avatar, update.RegionData.TimeDilation, isNewAvatar)); break; #endregion Avatar case PCode.ParticleSystem: DecodeParticleUpdate(block); // TODO: Create a callback for particle updates break; default: Logger.DebugLog("Got an ObjectUpdate block with an unrecognized PCode " + pcode.ToString(), Client); break; } } } protected void DecodeParticleUpdate(ObjectUpdatePacket.ObjectDataBlock block) { // TODO: Handle ParticleSystem ObjectUpdate blocks // float bounce_b // Vector4 scale_range // Vector4 alpha_range // Vector3 vel_offset // float dist_begin_fadeout // float dist_end_fadeout // UUID image_uuid // long flags // byte createme // Vector3 diff_eq_alpha // Vector3 diff_eq_scale // byte max_particles // byte initial_particles // float kill_plane_z // Vector3 kill_plane_normal // float bounce_plane_z // Vector3 bounce_plane_normal // float spawn_range // float spawn_frequency // float spawn_frequency_range // Vector3 spawn_direction // float spawn_direction_range // float spawn_velocity // float spawn_velocity_range // float speed_limit // float wind_weight // Vector3 current_gravity // float gravity_weight // float global_lifetime // float individual_lifetime // float individual_lifetime_range // float alpha_decay // float scale_decay // float distance_death // float damp_motion_factor // Vector3 wind_diffusion_factor } /// /// A terse object update, used when a transformation matrix or /// velocity/acceleration for an object changes but nothing else /// (scale/position/rotation/acceleration/velocity) /// /// The sender /// The EventArgs object containing the packet data protected void ImprovedTerseObjectUpdateHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ImprovedTerseObjectUpdatePacket terse = (ImprovedTerseObjectUpdatePacket)packet; UpdateDilation(simulator, terse.RegionData.TimeDilation); for (int i = 0; i < terse.ObjectData.Length; i++) { ImprovedTerseObjectUpdatePacket.ObjectDataBlock block = terse.ObjectData[i]; try { int pos = 4; uint localid = Utils.BytesToUInt(block.Data, 0); // Check if we are interested in this update if (!Client.Settings.ALWAYS_DECODE_OBJECTS && localid != Client.Self.localID && m_TerseObjectUpdate == null) { continue; } #region Decode update data ObjectMovementUpdate update = new ObjectMovementUpdate(); // LocalID update.LocalID = localid; // State update.State = block.Data[pos++]; // Avatar boolean update.Avatar = (block.Data[pos++] != 0); // Collision normal for avatar if (update.Avatar) { update.CollisionPlane = new Vector4(block.Data, pos); pos += 16; } // Position update.Position = new Vector3(block.Data, pos); pos += 12; // Velocity update.Velocity = new Vector3( Utils.UInt16ToFloat(block.Data, pos, -128.0f, 128.0f), Utils.UInt16ToFloat(block.Data, pos + 2, -128.0f, 128.0f), Utils.UInt16ToFloat(block.Data, pos + 4, -128.0f, 128.0f)); pos += 6; // Acceleration update.Acceleration = new Vector3( Utils.UInt16ToFloat(block.Data, pos, -64.0f, 64.0f), Utils.UInt16ToFloat(block.Data, pos + 2, -64.0f, 64.0f), Utils.UInt16ToFloat(block.Data, pos + 4, -64.0f, 64.0f)); pos += 6; // Rotation (theta) update.Rotation = new Quaternion( Utils.UInt16ToFloat(block.Data, pos, -1.0f, 1.0f), Utils.UInt16ToFloat(block.Data, pos + 2, -1.0f, 1.0f), Utils.UInt16ToFloat(block.Data, pos + 4, -1.0f, 1.0f), Utils.UInt16ToFloat(block.Data, pos + 6, -1.0f, 1.0f)); pos += 8; // Angular velocity (omega) update.AngularVelocity = new Vector3( Utils.UInt16ToFloat(block.Data, pos, -64.0f, 64.0f), Utils.UInt16ToFloat(block.Data, pos + 2, -64.0f, 64.0f), Utils.UInt16ToFloat(block.Data, pos + 4, -64.0f, 64.0f)); pos += 6; // Textures // FIXME: Why are we ignoring the first four bytes here? if (block.TextureEntry.Length != 0) update.Textures = new Primitive.TextureEntry(block.TextureEntry, 4, block.TextureEntry.Length - 4); #endregion Decode update data Primitive obj = !Client.Settings.OBJECT_TRACKING ? null : (update.Avatar) ? (Primitive)GetAvatar(simulator, update.LocalID, UUID.Zero) : (Primitive)GetPrimitive(simulator, update.LocalID, UUID.Zero); // Fire the pre-emptive notice (before we stomp the object) EventHandler handler = m_TerseObjectUpdate; if (handler != null) { ThreadPool.QueueUserWorkItem(delegate(object o) { handler(this, new TerseObjectUpdateEventArgs(simulator, obj, update, terse.RegionData.TimeDilation)); }); } #region Update Client.Self if (update.LocalID == Client.Self.localID) { Client.Self.collisionPlane = update.CollisionPlane; Client.Self.relativePosition = update.Position; Client.Self.velocity = update.Velocity; Client.Self.acceleration = update.Acceleration; Client.Self.relativeRotation = update.Rotation; Client.Self.angularVelocity = update.AngularVelocity; } #endregion Update Client.Self if (Client.Settings.OBJECT_TRACKING && obj != null) { obj.Position = update.Position; obj.Rotation = update.Rotation; obj.Velocity = update.Velocity; obj.CollisionPlane = update.CollisionPlane; obj.Acceleration = update.Acceleration; obj.AngularVelocity = update.AngularVelocity; obj.PrimData.State = update.State; if (update.Textures != null) obj.Textures = update.Textures; } } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Warning, Client, ex); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ObjectUpdateCompressedHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ObjectUpdateCompressedPacket update = (ObjectUpdateCompressedPacket)packet; for (int b = 0; b < update.ObjectData.Length; b++) { ObjectUpdateCompressedPacket.ObjectDataBlock block = update.ObjectData[b]; int i = 0; try { // UUID UUID FullID = new UUID(block.Data, 0); i += 16; // Local ID uint LocalID = (uint)(block.Data[i++] + (block.Data[i++] << 8) + (block.Data[i++] << 16) + (block.Data[i++] << 24)); // PCode PCode pcode = (PCode)block.Data[i++]; #region Relevance check if (!Client.Settings.ALWAYS_DECODE_OBJECTS) { switch (pcode) { case PCode.Grass: case PCode.Tree: case PCode.NewTree: case PCode.Prim: if (m_ObjectUpdate == null) continue; break; } } #endregion Relevance check bool isNew; lock (simulator.ObjectsPrimitives.Dictionary) isNew = simulator.ObjectsPrimitives.ContainsKey(LocalID); Primitive prim = GetPrimitive(simulator, LocalID, FullID); prim.LocalID = LocalID; prim.ID = FullID; prim.Flags = (PrimFlags)block.UpdateFlags; prim.PrimData.PCode = pcode; #region Decode block and update Prim // State prim.PrimData.State = block.Data[i++]; // CRC i += 4; // Material prim.PrimData.Material = (Material)block.Data[i++]; // Click action prim.ClickAction = (ClickAction)block.Data[i++]; // Scale prim.Scale = new Vector3(block.Data, i); i += 12; // Position prim.Position = new Vector3(block.Data, i); i += 12; // Rotation prim.Rotation = new Quaternion(block.Data, i, true); i += 12; // Compressed flags CompressedFlags flags = (CompressedFlags)Utils.BytesToUInt(block.Data, i); i += 4; prim.OwnerID = new UUID(block.Data, i); i += 16; // Angular velocity if ((flags & CompressedFlags.HasAngularVelocity) != 0) { prim.AngularVelocity = new Vector3(block.Data, i); i += 12; } // Parent ID if ((flags & CompressedFlags.HasParent) != 0) { prim.ParentID = (uint)(block.Data[i++] + (block.Data[i++] << 8) + (block.Data[i++] << 16) + (block.Data[i++] << 24)); } else { prim.ParentID = 0; } // Tree data if ((flags & CompressedFlags.Tree) != 0) { prim.TreeSpecies = (Tree)block.Data[i++]; //prim.ScratchPad = Utils.EmptyBytes; } // Scratch pad else if ((flags & CompressedFlags.ScratchPad) != 0) { prim.TreeSpecies = (Tree)0; int size = block.Data[i++]; //prim.ScratchPad = new byte[size]; //Buffer.BlockCopy(block.Data, i, prim.ScratchPad, 0, size); i += size; } prim.ScratchPad = Utils.EmptyBytes; // Floating text if ((flags & CompressedFlags.HasText) != 0) { string text = String.Empty; while (block.Data[i] != 0) { text += (char)block.Data[i]; i++; } i++; // Floating text prim.Text = text; // Text color prim.TextColor = new Color4(block.Data, i, false); i += 4; } else { prim.Text = String.Empty; } // Media URL if ((flags & CompressedFlags.MediaURL) != 0) { string text = String.Empty; while (block.Data[i] != 0) { text += (char)block.Data[i]; i++; } i++; prim.MediaURL = text; } // Particle system if ((flags & CompressedFlags.HasParticles) != 0) { prim.ParticleSys = new Primitive.ParticleSystem(block.Data, i); i += 86; } // Extra parameters i += prim.SetExtraParamsFromBytes(block.Data, i); //Sound data if ((flags & CompressedFlags.HasSound) != 0) { prim.Sound = new UUID(block.Data, i); i += 16; prim.SoundGain = Utils.BytesToFloat(block.Data, i); i += 4; prim.SoundFlags = (SoundFlags)block.Data[i++]; prim.SoundRadius = Utils.BytesToFloat(block.Data, i); i += 4; } // Name values if ((flags & CompressedFlags.HasNameValues) != 0) { string text = String.Empty; while (block.Data[i] != 0) { text += (char)block.Data[i]; i++; } i++; // Parse the name values if (text.Length > 0) { string[] lines = text.Split('\n'); prim.NameValues = new NameValue[lines.Length]; for (int j = 0; j < lines.Length; j++) { if (!String.IsNullOrEmpty(lines[j])) { NameValue nv = new NameValue(lines[j]); prim.NameValues[j] = nv; } } } } prim.PrimData.PathCurve = (PathCurve)block.Data[i++]; ushort pathBegin = Utils.BytesToUInt16(block.Data, i); i += 2; prim.PrimData.PathBegin = Primitive.UnpackBeginCut(pathBegin); ushort pathEnd = Utils.BytesToUInt16(block.Data, i); i += 2; prim.PrimData.PathEnd = Primitive.UnpackEndCut(pathEnd); prim.PrimData.PathScaleX = Primitive.UnpackPathScale(block.Data[i++]); prim.PrimData.PathScaleY = Primitive.UnpackPathScale(block.Data[i++]); prim.PrimData.PathShearX = Primitive.UnpackPathShear((sbyte)block.Data[i++]); prim.PrimData.PathShearY = Primitive.UnpackPathShear((sbyte)block.Data[i++]); prim.PrimData.PathTwist = Primitive.UnpackPathTwist((sbyte)block.Data[i++]); prim.PrimData.PathTwistBegin = Primitive.UnpackPathTwist((sbyte)block.Data[i++]); prim.PrimData.PathRadiusOffset = Primitive.UnpackPathTwist((sbyte)block.Data[i++]); prim.PrimData.PathTaperX = Primitive.UnpackPathTaper((sbyte)block.Data[i++]); prim.PrimData.PathTaperY = Primitive.UnpackPathTaper((sbyte)block.Data[i++]); prim.PrimData.PathRevolutions = Primitive.UnpackPathRevolutions(block.Data[i++]); prim.PrimData.PathSkew = Primitive.UnpackPathTwist((sbyte)block.Data[i++]); prim.PrimData.profileCurve = block.Data[i++]; ushort profileBegin = Utils.BytesToUInt16(block.Data, i); i += 2; prim.PrimData.ProfileBegin = Primitive.UnpackBeginCut(profileBegin); ushort profileEnd = Utils.BytesToUInt16(block.Data, i); i += 2; prim.PrimData.ProfileEnd = Primitive.UnpackEndCut(profileEnd); ushort profileHollow = Utils.BytesToUInt16(block.Data, i); i += 2; prim.PrimData.ProfileHollow = Primitive.UnpackProfileHollow(profileHollow); // TextureEntry int textureEntryLength = (int)Utils.BytesToUInt(block.Data, i); i += 4; prim.Textures = new Primitive.TextureEntry(block.Data, i, textureEntryLength); i += textureEntryLength; // Texture animation if ((flags & CompressedFlags.TextureAnimation) != 0) { //int textureAnimLength = (int)Utils.BytesToUIntBig(block.Data, i); i += 4; prim.TextureAnim = new Primitive.TextureAnimation(block.Data, i); } #endregion #region Raise Events bool isAttachment = (flags & CompressedFlags.HasNameValues) != 0 && prim.ParentID != 0; EventHandler handler = m_ObjectUpdate; if (handler != null) handler(this, new PrimEventArgs(simulator, prim, update.RegionData.TimeDilation, isNew, isAttachment)); #endregion } catch (IndexOutOfRangeException ex) { Logger.Log("Error decoding an ObjectUpdateCompressed packet", Helpers.LogLevel.Warning, Client, ex); Logger.Log(block, Helpers.LogLevel.Warning); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ObjectUpdateCachedHandler(object sender, PacketReceivedEventArgs e) { if (Client.Settings.ALWAYS_REQUEST_OBJECTS) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ObjectUpdateCachedPacket update = (ObjectUpdateCachedPacket)packet; List ids = new List(update.ObjectData.Length); // No object caching implemented yet, so request updates for all of these objects for (int i = 0; i < update.ObjectData.Length; i++) { ids.Add(update.ObjectData[i].ID); } RequestObjects(simulator, ids); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void KillObjectHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; KillObjectPacket kill = (KillObjectPacket)packet; // Notify first, so that handler has a chance to get a // reference from the ObjectTracker to the object being killed for (int i = 0; i < kill.ObjectData.Length; i++) { OnKillObject(new KillObjectEventArgs(simulator, kill.ObjectData[i].ID)); } lock (simulator.ObjectsPrimitives.Dictionary) { List removeAvatars = new List(); List removePrims = new List(); if (Client.Settings.OBJECT_TRACKING) { uint localID; for (int i = 0; i < kill.ObjectData.Length; i++) { localID = kill.ObjectData[i].ID; if (simulator.ObjectsPrimitives.Dictionary.ContainsKey(localID)) removePrims.Add(localID); foreach (KeyValuePair prim in simulator.ObjectsPrimitives.Dictionary) { if (prim.Value.ParentID == localID) { OnKillObject(new KillObjectEventArgs(simulator, prim.Key)); removePrims.Add(prim.Key); } } } } if (Client.Settings.AVATAR_TRACKING) { lock (simulator.ObjectsAvatars.Dictionary) { uint localID; for (int i = 0; i < kill.ObjectData.Length; i++) { localID = kill.ObjectData[i].ID; if (simulator.ObjectsAvatars.Dictionary.ContainsKey(localID)) removeAvatars.Add(localID); List rootPrims = new List(); foreach (KeyValuePair prim in simulator.ObjectsPrimitives.Dictionary) { if (prim.Value.ParentID == localID) { OnKillObject(new KillObjectEventArgs(simulator, prim.Key)); removePrims.Add(prim.Key); rootPrims.Add(prim.Key); } } foreach (KeyValuePair prim in simulator.ObjectsPrimitives.Dictionary) { if (rootPrims.Contains(prim.Value.ParentID)) { OnKillObject(new KillObjectEventArgs(simulator, prim.Key)); removePrims.Add(prim.Key); } } } //Do the actual removing outside of the loops but still inside the lock. //This safely prevents the collection from being modified during a loop. foreach (uint removeID in removeAvatars) simulator.ObjectsAvatars.Dictionary.Remove(removeID); } } foreach (uint removeID in removePrims) simulator.ObjectsPrimitives.Dictionary.Remove(removeID); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ObjectPropertiesHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ObjectPropertiesPacket op = (ObjectPropertiesPacket)packet; ObjectPropertiesPacket.ObjectDataBlock[] datablocks = op.ObjectData; for (int i = 0; i < datablocks.Length; ++i) { ObjectPropertiesPacket.ObjectDataBlock objectData = datablocks[i]; Primitive.ObjectProperties props = new Primitive.ObjectProperties(); props.ObjectID = objectData.ObjectID; props.AggregatePerms = objectData.AggregatePerms; props.AggregatePermTextures = objectData.AggregatePermTextures; props.AggregatePermTexturesOwner = objectData.AggregatePermTexturesOwner; props.Permissions = new Permissions(objectData.BaseMask, objectData.EveryoneMask, objectData.GroupMask, objectData.NextOwnerMask, objectData.OwnerMask); props.Category = (ObjectCategory)objectData.Category; props.CreationDate = Utils.UnixTimeToDateTime((uint)objectData.CreationDate); props.CreatorID = objectData.CreatorID; props.Description = Utils.BytesToString(objectData.Description); props.FolderID = objectData.FolderID; props.FromTaskID = objectData.FromTaskID; props.GroupID = objectData.GroupID; props.InventorySerial = objectData.InventorySerial; props.ItemID = objectData.ItemID; props.LastOwnerID = objectData.LastOwnerID; props.Name = Utils.BytesToString(objectData.Name); props.OwnerID = objectData.OwnerID; props.OwnershipCost = objectData.OwnershipCost; props.SalePrice = objectData.SalePrice; props.SaleType = (SaleType)objectData.SaleType; props.SitName = Utils.BytesToString(objectData.SitName); props.TouchName = Utils.BytesToString(objectData.TouchName); int numTextures = objectData.TextureID.Length / 16; props.TextureIDs = new UUID[numTextures]; for (int j = 0; j < numTextures; ++j) props.TextureIDs[j] = new UUID(objectData.TextureID, j * 16); if (Client.Settings.OBJECT_TRACKING) { Primitive findPrim = simulator.ObjectsPrimitives.Find( delegate(Primitive prim) { return prim.ID == props.ObjectID; }); if (findPrim != null) { OnObjectPropertiesUpdated(new ObjectPropertiesUpdatedEventArgs(simulator, findPrim, props)); lock (simulator.ObjectsPrimitives.Dictionary) { if (simulator.ObjectsPrimitives.Dictionary.ContainsKey(findPrim.LocalID)) simulator.ObjectsPrimitives.Dictionary[findPrim.LocalID].Properties = props; } } } OnObjectProperties(new ObjectPropertiesEventArgs(simulator, props)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ObjectPropertiesFamilyHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ObjectPropertiesFamilyPacket op = (ObjectPropertiesFamilyPacket)packet; Primitive.ObjectProperties props = new Primitive.ObjectProperties(); ReportType requestType = (ReportType)op.ObjectData.RequestFlags; props.ObjectID = op.ObjectData.ObjectID; props.Category = (ObjectCategory)op.ObjectData.Category; props.Description = Utils.BytesToString(op.ObjectData.Description); props.GroupID = op.ObjectData.GroupID; props.LastOwnerID = op.ObjectData.LastOwnerID; props.Name = Utils.BytesToString(op.ObjectData.Name); props.OwnerID = op.ObjectData.OwnerID; props.OwnershipCost = op.ObjectData.OwnershipCost; props.SalePrice = op.ObjectData.SalePrice; props.SaleType = (SaleType)op.ObjectData.SaleType; props.Permissions.BaseMask = (PermissionMask)op.ObjectData.BaseMask; props.Permissions.EveryoneMask = (PermissionMask)op.ObjectData.EveryoneMask; props.Permissions.GroupMask = (PermissionMask)op.ObjectData.GroupMask; props.Permissions.NextOwnerMask = (PermissionMask)op.ObjectData.NextOwnerMask; props.Permissions.OwnerMask = (PermissionMask)op.ObjectData.OwnerMask; if (Client.Settings.OBJECT_TRACKING) { Primitive findPrim = simulator.ObjectsPrimitives.Find( delegate(Primitive prim) { return prim.ID == op.ObjectData.ObjectID; }); if (findPrim != null) { lock (simulator.ObjectsPrimitives.Dictionary) { if (simulator.ObjectsPrimitives.Dictionary.ContainsKey(findPrim.LocalID)) { if (simulator.ObjectsPrimitives.Dictionary[findPrim.LocalID].Properties == null) simulator.ObjectsPrimitives.Dictionary[findPrim.LocalID].Properties = new Primitive.ObjectProperties(); simulator.ObjectsPrimitives.Dictionary[findPrim.LocalID].Properties.SetFamilyProperties(props); } } } } OnObjectPropertiesFamily(new ObjectPropertiesFamilyEventArgs(simulator, props, requestType)); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void PayPriceReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_PayPriceReply != null) { Packet packet = e.Packet; Simulator simulator = e.Simulator; PayPriceReplyPacket p = (PayPriceReplyPacket)packet; UUID objectID = p.ObjectData.ObjectID; int defaultPrice = p.ObjectData.DefaultPayPrice; int[] buttonPrices = new int[p.ButtonData.Length]; for (int i = 0; i < p.ButtonData.Length; i++) { buttonPrices[i] = p.ButtonData[i].PayButton; } OnPayPriceReply(new PayPriceReplyEventArgs(simulator, objectID, defaultPrice, buttonPrices)); } } /// /// /// /// /// /// protected void ObjectPhysicsPropertiesHandler(string capsKey, IMessage message, Simulator simulator) { ObjectPhysicsPropertiesMessage msg = (ObjectPhysicsPropertiesMessage)message; if (Client.Settings.OBJECT_TRACKING) { for (int i = 0; i < msg.ObjectPhysicsProperties.Length; i++) { lock (simulator.ObjectsPrimitives.Dictionary) { if (simulator.ObjectsPrimitives.Dictionary.ContainsKey(msg.ObjectPhysicsProperties[i].LocalID)) { simulator.ObjectsPrimitives.Dictionary[msg.ObjectPhysicsProperties[i].LocalID].PhysicsProps = msg.ObjectPhysicsProperties[i]; } } } } if (m_PhysicsProperties != null) { for (int i = 0; i < msg.ObjectPhysicsProperties.Length; i++) { OnPhysicsProperties(new PhysicsPropertiesEventArgs(simulator, msg.ObjectPhysicsProperties[i])); } } } #endregion Packet Handlers #region Utility Functions /// /// Setup construction data for a basic primitive shape /// /// Primitive shape to construct /// Construction data that can be plugged into a public static Primitive.ConstructionData BuildBasicShape(PrimType type) { Primitive.ConstructionData prim = new Primitive.ConstructionData(); prim.PCode = PCode.Prim; prim.Material = Material.Wood; switch (type) { case PrimType.Box: prim.ProfileCurve = ProfileCurve.Square; prim.PathCurve = PathCurve.Line; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 1f; prim.PathRevolutions = 1f; break; case PrimType.Cylinder: prim.ProfileCurve = ProfileCurve.Circle; prim.PathCurve = PathCurve.Line; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 1f; prim.PathRevolutions = 1f; break; case PrimType.Prism: prim.ProfileCurve = ProfileCurve.Square; prim.PathCurve = PathCurve.Line; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 0f; prim.PathScaleY = 0f; prim.PathRevolutions = 1f; break; case PrimType.Ring: prim.ProfileCurve = ProfileCurve.EqualTriangle; prim.PathCurve = PathCurve.Circle; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 0.25f; prim.PathRevolutions = 1f; break; case PrimType.Sphere: prim.ProfileCurve = ProfileCurve.HalfCircle; prim.PathCurve = PathCurve.Circle; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 1f; prim.PathRevolutions = 1f; break; case PrimType.Torus: prim.ProfileCurve = ProfileCurve.Circle; prim.PathCurve = PathCurve.Circle; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 0.25f; prim.PathRevolutions = 1f; break; case PrimType.Tube: prim.ProfileCurve = ProfileCurve.Square; prim.PathCurve = PathCurve.Circle; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 0.25f; prim.PathRevolutions = 1f; break; case PrimType.Sculpt: prim.ProfileCurve = ProfileCurve.Circle; prim.PathCurve = PathCurve.Circle; prim.ProfileEnd = 1f; prim.PathEnd = 1f; prim.PathScaleX = 1f; prim.PathScaleY = 0.5f; prim.PathRevolutions = 1f; break; default: throw new NotSupportedException("Unsupported shape: " + type.ToString()); } return prim; } /// /// /// /// /// /// /// protected void SetAvatarSittingOn(Simulator sim, Avatar av, uint localid, uint oldSeatID) { if (Client.Network.CurrentSim == sim && av.LocalID == Client.Self.localID) { Client.Self.sittingOn = localid; } av.ParentID = localid; if (m_AvatarSitChanged != null && oldSeatID != localid) { OnAvatarSitChanged(new AvatarSitChangedEventArgs(sim, av, localid, oldSeatID)); } } /// /// /// /// /// protected void UpdateDilation(Simulator s, uint dilation) { s.Stats.Dilation = (float)dilation / 65535.0f; } /// /// Set the Shape data of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// Data describing the prim shape public void SetShape(Simulator simulator, uint localID, Primitive.ConstructionData prim) { ObjectShapePacket shape = new ObjectShapePacket(); shape.AgentData.AgentID = Client.Self.AgentID; shape.AgentData.SessionID = Client.Self.SessionID; shape.ObjectData = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock[1]; shape.ObjectData[0] = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock(); shape.ObjectData[0].ObjectLocalID = localID; shape.ObjectData[0].PathCurve = (byte)prim.PathCurve; shape.ObjectData[0].PathBegin = Primitive.PackBeginCut(prim.PathBegin); shape.ObjectData[0].PathEnd = Primitive.PackEndCut(prim.PathEnd); shape.ObjectData[0].PathScaleX = Primitive.PackPathScale(prim.PathScaleX); shape.ObjectData[0].PathScaleY = Primitive.PackPathScale(prim.PathScaleY); shape.ObjectData[0].PathShearX = (byte)Primitive.PackPathShear(prim.PathShearX); shape.ObjectData[0].PathShearY = (byte)Primitive.PackPathShear(prim.PathShearY); shape.ObjectData[0].PathTwist = Primitive.PackPathTwist(prim.PathTwist); shape.ObjectData[0].PathTwistBegin = Primitive.PackPathTwist(prim.PathTwistBegin); shape.ObjectData[0].PathRadiusOffset = Primitive.PackPathTwist(prim.PathRadiusOffset); shape.ObjectData[0].PathTaperX = Primitive.PackPathTaper(prim.PathTaperX); shape.ObjectData[0].PathTaperY = Primitive.PackPathTaper(prim.PathTaperY); shape.ObjectData[0].PathRevolutions = Primitive.PackPathRevolutions(prim.PathRevolutions); shape.ObjectData[0].PathSkew = Primitive.PackPathTwist(prim.PathSkew); shape.ObjectData[0].ProfileCurve = prim.profileCurve; shape.ObjectData[0].ProfileBegin = Primitive.PackBeginCut(prim.ProfileBegin); shape.ObjectData[0].ProfileEnd = Primitive.PackEndCut(prim.ProfileEnd); shape.ObjectData[0].ProfileHollow = Primitive.PackProfileHollow(prim.ProfileHollow); Client.Network.SendPacket(shape, simulator); } /// /// Set the Material data of an object /// /// A reference to the object where the object resides /// The objects ID which is local to the simulator the object is in /// The new material of the object public void SetMaterial(Simulator simulator, uint localID, Material material) { ObjectMaterialPacket matPacket = new ObjectMaterialPacket(); matPacket.AgentData.AgentID = Client.Self.AgentID; matPacket.AgentData.SessionID = Client.Self.SessionID; matPacket.ObjectData = new ObjectMaterialPacket.ObjectDataBlock[1]; matPacket.ObjectData[0] = new ObjectMaterialPacket.ObjectDataBlock(); matPacket.ObjectData[0].ObjectLocalID = localID; matPacket.ObjectData[0].Material = (byte)material; Client.Network.SendPacket(matPacket, simulator); } #endregion Utility Functions #region Object Tracking Link /// /// /// /// /// /// /// protected Primitive GetPrimitive(Simulator simulator, uint localID, UUID fullID) { if (Client.Settings.OBJECT_TRACKING) { lock (simulator.ObjectsPrimitives.Dictionary) { Primitive prim; if (simulator.ObjectsPrimitives.Dictionary.TryGetValue(localID, out prim)) { return prim; } else { prim = new Primitive(); prim.LocalID = localID; prim.ID = fullID; prim.RegionHandle = simulator.Handle; simulator.ObjectsPrimitives.Dictionary[localID] = prim; return prim; } } } else { return new Primitive(); } } /// /// /// /// /// /// /// protected Avatar GetAvatar(Simulator simulator, uint localID, UUID fullID) { if (Client.Settings.AVATAR_TRACKING) { lock (simulator.ObjectsAvatars.Dictionary) { Avatar avatar; if (simulator.ObjectsAvatars.Dictionary.TryGetValue(localID, out avatar)) { return avatar; } else { avatar = new Avatar(); avatar.LocalID = localID; avatar.ID = fullID; avatar.RegionHandle = simulator.Handle; simulator.ObjectsAvatars.Dictionary[localID] = avatar; return avatar; } } } else { return new Avatar(); } } #endregion Object Tracking Link protected void InterpolationTimer_Elapsed(object obj) { int elapsed = 0; if (Client.Network.Connected) { int start = Environment.TickCount; int interval = Environment.TickCount - Client.Self.lastInterpolation; float seconds = (float)interval / 1000f; // Iterate through all of the simulators Simulator[] sims = Client.Network.Simulators.ToArray(); for (int i = 0; i < sims.Length; i++) { Simulator sim = sims[i]; float adjSeconds = seconds * sim.Stats.Dilation; // Iterate through all of this sims avatars sim.ObjectsAvatars.ForEach( delegate(Avatar avatar) { #region Linear Motion // Only do movement interpolation (extrapolation) when there is a non-zero velocity but // no acceleration if (avatar.Acceleration != Vector3.Zero && avatar.Velocity == Vector3.Zero) { avatar.Position += (avatar.Velocity + avatar.Acceleration * (0.5f * (adjSeconds - HAVOK_TIMESTEP))) * adjSeconds; avatar.Velocity += avatar.Acceleration * adjSeconds; } #endregion Linear Motion } ); // Iterate through all of this sims primitives sim.ObjectsPrimitives.ForEach( delegate(Primitive prim) { if (prim.Joint == JointType.Invalid) { #region Angular Velocity Vector3 angVel = prim.AngularVelocity; float omega = angVel.LengthSquared(); if (omega > 0.00001f) { omega = (float)Math.Sqrt(omega); float angle = omega * adjSeconds; angVel *= 1.0f / omega; Quaternion dQ = Quaternion.CreateFromAxisAngle(angVel, angle); prim.Rotation *= dQ; } #endregion Angular Velocity #region Linear Motion // Only do movement interpolation (extrapolation) when there is a non-zero velocity but // no acceleration if (prim.Acceleration != Vector3.Zero && prim.Velocity == Vector3.Zero) { prim.Position += (prim.Velocity + prim.Acceleration * (0.5f * (adjSeconds - HAVOK_TIMESTEP))) * adjSeconds; prim.Velocity += prim.Acceleration * adjSeconds; } #endregion Linear Motion } else if (prim.Joint == JointType.Hinge) { //FIXME: Hinge movement extrapolation } else if (prim.Joint == JointType.Point) { //FIXME: Point movement extrapolation } else { Logger.Log("Unhandled joint type " + prim.Joint, Helpers.LogLevel.Warning, Client); } } ); } // Make sure the last interpolated time is always updated Client.Self.lastInterpolation = Environment.TickCount; elapsed = Client.Self.lastInterpolation - start; } // Start the timer again. Use a minimum of a 50ms pause in between calculations int delay = Math.Max(50, Settings.INTERPOLATION_INTERVAL - elapsed); if (InterpolationTimer != null) { InterpolationTimer.Change(delay, Timeout.Infinite); } } } #region EventArgs classes /// Provides data for the event /// The event occurs when the simulator sends /// an containing a Primitive, Foliage or Attachment data /// Note 1: The event will not be raised when the object is an Avatar /// Note 2: It is possible for the to be /// raised twice for the same object if for example the primitive moved to a new simulator, then returned to the current simulator or /// if an Avatar crosses the border into a new simulator and returns to the current simulator /// /// /// The following code example uses the , , and /// properties to display new Primitives and Attachments on the window. /// /// // Subscribe to the event that gives us prim and foliage information /// Client.Objects.ObjectUpdate += Objects_ObjectUpdate; /// /// /// private void Objects_ObjectUpdate(object sender, PrimEventArgs e) /// { /// Console.WriteLine("Primitive {0} {1} in {2} is an attachment {3}", e.Prim.ID, e.Prim.LocalID, e.Simulator.Name, e.IsAttachment); /// } /// /// /// /// /// public class PrimEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly bool m_IsNew; private readonly bool m_IsAttachment; private readonly Primitive m_Prim; private readonly ushort m_TimeDilation; /// Get the simulator the originated from public Simulator Simulator { get { return m_Simulator; } } /// Get the details public Primitive Prim { get { return m_Prim; } } /// true if the did not exist in the dictionary before this update (always true if object tracking has been disabled) public bool IsNew { get { return m_IsNew; } } /// true if the is attached to an public bool IsAttachment { get { return m_IsAttachment; } } /// Get the simulator Time Dilation public ushort TimeDilation { get { return m_TimeDilation; } } /// /// Construct a new instance of the PrimEventArgs class /// /// The simulator the object originated from /// The Primitive /// The simulator time dilation /// The prim was not in the dictionary before this update /// true if the primitive represents an attachment to an agent public PrimEventArgs(Simulator simulator, Primitive prim, ushort timeDilation, bool isNew, bool isAttachment) { this.m_Simulator = simulator; this.m_IsNew = isNew; this.m_IsAttachment = isAttachment; this.m_Prim = prim; this.m_TimeDilation = timeDilation; } } /// Provides data for the event /// The event occurs when the simulator sends /// an containing Avatar data /// Note 1: The event will not be raised when the object is an Avatar /// Note 2: It is possible for the to be /// raised twice for the same avatar if for example the avatar moved to a new simulator, then returned to the current simulator /// /// /// The following code example uses the property to make a request for the top picks /// using the method in the class to display the names /// of our own agents picks listings on the window. /// /// // subscribe to the AvatarUpdate event to get our information /// Client.Objects.AvatarUpdate += Objects_AvatarUpdate; /// Client.Avatars.AvatarPicksReply += Avatars_AvatarPicksReply; /// /// private void Objects_AvatarUpdate(object sender, AvatarUpdateEventArgs e) /// { /// // we only want our own data /// if (e.Avatar.LocalID == Client.Self.LocalID) /// { /// // Unsubscribe from the avatar update event to prevent a loop /// // where we continually request the picks every time we get an update for ourselves /// Client.Objects.AvatarUpdate -= Objects_AvatarUpdate; /// // make the top picks request through AvatarManager /// Client.Avatars.RequestAvatarPicks(e.Avatar.ID); /// } /// } /// /// private void Avatars_AvatarPicksReply(object sender, AvatarPicksReplyEventArgs e) /// { /// // we'll unsubscribe from the AvatarPicksReply event since we now have the data /// // we were looking for /// Client.Avatars.AvatarPicksReply -= Avatars_AvatarPicksReply; /// // loop through the dictionary and extract the names of the top picks from our profile /// foreach (var pickName in e.Picks.Values) /// { /// Console.WriteLine(pickName); /// } /// } /// /// /// /// public class AvatarUpdateEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Avatar m_Avatar; private readonly ushort m_TimeDilation; private readonly bool m_IsNew; /// Get the simulator the object originated from public Simulator Simulator { get { return m_Simulator; } } /// Get the data public Avatar Avatar { get { return m_Avatar; } } /// Get the simulator time dilation public ushort TimeDilation { get { return m_TimeDilation; } } /// true if the did not exist in the dictionary before this update (always true if avatar tracking has been disabled) public bool IsNew { get { return m_IsNew; } } /// /// Construct a new instance of the AvatarUpdateEventArgs class /// /// The simulator the packet originated from /// The data /// The simulator time dilation /// The avatar was not in the dictionary before this update public AvatarUpdateEventArgs(Simulator simulator, Avatar avatar, ushort timeDilation, bool isNew) { this.m_Simulator = simulator; this.m_Avatar = avatar; this.m_TimeDilation = timeDilation; this.m_IsNew = isNew; } } /// Provides additional primitive data for the event /// The event occurs when the simulator sends /// an containing additional details for a Primitive, Foliage data or Attachment data /// The event is also raised when a request is /// made. /// /// /// The following code example uses the , and /// /// properties to display new attachments and send a request for additional properties containing the name of the /// attachment then display it on the window. /// /// // Subscribe to the event that provides additional primitive details /// Client.Objects.ObjectProperties += Objects_ObjectProperties; /// /// // handle the properties data that arrives /// private void Objects_ObjectProperties(object sender, ObjectPropertiesEventArgs e) /// { /// Console.WriteLine("Primitive Properties: {0} Name is {1}", e.Properties.ObjectID, e.Properties.Name); /// } /// /// public class ObjectPropertiesEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Primitive.ObjectProperties m_Properties; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// Get the primitive properties public Primitive.ObjectProperties Properties { get { return m_Properties; } } /// /// Construct a new instance of the ObjectPropertiesEventArgs class /// /// The simulator the object is located /// The primitive Properties public ObjectPropertiesEventArgs(Simulator simulator, Primitive.ObjectProperties props) { this.m_Simulator = simulator; this.m_Properties = props; } } /// Provides additional primitive data for the event /// The event occurs when the simulator sends /// an containing additional details for a Primitive or Foliage data that is currently /// being tracked in the dictionary /// The event is also raised when a request is /// made and is enabled /// public class ObjectPropertiesUpdatedEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Primitive m_Prim; private readonly Primitive.ObjectProperties m_Properties; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// Get the primitive details public Primitive Prim { get { return m_Prim; } } /// Get the primitive properties public Primitive.ObjectProperties Properties { get { return m_Properties; } } /// /// Construct a new instance of the ObjectPropertiesUpdatedEvenrArgs class /// /// The simulator the object is located /// The Primitive /// The primitive Properties public ObjectPropertiesUpdatedEventArgs(Simulator simulator, Primitive prim, Primitive.ObjectProperties props) { this.m_Simulator = simulator; this.m_Prim = prim; this.m_Properties = props; } } /// Provides additional primitive data, permissions and sale info for the event /// The event occurs when the simulator sends /// an containing additional details for a Primitive, Foliage data or Attachment. This includes /// Permissions, Sale info, and other basic details on an object /// The event is also raised when a request is /// made, the viewer equivalent is hovering the mouse cursor over an object /// public class ObjectPropertiesFamilyEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Primitive.ObjectProperties m_Properties; private readonly ReportType m_Type; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// public Primitive.ObjectProperties Properties { get { return m_Properties; } } /// public ReportType Type { get { return m_Type; } } public ObjectPropertiesFamilyEventArgs(Simulator simulator, Primitive.ObjectProperties props, ReportType type) { this.m_Simulator = simulator; this.m_Properties = props; this.m_Type = type; } } /// Provides primitive data containing updated location, velocity, rotation, textures for the event /// The event occurs when the simulator sends updated location, velocity, rotation, etc /// public class TerseObjectUpdateEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Primitive m_Prim; private readonly ObjectMovementUpdate m_Update; private readonly ushort m_TimeDilation; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// Get the primitive details public Primitive Prim { get { return m_Prim; } } /// public ObjectMovementUpdate Update { get { return m_Update; } } /// public ushort TimeDilation { get { return m_TimeDilation; } } public TerseObjectUpdateEventArgs(Simulator simulator, Primitive prim, ObjectMovementUpdate update, ushort timeDilation) { this.m_Simulator = simulator; this.m_Prim = prim; this.m_Update = update; this.m_TimeDilation = timeDilation; } } /// /// /// public class ObjectDataBlockUpdateEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Primitive m_Prim; private readonly Primitive.ConstructionData m_ConstructionData; private readonly ObjectUpdatePacket.ObjectDataBlock m_Block; private readonly ObjectMovementUpdate m_Update; private readonly NameValue[] m_NameValues; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// Get the primitive details public Primitive Prim { get { return m_Prim; } } /// public Primitive.ConstructionData ConstructionData { get { return m_ConstructionData; } } /// public ObjectUpdatePacket.ObjectDataBlock Block { get { return m_Block; } } /// public ObjectMovementUpdate Update { get { return m_Update; } } /// public NameValue[] NameValues { get { return m_NameValues; } } public ObjectDataBlockUpdateEventArgs(Simulator simulator, Primitive prim, Primitive.ConstructionData constructionData, ObjectUpdatePacket.ObjectDataBlock block, ObjectMovementUpdate objectupdate, NameValue[] nameValues) { this.m_Simulator = simulator; this.m_Prim = prim; this.m_ConstructionData = constructionData; this.m_Block = block; this.m_Update = objectupdate; this.m_NameValues = nameValues; } } /// Provides notification when an Avatar, Object or Attachment is DeRezzed or moves out of the avatars view for the /// event public class KillObjectEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly uint m_ObjectLocalID; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// The LocalID of the object public uint ObjectLocalID { get { return m_ObjectLocalID; } } public KillObjectEventArgs(Simulator simulator, uint objectID) { this.m_Simulator = simulator; this.m_ObjectLocalID = objectID; } } /// /// Provides updates sit position data /// public class AvatarSitChangedEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly Avatar m_Avatar; private readonly uint m_SittingOn; private readonly uint m_OldSeat; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// public Avatar Avatar { get { return m_Avatar; } } /// public uint SittingOn { get { return m_SittingOn; } } /// public uint OldSeat { get { return m_OldSeat; } } public AvatarSitChangedEventArgs(Simulator simulator, Avatar avatar, uint sittingOn, uint oldSeat) { this.m_Simulator = simulator; this.m_Avatar = avatar; this.m_SittingOn = sittingOn; this.m_OldSeat = oldSeat; } } /// /// /// public class PayPriceReplyEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly UUID m_ObjectID; private readonly int m_DefaultPrice; private readonly int[] m_ButtonPrices; /// Get the simulator the object is located public Simulator Simulator { get { return m_Simulator; } } /// public UUID ObjectID { get { return m_ObjectID; } } /// public int DefaultPrice { get { return m_DefaultPrice; } } /// public int[] ButtonPrices { get { return m_ButtonPrices; } } public PayPriceReplyEventArgs(Simulator simulator, UUID objectID, int defaultPrice, int[] buttonPrices) { this.m_Simulator = simulator; this.m_ObjectID = objectID; this.m_DefaultPrice = defaultPrice; this.m_ButtonPrices = buttonPrices; } } public class ObjectMediaEventArgs : EventArgs { /// /// Indicates if the operation was successful /// public bool Success { get; set; } /// /// Media version string /// public string Version { get; set; } /// /// Array of media entries indexed by face number /// public MediaEntry[] FaceMedia { get; set; } public ObjectMediaEventArgs(bool success, string version, MediaEntry[] faceMedia) { this.Success = success; this.Version = version; this.FaceMedia = faceMedia; } } /// /// Set when simulator sends us infomation on primitive's physical properties /// public class PhysicsPropertiesEventArgs : EventArgs { /// Simulator where the message originated public Simulator Simulator; /// Updated physical properties public Primitive.PhysicsProperties PhysicsProperties; /// /// Constructor /// /// Simulator where the message originated /// Updated physical properties public PhysicsPropertiesEventArgs(Simulator sim, Primitive.PhysicsProperties props) { Simulator = sim; PhysicsProperties = props; } } #endregion }