Files
libremetaverse/libsecondlife/ObjectManager.cs
John Hurliman 6e7af40682 * Fixed a crash when attempting to teleport with CAPS disabled
* The meaning of Settings.ALWAYS_DECODE_OBJECTS has changed slightly, agent updates are now always decoded regardless of this setting. Clients that are not doing object tracking and have this set to false should see less memory usage
* NameValue is now a struct
* LLObject.NameValues is now an array to help with performance and serialization

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1135 52acb1d6-8a22-11de-b505-999d5b087335
2007-04-20 16:03:24 +00:00

2227 lines
94 KiB
C#

/*
* 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
{
/// <summary>
/// Contains the variables sent in an object update packet for objects.
/// Used to track position and movement of prims and avatars
/// </summary>
public struct ObjectUpdate
{
/// <summary></summary>
public bool Avatar;
/// <summary></summary>
public LLVector4 CollisionPlane;
/// <summary></summary>
public byte State;
/// <summary></summary>
public uint LocalID;
/// <summary></summary>
public LLVector3 Position;
/// <summary></summary>
public LLVector3 Velocity;
/// <summary></summary>
public LLVector3 Acceleration;
/// <summary></summary>
public LLQuaternion Rotation;
/// <summary></summary>
public LLVector3 AngularVelocity;
/// <summary></summary>
public LLObject.TextureEntry Textures;
}
/// <summary>
/// Handles all network traffic related to prims and avatar positions and
/// movement.
/// </summary>
public class ObjectManager
{
#region CallBack Definitions
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="prim"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
public delegate void NewPrimCallback(Simulator simulator, Primitive prim, ulong regionHandle,
ushort timeDilation);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="prim"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
public delegate void NewAttachmentCallback(Simulator simulator, Primitive prim, ulong regionHandle,
ushort timeDilation);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="properties"></param>
public delegate void ObjectPropertiesCallback(Simulator simulator, LLObject.ObjectProperties properties);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="properties"></param>
public delegate void ObjectPropertiesFamilyCallback(Simulator simulator,
LLObject.ObjectPropertiesFamily properties);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="avatar"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
public delegate void NewAvatarCallback(Simulator simulator, Avatar avatar, ulong regionHandle,
ushort timeDilation);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="foliage"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
public delegate void NewFoliageCallback(Simulator simulator, Primitive foliage, ulong regionHandle,
ushort timeDilation);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="update"></param>
/// <param name="regionHandle"></param>
/// <param name="timeDilation"></param>
public delegate void ObjectUpdatedCallback(Simulator simulator, ObjectUpdate update, ulong regionHandle,
ushort timeDilation);
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="objectID"></param>
public delegate void KillObjectCallback(Simulator simulator, uint objectID);
/// <summary>
/// Called whenever the client avatar sits down or stands up
/// </summary>
/// <param name="simulator">Simulator the packet was received from</param>
/// <param name="sittingOn">The local ID of the object that is being sat
/// on. If this is zero the avatar is not sitting on an object</param>
public delegate void AvatarSitChanged(Simulator simulator, uint sittingOn);
#endregion
#region Object/Prim Enums
/// <summary>
///
/// </summary>
public enum PCode : byte
{
/// <summary></summary>
None = 0,
/// <summary></summary>
Prim = 9,
/// <summary></summary>
Avatar = 47,
/// <summary></summary>
Grass = 95,
/// <summary></summary>
NewTree = 111,
/// <summary></summary>
ParticleSystem = 143,
/// <summary></summary>
Tree = 255
}
/// <summary>
///
/// </summary>
public enum AttachmentPoint : byte
{
/// <summary></summary>
Default = 0,
/// <summary></summary>
Chest = 1,
/// <summary></summary>
Skull,
/// <summary></summary>
LeftShoulder,
/// <summary></summary>
RightShoulder,
/// <summary></summary>
LeftHand,
/// <summary></summary>
RightHand,
/// <summary></summary>
LeftFoot,
/// <summary></summary>
RightFoot,
/// <summary></summary>
Spine,
/// <summary></summary>
Pelvis,
/// <summary></summary>
Mouth,
/// <summary></summary>
Chin,
/// <summary></summary>
LeftEar,
/// <summary></summary>
RightEar,
/// <summary></summary>
LeftEyeball,
/// <summary></summary>
RightEyeball,
/// <summary></summary>
Nose,
/// <summary></summary>
RightUpperArm,
/// <summary></summary>
RightForearm,
/// <summary></summary>
LeftUpperArm,
/// <summary></summary>
LeftForearm,
/// <summary></summary>
RightHip,
/// <summary></summary>
RightUpperLeg,
/// <summary></summary>
RightLowerLeg,
/// <summary></summary>
LeftHip,
/// <summary></summary>
LeftUpperLeg,
/// <summary></summary>
LeftLowerLeg,
/// <summary></summary>
Stomach,
/// <summary></summary>
LeftPec,
/// <summary></summary>
RightPec,
/// <summary></summary>
HUDCenter2,
/// <summary></summary>
HUDTopRight,
/// <summary></summary>
HUDTop,
/// <summary></summary>
HUDTopLeft,
/// <summary></summary>
HUDCenter,
/// <summary></summary>
HUDBottomLeft,
/// <summary></summary>
HUDBottom,
/// <summary></summary>
HUDBottomRight
}
/// <summary>
/// Bitflag field for ObjectUpdateCompressed data blocks, describing
/// which options are present for each object
/// </summary>
[Flags]
public enum CompressedFlags : uint
{
/// <summary>Hasn't been spotted in the wild yet</summary>
ScratchPad = 0x01,
/// <summary>This may be incorrect</summary>
Tree = 0x02,
/// <summary>Whether the object has floating text ala llSetText</summary>
HasText = 0x04,
/// <summary>Whether the object has an active particle system</summary>
HasParticles = 0x08,
/// <summary>Whether the object has sound attached to it</summary>
HasSound = 0x10,
/// <summary>Whether the object is attached to a root object or not</summary>
HasParent = 0x20,
/// <summary>Whether the object has texture animation settings</summary>
TextureAnimation = 0x40,
/// <summary>Whether the object has an angular velocity</summary>
HasAngularVelocity = 0x80,
/// <summary>Whether the object has a name value pairs string</summary>
HasNameValues = 0x100,
/// <summary>Whether the object has a Media URL set</summary>
MediaURL = 0x200
}
/// <summary>
///
/// </summary>
public enum Tree : byte
{
/// <summary></summary>
Pine1 = 0,
/// <summary></summary>
Oak,
/// <summary></summary>
TropicalBush1,
/// <summary></summary>
Palm1,
/// <summary></summary>
Dogwood,
/// <summary></summary>
TropicalBush2,
/// <summary></summary>
Palm2,
/// <summary></summary>
Cypress1,
/// <summary></summary>
Cypress2,
/// <summary></summary>
Pine2,
/// <summary></summary>
Plumeria,
/// <summary></summary>
WinterPine1,
/// <summary></summary>
WinterAspen,
/// <summary></summary>
WinterPine2,
/// <summary></summary>
Eucalyptus,
/// <summary></summary>
Fern,
/// <summary></summary>
Eelgrass,
/// <summary></summary>
SeaSword,
/// <summary></summary>
Kelp1,
/// <summary></summary>
BeachGrass1,
/// <summary></summary>
Kelp2
}
/// <summary>
///
/// </summary>
public enum Grass : byte
{
/// <summary></summary>
Grass0 = 0,
/// <summary></summary>
Grass1,
/// <summary></summary>
Grass2,
/// <summary></summary>
Grass3,
/// <summary></summary>
Grass4,
/// <summary></summary>
Undergrowth1
}
/// <summary>
///
/// </summary>
public enum SaleType : byte
{
/// <summary></summary>
Not = 0,
/// <summary></summary>
Original = 1,
/// <summary></summary>
Copy = 2,
/// <summary></summary>
Contents = 3
}
/// <summary>
///
/// </summary>
public enum ClickAction : byte
{
/// <summary></summary>
Touch = 0,
/// <summary></summary>
Sit = 1,
/// <summary></summary>
Buy = 2
}
#endregion
#region Events
/// <summary>
/// This event will be raised for every ObjectUpdate block that
/// contains a prim that isn't attached to an avatar.
/// </summary>
/// <remarks>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.
/// </remarks>
public event NewPrimCallback OnNewPrim;
/// <summary>
/// This event will be raised for every ObjectUpdate block that
/// contains an avatar attachment.
/// </summary>
/// <remarks>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.
/// </remarks>
public event NewAttachmentCallback OnNewAttachment;
/// <summary>
/// This event will be raised for every ObjectUpdate block that
/// contains a new avatar.
/// </summary>
/// <remarks>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.
/// </remarks>
public event NewAvatarCallback OnNewAvatar;
/// <summary>
/// This event will be raised for every ObjectUpdate block that
/// contains a new tree or grass patch.
/// </summary>
/// <remarks>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.
/// </remarks>
public event NewFoliageCallback OnNewFoliage;
/// <summary>
/// This event will be raised when a terse object update packet is
/// received, containing the updated position, rotation, and
/// movement-related vectors
/// </summary>
public event ObjectUpdatedCallback OnObjectUpdated;
/// <summary>
/// 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.
/// </summary>
public event AvatarSitChanged OnAvatarSitChanged;
/// <summary>
/// This event will be raised when an object is removed from a
/// simulator.
/// </summary>
public event KillObjectCallback OnObjectKilled;
/// <summary>
/// This event will be raised when an objects properties are received
/// from the simulator
/// </summary>
public event ObjectPropertiesCallback OnObjectProperties;
/// <summary>
/// Thie event will be raised when an objects properties family
/// information is recieved from the simulator. ObjectPropertiesFamily
/// is a subset of the fields found in ObjectProperties
/// </summary>
public event ObjectPropertiesFamilyCallback OnObjectPropertiesFamily;
#endregion
private const float HAVOK_TIMESTEP = 1.0f / 45.0f;
/// <summary>
/// Reference to the SecondLife client
/// </summary>
protected SecondLife Client;
private System.Timers.Timer InterpolationTimer;
/// <summary>
/// Instantiates a new ObjectManager class. This class should only be accessed
/// through SecondLife.Objects, client applications should never create their own
/// </summary>
/// <param name="client">A reference to the client</param>
public ObjectManager(SecondLife client)
{
Client = client;
RegisterCallbacks();
}
/// <summary>
/// Instantiates a new ObjectManager class. This class should only be
/// accessed through SecondLife.Objects, client applications should
/// never create their own
/// </summary>
/// <param name="client">A reference to the client</param>
/// <param name="registerCallbacks">If false, the ObjectManager won't
/// register any packet callbacks and won't decode incoming object
/// packets</param>
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));
// If the callbacks aren't registered there's not point in doing client-side path prediction,
// so we set it up here
InterpolationTimer = new System.Timers.Timer(Settings.INTERPOLATION_UPDATE);
InterpolationTimer.Elapsed += new System.Timers.ElapsedEventHandler(InterpolationTimer_Elapsed);
InterpolationTimer.Start();
}
#region Action Methods
/// <summary>
/// Request object information from the sim, primarily used for stale
/// or missing cache entries
/// </summary>
/// <param name="simulator">The simulator containing the object you're
/// looking for</param>
/// <param name="localID">The local ID of the object</param>
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);
}
/// <summary>
/// Request object information for multiple objects all contained in
/// the same sim, primarily used for stale or missing cache entries
/// </summary>
/// <param name="simulator">The simulator containing the object you're
/// looking for</param>
/// <param name="localIDs">A list of local IDs of the objects</param>
public void RequestObjects(Simulator simulator, List<uint> 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);
}
/// <summary>
/// Attempt to purchase an original object, a copy, or the contents of
/// an object
/// </summary>
/// <param name="simulator">Simulator where the object resides</param>
/// <param name="localID">Sim-local ID of the object</param>
/// <param name="saleType">Whether the original, a copy, or the object
/// contents are on sale. This is used for verification, if the this
/// sale type is not valid for the object the purchase will fail</param>
/// <param name="price">Price of the object. This is used for
/// verification, if it does not match the actual price the purchase
/// will fail</param>
/// <param name="groupID">Group ID that will be associated with the new
/// purchase</param>
/// <param name="categoryID">Inventory folder UUID where the purchase
/// should go</param>
/// <example>BuyObject(Client.Network.CurrentSim, 500, SaleType.Copy,
/// 100, LLUUID.Zero, Client.Self.InventoryRootFolderUUID);</example>
public void BuyObject(Simulator simulator, uint localID, SaleType saleType, int price, LLUUID groupID,
LLUUID categoryID)
{
ObjectBuyPacket buy = new ObjectBuyPacket();
buy.AgentData.AgentID = Client.Network.AgentID;
buy.AgentData.SessionID = Client.Network.SessionID;
buy.AgentData.GroupID = groupID;
buy.AgentData.CategoryID = categoryID;
buy.ObjectData = new ObjectBuyPacket.ObjectDataBlock[1];
buy.ObjectData[0] = new ObjectBuyPacket.ObjectDataBlock();
buy.ObjectData[0].ObjectLocalID = localID;
buy.ObjectData[0].SaleType = (byte)saleType;
buy.ObjectData[0].SalePrice = price;
Client.Network.SendPacket(buy, simulator);
}
/// <summary>
/// Select an object. This will trigger the simulator to send us back
/// an ObjectProperties packet so we can get the full information for
/// this object
/// </summary>
/// <param name="simulator">Simulator where the object resides</param>
/// <param name="localID">Sim-local ID of the object to select</param>
public void SelectObject(Simulator simulator, uint localID)
{
ObjectSelectPacket select = new ObjectSelectPacket();
select.AgentData.AgentID = Client.Network.AgentID;
select.AgentData.SessionID = Client.Network.SessionID;
select.ObjectData = new ObjectSelectPacket.ObjectDataBlock[1];
select.ObjectData[0] = new ObjectSelectPacket.ObjectDataBlock();
select.ObjectData[0].ObjectLocalID = localID;
Client.Network.SendPacket(select, simulator);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
public void ClickObject(Simulator simulator, uint localID)
{
ObjectGrabPacket grab = new ObjectGrabPacket();
grab.AgentData.AgentID = Client.Network.AgentID;
grab.AgentData.SessionID = Client.Network.SessionID;
grab.ObjectData.GrabOffset = LLVector3.Zero;
grab.ObjectData.LocalID = localID;
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
ObjectDeGrabPacket degrab = new ObjectDeGrabPacket();
degrab.AgentData.AgentID = Client.Network.AgentID;
degrab.AgentData.SessionID = Client.Network.SessionID;
degrab.ObjectData.LocalID = localID;
Client.Network.SendPacket(degrab, simulator);
}
/// <summary>
/// Create, or "rez" a new prim object in a simulator
/// </summary>
/// <param name="simulator">The target simulator</param>
/// <param name="prim">Data describing the prim object to rez</param>
/// <param name="groupID">Group ID that this prim is set to, or LLUUID.Zero</param>
/// <param name="position">An approximation of the position at which to rez the prim</param>
/// <param name="scale">Scale vector to size this prim</param>
/// <param name="rotation">Rotation quaternion to rotate this prim</param>
/// <remarks>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.</remarks>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="scale"></param>
/// <param name="rotation"></param>
/// <param name="position"></param>
/// <param name="treeType"></param>
/// <param name="groupOwner"></param>
/// <param name="newTree"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="scale"></param>
/// <param name="rotation"></param>
/// <param name="position"></param>
/// <param name="grassType"></param>
/// <param name="groupOwner"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="textures"></param>
public void SetTextures(Simulator simulator, uint localID, LLObject.TextureEntry textures)
{
SetTextures(simulator, localID, textures, String.Empty);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="textures"></param>
/// <param name="mediaUrl"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="light"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="flexible"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localIDs"></param>
public void LinkPrims(Simulator simulator, List<uint> 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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="rotation"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="name"></param>
public void SetName(Simulator simulator, uint localID, string name)
{
SetNames(simulator, new uint[] { localID }, new string[] { name });
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localIDs"></param>
/// <param name="names"></param>
public void SetNames(Simulator simulator, uint[] localIDs, string[] names)
{
ObjectNamePacket namePacket = new ObjectNamePacket();
namePacket.AgentData.AgentID = Client.Network.AgentID;
namePacket.AgentData.SessionID = Client.Network.SessionID;
namePacket.ObjectData = new ObjectNamePacket.ObjectDataBlock[localIDs.Length];
for (int i = 0; i < localIDs.Length; ++i)
{
namePacket.ObjectData[i] = new ObjectNamePacket.ObjectDataBlock();
namePacket.ObjectData[i].LocalID = localIDs[i];
namePacket.ObjectData[i].Name = Helpers.StringToField(names[i]);
}
Client.Network.SendPacket(namePacket, simulator);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="description"></param>
public void SetDescription(Simulator simulator, uint localID, string description)
{
SetDescriptions(simulator, new uint[] { localID }, new string[] { description });
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localIDs"></param>
/// <param name="descriptions"></param>
public void SetDescriptions(Simulator simulator, uint[] localIDs, string[] descriptions)
{
ObjectDescriptionPacket descPacket = new ObjectDescriptionPacket();
descPacket.AgentData.AgentID = Client.Network.AgentID;
descPacket.AgentData.SessionID = Client.Network.SessionID;
descPacket.ObjectData = new ObjectDescriptionPacket.ObjectDataBlock[localIDs.Length];
for (int i = 0; i < localIDs.Length; ++i)
{
descPacket.ObjectData[i] = new ObjectDescriptionPacket.ObjectDataBlock();
descPacket.ObjectData[i].LocalID = localIDs[i];
descPacket.ObjectData[i].Description = Helpers.StringToField(descriptions[i]);
}
Client.Network.SendPacket(descPacket, simulator);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="attachPoint"></param>
/// <param name="rotation"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localIDs"></param>
public void DetachObjects(Simulator simulator, List<uint> 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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localID"></param>
/// <param name="position"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="localIDs"></param>
/// <param name="who"></param>
/// <param name="permissions"></param>
/// <param name="set"></param>
public void SetPermissions(Simulator simulator, List<uint> 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);
}
/// <summary>
/// Request additional properties for an object
/// </summary>
/// <param name="simulator"></param>
/// <param name="objectID"></param>
public void RequestObjectPropertiesFamily(Simulator simulator, LLUUID objectID)
{
RequestObjectPropertiesFamily(simulator, objectID, true);
}
/// <summary>
/// Request additional properties for an object
/// </summary>
/// <param name="simulator">Simulator containing the object</param>
/// <param name="objectID">Absolute UUID of the object</param>
/// <param name="reliable">Whether to require server acknowledgement of this request</param>
public void RequestObjectPropertiesFamily(Simulator simulator, LLUUID objectID, bool reliable)
{
RequestObjectPropertiesFamilyPacket properties = new RequestObjectPropertiesFamilyPacket();
properties.AgentData.AgentID = Client.Network.AgentID;
properties.AgentData.SessionID = Client.Network.SessionID;
properties.ObjectData.ObjectID = objectID;
// TODO: RequestFlags is typically only for bug report submissions, but we might be able to
// use it to pass an arbitrary uint back to the callback
properties.ObjectData.RequestFlags = 0;
properties.Header.Reliable = reliable;
Client.Network.SendPacket(properties, simulator);
}
#endregion
#region Packet Handlers
/// <summary>
/// Used for new prims, or significant changes to existing prims
/// </summary>
/// <param name="packet"></param>
/// <param name="simulator"></param>
protected void UpdateHandler(Packet packet, Simulator simulator)
{
ObjectUpdatePacket update = (ObjectUpdatePacket)packet;
UpdateDilation(simulator, update.RegionData.TimeDilation);
for (int b = 0; b < update.ObjectData.Length; b++)
{
ObjectUpdatePacket.ObjectDataBlock block = update.ObjectData[b];
LLVector4 collisionPlane = LLVector4.Zero;
LLVector3 position;
LLVector3 velocity;
LLVector3 acceleration;
LLQuaternion rotation;
LLVector3 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:
if (OnNewFoliage == null) continue;
break;
case PCode.Prim:
if (OnNewPrim == null) continue;
break;
case PCode.Avatar:
// Make an exception for updates about our own agent
if (block.FullID != Client.Network.AgentID && OnNewAvatar == null) continue;
break;
case PCode.ParticleSystem:
continue; // TODO: Do something with these
}
}
#endregion Relevance check
#region NameValue parsing
string nameValue = Helpers.FieldToUTF8String(block.NameValue);
if (nameValue.Length > 0)
{
string[] lines = nameValue.Split(new char[] { '\n' });
nameValues = new NameValue[lines.Length];
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Length > 0)
{
NameValue nv = new NameValue(lines[i]);
if (nv.Name == "AttachItemID") attachment = true;
nameValues[i] = nv;
}
}
}
else
{
nameValues = new NameValue[0];
}
#endregion NameValue parsing
#region Decode Object (primitive) parameters
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;
#endregion
#region Decode 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;
}
#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:
Primitive prim = GetPrimitive(simulator, block.ID, block.FullID);
#region Update Prim Info with decoded data
prim.Flags = (LLObject.ObjectFlags)block.UpdateFlags;
if ((prim.Flags & LLObject.ObjectFlags.ZlibCompressed) != 0)
{
Client.Log("Got a ZlibCompressed ObjectUpdate, implement me!",
Helpers.LogLevel.Warning);
continue;
}
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 = 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);
// PCode-specific data
prim.GenericData = block.Data;
// Packed parameters
prim.CollisionPlane = collisionPlane;
prim.Position = position;
prim.Velocity = velocity;
prim.Acceleration = acceleration;
prim.Rotation = rotation;
prim.AngularVelocity = angularVelocity;
#endregion
// Used to notify subclasses that a prim was updated
llObjectUpdated(simulator, prim);
if (attachment)
FireOnNewAttachment(simulator, prim, update.RegionData.RegionHandle,
update.RegionData.TimeDilation);
else if (pcode == PCode.Prim)
FireOnNewPrim(simulator, prim, update.RegionData.RegionHandle,
update.RegionData.TimeDilation);
else
FireOnNewFoliage(simulator, prim, update.RegionData.RegionHandle,
update.RegionData.TimeDilation);
break;
#endregion Prim and Foliage
#region Avatar
case PCode.Avatar:
// Update some internals if this is our avatar
if (block.FullID == Client.Network.AgentID)
{
#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 = 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);
}
#endregion
}
#region Create an Avatar from the decoded data
Avatar avatar = GetAvatar(simulator, block.ID, block.FullID);
avatar.ID = block.FullID;
avatar.LocalID = block.ID;
avatar.CollisionPlane = collisionPlane;
avatar.Position = position;
avatar.Velocity = velocity;
avatar.Acceleration = acceleration;
avatar.Rotation = rotation;
avatar.AngularVelocity = angularVelocity;
avatar.NameValues = nameValues;
avatar.data = data;
avatar.GenericData = block.Data;
SetAvatarSittingOn(avatar, block.ParentID);
// Set this avatar online and in a region
avatar.Online = true;
avatar.CurrentSim = simulator;
// Textures
avatar.Textures = new Primitive.TextureEntry(block.TextureEntry, 0,
block.TextureEntry.Length);
#endregion Create an Avatar from the decoded data
FireOnNewAvatar(simulator, avatar, update.RegionData.RegionHandle,
update.RegionData.TimeDilation);
break;
#endregion Avatar
case PCode.ParticleSystem:
DecodeParticleUpdate(block);
// TODO: Create a callback for particle updates
break;
default:
Client.DebugLog("Got an ObjectUpdate block with an unrecognized PCode " + pcode.ToString());
break;
}
}
}
protected void DecodeParticleUpdate(ObjectUpdatePacket.ObjectDataBlock block)
{
// TODO: Handle ParticleSystem ObjectUpdate blocks
// float bounce_b
// LLVector4 scale_range
// LLVector4 alpha_range
// LLVector3 vel_offset
// float dist_begin_fadeout
// float dist_end_fadeout
// LLUUID image_uuid
// long flags
// byte createme
// LLVector3 diff_eq_alpha
// LLVector3 diff_eq_scale
// byte max_particles
// byte initial_particles
// float kill_plane_z
// LLVector3 kill_plane_normal
// float bounce_plane_z
// LLVector3 bounce_plane_normal
// float spawn_range
// float spawn_frequency
// float spawn_frequency_range
// LLVector3 spawn_direction
// float spawn_direction_range
// float spawn_velocity
// float spawn_velocity_range
// float speed_limit
// float wind_weight
// LLVector3 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
// LLVector3 wind_diffusion_factor
}
/// <summary>
/// Usually called when an Prim moves.
/// </summary>
/// <param name="packet"></param>
/// <param name="simulator"></param>
protected void TerseUpdateHandler(Packet packet, Simulator simulator)
{
ImprovedTerseObjectUpdatePacket terse = (ImprovedTerseObjectUpdatePacket)packet;
UpdateDilation(simulator, terse.RegionData.TimeDilation);
for (int i = 0; i < terse.ObjectData.Length; i++)
{
ImprovedTerseObjectUpdatePacket.ObjectDataBlock block = terse.ObjectData[i];
try
{
int pos = 4;
uint localid = Helpers.BytesToUIntBig(block.Data, 0);
// Check if we are interested in this update
if (!Client.Settings.ALWAYS_DECODE_OBJECTS && localid != Client.Self.LocalID && OnObjectUpdated == null)
continue;
#region Decode update data
ObjectUpdate update = new ObjectUpdate();
// LocalID
update.LocalID = localid;
// State
update.State = block.Data[pos++];
// Avatar boolean
update.Avatar = (block.Data[pos++] != 0);
// Collision normal for avatar
if (update.Avatar)
{
update.CollisionPlane = new 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.Acceleration = 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 + 6, -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);
#endregion Decode update data
LLObject obj = (update.Avatar) ?
(LLObject)GetAvatar(simulator, update.LocalID, null):
(LLObject)GetPrimitive(simulator, update.LocalID, null);
#region Update Client.Self
if (update.LocalID == Client.Self.LocalID)
{
Client.Self.CollisionPlane = update.CollisionPlane;
Client.Self.Position = update.Position;
Client.Self.Velocity = update.Velocity;
Client.Self.Acceleration = update.Acceleration;
Client.Self.Rotation = update.Rotation;
Client.Self.AngularVelocity = update.AngularVelocity;
}
#endregion Update Client.Self
obj.Acceleration = update.Acceleration;
obj.AngularVelocity = update.AngularVelocity;
obj.CollisionPlane = update.CollisionPlane;
obj.Position = update.Position;
obj.Rotation = update.Rotation;
obj.Textures = update.Textures;
obj.Velocity = update.Velocity;
llObjectUpdated(simulator, obj);
// Fire the callback
FireOnObjectUpdated(simulator, update, terse.RegionData.RegionHandle, terse.RegionData.TimeDilation);
}
catch (Exception e)
{
Client.Log(e.ToString(), Helpers.LogLevel.Warning);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="packet"></param>
/// <param name="simulator"></param>
protected void CompressedUpdateHandler(Packet packet, Simulator simulator)
{
ObjectUpdateCompressedPacket update = (ObjectUpdateCompressedPacket)packet;
for (int b = 0; b < update.ObjectData.Length; b++)
{
ObjectUpdateCompressedPacket.ObjectDataBlock block = update.ObjectData[b];
int i = 0;
try
{
// UUID
LLUUID FullID = new LLUUID(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:
if (OnNewFoliage == null) continue;
break;
case PCode.Prim:
if (OnNewPrim == null) continue;
break;
}
}
#endregion Relevance check
Primitive prim = GetPrimitive(simulator, LocalID, FullID);
prim.LocalID = LocalID;
prim.ID = FullID;
prim.Flags = (LLObject.ObjectFlags)block.UpdateFlags;
switch (pcode)
{
case PCode.Grass:
case PCode.Tree:
case PCode.NewTree:
#region Foliage Decoding
// State
prim.data.State = (uint)block.Data[i++];
// CRC
i += 4;
// Material
prim.data.Material = (uint)block.Data[i++];
// Click action
prim.ClickAction = (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;
#endregion Foliage Decoding
// FIXME: We are leaving a lot of data left undecoded here, including the
// tree species. Need to understand what is going on with these packets
// and fix it soon!
FireOnNewFoliage(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation);
break;
case PCode.Prim:
#region Decode block and update Prim
// State
prim.data.State = (uint)block.Data[i++];
// CRC
i += 4;
// Material
prim.data.Material = (uint)block.Data[i++];
// Click action
prim.ClickAction = (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;
// Angular velocity
if ((flags & CompressedFlags.HasAngularVelocity) != 0)
{
prim.AngularVelocity = new LLVector3(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.GenericData = new byte[1];
prim.GenericData[0] = block.Data[i++];
}
// Scratch pad
else if ((flags & CompressedFlags.ScratchPad) != 0)
{
int size = block.Data[i++];
prim.GenericData = new byte[size];
Buffer.BlockCopy(block.Data, i, prim.GenericData, 0, size);
i += size;
}
// Floating text
if ((flags & CompressedFlags.HasText) != 0)
{
string text = String.Empty;
while (block.Data[i] != 0)
{
text += (char)block.Data[i];
i++;
}
i++;
// Floating text
prim.Text = text;
// Text color
prim.TextColor = new LLColor(block.Data, i);
i += 4;
}
else
{
prim.Text = String.Empty;
}
// Media URL
if ((flags & CompressedFlags.MediaURL) != 0)
{
string text = String.Empty;
while (block.Data[i] != 0)
{
text += (char)block.Data[i];
i++;
}
i++;
prim.MediaURL = text;
}
// Particle system
if ((flags & CompressedFlags.HasParticles) != 0)
{
prim.ParticleSys = new Primitive.ParticleSystem(block.Data, i);
i += 86;
}
// Extra parameters
i += prim.SetExtraParamsFromBytes(block.Data, i);
//Sound data
if ((flags & CompressedFlags.HasSound) != 0)
{
prim.Sound = new 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;
}
// 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(new char[] { '\n' });
prim.NameValues = new NameValue[lines.Length];
for (int j = 0; j < lines.Length; j++)
{
if (lines[j].Length > 0)
{
NameValue nv = new NameValue(lines[j]);
prim.NameValues[j] = nv;
}
}
}
}
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++];
// TextureEntry
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;
// Texture animation
if ((flags & CompressedFlags.TextureAnimation) != 0)
{
//int textureAnimLength = (int)(block.Data[i++] + (block.Data[i++] << 8) +
// (block.Data[i++] << 16) + (block.Data[i++] << 24));
i += 4;
prim.TextureAnim = new LLObject.TextureAnimation(block.Data, i);
}
#endregion
#region Fire Events
// Fire the appropriate callback
// TODO: We should use a better check to see if this is actually an attachment
if ((flags & CompressedFlags.HasNameValues) != 0)
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
FireOnNewPrim(simulator, prim, update.RegionData.RegionHandle,
update.RegionData.TimeDilation);
#endregion
break;
default:
Client.DebugLog("Got an ObjectUpdateCompressed for PCode " + pcode.ToString() +
", implement this!");
break;
}
llObjectUpdated(simulator, prim);
}
catch (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 (Client.Settings.ALWAYS_REQUEST_OBJECTS)
{
List<uint> ids = new List<uint>();
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 ObjectPropertiesHandler(Packet p, Simulator sim)
{
ObjectPropertiesPacket op = (ObjectPropertiesPacket)p;
ObjectPropertiesPacket.ObjectDataBlock[] datablocks = op.ObjectData;
for (int i = 0; i < datablocks.Length; ++i)
{
ObjectPropertiesPacket.ObjectDataBlock objectData = datablocks[i];
LLObject.ObjectProperties props = new LLObject.ObjectProperties();
props.AggregatePerms = objectData.AggregatePerms;
props.AggregatePermTextures = objectData.AggregatePermTextures;
props.AggregatePermTexturesOwner = objectData.AggregatePermTexturesOwner;
props.BaseMask = objectData.BaseMask;
props.Category = objectData.Category;
props.CreationDate = objectData.CreationDate;
props.CreatorID = objectData.CreatorID;
props.Description = Helpers.FieldToUTF8String(objectData.Description);
props.EveryoneMask = objectData.EveryoneMask;
props.FolderID = objectData.FolderID;
props.FromTaskID = objectData.FromTaskID;
props.GroupID = objectData.GroupID;
props.GroupMask = objectData.GroupMask;
props.InventorySerial = objectData.InventorySerial;
props.ItemID = objectData.ItemID;
props.LastOwnerID = objectData.LastOwnerID;
props.Name = Helpers.FieldToUTF8String(objectData.Name);
props.NextOwnerMask = objectData.NextOwnerMask;
props.ObjectID = objectData.ObjectID;
props.OwnerID = objectData.OwnerID;
props.OwnerMask = objectData.OwnerMask;
props.OwnershipCost = objectData.OwnershipCost;
props.SalePrice = objectData.SalePrice;
props.SaleType = objectData.SaleType;
props.SitName = Helpers.FieldToUTF8String(objectData.SitName);
props.TouchName = Helpers.FieldToUTF8String(objectData.TouchName);
int numTextures = objectData.TextureID.Length / 16;
props.TextureIDs = new LLUUID[numTextures];
for (int j = 0; j < numTextures; ++j)
props.TextureIDs[j] = new LLUUID(objectData.TextureID, j * 16);
FireOnObjectProperties(sim, props);
}
}
protected void ObjectPropertiesFamilyHandler(Packet p, Simulator sim)
{
ObjectPropertiesFamilyPacket op = (ObjectPropertiesFamilyPacket)p;
LLObject.ObjectPropertiesFamily props = new LLObject.ObjectPropertiesFamily();
props.RequestFlags = (LLObject.ObjectPropertiesFamily.RequestFlagsType)op.ObjectData.RequestFlags;
props.BaseMask = op.ObjectData.BaseMask;
props.Category = op.ObjectData.Category;
props.Description = Helpers.FieldToUTF8String(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.FieldToUTF8String(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.0f;
}
protected void InterpolationTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (Client.Network.Connected)
{
// For now we only update Client.Self since there is no object tracking
// built in to the library
// Only do movement interpolation (extrapolation) when there is a non-zero velocity but
// no acceleration
if (Client.Self.Velocity != LLVector3.Zero || Client.Self.Acceleration != LLVector3.Zero)
{
TimeSpan interval = DateTime.Now - Client.Self.lastInterpolation;
float adjSeconds = (float)interval.TotalSeconds * Client.Network.CurrentSim.Dilation;
Client.Self.Position += (Client.Self.Velocity + (0.5f * (adjSeconds - HAVOK_TIMESTEP)) *
Client.Self.Acceleration) * adjSeconds;
}
}
// Make sure the last interpolated time is always updated
Client.Self.lastInterpolation = DateTime.Now;
}
#endregion
#region Event Notification
protected void FireOnObjectProperties(Simulator sim, LLObject.ObjectProperties props)
{
if (OnObjectProperties != null)
{
try { OnObjectProperties(sim, props); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
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); }
}
else
{
prim = null;
}
}
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); }
}
else
{
prim = null;
}
}
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); }
}
else
{
prim = null;
}
}
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); }
}
else
{
avatar = null;
}
}
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
#region Subclass Indirection
/// <summary>
/// Primitive Factory, this allows a subclass to lookup a copy of the Primitive
/// and return it for updating, rather then always creating a new Primitive
/// </summary>
/// <param name="simulator"></param>
/// <param name="LocalID"></param>
/// <param name="UUID"></param>
/// <returns></returns>
virtual protected Primitive GetPrimitive(Simulator simulator, uint LocalID, LLUUID UUID)
{
return new Primitive();
}
/// <summary>
/// Primitive Factory, this allows a subclass to lookup a copy of the Avatar
/// and return it for updating, rather then always creating a new Avatar
/// </summary>
/// <param name="simulator"></param>
/// <param name="LocalID"></param>
/// <param name="UUID"></param>
/// <returns></returns>
virtual protected Avatar GetAvatar(Simulator simulator, uint LocalID, LLUUID UUID)
{
return new Avatar();
}
/// <summary>
/// Used to flag to a subclass that an LLObject was updated/created
/// </summary>
/// <param name="simulator"></param>
/// <param name="obj"></param>
virtual protected void llObjectUpdated(Simulator simulator, LLObject obj)
{
}
#endregion
}
}