* 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
2227 lines
94 KiB
C#
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
|
|
}
|
|
}
|