/* * Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2025, Sjofn LLC. * 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.co 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.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using LibreMetaverse.Materials; 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, /// Whether the object has, you guessed it, new particles HasParticlesNew = 0x400 } /// /// 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 selecting 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 #region ObjectAnimation event /// The event subscribers, null if no subscribers private EventHandler m_ObjectAnimation; ///Raises the ObjectAnimation Event /// An ObjectAnimationEventArgs object containing /// the data sent from the simulator protected virtual void OnObjectAnimation(ObjectAnimationEventArgs e) { EventHandler handler = m_ObjectAnimation; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ObjectAnimationLock = new object(); /// Raised when the simulator sends us data containing /// an agents animation playlist public event EventHandler ObjectAnimation { add { lock (m_ObjectAnimationLock) { m_ObjectAnimation += value; } } remove { lock (m_ObjectAnimationLock) { m_ObjectAnimation -= value; } } } #endregion ObjectAnimation event #region ObjectUpdate event /// The event subscribers, null if 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; } } } #endregion ObjectUpdate event #region ObjectProperties event /// The event subscribers, null if 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; handler?.Invoke(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 if 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; handler?.Invoke(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; } } } #endregion ObjectProperties event #region ObjectPropertiesFamily event /// The event subscribers, null if 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; handler?.Invoke(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; } } } #endregion ObjectPropertiesFamily #region AvatarUpdate event /// The event subscribers, null if no subscribers private EventHandler m_AvatarUpdate; private EventHandler m_ParticleUpdate; ///Raises the AvatarUpdate Event /// A AvatarUpdateEventArgs object containing /// the data sent from the simulator protected virtual void OnAvatarUpdate(AvatarUpdateEventArgs e) { EventHandler handler = m_AvatarUpdate; handler?.Invoke(this, e); } /// /// Raises the ParticleUpdate Event /// /// A ParticleUpdateEventArgs object containing /// the data sent from the simulator protected virtual void OnParticleUpdate(ParticleUpdateEventArgs e) { EventHandler handler = m_ParticleUpdate; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_AvatarUpdateLock = new object(); private readonly object m_ParticleUpdateLock = 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; } } } #endregion AvatarUpdate event #region TerseObjectUpdate event public event EventHandler ParticleUpdate { add { lock (m_ParticleUpdateLock) { m_ParticleUpdate += value; } } remove { lock (m_ParticleUpdateLock) { m_ParticleUpdate -= value; } } } /// The event subscribers, null if 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; } } } #endregion TerseObjectUpdate event #region ObjectDataBlockUpdate event /// The event subscribers, null if 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; handler?.Invoke(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; } } } #endregion ObjectDataBlockUpdate event #region KillObject event /// The event subscribers, null if 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; handler?.Invoke(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; } } } #endregion KillObject event #region KillObjects event /// The event subscribers, null if no subscribers private EventHandler m_KillObjects; ///Raises the KillObjects Event /// A KillObjectsEventArgs object containing /// the data sent from the simulator protected virtual void OnKillObjects(KillObjectsEventArgs e) { EventHandler handler = m_KillObjects; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_KillObjectsLock = new object(); /// Raised when the simulator informs us when a group of /// or is no longer within view public event EventHandler KillObjects { add { lock (m_KillObjectsLock) { m_KillObjects += value; } } remove { lock (m_KillObjectsLock) { m_KillObjects -= value; } } } #endregion KillObjects event #region AvatarSitChanged event /// The event subscribers, null if 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; handler?.Invoke(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; } } } #endregion AvatarSitChanged event #region PayPriceReply event /// The event subscribers, null if 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; handler?.Invoke(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; } } } #endregion PayPriceReply #region PhysicsProperties event /// /// Callback for getting object media data via CAP /// /// Indicates if the operation was successful /// 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 if 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; handler?.Invoke(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 PhysicsProperties event #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.RegisterCallback(PacketType.ObjectAnimation, ObjectAnimationHandler); 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, Client.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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[1] }; request.ObjectData[0] = new RequestMultipleObjectsPacket.ObjectDataBlock { ID = localID, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[localIDs.Count] }; for (var i = 0; i < localIDs.Count; i++) { request.ObjectData[i] = new RequestMultipleObjectsPacket.ObjectDataBlock { ID = localIDs[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 /// 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, GroupID = groupID, CategoryID = categoryID }, ObjectData = new ObjectBuyPacket.ObjectDataBlock[1] }; buy.ObjectData[0] = new ObjectBuyPacket.ObjectDataBlock { ObjectLocalID = localID, SaleType = (byte)saleType, 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 { ObjectData = new RequestPayPricePacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectSelectPacket.ObjectDataBlock[1] }; select.ObjectData[0] = new ObjectSelectPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectSelectPacket.ObjectDataBlock[localIDs.Length] }; for (var i = 0; i < localIDs.Length; i++) { select.ObjectData[i] = new ObjectSelectPacket.ObjectDataBlock { 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 representation prim will have in the physics engine /// Density - normal value 1000 /// Friction - normal value 0.6 /// Restitution - standard value 0.5 /// Gravity multiplier - standard 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, ObjectLocalID = localID, UsePhysics = physical, IsTemporary = temporary, IsPhantom = phantom, CastsShadows = castsShadow }, ExtraPhysics = new ObjectFlagUpdatePacket.ExtraPhysicsBlock[1] }; flags.ExtraPhysics[0] = new ObjectFlagUpdatePacket.ExtraPhysicsBlock { PhysicsShapeType = (byte)physicsType, Density = density, Friction = friction, Restitution = restitution, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectSaleInfoPacket.ObjectDataBlock[1] }; sale.ObjectData[0] = new ObjectSaleInfoPacket.ObjectDataBlock { LocalID = localID, SalePrice = price, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectSaleInfoPacket.ObjectDataBlock[localIDs.Count] }; for (var i = 0; i < localIDs.Count; i++) { sale.ObjectData[i] = new ObjectSaleInfoPacket.ObjectDataBlock { LocalID = localIDs[i], SalePrice = price, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectDeselectPacket.ObjectDataBlock[1] }; deselect.ObjectData[0] = new ObjectDeselectPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectDeselectPacket.ObjectDataBlock[localIDs.Length] }; for (var i = 0; i < localIDs.Length; i++) { deselect.ObjectData[i] = new ObjectDeselectPacket.ObjectDataBlock { 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 perpendicular to the surface) /// The surface binormal of the position to touch (A binormal is a vector tangent 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = { GrabOffset = Vector3.Zero, LocalID = localID }, SurfaceInfo = new ObjectGrabPacket.SurfaceInfoBlock[1] }; grab.SurfaceInfo[0] = new ObjectGrabPacket.SurfaceInfoBlock { UVCoord = uvCoord, STCoord = stCoord, FaceIndex = faceIndex, Position = position, Normal = normal, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = { LocalID = localID }, SurfaceInfo = new ObjectDeGrabPacket.SurfaceInfoBlock[1] }; degrab.SurfaceInfo[0] = new ObjectDeGrabPacket.SurfaceInfoBlock { UVCoord = uvCoord, STCoord = stCoord, FaceIndex = faceIndex, Position = position, Normal = normal, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, GroupID = groupID }, ObjectData = { State = prim.State, AddFlags = (uint)createFlags, PCode = (byte)PCode.Prim, Material = (byte)prim.Material, Scale = scale, Rotation = rotation, PathCurve = (byte)prim.PathCurve, PathBegin = Primitive.PackBeginCut(prim.PathBegin), PathEnd = Primitive.PackEndCut(prim.PathEnd), PathRadiusOffset = Primitive.PackPathTwist(prim.PathRadiusOffset), PathRevolutions = Primitive.PackPathRevolutions(prim.PathRevolutions), PathScaleX = Primitive.PackPathScale(prim.PathScaleX), PathScaleY = Primitive.PackPathScale(prim.PathScaleY), PathShearX = (byte)Primitive.PackPathShear(prim.PathShearX), PathShearY = (byte)Primitive.PackPathShear(prim.PathShearY), PathSkew = Primitive.PackPathTwist(prim.PathSkew), PathTaperX = Primitive.PackPathTaper(prim.PathTaperX), PathTaperY = Primitive.PackPathTaper(prim.PathTaperY), PathTwist = Primitive.PackPathTwist(prim.PathTwist), PathTwistBegin = Primitive.PackPathTwist(prim.PathTwistBegin), ProfileCurve = prim.profileCurve, ProfileBegin = Primitive.PackBeginCut(prim.ProfileBegin), ProfileEnd = Primitive.PackEndCut(prim.ProfileEnd), ProfileHollow = Primitive.PackProfileHollow(prim.ProfileHollow), RayStart = position, RayEnd = position, RayEndIsIntersection = 0, RayTargetID = UUID.Zero, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, GroupID = groupOwner }, ObjectData = { BypassRaycast = 1, Material = 3, PathCurve = 16, PCode = newTree ? (byte)PCode.NewTree : (byte)PCode.Tree, RayEnd = position, RayStart = position, RayTargetID = UUID.Zero, Rotation = rotation, Scale = scale, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, GroupID = groupOwner }, ObjectData = { BypassRaycast = 1, Material = 3, PathCurve = 16, PCode = (byte)PCode.Grass, RayEnd = position, RayStart = position, RayTargetID = UUID.Zero, Rotation = rotation, Scale = scale, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectImagePacket.ObjectDataBlock[1] }; image.ObjectData[0] = new ObjectImagePacket.ObjectDataBlock { ObjectLocalID = localID, TextureEntry = textures.GetBytes(), 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 /// object containing the data to set public void SetLight(Simulator simulator, uint localID, Primitive.LightData light) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1] }; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock { ObjectLocalID = localID, ParamType = (byte)ExtraParamType.Light, // Disables the light if intensity is 0 ParamInUse = light.Intensity != 0.0f, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1] }; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock { ObjectLocalID = localID, ParamType = (byte)ExtraParamType.Flexible, ParamInUse = true, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1] }; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock { ObjectLocalID = localID, ParamType = (byte)ExtraParamType.Sculpt, ParamInUse = true, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock[1] }; shape.ObjectData[0] = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock { ObjectLocalID = localID, PathScaleX = 100, PathScaleY = 150, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1] }; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock { ObjectLocalID = localID, ParamType = (byte)type, ParamInUse = false, ParamData = Utils.EmptyBytes, 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 public void LinkPrims(Simulator simulator, List localIDs) { ObjectLinkPacket packet = new ObjectLinkPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectLinkPacket.ObjectDataBlock[localIDs.Count] }; for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectLinkPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectDelinkPacket.ObjectDataBlock[localIDs.Count] }; int i = 0; foreach (uint localID in localIDs) { packet.ObjectData[i] = new ObjectDelinkPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectRotationPacket.ObjectDataBlock[1] }; objRotPacket.ObjectData[0] = new ObjectRotationPacket.ObjectDataBlock { ObjectLocalID = localID, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectNamePacket.ObjectDataBlock[localIDs.Length] }; for (int i = 0; i < localIDs.Length; ++i) { namePacket.ObjectData[i] = new ObjectNamePacket.ObjectDataBlock { LocalID = localIDs[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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectDescriptionPacket.ObjectDataBlock[localIDs.Length] }; for (int i = 0; i < localIDs.Length; ++i) { descPacket.ObjectData[i] = new ObjectDescriptionPacket.ObjectDataBlock { LocalID = localIDs[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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, AttachmentPoint = (byte)attachPoint }, ObjectData = new ObjectAttachPacket.ObjectDataBlock[1] }; attach.ObjectData[0] = new ObjectAttachPacket.ObjectDataBlock { ObjectLocalID = localID, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectDropPacket.ObjectDataBlock[1] }; dropit.ObjectData[0] = new ObjectDropPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectDetachPacket.ObjectDataBlock[localIDs.Count] }; for (int i = 0; i < localIDs.Count; i++) { detach.ObjectData[i] = new ObjectDetachPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new MultipleObjectUpdatePacket.ObjectDataBlock[1] }; multiObjectUpdate.ObjectData[0] = new MultipleObjectUpdatePacket.ObjectDataBlock { Type = (byte)type, ObjectLocalID = localID, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new MultipleObjectUpdatePacket.ObjectDataBlock[1] }; multiObjectUpdate.ObjectData[0] = new MultipleObjectUpdatePacket.ObjectDataBlock { Type = (byte)type, ObjectLocalID = localID, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, HeaderData = { // Can only be use in God mode Override = false, OwnerID = UUID.Zero, GroupID = groupOwner }, ObjectData = new ObjectOwnerPacket.ObjectDataBlock[1] }; objDeedPacket.ObjectData[0] = new ObjectOwnerPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, HeaderData = { // Can only be use in God mode Override = false, OwnerID = UUID.Zero, GroupID = groupOwner }, ObjectData = new ObjectOwnerPacket.ObjectDataBlock[localIDs.Count] }; for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectOwnerPacket.ObjectDataBlock { 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, HeaderData = { // Override can only be used by gods Override = false }, ObjectData = new ObjectPermissionsPacket.ObjectDataBlock[localIDs.Count] }; for (int i = 0; i < localIDs.Count; i++) { packet.ObjectData[i] = new ObjectPermissionsPacket.ObjectDataBlock { ObjectLocalID = localIDs[i], Field = (byte)who, Mask = (uint)permissions, 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, 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 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 { AgentData = { AgentID = Client.Self.AgentID, GroupID = groupID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectGroupPacket.ObjectDataBlock[localIds.Count] }; for (int i = 0; i < localIds.Count; i++) { packet.ObjectData[i] = new ObjectGroupPacket.ObjectDataBlock { 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 cap; if ((cap = Client.Network.CurrentSim.Caps?.CapabilityURI("ObjectMediaNavigate")) == null) { Logger.Log("ObjectMediaNavigate capability not available", Helpers.LogLevel.Error, Client); return; } ObjectMediaNavigateMessage payload = new ObjectMediaNavigateMessage { PrimID = primID, URL = newURL, Face = face }; Task req = Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, payload.Serialize(), CancellationToken.None, (response, data, error) => { if (error != null) { Logger.Log($"ObjectMediaNavigate: {error.Message}", Helpers.LogLevel.Error, Client, error); } }); } /// /// 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 /// Simulator in which prim is located public void UpdateObjectMedia(UUID primID, MediaEntry[] faceMedia, Simulator sim) { Uri cap; if (sim.Caps == null || (cap = Client.Network.CurrentSim.Caps.CapabilityURI("ObjectMedia")) == null) { Logger.Log("ObjectMedia capability not available", Helpers.LogLevel.Error, Client); return; } ObjectMediaUpdate payload = new ObjectMediaUpdate {PrimID = primID, FaceMedia = faceMedia, Verb = "UPDATE"}; Task req = Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, payload.Serialize(), CancellationToken.None, (response, data, error) => { if (error != null) { Logger.Log($"ObjectMediaUpdate: {error.Message}", Helpers.LogLevel.Error, Client, error); } }); } /// /// 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 cap; if (sim.Caps != null && (cap = Client.Network.CurrentSim.Caps.CapabilityURI("ObjectMedia")) != null) { ObjectMediaRequest payload = new ObjectMediaRequest {PrimID = primID, Verb = "GET"}; Task req = Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, payload.Serialize(), CancellationToken.None, (httpResponse, data, error) => { if (error != null) { Logger.Log("Failed retrieving ObjectMedia data", Helpers.LogLevel.Error, Client, error); try { callback(false, string.Empty, null); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } return; } ObjectMediaMessage msg = new ObjectMediaMessage(); OSD result = OSDParser.Deserialize(data); msg.Deserialize((OSDMap)result); if (msg.Request is ObjectMediaResponse response) { if (Client.Settings.OBJECT_TRACKING) { var kvp = sim.ObjectsPrimitives.FirstOrDefault( p => p.Value.ID == primID); if (kvp.Value != null) { Primitive prim = kvp.Value; if (prim != null) { prim.MediaVersion = response.Version; prim.FaceMedia = response.FaceMedia; } sim.ObjectsPrimitives.TryUpdate(kvp.Key, prim, kvp.Value); } } 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); } } }); } 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); } } } public async Task> RequestMaterials(Simulator sim) { if (sim == null) { return null; } if (sim.Caps == null) { await Task.Delay(TimeSpan.FromSeconds(10)); } if (sim.Caps == null) { Logger.Log("Caps are down, unable to retrieve materials.", Helpers.LogLevel.Info, Client); return null; } var uri = sim.Caps.CapabilityURI("RenderMaterials"); List matsToReturn = new List(); Logger.Log($"Awaiting materials from {uri}", Helpers.LogLevel.Info, Client); await Client.HttpCapsClient.GetRequestAsync(uri, CancellationToken.None, ((response, data, error) => { if (error != null) { Logger.Log("Failed fetching materials", Helpers.LogLevel.Error, Client, error); return; } if (data == null || data.Length == 0) { Logger.Log("Failed fetching materials; result was empty.", Helpers.LogLevel.Error, Client); return; } try { OSD result = OSDParser.Deserialize(data); RenderMaterialsMessage info = new RenderMaterialsMessage(); info.Deserialize(result as OSDMap); if (info.MaterialData is OSDArray mats) { foreach (var entry in mats) { if (entry is OSDMap map) { matsToReturn.Add(new LegacyMaterial(map)); } else { Logger.Log("Unexpected OSD return;\n" + OSDParser.SerializeJson(entry, true).ToJson(), Helpers.LogLevel.Info, Client); } } } else { Logger.Log("Unexpected OSD return;\n" + OSDParser.SerializeJson(result, true).ToJson(), Helpers.LogLevel.Info, Client); } Logger.Log($"Fetched (x{matsToReturn.Count}) from {uri}", Helpers.LogLevel.Info, Client); } catch (Exception ex) { Logger.Log("Failed fetching RenderMaterials", Helpers.LogLevel.Error, Client, ex); if (data.Length > 0) { Logger .Log("Response unparsable; " + System.Text.Encoding.UTF8.GetString(data), Helpers.LogLevel.Info, Client); } } })); return matsToReturn; } public async Task> RequestMaterials(Simulator sim, IEnumerable materials) { if (sim == null) { return null; } if (sim.Caps == null) { await Task.Delay(TimeSpan.FromSeconds(10)); } if (sim.Caps == null) { Logger.Log("Caps are down, unable to retrieve materials.", Helpers.LogLevel.Info, Client); return null; } var array = new OSDArray(); foreach (var material in materials) { array.Add(material); } OSDMap request = new OSDMap(new Dictionary { {"Zipped", Helpers.ZCompressOSD(array)} }); var uri = sim.Caps.CapabilityURI("RenderMaterials"); List matsToReturn = new List(); Logger.Log($"Awaiting materials (x{array.Count}) from {uri}", Helpers.LogLevel.Info, Client); await Client.HttpCapsClient.PostRequestAsync(uri, OSDFormat.Xml, request, CancellationToken.None, (response, data, error) => { if (error != null) { Logger.Log("Failed fetching materials", Helpers.LogLevel.Error, Client, error); return; } if (data == null || data.Length == 0) { Logger.Log("Failed fetching materials; result was empty.", Helpers.LogLevel.Error, Client); Logger .Log($"Sent:\n{uri}\n{Convert.ToBase64String(OSDParser.SerializeLLSDBinary(request), Base64FormattingOptions.InsertLineBreaks)}", Helpers.LogLevel.Info, Client); return; } try { OSD result = OSDParser.Deserialize(data); RenderMaterialsMessage info = new RenderMaterialsMessage(); info.Deserialize(result as OSDMap); if (info.MaterialData is OSDArray mats) { foreach (var entry in mats) { if (entry is OSDMap map) { matsToReturn.Add(new LegacyMaterial(map)); } else { Logger.Log("Unexpected OSD return;\n" + OSDParser.SerializeJson(entry, true).ToJson(), Helpers.LogLevel.Info, Client); } } } else { Logger.Log("Unexpected OSD return;\n" + OSDParser.SerializeJson(result, true).ToJson(), Helpers.LogLevel.Info, Client); } Logger.Log($"Fetched (x{matsToReturn.Count}) from {uri}", Helpers.LogLevel.Info, Client); } catch (Exception ex) { Logger.Log("Failed fetching RenderMaterials", Helpers.LogLevel.Error, Client, ex); Logger.Log($"Sent:\n{uri}\n{System.Text.Encoding.UTF8.GetString(OSDParser.SerializeLLSDXmlBytes(request))}", Helpers.LogLevel.Info, Client); Logger.Log("Requests: " + string.Join(",", materials.Select(m => m.ToString())), Helpers.LogLevel.Info); if (data.Length > 0) { Logger .Log("Unable to parse response; " + System.Text.Encoding.UTF8.GetString(data), Helpers.LogLevel.Info, Client); } } }); return matsToReturn; } #endregion #region Packet Handlers private void ObjectAnimationHandler(object sender, PacketReceivedEventArgs e) { if (!(e.Packet is ObjectAnimationPacket data)) { return; } List signaledAnimations = new List(data.AnimationList.Length); for (var i = 0; i < data.AnimationList.Length; i++) { Animation animation = new Animation { AnimationID = data.AnimationList[i].AnimID, AnimationSequence = data.AnimationList[i].AnimSequenceID }; if (i < data.AnimationList.Length) { animation.AnimationSourceObjectID = data.Sender.ID; } signaledAnimations.Add(animation); } OnObjectAnimation(new ObjectAnimationEventArgs(data.Sender.ID, signaledAnimations)); } /// 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); foreach (var block in update.ObjectData) { 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 = Array.Empty(); } #endregion NameValue parsing #region Decode Object (primitive) parameters Primitive.ConstructionData data = new Primitive.ConstructionData { State = block.State, Material = (Material)block.Material, PathCurve = (PathCurve)block.PathCurve, profileCurve = block.ProfileCurve, PathBegin = Primitive.UnpackBeginCut(block.PathBegin), PathEnd = Primitive.UnpackEndCut(block.PathEnd), PathScaleX = Primitive.UnpackPathScale(block.PathScaleX), PathScaleY = Primitive.UnpackPathScale(block.PathScaleY), PathShearX = Primitive.UnpackPathShear((sbyte)block.PathShearX), PathShearY = Primitive.UnpackPathShear((sbyte)block.PathShearY), PathTwist = Primitive.UnpackPathTwist(block.PathTwist), PathTwistBegin = Primitive.UnpackPathTwist(block.PathTwistBegin), PathRadiusOffset = Primitive.UnpackPathTwist(block.PathRadiusOffset), PathTaperX = Primitive.UnpackPathTaper(block.PathTaperX), PathTaperY = Primitive.UnpackPathTaper(block.PathTaperY), PathRevolutions = Primitive.UnpackPathRevolutions(block.PathRevolutions), PathSkew = Primitive.UnpackPathTwist(block.PathSkew), ProfileBegin = Primitive.UnpackBeginCut(block.ProfileBegin), ProfileEnd = Primitive.UnpackEndCut(block.ProfileEnd), ProfileHollow = Primitive.UnpackProfileHollow(block.ProfileHollow), 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 = !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); prim.IsAttachment = attachment; // 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.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)); }); } //OnParticleUpdate handler replacing decode particles, PCode.Particle system appears to be deprecated this is a fix if (prim.ParticleSys.PartMaxAge != 0) { OnParticleUpdate(new ParticleUpdateEventArgs(simulator, prim.ParticleSys, prim)); } break; #endregion Prim and Foliage #region Avatar case PCode.Avatar: bool 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; if (nameValues.Length > 0) { // Not great modularity, but considering how often this method runs, better to not, e.g., have Avatar define an ObjectDataBlockUpdate handler. avatar._cachedName = avatar._cachedGroupName = null; } 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); break; default: Logger.DebugLog("Got an ObjectUpdate block with an unrecognized PCode " + pcode, 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); foreach (var block in terse.ObjectData) { 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 LocalID = localid, // State State = block.Data[pos++], // Avatar boolean 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) ? GetAvatar(simulator, update.LocalID, UUID.Zero) : 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; foreach (var block in update.ObjectData) { 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 = !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 = 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) { int idx = i; while (block.Data[i] != 0) { i++; } // Floating text prim.Text = Utils.BytesToString(block.Data, idx, i - idx); i++; // Text color prim.TextColor = new Color4(block.Data, i,false,true); i += 4; } else { prim.Text = string.Empty; } // Media URL if ((flags & CompressedFlags.MediaURL) != 0) { int idx = i; while (block.Data[i] != 0) { i++; } prim.MediaURL = Utils.BytesToString(block.Data, idx, i - idx); i++; } // 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 prim.IsAttachment = (flags & CompressedFlags.HasNameValues) != 0 && prim.ParentID != 0; #region Raise Events EventHandler handler = m_ObjectUpdate; handler?.Invoke(this, new PrimEventArgs(simulator, prim, update.RegionData.TimeDilation, isNew, prim.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) { bool cachedPrimitives = Client.Settings.CACHE_PRIMITIVES; Packet packet = e.Packet; Simulator simulator = e.Simulator; ObjectUpdateCachedPacket update = (ObjectUpdateCachedPacket)packet; List ids = new List(update.ObjectData.Length); // Object caching is implemented when Client.Settings.PRIMITIVES_FACTORY is True, otherwise request updates for all of these objects foreach (var odb in update.ObjectData) { uint localID = odb.ID; uint crc = odb.CRC; if (cachedPrimitives) { if (!simulator.DataPool.NeedsRequest(localID, crc)) { continue; } } ids.Add(localID); } 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 var localIdsToKill = new List(kill.ObjectData.Length); foreach (var objectToKill in kill.ObjectData) { if(objectToKill.ID == Client.Self.localID) { continue; } localIdsToKill.Add(objectToKill.ID); OnKillObject(new KillObjectEventArgs(simulator, objectToKill.ID)); } OnKillObjects(new KillObjectsEventArgs(e.Simulator, localIdsToKill.ToArray())); List removeAvatars = new List(); List removePrims = new List(); if (Client.Settings.OBJECT_TRACKING) { foreach (var localID in localIdsToKill) { if (simulator.ObjectsPrimitives.ContainsKey(localID)) { removePrims.Add(localID); } foreach (var prim in simulator.ObjectsPrimitives) { if (prim.Value.ParentID == localID) { OnKillObject(new KillObjectEventArgs(simulator, prim.Key)); removePrims.Add(prim.Key); } } } } if (Client.Settings.AVATAR_TRACKING) { foreach (var localID in localIdsToKill) { var rootPrims = new List(); foreach (var prim in simulator.ObjectsPrimitives .Where(prim => prim.Value.ParentID == localID)) { OnKillObject(new KillObjectEventArgs(simulator, prim.Key)); removePrims.Add(prim.Key); rootPrims.Add(prim.Key); } foreach (var prim in simulator.ObjectsPrimitives .Where(prim => rootPrims.Contains(prim.Value.ParentID))) { OnKillObject(new KillObjectEventArgs(simulator, prim.Key)); removePrims.Add(prim.Key); } _ = simulator.ObjectsAvatars.TryRemove(localID, out _); } } if (Client.Settings.CACHE_PRIMITIVES) { simulator.DataPool.ReleasePrims(removePrims); } foreach (uint removeID in removePrims) { simulator.ObjectsPrimitives.TryRemove(removeID, out _); } } /// 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; foreach (var objectData in datablocks) { Primitive.ObjectProperties props = new Primitive.ObjectProperties { ObjectID = objectData.ObjectID, AggregatePerms = objectData.AggregatePerms, AggregatePermTextures = objectData.AggregatePermTextures, AggregatePermTexturesOwner = objectData.AggregatePermTexturesOwner, Permissions = new Permissions(objectData.BaseMask, objectData.EveryoneMask, objectData.GroupMask, objectData.NextOwnerMask, objectData.OwnerMask), Category = (ObjectCategory)objectData.Category, CreationDate = Utils.UnixTimeToDateTime((uint)objectData.CreationDate), CreatorID = objectData.CreatorID, Description = Utils.BytesToString(objectData.Description), FolderID = objectData.FolderID, FromTaskID = objectData.FromTaskID, GroupID = objectData.GroupID, InventorySerial = objectData.InventorySerial, ItemID = objectData.ItemID, LastOwnerID = objectData.LastOwnerID, Name = Utils.BytesToString(objectData.Name), OwnerID = objectData.OwnerID, OwnershipCost = objectData.OwnershipCost, SalePrice = objectData.SalePrice, SaleType = (SaleType)objectData.SaleType, SitName = Utils.BytesToString(objectData.SitName), 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) { if (simulator.GlobalToLocalID.TryGetValue(props.ObjectID, out var localID)) { if (simulator.ObjectsPrimitives.TryGetValue(localID, out var findPrim)) { if (findPrim != null) { OnObjectPropertiesUpdated(new ObjectPropertiesUpdatedEventArgs(simulator, findPrim, props)); if (simulator.ObjectsPrimitives.TryGetValue(findPrim.LocalID, out var primitive)) { primitive.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) { if (simulator.GlobalToLocalID.TryGetValue(props.ObjectID, out var localID)) { if (simulator.ObjectsPrimitives.TryGetValue(localID, out var findPrim)) { if (findPrim != null) { if (simulator.ObjectsPrimitives.TryGetValue(findPrim.LocalID, out var prim)) { if (prim.Properties == null) { prim.Properties = new Primitive.ObjectProperties(); } prim.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) { foreach (var prop in msg.ObjectPhysicsProperties) { if (simulator.ObjectsPrimitives.TryGetValue(prop.LocalID, out var primitive)) { primitive.PhysicsProps = prop; } } } if (m_PhysicsProperties != null) { foreach (var prop in msg.ObjectPhysicsProperties) { OnPhysicsProperties(new PhysicsPropertiesEventArgs(simulator, prop)); } } } #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 { PCode = PCode.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.EqualTriangle; 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); } 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 = 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock[1] }; shape.ObjectData[0] = new OpenMetaverse.Packets.ObjectShapePacket.ObjectDataBlock { ObjectLocalID = localID, PathCurve = (byte)prim.PathCurve, PathBegin = Primitive.PackBeginCut(prim.PathBegin), PathEnd = Primitive.PackEndCut(prim.PathEnd), PathScaleX = Primitive.PackPathScale(prim.PathScaleX), PathScaleY = Primitive.PackPathScale(prim.PathScaleY), PathShearX = (byte)Primitive.PackPathShear(prim.PathShearX), PathShearY = (byte)Primitive.PackPathShear(prim.PathShearY), PathTwist = Primitive.PackPathTwist(prim.PathTwist), PathTwistBegin = Primitive.PackPathTwist(prim.PathTwistBegin), PathRadiusOffset = Primitive.PackPathTwist(prim.PathRadiusOffset), PathTaperX = Primitive.PackPathTaper(prim.PathTaperX), PathTaperY = Primitive.PackPathTaper(prim.PathTaperY), PathRevolutions = Primitive.PackPathRevolutions(prim.PathRevolutions), PathSkew = Primitive.PackPathTwist(prim.PathSkew), ProfileCurve = prim.profileCurve, ProfileBegin = Primitive.PackBeginCut(prim.ProfileBegin), ProfileEnd = Primitive.PackEndCut(prim.ProfileEnd), 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 { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = new ObjectMaterialPacket.ObjectDataBlock[1] }; matPacket.ObjectData[0] = new ObjectMaterialPacket.ObjectDataBlock { ObjectLocalID = localID, Material = (byte)material }; Client.Network.SendPacket(matPacket, simulator); } #endregion Utility Functions #region Object Tracking Link /// /// /// /// /// /// /// protected Primitive GetPrimitive(Simulator simulator, uint localID, UUID fullID) { return GetPrimitive(simulator, localID, fullID, true); } /// /// /// /// /// /// /// /// public Primitive GetPrimitive(Simulator simulator, uint localID, UUID fullID, bool createIfMissing) { if (Client.Settings.OBJECT_TRACKING) { if (simulator.ObjectsPrimitives.TryGetValue(localID, out var prim)) { return prim; } if (!createIfMissing) {return null;} if (Client.Settings.CACHE_PRIMITIVES) { prim = simulator.DataPool.MakePrimitive(localID); } else { prim = new Primitive { LocalID = localID, RegionHandle = simulator.Handle }; } prim.ActiveClients++; prim.ID = fullID; prim = simulator.ObjectsPrimitives.GetOrAdd(localID, prim); simulator.GlobalToLocalID.AddOrUpdate(prim.ID, prim.LocalID, (uuid, u) => prim.LocalID); return prim; } return new Primitive(); } /// /// /// /// /// /// /// protected Avatar GetAvatar(Simulator simulator, uint localID, UUID fullID) { if (!Client.Settings.AVATAR_TRACKING) { return new Avatar(); } return simulator.ObjectsAvatars.GetOrAdd(localID, new Avatar { LocalID = localID, ID = fullID, RegionHandle = simulator.Handle }); } #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 = interval / 1000f; // Iterate through all simulators var sims = Client.Network.Simulators.ToImmutableArray(); foreach (var sim in sims) { float adjSeconds = seconds * sim.Stats.Dilation; // Iterate through all of this region's avatars foreach (var avatar in sim.ObjectsAvatars) { #region Linear Motion var av = avatar.Value; lock (av) { if (av.Acceleration != Vector3.Zero) { av.Velocity += av.Acceleration * adjSeconds; } if (av.Velocity != Vector3.Zero) { av.Position += (av.Velocity) * adjSeconds; } } #endregion Linear Motion } // Iterate through all the simulator's primitives foreach (var prim in sim.ObjectsPrimitives) { var pv = prim.Value; lock (pv) { if (pv.Joint == JointType.Invalid) { Vector3 angVel = pv.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); pv.Rotation *= dQ; } // Only do movement interpolation (extrapolation) when there is a non-zero velocity but // no acceleration if (pv.Acceleration != Vector3.Zero && pv.Velocity == Vector3.Zero) { pv.Position += (pv.Velocity + pv.Acceleration * (0.5f * (adjSeconds - HAVOK_TIMESTEP))) * adjSeconds; pv.Velocity += pv.Acceleration * adjSeconds; } } else if (pv.Joint == JointType.Hinge) { //FIXME: Hinge movement extrapolation } else if (pv.Joint == JointType.Point) { //FIXME: Point movement extrapolation } else { Logger.Log($"Unhandled joint type {pv.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, Client.Settings.INTERPOLATION_INTERVAL - elapsed); 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 { /// Get the simulator the originated from public Simulator Simulator { get; } /// Get the details public Primitive Prim { get; } /// true if the did not exist in the dictionary before this update (always true if object tracking has been disabled) public bool IsNew { get; } /// true if the is attached to an public bool IsAttachment { get; } /// Get the simulator Time Dilation public ushort TimeDilation { get; } /// /// 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.Simulator = simulator; this.IsNew = isNew; this.IsAttachment = isAttachment; this.Prim = prim; this.TimeDilation = timeDilation; } } /// Provides data for the event /// The event occurs when the simulator sends /// 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 { /// Get the simulator the object originated from public Simulator Simulator { get; } /// Get the data public Avatar Avatar { get; } /// Get the simulator time dilation public ushort TimeDilation { get; } /// true if the did not exist in the dictionary before this update (always true if avatar tracking has been disabled) public bool IsNew { get; } /// /// 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.Simulator = simulator; this.Avatar = avatar; this.TimeDilation = timeDilation; this.IsNew = isNew; } } public class ObjectAnimationEventArgs : EventArgs { /// Get the ID of the agent public UUID ObjectID { get; } /// Get the list of animations to start public List Animations { get; } /// /// Construct a new instance of the AvatarAnimationEventArgs class /// /// The ID of the agent /// The list of animations to start public ObjectAnimationEventArgs(UUID objectID, List anims) { this.ObjectID = objectID; this.Animations = anims; } } public class ParticleUpdateEventArgs : EventArgs { /// Get the simulator the object originated from public Simulator Simulator { get; } /// Get the data public Primitive.ParticleSystem ParticleSystem { get; } /// Get source public Primitive Source { get; } /// /// Construct a new instance of the ParticleUpdateEventArgs class /// /// The simulator the packet originated from /// The ParticleSystem data /// The Primitive source public ParticleUpdateEventArgs(Simulator simulator, Primitive.ParticleSystem particlesystem, Primitive source) { this.Simulator = simulator; this.ParticleSystem = particlesystem; this.Source = source; } } /// Provides additional primitive data for the event /// The event occurs when the simulator sends /// 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 { protected readonly Simulator m_Simulator; protected readonly Primitive.ObjectProperties m_Properties; /// Get the simulator the object is located public Simulator Simulator => m_Simulator; /// Get the primitive properties public Primitive.ObjectProperties Properties => 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 : ObjectPropertiesEventArgs { /// Get the primitive details public Primitive Prim { get; } /// /// Construct a new instance of the ObjectPropertiesUpdatedEventArgs class /// /// The simulator the object is located /// The Primitive /// The primitive Properties public ObjectPropertiesUpdatedEventArgs(Simulator simulator, Primitive prim, Primitive.ObjectProperties props) : base(simulator, props) { this.Prim = prim; } } /// 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 { /// Get the simulator the object is located public Simulator Simulator { get; } /// public Primitive.ObjectProperties Properties { get; } /// public ReportType Type { get; } public ObjectPropertiesFamilyEventArgs(Simulator simulator, Primitive.ObjectProperties props, ReportType type) { this.Simulator = simulator; this.Properties = props; this.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 { /// Get the simulator the object is located public Simulator Simulator { get; } /// Get the primitive details public Primitive Prim { get; } /// public ObjectMovementUpdate Update { get; } /// public ushort TimeDilation { get; } public TerseObjectUpdateEventArgs(Simulator simulator, Primitive prim, ObjectMovementUpdate update, ushort timeDilation) { this.Simulator = simulator; this.Prim = prim; this.Update = update; this.TimeDilation = timeDilation; } } /// /// /// public class ObjectDataBlockUpdateEventArgs : EventArgs { /// Get the simulator the object is located public Simulator Simulator { get; } /// Get the primitive details public Primitive Prim { get; } /// public Primitive.ConstructionData ConstructionData { get; } /// public ObjectUpdatePacket.ObjectDataBlock Block { get; } /// public ObjectMovementUpdate Update { get; } /// public NameValue[] NameValues { get; } public ObjectDataBlockUpdateEventArgs(Simulator simulator, Primitive prim, Primitive.ConstructionData constructionData, ObjectUpdatePacket.ObjectDataBlock block, ObjectMovementUpdate objectupdate, NameValue[] nameValues) { this.Simulator = simulator; this.Prim = prim; this.ConstructionData = constructionData; this.Block = block; this.Update = objectupdate; this.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 { /// Get the simulator the object is located public Simulator Simulator { get; } /// The LocalID of the object public uint ObjectLocalID { get; } public KillObjectEventArgs(Simulator simulator, uint objectID) { this.Simulator = simulator; this.ObjectLocalID = objectID; } } /// Provides notification when an Avatar, Object or Attachment is DeRezzed or moves out of the avatars view for the /// event public class KillObjectsEventArgs : EventArgs { /// Get the simulator the object is located public Simulator Simulator { get; } /// The LocalID of the object public uint[] ObjectLocalIDs { get; } public KillObjectsEventArgs(Simulator simulator, uint[] objectIDs) { this.Simulator = simulator; this.ObjectLocalIDs = objectIDs; } } /// /// Provides updates sit position data /// public class AvatarSitChangedEventArgs : EventArgs { /// Get the simulator the object is located public Simulator Simulator { get; } /// public Avatar Avatar { get; } /// public uint SittingOn { get; } /// public uint OldSeat { get; } public AvatarSitChangedEventArgs(Simulator simulator, Avatar avatar, uint sittingOn, uint oldSeat) { this.Simulator = simulator; this.Avatar = avatar; this.SittingOn = sittingOn; this.OldSeat = oldSeat; } } /// /// /// public class PayPriceReplyEventArgs : EventArgs { /// Get the simulator the object is located public Simulator Simulator { get; } /// public UUID ObjectID { get; } /// public int DefaultPrice { get; } /// public int[] ButtonPrices { get; } public PayPriceReplyEventArgs(Simulator simulator, UUID objectID, int defaultPrice, int[] buttonPrices) { this.Simulator = simulator; this.ObjectID = objectID; this.DefaultPrice = defaultPrice; this.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 information 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 }