/* * Copyright (c) 2006, Second Life Reverse Engineering Team * 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 Second Life Reverse Engineering Team 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.Xml.Serialization; using System.Text; using libsecondlife.Packets; namespace libsecondlife { /// /// Contains the variables sent in an object update packet for objects. /// Used to track position and movement of prims and avatars /// public struct ObjectUpdate { /// public bool Avatar; /// public LLVector4 CollisionPlane; /// public byte State; /// public uint LocalID; /// public LLVector3 Position; /// public LLVector3 Velocity; /// public LLVector3 Acceleration; /// public LLQuaternion Rotation; /// public LLVector3 AngularVelocity; /// public LLObject.TextureEntry Textures; } /// /// Handles all network traffic related to prims and avatar positions and /// movement. /// public class ObjectManager { #region CallBack Definitions /// /// /// /// /// /// /// public delegate void NewPrimCallback(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation); /// /// /// /// /// /// /// public delegate void NewAttachmentCallback(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation); /// /// /// /// /// public delegate void ObjectPropertiesFamilyCallback(Simulator simulator, LLObject.ObjectPropertiesFamily properties); /// /// /// /// /// /// /// public delegate void NewAvatarCallback(Simulator simulator, Avatar avatar, ulong regionHandle, ushort timeDilation); /// /// /// /// /// /// /// public delegate void NewFoliageCallback(Simulator simulator, Primitive foliage, ulong regionHandle, ushort timeDilation); /// /// /// /// /// /// /// public delegate void ObjectUpdatedCallback(Simulator simulator, ObjectUpdate update, ulong regionHandle, ushort timeDilation); /// /// /// /// /// public delegate void KillObjectCallback(Simulator simulator, uint objectID); /// /// Called whenever the client avatar sits down or stands up /// /// Simulator the packet was received from /// The local ID of the object that is being sat /// on. If this is zero the avatar is not sitting on an object public delegate void AvatarSitChanged(Simulator simulator, uint sittingOn); #endregion #region Object/Prim Enums /// /// /// public enum PCode { /// Prim = 9, /// Avatar = 47, /// Grass = 95, /// NewTree = 111, /// ParticleSystem = 143, /// Tree = 255 } /// /// /// public enum AttachmentPoint { /// Default = 0, /// Chest = 1, /// Skull, /// LeftShoulder, /// RightShoulder, /// LeftHand, /// RightHand, /// LeftFoot, /// RightFoot, /// Spine, /// Pelvis, /// Mouth, /// Chin, /// LeftEar, /// RightEar, /// LeftEyeball, /// RightEyeball, /// Nose, /// RightUpperArm, /// RightForearm, /// LeftUpperArm, /// LeftForearm, /// RightHip, /// RightUpperLeg, /// RightLowerLeg, /// LeftHip, /// LeftUpperLeg, /// LeftLowerLeg, /// Stomach, /// LeftPec, /// RightPec } /// /// Bitflag field for ObjectUpdateCompressed data blocks, describing /// which options are present for each object /// [Flags] public enum CompressedFlags { /// Hasn't been spotted in the wild yet ScratchPad = 0x01, /// This may be incorrect 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, /// Unknown Unknown = 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 } /// /// /// public enum Tree { /// Pine1 = 0, /// Oak, /// TropicalBush1, /// Palm1, /// Dogwood, /// TropicalBush2, /// Palm2, /// Cypress1, /// Cypress2, /// Pine2, /// Plumeria, /// WinterPine1, /// WinterAspen, /// WinterPine2, /// Eucalyptus, /// Fern, /// Eelgrass, /// SeaSword, /// Kelp1, /// BeachGrass1, /// Kelp2 } /// /// /// public enum Grass { /// Grass0 = 0, /// Grass1, /// Grass2, /// Grass3, /// Grass4, /// Undergrowth1 } #endregion #region Events /// /// This event will be raised for every ObjectUpdate block that /// contains a prim that isn't attached to an avatar. /// /// Depending on the circumstances a client could /// receive two or more of these events for the same object, if you /// or the object left the current sim and returned for example. Client /// applications are responsible for tracking and storing objects. /// public event NewPrimCallback OnNewPrim; /// /// This event will be raised for every ObjectUpdate block that /// contains an avatar attachment. /// /// Depending on the circumstances a client could /// receive two or more of these events for the same object, if you /// or the object left the current sim and returned for example. Client /// applications are responsible for tracking and storing objects. /// public event NewAttachmentCallback OnNewAttachment; /// /// This event will be raised for every ObjectUpdate block that /// contains a new avatar. /// /// Depending on the circumstances a client /// could receive two or more of these events for the same avatar, if /// you or the other avatar left the current sim and returned for /// example. Client applications are responsible for tracking and /// storing objects. /// public event NewAvatarCallback OnNewAvatar; /// /// This event will be raised for every ObjectUpdate block that /// contains a new tree or grass patch. /// /// Depending on the circumstances a client could /// receive two or more of these events for the same object, if you /// or the object left the current sim and returned for example. Client /// applications are responsible for tracking and storing objects. /// public event NewFoliageCallback OnNewFoliage; /// /// This event will be raised when a terse object update packet is /// received, containing the updated position, rotation, and /// movement-related vectors /// public event ObjectUpdatedCallback OnObjectUpdated; /// /// This event will be raised when the main avatar sits on an /// object or stands up, with a local ID of the current seat or /// zero. /// public event AvatarSitChanged OnAvatarSitChanged; /// /// This event will be raised when an object is removed from a /// simulator. /// public event KillObjectCallback OnObjectKilled; /// /// Thie event will be raised when an object's properties are recieved /// from the simulator /// public event ObjectPropertiesFamilyCallback OnObjectPropertiesFamily; #endregion /// /// If true, when a cached object check is received from the server /// the full object info will automatically be requested. /// /// public bool RequestAllObjects = false; /// /// Reference to the SecondLife client /// protected SecondLife Client; /// /// Instantiates a new ObjectManager class. This class should only be accessed /// through SecondLife.Objects, client applications should never create their own /// /// A reference to the client public ObjectManager(SecondLife client) { Client = client; RegisterCallbacks(); } /// /// Instantiates a new ObjectManager class. This class should only be accessed /// through SecondLife.Objects, client applications should never create their own /// /// This constructor allows a subclass to determine if callbacks should be registered here or not. /// A reference to the client protected ObjectManager(SecondLife client, bool registerCallbacks) { Client = client; if (registerCallbacks) { RegisterCallbacks(); } } protected void RegisterCallbacks() { Client.Network.RegisterCallback(PacketType.ObjectUpdate, new NetworkManager.PacketCallback(UpdateHandler)); Client.Network.RegisterCallback(PacketType.ImprovedTerseObjectUpdate, new NetworkManager.PacketCallback(TerseUpdateHandler)); Client.Network.RegisterCallback(PacketType.ObjectUpdateCompressed, new NetworkManager.PacketCallback(CompressedUpdateHandler)); Client.Network.RegisterCallback(PacketType.ObjectUpdateCached, new NetworkManager.PacketCallback(CachedUpdateHandler)); Client.Network.RegisterCallback(PacketType.KillObject, new NetworkManager.PacketCallback(KillObjectHandler)); Client.Network.RegisterCallback(PacketType.ObjectPropertiesFamily, new NetworkManager.PacketCallback(ObjectPropertiesFamilyHandler)); } #region Action Methods /// /// Request object information from the sim, primarily used for stale /// or missing cache entries /// /// The simulator containing the object you're /// looking for /// The local ID of the object public void RequestObject(Simulator simulator, uint localID) { RequestMultipleObjectsPacket request = new RequestMultipleObjectsPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; request.ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[1]; request.ObjectData[0] = new RequestMultipleObjectsPacket.ObjectDataBlock(); request.ObjectData[0].ID = localID; request.ObjectData[0].CacheMissType = 0; Client.Network.SendPacket(request, simulator); } /// /// Request object information for multiple objects all contained in /// the same sim, primarily used for stale or missing cache entries /// /// The simulator containing the object you're /// looking for /// A list of local IDs of the objects public void RequestObjects(Simulator simulator, List localIDs) { int i = 0; RequestMultipleObjectsPacket request = new RequestMultipleObjectsPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; request.ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[localIDs.Count]; foreach (uint localID in localIDs) { request.ObjectData[i] = new RequestMultipleObjectsPacket.ObjectDataBlock(); request.ObjectData[i].ID = localID; request.ObjectData[i].CacheMissType = 0; i++; } Client.Network.SendPacket(request, simulator); } /// /// Create, or "rez" a new prim object in a simulator /// /// The target simulator /// Data describing the prim object to rez /// Group ID that this prim is set to, or LLUUID.Zero /// 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. public void AddPrim(Simulator simulator, LLObject.ObjectData prim, LLUUID groupID, LLVector3 position, LLVector3 scale, LLQuaternion rotation) { ObjectAddPacket packet = new ObjectAddPacket(); packet.AgentData.AgentID = Client.Network.AgentID; packet.AgentData.SessionID = Client.Network.SessionID; packet.AgentData.GroupID = groupID; packet.ObjectData.State = (byte)prim.State; packet.ObjectData.AddFlags = (uint)LLObject.ObjectFlags.CreateSelected; packet.ObjectData.PCode = (byte)PCode.Prim; packet.ObjectData.Material = (byte)prim.Material; packet.ObjectData.Scale = scale; packet.ObjectData.Rotation = rotation; packet.ObjectData.PathBegin = LLObject.PathBeginByte(prim.PathBegin); packet.ObjectData.PathCurve = (byte)prim.PathCurve; packet.ObjectData.PathEnd = LLObject.PathEndByte(prim.PathEnd); packet.ObjectData.PathRadiusOffset = LLObject.PathRadiusOffsetByte(prim.PathRadiusOffset); packet.ObjectData.PathRevolutions = LLObject.PathRevolutionsByte(prim.PathRevolutions); packet.ObjectData.PathScaleX = LLObject.PathScaleByte(prim.PathScaleX); packet.ObjectData.PathScaleY = LLObject.PathScaleByte(prim.PathScaleY); packet.ObjectData.PathShearX = LLObject.PathShearByte(prim.PathShearX); packet.ObjectData.PathShearY = LLObject.PathShearByte(prim.PathShearY); packet.ObjectData.PathSkew = LLObject.PathSkewByte(prim.PathSkew); packet.ObjectData.PathTaperX = LLObject.PathTaperByte(prim.PathTaperX); packet.ObjectData.PathTaperY = LLObject.PathTaperByte(prim.PathTaperY); packet.ObjectData.PathTwist = (sbyte)prim.PathTwist; packet.ObjectData.PathTwistBegin = (sbyte)prim.PathTwistBegin; packet.ObjectData.ProfileCurve = (byte)prim.ProfileCurve; packet.ObjectData.ProfileBegin = LLObject.ProfileBeginByte(prim.ProfileBegin); packet.ObjectData.ProfileEnd = LLObject.ProfileEndByte(prim.ProfileEnd); packet.ObjectData.ProfileHollow = (byte)prim.ProfileHollow; packet.ObjectData.RayStart = position; packet.ObjectData.RayEnd = position; packet.ObjectData.RayEndIsIntersection = 0; packet.ObjectData.RayTargetID = LLUUID.Zero; packet.ObjectData.BypassRaycast = 1; // TODO: Automatically send another packet to set the TextureEntry? Client.Network.SendPacket(packet, simulator); } /// /// /// /// /// /// /// /// /// /// public void AddTree(Simulator simulator, LLVector3 scale, LLQuaternion rotation, LLVector3 position, Tree treeType, LLUUID groupOwner, bool newTree) { ObjectAddPacket add = new ObjectAddPacket(); add.AgentData.AgentID = Client.Network.AgentID; add.AgentData.SessionID = Client.Network.SessionID; add.AgentData.GroupID = groupOwner; add.ObjectData.BypassRaycast = 1; add.ObjectData.Material = 3; add.ObjectData.PathCurve = 16; add.ObjectData.PCode = newTree ? (byte)PCode.NewTree : (byte)PCode.Tree; add.ObjectData.RayEnd = position; add.ObjectData.RayStart = position; add.ObjectData.RayTargetID = LLUUID.Zero; add.ObjectData.Rotation = rotation; add.ObjectData.Scale = scale; add.ObjectData.State = (byte)treeType; Client.Network.SendPacket(add, simulator); } /// /// /// /// /// /// /// /// /// public void AddGrass(Simulator simulator, LLVector3 scale, LLQuaternion rotation, LLVector3 position, Grass grassType, LLUUID groupOwner) { ObjectAddPacket add = new ObjectAddPacket(); add.AgentData.AgentID = Client.Network.AgentID; add.AgentData.SessionID = Client.Network.SessionID; add.AgentData.GroupID = groupOwner; add.ObjectData.BypassRaycast = 1; add.ObjectData.Material = 3; add.ObjectData.PathCurve = 16; add.ObjectData.PCode = (byte)PCode.Grass; add.ObjectData.RayEnd = position; add.ObjectData.RayStart = position; add.ObjectData.RayTargetID = LLUUID.Zero; add.ObjectData.Rotation = rotation; add.ObjectData.Scale = scale; add.ObjectData.State = (byte)grassType; Client.Network.SendPacket(add, simulator); } /// /// /// /// /// /// public void SetTextures(Simulator simulator, uint localID, LLObject.TextureEntry textures) { SetTextures(simulator, localID, textures, String.Empty); } /// /// /// /// /// /// /// public void SetTextures(Simulator simulator, uint localID, LLObject.TextureEntry textures, string mediaUrl) { ObjectImagePacket image = new ObjectImagePacket(); image.AgentData.AgentID = Client.Network.AgentID; image.AgentData.SessionID = Client.Network.SessionID; image.ObjectData = new ObjectImagePacket.ObjectDataBlock[1]; image.ObjectData[0] = new ObjectImagePacket.ObjectDataBlock(); image.ObjectData[0].ObjectLocalID = localID; image.ObjectData[0].TextureEntry = textures.ToBytes(); image.ObjectData[0].MediaURL = Helpers.StringToField(mediaUrl); Client.Network.SendPacket(image, simulator); } /// /// /// /// /// /// public void SetLight(Simulator simulator, uint localID, Primitive.LightData light) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); extra.AgentData.AgentID = Client.Network.AgentID; extra.AgentData.SessionID = Client.Network.SessionID; extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)Primitive.ExtraParamType.Light; if (light == null) { extra.ObjectData[0].ParamInUse = false; extra.ObjectData[0].ParamData = new byte[0]; } else { extra.ObjectData[0].ParamInUse = true; extra.ObjectData[0].ParamData = light.GetBytes(); } extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); } /// /// /// /// /// /// public void SetFlexible(Simulator simulator, uint localID, Primitive.FlexibleData flexible) { ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket(); extra.AgentData.AgentID = Client.Network.AgentID; extra.AgentData.SessionID = Client.Network.SessionID; extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1]; extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock(); extra.ObjectData[0].ObjectLocalID = localID; extra.ObjectData[0].ParamType = (byte)Primitive.ExtraParamType.Flexible; if (flexible == null) { extra.ObjectData[0].ParamInUse = false; extra.ObjectData[0].ParamData = new byte[0]; } else { extra.ObjectData[0].ParamInUse = true; extra.ObjectData[0].ParamData = flexible.GetBytes(); } extra.ObjectData[0].ParamSize = (uint)extra.ObjectData[0].ParamData.Length; Client.Network.SendPacket(extra, simulator); } /// /// /// /// /// public void LinkPrims(Simulator simulator, List localIDs) { ObjectLinkPacket packet = new ObjectLinkPacket(); packet.AgentData.AgentID = Client.Network.AgentID; packet.AgentData.SessionID = Client.Network.SessionID; packet.ObjectData = new ObjectLinkPacket.ObjectDataBlock[localIDs.Count]; int i = 0; foreach (uint localID in localIDs) { packet.ObjectData[i] = new ObjectLinkPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localID; i++; } Client.Network.SendPacket(packet, simulator); } /// /// /// /// /// /// public void SetRotation(Simulator simulator, uint localID, LLQuaternion rotation) { ObjectRotationPacket objRotPacket = new ObjectRotationPacket(); objRotPacket.AgentData.AgentID = Client.Network.AgentID; objRotPacket.AgentData.SessionID = Client.Network.SessionID; objRotPacket.ObjectData = new ObjectRotationPacket.ObjectDataBlock[1]; objRotPacket.ObjectData[0] = new ObjectRotationPacket.ObjectDataBlock(); objRotPacket.ObjectData[0].ObjectLocalID = localID; objRotPacket.ObjectData[0].Rotation = rotation; Client.Network.SendPacket(objRotPacket, simulator); } /// /// /// /// /// /// /// public void AttachObject(Simulator simulator, uint localID, AttachmentPoint attachPoint, LLQuaternion rotation) { ObjectAttachPacket attach = new ObjectAttachPacket(); attach.AgentData.AgentID = Client.Network.AgentID; attach.AgentData.SessionID = Client.Network.SessionID; attach.AgentData.AttachmentPoint = (byte)attachPoint; attach.ObjectData = new ObjectAttachPacket.ObjectDataBlock[1]; attach.ObjectData[0] = new ObjectAttachPacket.ObjectDataBlock(); attach.ObjectData[0].ObjectLocalID = localID; attach.ObjectData[0].Rotation = rotation; Client.Network.SendPacket(attach, simulator); } /// /// /// /// /// public void DetachObjects(Simulator simulator, List localIDs) { ObjectDetachPacket detach = new ObjectDetachPacket(); detach.AgentData.AgentID = Client.Network.AgentID; detach.AgentData.SessionID = Client.Network.SessionID; detach.ObjectData = new ObjectDetachPacket.ObjectDataBlock[localIDs.Count]; int i = 0; foreach (uint localid in localIDs) { detach.ObjectData[i] = new ObjectDetachPacket.ObjectDataBlock(); detach.ObjectData[i].ObjectLocalID = localid; i++; } Client.Network.SendPacket(detach, simulator); } /// /// /// /// /// /// public void SetPosition(Simulator simulator, uint localID, LLVector3 position) { ObjectPositionPacket objPosPacket = new ObjectPositionPacket(); objPosPacket.AgentData.AgentID = Client.Self.ID; objPosPacket.AgentData.SessionID = Client.Network.SessionID; objPosPacket.ObjectData = new ObjectPositionPacket.ObjectDataBlock[1]; objPosPacket.ObjectData[0] = new ObjectPositionPacket.ObjectDataBlock(); objPosPacket.ObjectData[0].ObjectLocalID = localID; objPosPacket.ObjectData[0].Position = position; Client.Network.SendPacket(objPosPacket, simulator); } /// /// /// /// /// /// /// /// public void SetPermissions(Simulator simulator, List localIDs, Helpers.PermissionWho who, Helpers.PermissionType permissions, bool set) { ObjectPermissionsPacket packet = new ObjectPermissionsPacket(); packet.AgentData.AgentID = Client.Network.AgentID; packet.AgentData.SessionID = Client.Network.SessionID; packet.HeaderData.Override = false; packet.ObjectData = new ObjectPermissionsPacket.ObjectDataBlock[localIDs.Count]; int i = 0; foreach (uint localID in localIDs) { packet.ObjectData[i] = new ObjectPermissionsPacket.ObjectDataBlock(); packet.ObjectData[i].ObjectLocalID = localID; packet.ObjectData[i].Field = (byte)who; packet.ObjectData[i].Mask = (uint)permissions; packet.ObjectData[i].Set = Convert.ToByte(set); i++; } Client.Network.SendPacket(packet, simulator); } /// /// Request additional properties for an object /// /// /// public void RequestObjectPropertiesFamily(Simulator simulator, LLUUID objectID) { RequestObjectPropertiesFamilyPacket properties = new RequestObjectPropertiesFamilyPacket(); properties.AgentData.AgentID = Client.Network.AgentID; properties.AgentData.SessionID = Client.Network.SessionID; properties.ObjectData.ObjectID = objectID; properties.ObjectData.RequestFlags = 0; Client.Network.SendPacket(properties, simulator); } #endregion #region Packet Handlers /// /// Used for new prims, or significant changes to existing prims /// /// /// protected void UpdateHandler(Packet packet, Simulator simulator) { if (OnNewPrim != null || OnNewAttachment != null || OnNewAvatar != null || OnNewFoliage != null) { ObjectUpdatePacket update = (ObjectUpdatePacket)packet; UpdateDilation(simulator, update.RegionData.TimeDilation); foreach (ObjectUpdatePacket.ObjectDataBlock block in update.ObjectData) { LLVector4 collisionPlane = LLVector4.Zero; LLVector3 position; LLVector3 velocity; LLVector3 acceleration; LLQuaternion rotation; LLVector3 angularVelocity; // TODO: Parse NameValue here string nameValue = Helpers.FieldToString(block.NameValue); // Object (primitive) parameters LLObject.ObjectData data = new LLObject.ObjectData(); data.State = block.State; data.Material = block.Material; data.PathCurve = block.PathCurve; data.ProfileCurve = block.ProfileCurve; data.PathBegin = LLObject.PathBeginFloat(block.PathBegin); data.PathEnd = LLObject.PathEndFloat(block.PathEnd); data.PathScaleX = LLObject.PathScaleFloat(block.PathScaleX); data.PathScaleY = LLObject.PathScaleFloat(block.PathScaleY); data.PathShearX = LLObject.PathShearFloat(block.PathShearX); data.PathShearY = LLObject.PathShearFloat(block.PathShearY); data.PathTwist = block.PathTwist; data.PathTwistBegin = block.PathTwistBegin; data.PathRadiusOffset = LLObject.PathRadiusOffsetFloat(block.PathRadiusOffset); data.PathTaperX = LLObject.PathTaperFloat(block.PathTaperX); data.PathTaperY = LLObject.PathTaperFloat(block.PathTaperY); data.PathRevolutions = LLObject.PathRevolutionsFloat(block.PathRevolutions); data.PathSkew = LLObject.PathSkewFloat(block.PathSkew); data.ProfileBegin = LLObject.ProfileBeginFloat(block.ProfileBegin); data.ProfileEnd = LLObject.ProfileEndFloat(block.ProfileEnd); data.ProfileHollow = block.ProfileHollow; // Additional packed parameters in ObjectData int pos = 0; switch (block.ObjectData.Length) { case 76: // Collision normal for avatar collisionPlane = new LLVector4(block.ObjectData, pos); pos += 16; goto case 60; case 60: // Position position = new LLVector3(block.ObjectData, pos); pos += 12; // Velocity velocity = new LLVector3(block.ObjectData, pos); pos += 12; // Acceleration acceleration = new LLVector3(block.ObjectData, pos); pos += 12; // Rotation (theta) rotation = new LLQuaternion(block.ObjectData, pos, true); pos += 12; // Angular velocity (omega) angularVelocity = new LLVector3(block.ObjectData, pos); pos += 12; break; case 48: // Collision normal for avatar collisionPlane = new LLVector4(block.ObjectData, pos); pos += 16; goto case 32; case 32: // The data is an array of unsigned shorts // Position position = new LLVector3( Helpers.UInt16ToFloat(block.ObjectData, pos, -0.5f * 256.0f, 1.5f * 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 2, -0.5f * 256.0f, 1.5f * 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 3.0f * 256.0f)); pos += 6; // Velocity velocity = new LLVector3( Helpers.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f)); pos += 6; // Acceleration acceleration = new LLVector3( Helpers.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f)); pos += 6; // Rotation (theta) rotation = new LLQuaternion( Helpers.UInt16ToFloat(block.ObjectData, pos, -1.0f, 1.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 2, -1.0f, 1.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 4, -1.0f, 1.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 6, -1.0f, 1.0f)); pos += 8; // Angular velocity (omega) angularVelocity = new LLVector3( Helpers.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f), Helpers.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 position = new LLVector3( Helpers.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; // Velocity velocity = new LLVector3( Helpers.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; // Accleration acceleration = new LLVector3( Helpers.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; // Rotation rotation = new LLQuaternion( Helpers.ByteToFloat(block.ObjectData, pos, -1.0f, 1.0f), Helpers.ByteToFloat(block.ObjectData, pos + 1, -1.0f, 1.0f), Helpers.ByteToFloat(block.ObjectData, pos + 2, -1.0f, 1.0f), Helpers.ByteToFloat(block.ObjectData, pos + 3, -1.0f, 1.0f)); pos += 4; // Angular Velocity angularVelocity = new LLVector3( Helpers.ByteToFloat(block.ObjectData, pos, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f), Helpers.ByteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f)); pos += 3; break; default: Client.Log("Got an ObjectUpdate block with ObjectUpdate field length of " + block.ObjectData.Length, Helpers.LogLevel.Warning); continue; } // Unknown if (block.Data.Length != 0) Client.DebugLog("Got an ObjectUpdate block with Data length " + block.Data.Length); // Determine the object type and create the appropriate class byte pcode = block.PCode; switch (pcode) { case (byte)PCode.Grass: case (byte)PCode.Tree: case (byte)PCode.Prim: Primitive prim = new Primitive(); prim.Flags = (LLObject.ObjectFlags)block.UpdateFlags; if ((prim.Flags & LLObject.ObjectFlags.ZlibCompressed) != 0) { Client.Log("Got a ZlibCompressed ObjectUpdate, implement me!", Helpers.LogLevel.Info); continue; } prim.NameValue = nameValue; prim.LocalID = block.ID; prim.ID = block.FullID; prim.ParentID = block.ParentID; prim.RegionHandle = update.RegionData.RegionHandle; prim.Scale = block.Scale; prim.ClickAction = block.ClickAction; prim.OwnerID = block.OwnerID; prim.MediaURL = Helpers.FieldToUTF8String(block.MediaURL); prim.Text = Helpers.FieldToUTF8String(block.Text); prim.TextColor = new LLColor(block.TextColor, 0); // Alpha is inversed to help zero encoding prim.TextColor.A = (byte)(255 - prim.TextColor.A); // Sound information prim.Sound = block.Sound; prim.SoundFlags = block.Flags; prim.SoundGain = block.Gain; prim.SoundRadius = block.Radius; // Joint information prim.Joint = (Primitive.JointType)block.JointType; prim.JointPivot = block.JointPivot; prim.JointAxisOrAnchor = block.JointAxisOrAnchor; // Object parameters prim.data = data; // Textures, texture animations, particle system, and extra params prim.Textures = new Primitive.TextureEntry(block.TextureEntry, 0, block.TextureEntry.Length); prim.TextureAnim = new Primitive.TextureAnimation(block.TextureAnim, 0); prim.ParticleSys = new Primitive.ParticleSystem(block.PSBlock, 0); prim.SetExtraParamsFromBytes(block.ExtraParams, 0); // Unknown prim.GenericData = block.Data; // Packed parameters prim.CollisionPlane = collisionPlane; prim.Position = position; prim.Velocity = velocity; prim.Acceleration = acceleration; prim.Rotation = rotation; prim.AngularVelocity = angularVelocity; if (prim.NameValue.StartsWith("AttachItemID")) { FireOnNewAttachment(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } else if (block.PCode == (byte)PCode.Tree || block.PCode == (byte)PCode.Grass) { FireOnNewFoliage(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } else { FireOnNewPrim(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } break; case (byte)PCode.Avatar: // Update some internals if this is our avatar if (block.FullID == Client.Network.AgentID) { // Interesting to know Client.Self.LocalID = block.ID; // Packed parameters Client.Self.CollisionPlane = collisionPlane; Client.Self.Position = position; Client.Self.Velocity = velocity; Client.Self.Acceleration = acceleration; Client.Self.Rotation = rotation; Client.Self.AngularVelocity = angularVelocity; // Detect if we are sitting or standing uint oldSittingOn = Client.Self.SittingOn; // Fire the callback for our sitting orientation changing if (block.ParentID != oldSittingOn) { SetAvatarSelfSittingOn(block.ParentID); FireOnAvatarSitChanged(simulator, Client.Self.SittingOn); } } if (OnNewAvatar != null) { Avatar avatar = new Avatar(); avatar.ID = block.FullID; avatar.LocalID = block.ID; // TODO: This will probably change with the NameValue parsing string FirstName = String.Empty; string LastName = String.Empty; string GroupName = String.Empty; // Packed parameters avatar.CollisionPlane = collisionPlane; avatar.Position = position; avatar.Velocity = velocity; avatar.Acceleration = acceleration; avatar.Rotation = rotation; avatar.AngularVelocity = angularVelocity; SetAvatarSittingOn(avatar, block.ParentID); // Object parameters avatar.data = data; // Unknown avatar.GenericData = block.Data; // TODO: Replace this ParseAvName(Helpers.FieldToString(block.NameValue), ref FirstName, ref LastName, ref GroupName); // Basic vitals avatar.Name = FirstName + " " + LastName; avatar.GroupName = GroupName; avatar.Online = true; avatar.CurrentRegion = simulator.Region; // Texutres avatar.Textures = new Primitive.TextureEntry(block.TextureEntry, 0, block.TextureEntry.Length); FireOnNewAvatar(simulator, avatar, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } break; case (byte)PCode.ParticleSystem: // FIXME: Handle ParticleSystem Client.DebugLog("Got an ObjectUpdate block with a ParticleSystem PCode"); break; default: Client.DebugLog("Got an ObjectUpdate block with an unrecognized PCode " + ((PCode)pcode).ToString()); break; } } } } /// /// Usually called when an Prim moves. /// /// /// protected void TerseUpdateHandler(Packet packet, Simulator simulator) { ImprovedTerseObjectUpdatePacket terse = (ImprovedTerseObjectUpdatePacket)packet; UpdateDilation(simulator, terse.RegionData.TimeDilation); foreach (ImprovedTerseObjectUpdatePacket.ObjectDataBlock block in terse.ObjectData) { try { ObjectUpdate update = new ObjectUpdate(); int pos = 0; // Local ID update.LocalID = Helpers.BytesToUIntBig(block.Data, pos); pos += 4; // State update.State = block.Data[pos++]; // Avatar boolean update.Avatar = (block.Data[pos++] != 0); // Collision normal for avatar if (update.Avatar) { update.CollisionPlane = new LLVector4(block.Data, pos); pos += 16; } // Position update.Position = new LLVector3(block.Data, pos); pos += 12; // Velocity update.Velocity = new LLVector3( Helpers.UInt16ToFloat(block.Data, pos, -128.0f, 128.0f), Helpers.UInt16ToFloat(block.Data, pos + 2, -128.0f, 128.0f), Helpers.UInt16ToFloat(block.Data, pos + 4, -128.0f, 128.0f)); pos += 6; // Acceleration update.Velocity = new LLVector3( Helpers.UInt16ToFloat(block.Data, pos, -64.0f, 64.0f), Helpers.UInt16ToFloat(block.Data, pos + 2, -64.0f, 64.0f), Helpers.UInt16ToFloat(block.Data, pos + 4, -64.0f, 64.0f)); pos += 6; // Rotation (theta) update.Rotation = new LLQuaternion( Helpers.UInt16ToFloat(block.Data, pos, -1.0f, 1.0f), Helpers.UInt16ToFloat(block.Data, pos + 2, -1.0f, 1.0f), Helpers.UInt16ToFloat(block.Data, pos + 4, -1.0f, 1.0f), Helpers.UInt16ToFloat(block.Data, pos + 8, -1.0f, 1.0f)); pos += 8; // Angular velocity update.AngularVelocity = new LLVector3( Helpers.UInt16ToFloat(block.Data, pos, -64.0f, 64.0f), Helpers.UInt16ToFloat(block.Data, pos + 2, -64.0f, 64.0f), Helpers.UInt16ToFloat(block.Data, pos + 4, -64.0f, 64.0f)); pos += 6; // Textures // FIXME: Why are we ignoring the first four bytes here? update.Textures = new LLObject.TextureEntry(block.TextureEntry, 4, block.TextureEntry.Length - 4); // Fire the callback FireOnObjectUpdated(simulator, update, terse.RegionData.RegionHandle, terse.RegionData.TimeDilation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Warning); } } } /// /// /// /// /// protected void CompressedUpdateHandler(Packet packet, Simulator simulator) { if (OnNewPrim != null || OnNewAvatar != null || OnNewAttachment != null || OnNewFoliage != null) { ObjectUpdateCompressedPacket update = (ObjectUpdateCompressedPacket)packet; Primitive prim; foreach (ObjectUpdateCompressedPacket.ObjectDataBlock block in update.ObjectData) { int i = 0; prim = new Primitive(); prim.Flags = (LLObject.ObjectFlags)block.UpdateFlags; try { prim.ID = new LLUUID(block.Data, 0); i += 16; prim.LocalID = (uint)(block.Data[i++] + (block.Data[i++] << 8) + (block.Data[i++] << 16) + (block.Data[i++] << 24)); byte pcode = block.Data[i++]; if (pcode == (byte)PCode.Prim || pcode == (byte)PCode.Grass || pcode == (byte)PCode.Tree || pcode == (byte)PCode.NewTree) { #region Prim // State prim.data.State = (uint)block.Data[i++]; // CRC i += 4; // Material prim.data.Material = (uint)block.Data[i++]; // Click action prim.ClickAction = block.Data[i++]; // Scale prim.Scale = new LLVector3(block.Data, i); i += 12; // Position prim.Position = new LLVector3(block.Data, i); i += 12; // Rotation prim.Rotation = new LLQuaternion(block.Data, i, true); i += 12; // Compressed flags CompressedFlags flags = (CompressedFlags)Helpers.BytesToUIntBig(block.Data, i); i += 4; if ((flags & CompressedFlags.Tree) != 0) { // FIXME: Decode the tree data byte Unknown1 = block.Data[i++]; byte Unknown2 = block.Data[i++]; Client.DebugLog("Compressed object with Tree flag set: " + Environment.NewLine + "Unknown byte 1: " + Unknown1 + Environment.NewLine + "Unknown byte 2: " + Unknown2); } 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; } if ((flags & CompressedFlags.HasAngularVelocity) != 0) { prim.AngularVelocity = new LLVector3(block.Data, i); i += 12; } if ((flags & CompressedFlags.HasText) != 0) { string text = String.Empty; while (block.Data[i] != 0) { // TODO: Make this UTF8 compliant? text += (char)block.Data[i]; i++; } i++; // Floating text prim.Text = text; // Text color prim.TextColor = new LLColor(block.Data, i); i += 4; } else { prim.Text = String.Empty; } if ((flags & CompressedFlags.HasParticles) != 0) { prim.ParticleSys = new Primitive.ParticleSystem(block.Data, i); i += 86; } i += prim.SetExtraParamsFromBytes(block.Data, i); //Sound data if ((flags & CompressedFlags.HasSound) != 0) { prim.Sound = new LLUUID(block.Data, i); i += 16; prim.OwnerID = new LLUUID(block.Data, i); i += 16; if (!BitConverter.IsLittleEndian) { Array.Reverse(block.Data, i, 4); Array.Reverse(block.Data, i + 5, 4); } prim.SoundGain = BitConverter.ToSingle(block.Data, i); i += 4; prim.SoundFlags = block.Data[i++]; prim.SoundRadius = BitConverter.ToSingle(block.Data, i); i += 4; } if ((flags & CompressedFlags.HasNameValues) != 0) { string text = String.Empty; while (block.Data[i] != 0) { // TODO: Make UTF-8 compliant? text += (char)block.Data[i]; i++; } i++; // FIXME: Parse the NameValue pairs prim.NameValue = text; } if ((flags & CompressedFlags.ScratchPad) != 0) { // TODO: What is this? Client.DebugLog("Compressed object with ScratchPad flag set: " + Environment.NewLine + "Flags: " + flags.ToString() + Environment.NewLine + Helpers.FieldToString(block.Data)); } if ((flags & CompressedFlags.Unknown) != 0) { // TODO: Implement CompressedFlags.Unknown2 Client.DebugLog("Compressed object with Unknown flag set: " + Environment.NewLine + "Flags: " + flags.ToString() + Environment.NewLine + Helpers.FieldToString(block.Data)); } prim.data.PathCurve = (uint)block.Data[i++]; prim.data.PathBegin = LLObject.PathBeginFloat(block.Data[i++]); prim.data.PathEnd = LLObject.PathEndFloat(block.Data[i++]); prim.data.PathScaleX = LLObject.PathScaleFloat(block.Data[i++]); prim.data.PathScaleY = LLObject.PathScaleFloat(block.Data[i++]); prim.data.PathShearX = LLObject.PathShearFloat(block.Data[i++]); prim.data.PathShearY = LLObject.PathShearFloat(block.Data[i++]); prim.data.PathTwist = (int)block.Data[i++]; prim.data.PathTwistBegin = (int)block.Data[i++]; prim.data.PathRadiusOffset = LLObject.PathRadiusOffsetFloat((sbyte)block.Data[i++]); prim.data.PathTaperX = LLObject.PathTaperFloat((sbyte)block.Data[i++]); prim.data.PathTaperY = LLObject.PathTaperFloat((sbyte)block.Data[i++]); prim.data.PathRevolutions = LLObject.PathRevolutionsFloat(block.Data[i++]); prim.data.PathSkew = LLObject.PathSkewFloat((sbyte)block.Data[i++]); prim.data.ProfileCurve = (uint)block.Data[i++]; prim.data.ProfileBegin = Primitive.ProfileBeginFloat(block.Data[i++]); prim.data.ProfileEnd = Primitive.ProfileEndFloat(block.Data[i++]); prim.data.ProfileHollow = (uint)block.Data[i++]; int textureEntryLength = (int)(block.Data[i++] + (block.Data[i++] << 8) + (block.Data[i++] << 16) + (block.Data[i++] << 24)); prim.Textures = new LLObject.TextureEntry(block.Data, i, textureEntryLength); i += textureEntryLength; // Assume everything else is texture animation data if (i < block.Data.Length) { int textureAnimLength = (int)(block.Data[i++] + (block.Data[i++] << 8) + (block.Data[i++] << 16) + (block.Data[i++] << 24)); prim.TextureAnim = new LLObject.TextureAnimation(block.Data, i); } // Fire the appropriate callback if ((flags & CompressedFlags.HasNameValues) != 0) { // TODO: We should use a better check to see if this is actually an attachment FireOnNewAttachment(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } else if ((flags & CompressedFlags.Tree) != 0) { FireOnNewFoliage(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } else if (OnNewPrim != null) { FireOnNewPrim(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); } #endregion Prim } else { Client.Log("######### Got an ObjectUpdateCompressed for PCode=" + pcode.ToString() + ", implement this! #########", Helpers.LogLevel.Debug); } } catch (System.IndexOutOfRangeException e) { Client.Log("Had a problem decoding an ObjectUpdateCompressed packet: " + e.ToString(), Helpers.LogLevel.Warning); Client.Log(block.ToString(), Helpers.LogLevel.Warning); } } } } protected void CachedUpdateHandler(Packet packet, Simulator simulator) { if (RequestAllObjects) { List ids = new List(); ObjectUpdateCachedPacket update = (ObjectUpdateCachedPacket)packet; // Assume clients aren't caching objects for now, so request updates for all of these objects foreach (ObjectUpdateCachedPacket.ObjectDataBlock block in update.ObjectData) { ids.Add(block.ID); } RequestObjects(simulator, ids); } } protected void KillObjectHandler(Packet packet, Simulator simulator) { foreach (KillObjectPacket.ObjectDataBlock block in ((KillObjectPacket)packet).ObjectData) { FireOnObjectKilled(simulator, block.ID); } } protected void ObjectPropertiesFamilyHandler(Packet p, Simulator sim) { ObjectPropertiesFamilyPacket op = (ObjectPropertiesFamilyPacket)p; LLObject.ObjectPropertiesFamily props = new LLObject.ObjectPropertiesFamily(); props.BaseMask = op.ObjectData.BaseMask; props.Category = op.ObjectData.Category; props.Description = Helpers.FieldToString(op.ObjectData.Description); props.EveryoneMask = op.ObjectData.EveryoneMask; props.GroupID = op.ObjectData.GroupID; props.GroupMask = op.ObjectData.GroupMask; props.LastOwnerID = op.ObjectData.LastOwnerID; props.Name = Helpers.FieldToString(op.ObjectData.Name); props.NextOwnerMask = op.ObjectData.NextOwnerMask; props.ObjectID = op.ObjectData.ObjectID; props.OwnerID = op.ObjectData.OwnerID; props.OwnerMask = op.ObjectData.OwnerMask; props.OwnershipCost = op.ObjectData.OwnershipCost; props.SalePrice = op.ObjectData.SalePrice; props.SaleType = op.ObjectData.SaleType; FireOnObjectPropertiesFamily(sim, props); } #endregion #region Utility Functions protected void SetAvatarSelfSittingOn(uint localid) { Client.Self.sittingOn = localid; } protected void SetAvatarSittingOn(Avatar av, uint localid) { av.sittingOn = localid; } protected void UpdateDilation(Simulator s, uint dilation) { s.Dilation = (float) dilation / 65535; } protected void ParseAvName(string name, ref string firstName, ref string lastName, ref string groupName) { // FIXME: This needs to be reworked completely. It fails on name strings that don't contain the // most common attributes which is all we handle right now string[] lines = name.Split('\n'); foreach (string line in lines) { if (line.Substring(0, 19) == "Title STRING RW SV ") { groupName = line.Substring(19); } else if (line.Substring(0, 23) == "FirstName STRING RW SV ") { firstName = line.Substring(23); } else if (line.Substring(0, 22) == "LastName STRING RW SV ") { lastName = line.Substring(22); } else { Client.Log("Unhandled line in an avatar name: " + line, Helpers.LogLevel.Warning); } } } /// /// Takes a quantized 16-bit value from a byte array and its range and returns /// a float representation of the continuous value. For example, a value of /// 32767 and a range of -128.0 to 128.0 would return 0.0. The endian conversion /// from the 16-bit little endian to the native platform will also be handled. /// /// The byte array containing the short value /// The beginning position of the short (quantized) value /// The lower quantization range /// The upper quantization range /// A 32-bit floating point representation of the dequantized value protected float Dequantize(byte[] byteArray, int pos, float lower, float upper) { // FIXME: Move this to Helpers and make it as solid as ByteToFloat ushort value = (ushort)(byteArray[pos] + (byteArray[pos + 1] << 8)); float QV = (float)value; float range = upper - lower; float QF = range / 65536.0F; return (float)((QV * QF - (0.5F * range)) + QF); } #endregion #region Event Notification protected void FireOnObjectPropertiesFamily(Simulator sim, LLObject.ObjectPropertiesFamily props) { if (OnObjectPropertiesFamily != null) { try { OnObjectPropertiesFamily(sim, props); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnObjectKilled(Simulator simulator, uint localid) { if (OnObjectKilled != null) { try { OnObjectKilled(simulator, localid); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnNewPrim(Simulator simulator, Primitive prim, ulong RegionHandle, ushort TimeDilation) { if (OnNewPrim != null) { try { OnNewPrim(simulator, prim, RegionHandle, TimeDilation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnNewFoliage(Simulator simulator, Primitive prim, ulong RegionHandle, ushort TimeDilation) { if (OnNewFoliage != null) { try { OnNewFoliage(simulator, prim, RegionHandle, TimeDilation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnNewAttachment(Simulator simulator, Primitive prim, ulong RegionHandle, ushort TimeDilation) { if (OnNewAttachment != null) { try { OnNewAttachment(simulator, prim, RegionHandle, TimeDilation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnAvatarSitChanged(Simulator simulator, uint LocalID) { if (OnAvatarSitChanged != null) { try { OnAvatarSitChanged(simulator, Client.Self.sittingOn); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnNewAvatar(Simulator simulator, Avatar avatar, ulong RegionHandle, ushort TimeDilation) { if (OnNewAvatar != null) { try { OnNewAvatar(simulator, avatar, RegionHandle, TimeDilation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } protected void FireOnObjectUpdated(Simulator simulator, ObjectUpdate update, ulong RegionHandle, ushort TimeDilation) { if (OnObjectUpdated != null) { try { OnObjectUpdated(simulator, update, RegionHandle, TimeDilation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } #endregion } }