/*
* Copyright (c) 2006-2007, 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.Net;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.Reflection;
using libsecondlife.StructuredData;
using libsecondlife.Capabilities;
using libsecondlife.Packets;
namespace libsecondlife
{
#region Enums
///
/// Permission request flags, asked when a script wants to control an Avatar
///
[Flags]
public enum ScriptPermission : int
{
/// Placeholder for empty values, shouldn't ever see this
None = 0,
/// Script wants ability to take money from you
Debit = 1 << 1,
/// Script wants to take camera controls for you
TakeControls = 1 << 2,
/// Script wants to remap avatars controls
RemapControls = 1 << 3,
/// Script wants to trigger avatar animations
/// This function is not implemented on the grid
TriggerAnimation = 1 << 4,
/// Script wants to attach or detach the prim or primset to your avatar
Attach = 1 << 5,
/// Script wants permission to release ownership
/// This function is not implemented on the grid
/// The concept of "public" objects does not exist anymore.
ReleaseOwnership = 1 << 6,
/// Script wants ability to link/delink with other prims
ChangeLinks = 1 << 7,
/// Script wants permission to change joints
/// This function is not implemented on the grid
ChangeJoints = 1 << 8,
/// Script wants permissions to change permissions
/// This function is not implemented on the grid
ChangePermissions = 1 << 9,
/// Script wants to track avatars camera position and rotation
TrackCamera = 1 << 10,
/// Script wants to control your camera
ControlCamera = 1 << 11
}
///
/// Special commands used in Instant Messages
///
public enum InstantMessageDialog : byte
{
/// Indicates a regular IM from another agent
MessageFromAgent = 0,
/// Simple notification box with an OK button
MessageBox = 1,
/// Used to show a countdown notification with an OK
/// button, deprecated now
[Obsolete]
MessageBoxCountdown = 2,
/// You've been invited to join a group.
GroupInvitation = 3,
/// Inventory offer
InventoryOffered = 4,
/// Accepted inventory offer
InventoryAccepted = 5,
/// Declined inventory offer
InventoryDeclined = 6,
/// Group vote
GroupVote = 7,
/// A message to everyone in the agent's group, no longer
/// used
[Obsolete]
DeprecatedGroupMessage = 8,
/// An object is offering its inventory
TaskInventoryOffered = 9,
/// Accept an inventory offer from an object
TaskInventoryAccepted = 10,
/// Decline an inventory offer from an object
TaskInventoryDeclined = 11,
/// Unknown
NewUserDefault = 12,
/// Start a session, or add users to a session
SessionAdd = 13,
/// Start a session, but don't prune offline users
SessionOfflineAdd = 14,
/// Start a session with your group
SessionGroupStart = 15,
/// Start a session without a calling card (finder or objects)
SessionCardlessStart = 16,
/// Send a message to a session
SessionSend = 17,
/// Leave a session
SessionDrop = 18,
/// Indicates that the IM is from an object
MessageFromObject = 19,
/// Sent an IM to a busy user, this is the auto response
BusyAutoResponse = 20,
/// Shows the message in the console and chat history
ConsoleAndChatHistory = 21,
/// Send a teleport lure
RequestTeleport = 22,
/// Response sent to the agent which inititiated a teleport invitation
AcceptTeleport = 23,
/// Response sent to the agent which inititiated a teleport invitation
DenyTeleport = 24,
/// Only useful if you have Linden permissions
GodLikeRequestTeleport = 25,
/// A placeholder type for future expansion, currently not
/// used
CurrentlyUnused = 26,
/// Notification of a new group election, this is
/// deprecated
[Obsolete]
DeprecatedGroupElection = 27,
/// IM to tell the user to go to an URL
GotoUrl = 28,
/// IM for help
Session911Start = 29,
/// IM sent automatically on call for help, sends a lure
/// to each Helper reached
Lure911 = 30,
/// Like an IM but won't go to email
FromTaskAsAlert = 31,
/// IM from a group officer to all group members
GroupNotice = 32,
/// Unknown
GroupNoticeInventoryAccepted = 33,
/// Unknown
GroupNoticeInventoryDeclined = 34,
/// Accept a group invitation
GroupInvitationAccept = 35,
/// Decline a group invitation
GroupInvitationDecline = 36,
/// Unknown
GroupNoticeRequested = 37,
/// An avatar is offering you friendship
FriendshipOffered = 38,
/// An avatar has accepted your friendship offer
FriendshipAccepted = 39,
/// An avatar has declined your friendship offer
FriendshipDeclined = 40,
/// Indicates that a user has started typing
StartTyping = 41,
/// Indicates that a user has stopped typing
StopTyping = 42
}
///
/// Flag in Instant Messages, whether the IM should be delivered to
/// offline avatars as well
///
public enum InstantMessageOnline
{
/// Only deliver to online avatars
Online = 0,
/// If the avatar is offline the message will be held until
/// they login next, and possibly forwarded to their e-mail account
Offline = 1
}
///
/// Conversion type to denote Chat Packet types in an easier-to-understand format
///
public enum ChatType : byte
{
/// Whisper (5m radius)
Whisper = 0,
/// Normal chat (10/20m radius), what the official viewer typically sends
Normal = 1,
/// Shouting! (100m radius)
Shout = 2,
/// Say chat (10/20m radius) - The official viewer will
/// print "[4:15] You say, hey" instead of "[4:15] You: hey"
[Obsolete]
Say = 3,
/// Event message when an Avatar has begun to type
StartTyping = 4,
/// Event message when an Avatar has stopped typing
StopTyping = 5,
/// Unknown
Debug = 6
}
///
/// Identifies the source of a chat message
///
public enum ChatSourceType : byte
{
/// Chat from the grid or simulator
System = 0,
/// Chat from another avatar
Agent = 1,
/// Chat from an object
Object = 2
}
///
///
///
public enum ChatAudibleLevel : sbyte
{
///
Not = -1,
///
Barely = 0,
///
Fully = 1
}
///
/// Effect type used in ViewerEffect packets
///
public enum EffectType : byte
{
///
Text = 0,
///
Icon,
///
Connector,
///
FlexibleObject,
///
AnimalControls,
///
AnimationObject,
///
Cloth,
/// Project a beam from a source to a destination, such as
/// the one used when editing an object
Beam,
///
Glow,
///
Point,
///
Trail,
/// Create a swirl of particles around an object
Sphere,
///
Spiral,
///
Edit,
/// Cause an avatar to look at an object
LookAt,
/// Cause an avatar to point at an object
PointAt
}
///
/// The action an avatar is doing when looking at something, used in
/// ViewerEffect packets for the LookAt effect
///
public enum LookAtType : byte
{
///
None,
///
Idle,
///
AutoListen,
///
FreeLook,
///
Respond,
///
Hover,
/// Deprecated
[Obsolete]
Conversation,
///
Select,
///
Focus,
///
Mouselook,
///
Clear
}
///
/// The action an avatar is doing when pointing at something, used in
/// ViewerEffect packets for the PointAt effect
///
public enum PointAtType : byte
{
///
None,
///
Select,
///
Grab,
///
Clear
}
///
/// Money transaction types
///
public enum MoneyTransactionType : int
{
///
None = 0,
///
FailSimulatorTimeout = 1,
///
FailDataserverTimeout = 2,
///
ObjectClaim = 1000,
///
LandClaim = 1001,
///
GroupCreate = 1002,
///
ObjectPublicClaim = 1003,
///
GroupJoin = 1004,
///
TeleportCharge = 1100,
///
UploadCharge = 1101,
///
LandAuction = 1102,
///
ClassifiedCharge = 1103,
///
ObjectTax = 2000,
///
LandTax = 2001,
///
LightTax = 2002,
///
ParcelDirFee = 2003,
///
GroupTax = 2004,
///
ClassifiedRenew = 2005,
///
GiveInventory = 3000,
///
ObjectSale = 5000,
///
Gift = 5001,
///
LandSale = 5002,
///
ReferBonus = 5003,
///
InventorySale = 5004,
///
RefundPurchase = 5005,
///
LandPassSale = 5006,
///
DwellBonus = 5007,
///
PayObject = 5008,
///
ObjectPays = 5009,
///
GroupLandDeed = 6001,
///
GroupObjectDeed = 6002,
///
GroupLiability = 6003,
///
GroupDividend = 6004,
///
GroupMembershipDues = 6005,
///
ObjectRelease = 8000,
///
LandRelease = 8001,
///
ObjectDelete = 8002,
///
ObjectPublicDecay = 8003,
///
ObjectPublicDelete = 8004,
///
LindenAdjustment = 9000,
///
LindenGrant = 9001,
///
LindenPenalty = 9002,
///
EventFee = 9003,
///
EventPrize = 9004,
///
StipendBasic = 10000,
///
StipendDeveloper = 10001,
///
StipendAlways = 10002,
///
StipendDaily = 10003,
///
StipendRating = 10004,
///
StipendDelta = 10005
}
///
///
///
[Flags]
public enum TransactionFlags : byte
{
///
None = 0,
///
SourceGroup = 1,
///
DestGroup = 2,
///
OwnerGroup = 4,
///
SimultaneousContribution = 8,
///
ContributionRemoval = 16
}
///
///
///
public enum MeanCollisionType : byte
{
///
None,
///
Bump,
///
LLPushObject,
///
SelectedObjectCollide,
///
ScriptedObjectCollide,
///
PhysicalObjectCollide
}
///
/// Flags sent when a script takes or releases a control
///
/// NOTE: (need to verify) These might be a subset of the ControlFlags enum in Movement,
[Flags]
public enum ScriptControlChange : uint
{
/// No Flags set
None = 0,
/// Forward (W or up Arrow)
Forward = 1,
/// Back (S or down arrow)
Back = 2,
/// Move left (shift+A or left arrow)
Left = 4,
/// Move right (shift+D or right arrow)
Right = 8,
/// Up (E or PgUp)
Up = 16,
/// Down (C or PgDown
Down = 32,
/// Rotate left (A or left arrow)
RotateLeft = 256,
/// Rotate right (D or right arrow)
RotateRight = 512,
/// Left Mouse Button
LeftButton = 268435456,
/// Left Mouse button in MouseLook
MouseLookLeftButton = 1073741824
}
#endregion Enums
#region Structs
///
/// Instant Message
///
public struct InstantMessage
{
/// Key of sender
public LLUUID FromAgentID;
/// Name of sender
public string FromAgentName;
/// Key of destination avatar
public LLUUID ToAgentID;
/// ID of originating estate
public uint ParentEstateID;
/// Key of originating region
public LLUUID RegionID;
/// Coordinates in originating region
public LLVector3 Position;
/// Instant message type
public InstantMessageDialog Dialog;
/// Group IM session toggle
public bool GroupIM;
/// Key of IM session
public LLUUID IMSessionID;
/// Timestamp of the instant message
public DateTime Timestamp;
/// Instant message text
public string Message;
/// Whether this message is held for offline avatars
public InstantMessageOnline Offline;
/// Context specific packed data
public byte[] BinaryBucket;
//Print the contents of a message
public override string ToString(){
string result="";
Type imType = this.GetType();
FieldInfo[] fields = imType.GetFields();
foreach (FieldInfo field in fields){
result += (field.Name + " = " + field.GetValue(this) );
}
return result;
}
}
#endregion Structs
///
/// Manager class for our own avatar
///
/// This class is instantianted automatically by the SecondLife class.
public partial class AgentManager
{
#region Enums
///
/// Currently only used to hide your group title
///
[Flags]
public enum AgentFlags : byte
{
/// No flags set
None = 0,
/// Hide your group title
HideTitle = 0x01,
}
///
/// Action state of the avatar, which can currently be typing and
/// editing
///
[Flags]
public enum AgentState : byte
{
///
None = 0x00,
///
Typing = 0x04,
///
Editing = 0x10
}
///
/// Current teleport status
///
public enum TeleportStatus
{
/// Unknown status
None,
/// Teleport initialized
Start,
/// Teleport in progress
Progress,
/// Teleport failed
Failed,
/// Teleport completed
Finished,
/// Teleport cancelled
Cancelled
}
///
///
///
[Flags]
public enum TeleportFlags : uint
{
///
Default = 0,
///
SetHomeToTarget = 1 << 0,
///
SetLastToTarget = 1 << 1,
///
ViaLure = 1 << 2,
///
ViaLandmark = 1 << 3,
///
ViaLocation = 1 << 4,
///
ViaHome = 1 << 5,
///
ViaTelehub = 1 << 6,
///
ViaLogin = 1 << 7,
///
ViaGodlikeLure = 1 << 8,
///
Godlike = 1 << 9,
///
NineOneOne = 1 << 10,
///
DisableCancel = 1 << 11,
///
ViaRegionID = 1 << 12,
///
IsFlying = 1 << 13
}
///
///
///
[Flags]
public enum TeleportLureFlags
{
///
NormalLure = 0,
///
GodlikeLure = 1,
///
GodlikePursuit = 2
}
#endregion
#region Callbacks & Events
///
/// Triggered on incoming chat messages
///
/// Text of chat message
/// Audible level of this chat message
/// Type of chat (whisper, shout, status, etc.)
/// Source of the chat message
/// Name of the sending object
/// Key of source
/// Key of the sender
/// Senders position
public delegate void ChatCallback(string message, ChatAudibleLevel audible, ChatType type,
ChatSourceType sourceType, string fromName, LLUUID id, LLUUID ownerid, LLVector3 position);
///
/// Triggered when a script pops up a dialog box
///
/// The dialog box message
/// Name of the object that sent the dialog
/// Image to be displayed in the dialog
/// ID of the object that sent the dialog
/// First name of the object owner
/// Last name of the object owner
/// Chat channel that the object is communicating on
/// List of button labels
public delegate void ScriptDialogCallback(string message, string objectName, LLUUID imageID,
LLUUID objectID, string firstName, string lastName, int chatChannel, List buttons);
///
/// Triggered when a script asks for permissions
///
/// Task ID of the script requesting permissions
/// ID of the object containing the script
/// Name of the object containing the script
/// Name of the object's owner
/// Bitwise value representing the requested permissions
public delegate void ScriptQuestionCallback(Simulator simulator, LLUUID taskID, LLUUID itemID, string objectName, string objectOwner, ScriptPermission questions);
///
/// Triggered when a script displays a URL via llLoadURL
///
/// Name of the scripted object
/// ID of the scripted object
/// ID of the object's owner
/// Whether or not ownerID is a group
/// Message displayed along with URL
/// Offered URL
public delegate void LoadURLCallback( string objectName, LLUUID objectID, LLUUID ownerID, bool ownerIsGroup, string message, string URL);
///
/// Triggered when the L$ account balance for this avatar changes
///
/// The new account balance
public delegate void BalanceCallback(int balance);
///
/// Triggered on Money Balance Reply
///
/// ID provided in Request Money Balance, or auto-generated by system events
/// Was the transaction successful
/// Current balance
/// Land use credits you have
/// Tier committed to group(s)
/// Description of the transaction
public delegate void MoneyBalanceReplyCallback(LLUUID transactionID, bool transactionSuccess, int balance, int metersCredit, int metersCommitted, string description);
///
/// Triggered on incoming instant messages
///
/// Instant message data structure
/// Simulator where this IM was received from
public delegate void InstantMessageCallback(InstantMessage im, Simulator simulator);
///
/// Triggered for any status updates of a teleport (progress, failed, succeeded)
///
/// A message about the current teleport status
/// The current status of the teleport
/// Various flags describing the teleport
public delegate void TeleportCallback(string message, TeleportStatus status, TeleportFlags flags);
///
/// Reply to a request to join a group, informs whether it was successful or not
///
/// The group we attempted to join
/// Whether we joined the group or not
public delegate void JoinGroupCallback(LLUUID groupID, bool success);
///
/// Reply to a request to leave a group, informs whether it was successful or not
///
/// The group we attempted to leave
/// Whether we left the group or not
public delegate void LeaveGroupCallback(LLUUID groupID, bool success);
///
/// Informs the avatar that it is no longer a member of a group
///
/// The group Key we are no longer a member of
public delegate void GroupDroppedCallback(LLUUID groupID);
///
/// Reply to an AgentData request
///
/// First name of Avatar
/// Last name of Avatar
/// Key of Group Avatar has active
/// Avatars Active Title
/// Powers Avatar has in group
/// Name of the Group
public delegate void AgentDataCallback(string firstName, string lastName, LLUUID activeGroupID,
string groupTitle, GroupPowers groupPowers, string groupName);
///
/// Triggered when the current agent animations change
///
/// A convenience reference to the
/// SignaledAnimations collection
public delegate void AnimationsChangedCallback(InternalDictionary agentAnimations);
///
/// Triggered when an object or avatar forcefully collides with our
/// agent
///
/// Collision type
/// Colliding object or avatar ID
/// Victim ID, should be our own AgentID
/// Velocity or total force of the collision
/// Time the collision occurred
public delegate void MeanCollisionCallback(MeanCollisionType type, LLUUID perp, LLUUID victim,
float magnitude, DateTime time);
///
/// Triggered when the agent physically moves in to a neighboring region
///
/// Simulator agent was previously occupying
/// Simulator agent is now currently occupying
public delegate void RegionCrossedCallback(Simulator oldSim, Simulator newSim);
///
/// Fired when group chat session confirmed joined
/// Key of Session (groups UUID)
/// Temporary session Key
/// if session start successful,
/// otherwise
public delegate void GroupChatJoined(LLUUID groupChatSessionID, LLUUID tmpSessionID, bool success);
/// Fired when agent group chat session terminated
/// Key of Session (groups UUID)
public delegate void GroupChatLeft(LLUUID groupchatSessionID);
///
/// Fired when alert message received from simulator
///
/// the message sent from the grid to our avatar.
public delegate void AlertMessage(string message);
///
/// Fired when a script wants to give or release controls.
///
/// Control to give or take
/// true of passing control to agent
/// true of taking control from agent
public delegate void ScriptControlCallback(ScriptControlChange controls, bool pass, bool take);
///
/// Fired when camera tries to view beyond its view limits
///
/// LLVector4 representing plane where constraints were hit
public delegate void CameraConstraintCallback(LLVector4 collidePlane);
/// Callback for incoming chat packets
public event ChatCallback OnChat;
/// Callback for pop-up dialogs from scripts
public event ScriptDialogCallback OnScriptDialog;
/// Callback for pop-up dialogs regarding permissions
public event ScriptQuestionCallback OnScriptQuestion;
/// Callback for URL popups
public event LoadURLCallback OnLoadURL;
/// Callback for incoming IMs
public event InstantMessageCallback OnInstantMessage;
/// Callback for Teleport request update
public event TeleportCallback OnTeleport;
/// Callback for incoming change in L$ balance
public event BalanceCallback OnBalanceUpdated;
/// Callback for incoming Money Balance Replies
public event MoneyBalanceReplyCallback OnMoneyBalanceReplyReceived;
/// Callback for agent data updates, such as the active
/// group changing
public event AgentDataCallback OnAgentDataUpdated;
/// Callback for the current agent animations changing
public event AnimationsChangedCallback OnAnimationsChanged;
/// Callback for an object or avatar forcefully colliding
/// with the agent
public event MeanCollisionCallback OnMeanCollision;
/// Callback for the agent moving in to a neighboring sim
public event RegionCrossedCallback OnRegionCrossed;
/// Callback for when agent is confirmed joined group chat session.
public event GroupChatJoined OnGroupChatJoin;
/// Callback for when agent is confirmed to have left group chat session.
public event GroupChatLeft OnGroupChatLeft;
/// Alert messages sent to client from simulator
public event AlertMessage OnAlertMessage;
/// Fired when a script wants to take or release control of your avatar.
public event ScriptControlCallback OnScriptControlChange;
/// Fired when our avatar camera reaches the maximum possible point
public event CameraConstraintCallback OnCameraConstraint;
#endregion
/// Reference to the SecondLife client object
public readonly SecondLife Client;
/// Used for movement and camera tracking
public readonly AgentMovement Movement;
/// Currently playing animations for the agent. Can be used to
/// check the current movement status such as walking, hovering, aiming,
/// etc. by checking for system animations in the Animations
/// class
public InternalDictionary SignaledAnimations = new InternalDictionary();
///
/// Dictionary containing current Group Chat sessions and members
///
public InternalDictionary> GroupChatSessions = new InternalDictionary>();
#region Properties
/// Your (client) avatars
/// "client", "agent", and "avatar" all represent the same thing
public LLUUID AgentID { get { return id; } }
/// Temporary assigned to this session, used for
/// verifying our identity in packets
public LLUUID SessionID { get { return sessionID; } }
/// Shared secret that is never sent over the wire
public LLUUID SecureSessionID { get { return secureSessionID; } }
/// Your (client) avatar ID, local to the current region/sim
public uint LocalID { get { return localID; } }
/// Where the avatar started at login. Can be "last", "home"
/// or a login
public string StartLocation { get { return startLocation; } }
/// The access level of this agent, usually M or PG
public string AgentAccess { get { return agentAccess; } }
///
public LLVector4 CollisionPlane { get { return collisionPlane; } }
/// An representing the velocity of our agent
public LLVector3 Velocity { get { return velocity; } }
/// An representing the acceleration of our agent
public LLVector3 Acceleration { get { return acceleration; } }
///
public LLVector3 AngularVelocity { get { return angularVelocity; } }
/// Position avatar client will goto when login to 'home' or during
/// teleport request to 'home' region.
public LLVector3 HomePosition { get { return homePosition; } }
/// LookAt point saved/restored with HomePosition
public LLVector3 HomeLookAt { get { return homeLookAt; } }
/// Avatar First Name (i.e. Philip)
public string FirstName { get { return firstName; } }
/// Avatar Last Name (i.e. Linden)
public string LastName { get { return lastName; } }
/// Avatar Full Name (i.e. Philip Linden)
public string Name
{
get
{
// This is a fairly common request, so assume the name doesn't
// change mid-session and cache the result
if (fullName == null)
fullName = String.Format("{0} {1}", firstName, lastName);
return fullName;
}
}
/// Gets the health of the agent
public float Health { get { return health; } }
/// Gets the current balance of the agent
public int Balance { get { return balance; } }
/// Gets the local ID of the prim the agent is sitting on,
/// zero if the avatar is not currently sitting
public uint SittingOn { get { return sittingOn; } }
/// Gets the of the agents active group.
public LLUUID ActiveGroup { get { return activeGroup; } }
/// Current status message for teleporting
public string TeleportMessage { get { return teleportMessage; } }
/// Current position of the agent as a relative offset from
/// the simulator, or the parent object if we are sitting on something
public LLVector3 RelativePosition { get { return relativePosition; } }
/// Current rotation of the agent as a relative rotation from
/// the simulator, or the parent object if we are sitting on something
public LLQuaternion RelativeRotation { get { return relativeRotation; } }
/// Current position of the agent in the simulator
public LLVector3 SimPosition
{
get
{
if (sittingOn != 0)
{
Primitive parent;
if(Client.Network.CurrentSim != null && Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(sittingOn, out parent))
{
return parent.Position + relativePosition;
}
else
{
Client.Log("Currently sitting on object " + sittingOn + " which is not tracked, SimPosition will be inaccurate",
Helpers.LogLevel.Warning);
return relativePosition;
}
}
else
{
return relativePosition;
}
}
}
///
/// A representing the agents current rotation
///
public LLQuaternion SimRotation
{
get
{
if (sittingOn != 0)
{
Primitive parent;
if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(sittingOn, out parent))
{
return relativeRotation * parent.Rotation;
}
else
{
Client.Log("Currently sitting on object " + sittingOn + " which is not tracked, SimRotation will be inaccurate",
Helpers.LogLevel.Warning);
return relativeRotation;
}
}
else
{
return relativeRotation;
}
}
}
/// Returns the global grid position of the avatar
public LLVector3d GlobalPosition
{
get
{
if (Client.Network.CurrentSim != null)
{
uint globalX, globalY;
Helpers.LongToUInts(Client.Network.CurrentSim.Handle, out globalX, out globalY);
LLVector3 pos = SimPosition;
return new LLVector3d(
(double)globalX + (double)pos.X,
(double)globalY + (double)pos.Y,
(double)pos.Z);
}
else
return LLVector3d.Zero;
}
}
/// The Position field has been replaced by RelativePosition, SimPosition, and GlobalPosition
[Obsolete("Position has been replaced by RelativePosition, SimPosition, and GlobalPosition")]
public LLVector3 Position { get { return SimPosition; } }
/// The Rotation field has been replaced by RelativeRotation and SimRotation
[Obsolete("Rotation has been replaced by RelativeRotation and SimRotation")]
public LLQuaternion Rotation { get { return SimRotation; } }
#endregion Properties
internal uint localID;
internal LLVector3 relativePosition;
internal LLQuaternion relativeRotation = LLQuaternion.Identity;
internal LLVector4 collisionPlane;
internal LLVector3 velocity;
internal LLVector3 acceleration;
internal LLVector3 angularVelocity;
internal uint sittingOn;
internal int lastInterpolation;
#region Private Members
private LLUUID id;
private LLUUID sessionID;
private LLUUID secureSessionID;
private string startLocation = String.Empty;
private string agentAccess = String.Empty;
private LLVector3 homePosition;
private LLVector3 homeLookAt;
private string firstName = String.Empty;
private string lastName = String.Empty;
private string fullName;
private string teleportMessage = String.Empty;
private TeleportStatus teleportStat = TeleportStatus.None;
private ManualResetEvent teleportEvent = new ManualResetEvent(false);
private uint heightWidthGenCounter;
private float health;
private int balance;
private LLUUID activeGroup;
#endregion Private Members
///
/// Constructor, setup callbacks for packets related to our avatar
///
/// A reference to the Class
public AgentManager(SecondLife client)
{
Client = client;
Movement = new AgentMovement(Client);
NetworkManager.PacketCallback callback;
Client.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected);
// Teleport callbacks
callback = new NetworkManager.PacketCallback(TeleportHandler);
Client.Network.RegisterCallback(PacketType.TeleportStart, callback);
Client.Network.RegisterCallback(PacketType.TeleportProgress, callback);
Client.Network.RegisterCallback(PacketType.TeleportFailed, callback);
Client.Network.RegisterCallback(PacketType.TeleportFinish, callback);
Client.Network.RegisterCallback(PacketType.TeleportCancel, callback);
Client.Network.RegisterCallback(PacketType.TeleportLocal, callback);
// Instant message callback
Client.Network.RegisterCallback(PacketType.ImprovedInstantMessage, new NetworkManager.PacketCallback(InstantMessageHandler));
// Chat callback
Client.Network.RegisterCallback(PacketType.ChatFromSimulator, new NetworkManager.PacketCallback(ChatHandler));
// Script dialog callback
Client.Network.RegisterCallback(PacketType.ScriptDialog, new NetworkManager.PacketCallback(ScriptDialogHandler));
// Script question callback
Client.Network.RegisterCallback(PacketType.ScriptQuestion, new NetworkManager.PacketCallback(ScriptQuestionHandler));
// Script URL callback
Client.Network.RegisterCallback(PacketType.LoadURL, new NetworkManager.PacketCallback(LoadURLHandler));
// Movement complete callback
Client.Network.RegisterCallback(PacketType.AgentMovementComplete, new NetworkManager.PacketCallback(MovementCompleteHandler));
// Health callback
Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler));
// Money callback
Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, new NetworkManager.PacketCallback(BalanceHandler));
//Agent update callback
Client.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(AgentDataUpdateHandler));
// Animation callback
Client.Network.RegisterCallback(PacketType.AvatarAnimation, new NetworkManager.PacketCallback(AvatarAnimationHandler));
// Object colliding into our agent callback
Client.Network.RegisterCallback(PacketType.MeanCollisionAlert, new NetworkManager.PacketCallback(MeanCollisionAlertHandler));
// Region Crossing
Client.Network.RegisterCallback(PacketType.CrossedRegion, new NetworkManager.PacketCallback(CrossedRegionHandler));
// CAPS callbacks
Client.Network.RegisterEventCallback("EstablishAgentCommunication", new Caps.EventQueueCallback(EstablishAgentCommunicationEventHandler));
// Incoming Group Chat
Client.Network.RegisterEventCallback("ChatterBoxInvitation", new Caps.EventQueueCallback(ChatterBoxInvitationHandler));
// Outgoing Group Chat Reply
Client.Network.RegisterEventCallback("ChatterBoxSessionEventReply", new Caps.EventQueueCallback(ChatterBoxSessionEventHandler));
Client.Network.RegisterEventCallback("ChatterBoxSessionStartReply", new Caps.EventQueueCallback(ChatterBoxSessionStartReplyHandler));
Client.Network.RegisterEventCallback("ChatterBoxSessionAgentListUpdates", new Caps.EventQueueCallback(ChatterBoxSessionAgentListReplyHandler));
// Login
Client.Network.RegisterLoginResponseCallback(new NetworkManager.LoginResponseCallback(Network_OnLoginResponse));
// Alert Messages
Client.Network.RegisterCallback(PacketType.AlertMessage, new NetworkManager.PacketCallback(AlertMessageHandler));
// script control change messages, ie: when an in-world LSL script wants to take control of your agent.
Client.Network.RegisterCallback(PacketType.ScriptControlChange, new NetworkManager.PacketCallback(ScriptControlChangeHandler));
// Camera Constraint (probably needs to move to AgentManagerCamera TODO:
Client.Network.RegisterCallback(PacketType.CameraConstraint, new NetworkManager.PacketCallback(CameraConstraintHandler));
}
#region Chat and instant messages
///
/// Send a chat message
///
/// The Message you're sending out.
/// Channel number (0 would be default 'Say' message, other numbers
/// denote the equivalent of /# in normal client).
/// Chat Type, see above.
public void Chat(string message, int channel, ChatType type)
{
ChatFromViewerPacket chat = new ChatFromViewerPacket();
chat.AgentData.AgentID = this.id;
chat.AgentData.SessionID = Client.Self.SessionID;
chat.ChatData.Channel = channel;
chat.ChatData.Message = Helpers.StringToField(message);
chat.ChatData.Type = (byte)type;
Client.Network.SendPacket(chat);
}
/// Requests missed/offline messages
public void RetrieveInstantMessages()
{
RetrieveInstantMessagesPacket p = new RetrieveInstantMessagesPacket();
p.AgentData.AgentID = Client.Self.AgentID;
p.AgentData.SessionID = Client.Self.SessionID;
Client.Network.SendPacket(p);
}
///
/// Send an Instant Message
///
/// Target of the Instant Message
/// Text message being sent
public void InstantMessage(LLUUID target, string message)
{
InstantMessage(Name, target, message, AgentID.Equals(target) ? AgentID : target ^ AgentID,
InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.SimPosition,
LLUUID.Zero, new byte[0]);
}
///
/// Send an Instant Message
///
/// Target of the Instant Message
/// Text message being sent
/// IM session ID (to differentiate between IM windows)
public void InstantMessage(LLUUID target, string message, LLUUID imSessionID)
{
InstantMessage(Name, target, message, imSessionID,
InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.SimPosition,
LLUUID.Zero, new byte[0]);
}
///
/// Send an Instant Message
///
/// The name this IM will show up as being from
/// Key of Avatar
/// Text message being sent
/// IM session ID (to differentiate between IM windows)
/// IDs of sessions for a conference
public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID,
LLUUID[] conferenceIDs)
{
byte[] binaryBucket;
if (conferenceIDs != null && conferenceIDs.Length > 0)
{
binaryBucket = new byte[16 * conferenceIDs.Length];
for (int i = 0; i < conferenceIDs.Length; ++i)
Buffer.BlockCopy(conferenceIDs[i].GetBytes(), 0, binaryBucket, i * 16, 16);
}
else
{
binaryBucket = new byte[0];
}
InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent,
InstantMessageOnline.Offline, LLVector3.Zero, LLUUID.Zero, binaryBucket);
}
///
/// Send an Instant Message
///
/// The name this IM will show up as being from
/// Key of Avatar
/// Text message being sent
/// IM session ID (to differentiate between IM windows)
/// Type of instant message to send
/// Whether to IM offline avatars as well
/// Senders Position
/// RegionID Sender is In
/// Packed binary data that is specific to
/// the dialog type
public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID,
InstantMessageDialog dialog, InstantMessageOnline offline, LLVector3 position, LLUUID regionID,
byte[] binaryBucket)
{
if (target != LLUUID.Zero)
{
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
if (imSessionID.Equals(LLUUID.Zero) || imSessionID.Equals(AgentID))
imSessionID = AgentID.Equals(target) ? AgentID : target ^ AgentID;
im.AgentData.AgentID = Client.Self.AgentID;
im.AgentData.SessionID = Client.Self.SessionID;
im.MessageBlock.Dialog = (byte)dialog;
im.MessageBlock.FromAgentName = Helpers.StringToField(fromName);
im.MessageBlock.FromGroup = false;
im.MessageBlock.ID = imSessionID;
im.MessageBlock.Message = Helpers.StringToField(message);
im.MessageBlock.Offline = (byte)offline;
im.MessageBlock.ToAgentID = target;
if (binaryBucket != null)
im.MessageBlock.BinaryBucket = binaryBucket;
else
im.MessageBlock.BinaryBucket = new byte[0];
// These fields are mandatory, even if we don't have valid values for them
im.MessageBlock.Position = LLVector3.Zero;
//TODO: Allow region id to be correctly set by caller or fetched from Client.*
im.MessageBlock.RegionID = regionID;
// Send the message
Client.Network.SendPacket(im);
}
else
{
Client.Log(String.Format("Suppressing instant message \"{0}\" to LLUUID.Zero", message),
Helpers.LogLevel.Error);
}
}
///
/// Send an Instant Message to a group
///
/// Key of Group
/// Text Message being sent.
public void InstantMessageGroup(LLUUID groupUUID, string message)
{
InstantMessageGroup(Name, groupUUID, message);
}
///
/// Send an Instant Message to a group
///
/// The name this IM will show up as being from
/// Key of the group
/// Text message being sent
/// This does not appear to function with groups the agent is not in
public void InstantMessageGroup(string fromName, LLUUID groupUUID, string message)
{
lock (GroupChatSessions.Dictionary)
if (GroupChatSessions.ContainsKey(groupUUID))
{
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
im.AgentData.AgentID = Client.Self.AgentID;
im.AgentData.SessionID = Client.Self.SessionID;
im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionSend;
im.MessageBlock.FromAgentName = Helpers.StringToField(fromName);
im.MessageBlock.FromGroup = false;
im.MessageBlock.Message = Helpers.StringToField(message);
im.MessageBlock.Offline = 0;
im.MessageBlock.ID = groupUUID;
im.MessageBlock.ToAgentID = groupUUID;
im.MessageBlock.Position = LLVector3.Zero;
im.MessageBlock.RegionID = LLUUID.Zero;
im.MessageBlock.BinaryBucket = Helpers.StringToField("\0");
Client.Network.SendPacket(im);
}
else
{
Client.Log("No Active group chat session appears to exist, use RequestJoinGroupChat() to join one",
Helpers.LogLevel.Error);
}
}
///
/// Send a request to join a group chat session
///
/// UUID of Group
public void RequestJoinGroupChat(LLUUID groupUUID)
{
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
im.AgentData.AgentID = Client.Self.AgentID;
im.AgentData.SessionID = Client.Self.SessionID;
im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionGroupStart;
im.MessageBlock.FromAgentName = Helpers.StringToField(Client.Self.Name);
im.MessageBlock.FromGroup = false;
im.MessageBlock.Message = new byte[0];
im.MessageBlock.Offline = 0;
im.MessageBlock.ID = groupUUID;
im.MessageBlock.ToAgentID = groupUUID;
im.MessageBlock.BinaryBucket = new byte[0];
im.MessageBlock.Position = LLVector3.Zero;
im.MessageBlock.RegionID = LLUUID.Zero;
Client.Network.SendPacket(im);
}
///
/// Request self terminates group chat. This will stop Group IM's from showing up
/// until session is rejoined or expires.
///
/// UUID of Group
public void RequestLeaveGroupChat(LLUUID groupUUID)
{
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
im.AgentData.AgentID = Client.Self.AgentID;
im.AgentData.SessionID = Client.Self.SessionID;
im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionDrop;
im.MessageBlock.FromAgentName = Helpers.StringToField(Client.Self.Name);
im.MessageBlock.FromGroup = false;
im.MessageBlock.Message = new byte[0];
im.MessageBlock.Offline = 0;
im.MessageBlock.ID = groupUUID;
im.MessageBlock.ToAgentID = groupUUID;
im.MessageBlock.BinaryBucket = new byte[0];
im.MessageBlock.Position = LLVector3.Zero;
im.MessageBlock.RegionID = LLUUID.Zero;
Client.Network.SendPacket(im);
}
///
/// Reply to script dialog questions.
///
/// Channel initial request came on
/// Index of button you're "clicking"
/// Label of button you're "clicking"
/// UUID of Object that sent the request
///
public void ReplyToScriptDialog(int channel, int buttonIndex, string buttonlabel, LLUUID objectID)
{
ScriptDialogReplyPacket reply = new ScriptDialogReplyPacket();
reply.AgentData.AgentID = Client.Self.AgentID;
reply.AgentData.SessionID = Client.Self.SessionID;
reply.Data.ButtonIndex = buttonIndex;
reply.Data.ButtonLabel = Helpers.StringToField(buttonlabel);
reply.Data.ChatChannel = channel;
reply.Data.ObjectID = objectID;
Client.Network.SendPacket(reply);
}
#endregion Chat and instant messages
#region Viewer Effects
///
/// Start a particle stream between an agent and an object
///
/// Key of the source agent
/// Key of the target object
///
///
///
public void PointAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, PointAtType type,
LLUUID effectID)
{
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = Client.Self.AgentID;
effect.AgentData.SessionID = Client.Self.SessionID;
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
effect.Effect[0].AgentID = Client.Self.AgentID;
effect.Effect[0].Color = new byte[4];
effect.Effect[0].Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f;
effect.Effect[0].ID = effectID;
effect.Effect[0].Type = (byte)EffectType.PointAt;
byte[] typeData = new byte[57];
if (sourceAvatar != LLUUID.Zero)
Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16);
if (targetObject != LLUUID.Zero)
Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16);
Buffer.BlockCopy(globalOffset.GetBytes(), 0, typeData, 32, 24);
typeData[56] = (byte)type;
effect.Effect[0].TypeData = typeData;
Client.Network.SendPacket(effect);
}
///
/// Start a particle stream between an agent and an object
///
/// Key of the source agent
/// Key of the target object
///
///
///
public void LookAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LookAtType type,
LLUUID effectID)
{
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = Client.Self.AgentID;
effect.AgentData.SessionID = Client.Self.SessionID;
float duration;
switch (type)
{
case LookAtType.Clear:
duration = 0.0f;
break;
case LookAtType.Hover:
duration = 1.0f;
break;
case LookAtType.FreeLook:
duration = 2.0f;
break;
case LookAtType.Idle:
duration = 3.0f;
break;
case LookAtType.AutoListen:
case LookAtType.Respond:
duration = 4.0f;
break;
case LookAtType.None:
case LookAtType.Select:
case LookAtType.Focus:
case LookAtType.Mouselook:
duration = Single.MaxValue / 2.0f;
break;
default:
duration = 0.0f;
break;
}
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
effect.Effect[0].AgentID = Client.Self.AgentID;
effect.Effect[0].Color = new byte[4];
effect.Effect[0].Duration = duration;
effect.Effect[0].ID = effectID;
effect.Effect[0].Type = (byte)EffectType.LookAt;
byte[] typeData = new byte[57];
if (sourceAvatar != LLUUID.Zero)
Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16);
if (targetObject != LLUUID.Zero)
Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16);
typeData[56] = (byte)type;
effect.Effect[0].TypeData = typeData;
Client.Network.SendPacket(effect);
}
///
///
///
///
///
///
///
///
///
public void BeamEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LLColor color,
float duration, LLUUID effectID)
{
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = Client.Self.AgentID;
effect.AgentData.SessionID = Client.Self.SessionID;
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
effect.Effect[0].AgentID = Client.Self.AgentID;
effect.Effect[0].Color = color.GetFloatBytes();
effect.Effect[0].Duration = duration;
effect.Effect[0].ID = effectID;
effect.Effect[0].Type = (byte)EffectType.Beam;
byte[] typeData = new byte[56];
Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16);
Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16);
Buffer.BlockCopy(globalOffset.GetBytes(), 0, typeData, 32, 24);
effect.Effect[0].TypeData = typeData;
Client.Network.SendPacket(effect);
}
#endregion Viewer Effects
#region Movement Actions
///
/// Sends a request to sit on the specified object
///
/// LLUUID of the object to sit on
/// Sit at offset
public void RequestSit(LLUUID targetID, LLVector3 offset)
{
AgentRequestSitPacket requestSit = new AgentRequestSitPacket();
requestSit.AgentData.AgentID = Client.Self.AgentID;
requestSit.AgentData.SessionID = Client.Self.SessionID;
requestSit.TargetObject.TargetID = targetID;
requestSit.TargetObject.Offset = offset;
Client.Network.SendPacket(requestSit);
}
///
/// Follows a call to RequestSit() to actually sit on the object
///
public void Sit()
{
AgentSitPacket sit = new AgentSitPacket();
sit.AgentData.AgentID = Client.Self.AgentID;
sit.AgentData.SessionID = Client.Self.SessionID;
Client.Network.SendPacket(sit);
}
/// Stands up from sitting on a prim or the ground
public bool Stand()
{
if (Client.Settings.SEND_AGENT_UPDATES)
{
Movement.StandUp = true;
Movement.SendUpdate();
return true;
}
else
{
Client.Log("Attempted Stand but agent updates are disabled", Helpers.LogLevel.Warning);
return false;
}
}
///
/// Does a "ground sit" at the avatar's current position
///
public void SitOnGround()
{
Movement.SitOnGround = true;
Movement.SendUpdate(true);
}
///
/// Starts or stops flying
///
/// True to start flying, false to stop flying
public void Fly(bool start)
{
if (start)
Movement.Fly = true;
else
Movement.Fly = false;
Movement.SendUpdate(true);
}
///
/// Starts or stops crouching
///
/// True to start crouching, false to stop crouching
public void Crouch(bool start)
{
if (start)
Movement.UpNeg = true;
else
Movement.UpNeg = false;
Movement.SendUpdate(true);
}
///
/// Starts a jump (begin holding the jump key)
///
public void Jump()
{
Movement.UpPos = true;
Movement.FastUp = true;
Movement.SendUpdate(true);
}
///
/// Use the autopilot sim function to move the avatar to a new
/// position. Uses double precision to get precise movements
///
/// The z value is currently not handled properly by the simulator
/// Global X coordinate to move to
/// Global Y coordinate to move to
/// Z coordinate to move to
public void AutoPilot(double globalX, double globalY, double z)
{
GenericMessagePacket autopilot = new GenericMessagePacket();
autopilot.AgentData.AgentID = Client.Self.AgentID;
autopilot.AgentData.SessionID = Client.Self.SessionID;
autopilot.AgentData.TransactionID = LLUUID.Zero;
autopilot.MethodData.Invoice = LLUUID.Zero;
autopilot.MethodData.Method = Helpers.StringToField("autopilot");
autopilot.ParamList = new GenericMessagePacket.ParamListBlock[3];
autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[0].Parameter = Helpers.StringToField(globalX.ToString());
autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[1].Parameter = Helpers.StringToField(globalY.ToString());
autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[2].Parameter = Helpers.StringToField(z.ToString());
Client.Network.SendPacket(autopilot);
}
///
/// Use the autopilot sim function to move the avatar to a new position
///
/// The z value is currently not handled properly by the simulator
/// Integer value for the global X coordinate to move to
/// Integer value for the global Y coordinate to move to
/// Floating-point value for the Z coordinate to move to
public void AutoPilot(ulong globalX, ulong globalY, float z)
{
GenericMessagePacket autopilot = new GenericMessagePacket();
autopilot.AgentData.AgentID = Client.Self.AgentID;
autopilot.AgentData.SessionID = Client.Self.SessionID;
autopilot.AgentData.TransactionID = LLUUID.Zero;
autopilot.MethodData.Invoice = LLUUID.Zero;
autopilot.MethodData.Method = Helpers.StringToField("autopilot");
autopilot.ParamList = new GenericMessagePacket.ParamListBlock[3];
autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[0].Parameter = Helpers.StringToField(globalX.ToString());
autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[1].Parameter = Helpers.StringToField(globalY.ToString());
autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[2].Parameter = Helpers.StringToField(z.ToString());
Client.Network.SendPacket(autopilot);
}
///
/// Use the autopilot sim function to move the avatar to a new position
///
/// The z value is currently not handled properly by the simulator
/// Integer value for the local X coordinate to move to
/// Integer value for the local Y coordinate to move to
/// Floating-point value for the Z coordinate to move to
public void AutoPilotLocal(int localX, int localY, float z)
{
uint x, y;
Helpers.LongToUInts(Client.Network.CurrentSim.Handle, out x, out y);
AutoPilot((ulong)(x + localX), (ulong)(y + localY), z);
}
/// Cancels autopilot sim function
/// Not certain if this is how it is really done
public bool AutoPilotCancel()
{
if (Client.Settings.SEND_AGENT_UPDATES)
{
Movement.AtPos = true;
Movement.SendUpdate();
Movement.AtPos = false;
Movement.SendUpdate();
return true;
}
else
{
Client.Log("Attempted AutoPilotCancel but agent updates are disabled", Helpers.LogLevel.Warning);
return false;
}
}
#endregion Movement actions
#region Touch and grab
///
/// Grabs an object
///
/// Local ID of Object to grab
public void Grab(uint objectLocalID)
{
ObjectGrabPacket grab = new ObjectGrabPacket();
grab.AgentData.AgentID = Client.Self.AgentID;
grab.AgentData.SessionID = Client.Self.SessionID;
grab.ObjectData.LocalID = objectLocalID;
grab.ObjectData.GrabOffset = new LLVector3(0, 0, 0);
Client.Network.SendPacket(grab);
}
///
/// Drags on an object
///
/// LLUUID of the object to drag
/// Drag target in region coordinates
public void GrabUpdate(LLUUID objectID, LLVector3 grabPosition)
{
ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket();
grab.AgentData.AgentID = Client.Self.AgentID;
grab.AgentData.SessionID = Client.Self.SessionID;
grab.ObjectData.ObjectID = objectID;
grab.ObjectData.GrabOffsetInitial = new LLVector3(0, 0, 0);
grab.ObjectData.GrabPosition = grabPosition;
grab.ObjectData.TimeSinceLast = 0;
Client.Network.SendPacket(grab);
}
///
/// Releases a grabbed object
///
public void DeGrab(uint objectLocalID)
{
ObjectDeGrabPacket degrab = new ObjectDeGrabPacket();
degrab.AgentData.AgentID = Client.Self.AgentID;
degrab.AgentData.SessionID = Client.Self.SessionID;
degrab.ObjectData.LocalID = objectLocalID;
Client.Network.SendPacket(degrab);
}
///
/// Touches an object
///
public void Touch(uint objectLocalID)
{
Client.Self.Grab(objectLocalID);
Client.Self.DeGrab(objectLocalID);
}
#endregion Touch and grab
#region Money
///
/// Request the current L$ balance
///
public void RequestBalance()
{
MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket();
money.AgentData.AgentID = Client.Self.AgentID;
money.AgentData.SessionID = Client.Self.SessionID;
money.MoneyData.TransactionID = LLUUID.Zero;
Client.Network.SendPacket(money);
}
///
/// Give Money to destination Avatar
///
/// UUID of the Target Avatar
/// Amount in L$
public void GiveAvatarMoney(LLUUID target, int amount)
{
GiveMoney(target, amount, String.Empty, MoneyTransactionType.Gift, TransactionFlags.None);
}
///
/// Give Money to destination Avatar
///
/// UUID of the Target Avatar
/// Amount in L$
/// Description that will show up in the
/// recipients transaction history
public void GiveAvatarMoney(LLUUID target, int amount, string description)
{
GiveMoney(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.None);
}
///
/// Give L$ to an object
///
/// object to give money to
/// amount of L$ to give
/// name of object
public void GiveObjectMoney(LLUUID target, int amount, string objectName)
{
GiveMoney(target, amount, objectName, MoneyTransactionType.PayObject, TransactionFlags.None);
}
///
/// Give L$ to a group
///
/// group to give money to
/// amount of L$ to give
public void GiveGroupMoney(LLUUID target, int amount)
{
GiveMoney(target, amount, String.Empty, MoneyTransactionType.Gift, TransactionFlags.DestGroup);
}
///
/// Give L$ to a group
///
/// group to give money to
/// amount of L$ to give
/// description of transaction
public void GiveGroupMoney(LLUUID target, int amount, string description)
{
GiveMoney(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.DestGroup);
}
///
/// Pay texture/animation upload fee
///
public void PayUploadFee()
{
GiveMoney(LLUUID.Zero, Client.Settings.UPLOAD_COST, String.Empty, MoneyTransactionType.UploadCharge,
TransactionFlags.None);
}
///
/// Pay texture/animation upload fee
///
/// description of the transaction
public void PayUploadFee(string description)
{
GiveMoney(LLUUID.Zero, Client.Settings.UPLOAD_COST, description, MoneyTransactionType.UploadCharge,
TransactionFlags.None);
}
///
/// Give Money to destionation Object or Avatar
///
/// UUID of the Target Object/Avatar
/// Amount in L$
/// Reason (Optional normally)
/// The type of transaction
/// Transaction flags, mostly for identifying group
/// transactions
public void GiveMoney(LLUUID target, int amount, string description, MoneyTransactionType type, TransactionFlags flags)
{
MoneyTransferRequestPacket money = new MoneyTransferRequestPacket();
money.AgentData.AgentID = this.id;
money.AgentData.SessionID = Client.Self.SessionID;
money.MoneyData.Description = Helpers.StringToField(description);
money.MoneyData.DestID = target;
money.MoneyData.SourceID = this.id;
money.MoneyData.TransactionType = (int)type;
money.MoneyData.AggregatePermInventory = 0; // This is weird, apparently always set to zero though
money.MoneyData.AggregatePermNextOwner = 0; // This is weird, apparently always set to zero though
money.MoneyData.Flags = (byte)flags;
money.MoneyData.Amount = amount;
Client.Network.SendPacket(money);
}
#endregion Money
#region Animations
///
/// Send an AgentAnimation packet that toggles a single animation on
///
/// The of the animation to start playing
/// Whether to ensure delivery of this packet or not
public void AnimationStart(LLUUID animation, bool reliable)
{
Dictionary animations = new Dictionary();
animations[animation] = true;
Animate(animations, reliable);
}
///
/// Send an AgentAnimation packet that toggles a single animation off
///
/// The of a
/// currently playing animation to stop playing
/// Whether to ensure delivery of this packet or not
public void AnimationStop(LLUUID animation, bool reliable)
{
Dictionary animations = new Dictionary();
animations[animation] = false;
Animate(animations, reliable);
}
///
/// Send an AgentAnimation packet that will toggle animations on or off
///
/// A list of animation s, and whether to
/// turn that animation on or off
/// Whether to ensure delivery of this packet or not
public void Animate(Dictionary animations, bool reliable)
{
AgentAnimationPacket animate = new AgentAnimationPacket();
animate.Header.Reliable = reliable;
animate.AgentData.AgentID = Client.Self.AgentID;
animate.AgentData.SessionID = Client.Self.SessionID;
animate.AnimationList = new AgentAnimationPacket.AnimationListBlock[animations.Count];
int i = 0;
foreach (KeyValuePair animation in animations)
{
animate.AnimationList[i] = new AgentAnimationPacket.AnimationListBlock();
animate.AnimationList[i].AnimID = animation.Key;
animate.AnimationList[i].StartAnim = animation.Value;
i++;
}
Client.Network.SendPacket(animate);
}
#endregion Animations
#region Teleporting
///
/// Teleports agent to their stored home location
///
public bool GoHome()
{
return Teleport(LLUUID.Zero);
}
///
/// Teleport agent to a landmark
///
/// of the landmark to teleport agent to
/// true on success, false on failure
public bool Teleport(LLUUID landmark)
{
teleportStat = TeleportStatus.None;
teleportEvent.Reset();
TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket();
p.Info = new TeleportLandmarkRequestPacket.InfoBlock();
p.Info.AgentID = Client.Self.AgentID;
p.Info.SessionID = Client.Self.SessionID;
p.Info.LandmarkID = landmark;
Client.Network.SendPacket(p);
teleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false);
if (teleportStat == TeleportStatus.None ||
teleportStat == TeleportStatus.Start ||
teleportStat == TeleportStatus.Progress)
{
teleportMessage = "Teleport timed out.";
teleportStat = TeleportStatus.Failed;
}
return (teleportStat == TeleportStatus.Finished);
}
///
/// Attempt to look up a simulator name and teleport to the discovered
/// destination
///
/// Region name to look up
/// Position to teleport to
/// True if the lookup and teleport were successful, otherwise
/// false
public bool Teleport(string simName, LLVector3 position)
{
return Teleport(simName, position, new LLVector3(0, 1.0f, 0));
}
///
/// Attempt to look up a simulator name and teleport to the discovered
/// destination
///
/// Region name to look up
/// Position to teleport to
/// Target to look at
/// True if the lookup and teleport were successful, otherwise
/// false
public bool Teleport(string simName, LLVector3 position, LLVector3 lookAt)
{
teleportStat = TeleportStatus.None;
simName = simName.ToLower();
if (simName != Client.Network.CurrentSim.Name.ToLower())
{
// Teleporting to a foreign sim
GridRegion region;
if (Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out region))
{
return Teleport(region.RegionHandle, position, lookAt);
}
else
{
teleportMessage = "Unable to resolve name: " + simName;
teleportStat = TeleportStatus.Failed;
return false;
}
}
else
{
// Teleporting to the sim we're already in
return Teleport(Client.Network.CurrentSim.Handle, position, lookAt);
}
}
///
/// Teleport agent to another region
///
/// handle of region to teleport agent to
/// position in destination sim to teleport to
/// true on success, false on failure
/// This call is blocking
public bool Teleport(ulong regionHandle, LLVector3 position)
{
return Teleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f));
}
///
/// Teleport agent to another region
///
/// handle of region to teleport agent to
/// position in destination sim to teleport to
/// direction in destination sim agent will look at
/// true on success, false on failure
/// This call is blocking
public bool Teleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt)
{
teleportStat = TeleportStatus.None;
teleportEvent.Reset();
RequestTeleport(regionHandle, position, lookAt);
teleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false);
if (teleportStat == TeleportStatus.None ||
teleportStat == TeleportStatus.Start ||
teleportStat == TeleportStatus.Progress)
{
teleportMessage = "Teleport timed out.";
teleportStat = TeleportStatus.Failed;
}
return (teleportStat == TeleportStatus.Finished);
}
///
/// Request teleport to a another simulator
///
/// handle of region to teleport agent to
/// position in destination sim to teleport to
public void RequestTeleport(ulong regionHandle, LLVector3 position)
{
RequestTeleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f));
}
///
/// Request teleport to a another simulator
///
/// handle of region to teleport agent to
/// position in destination sim to teleport to
/// direction in destination sim agent will look at
public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt)
{
if (Client.Network.CurrentSim != null &&
Client.Network.CurrentSim.Caps != null &&
Client.Network.CurrentSim.Caps.IsEventQueueRunning)
{
TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket();
teleport.AgentData.AgentID = Client.Self.AgentID;
teleport.AgentData.SessionID = Client.Self.SessionID;
teleport.Info.LookAt = lookAt;
teleport.Info.Position = position;
teleport.Info.RegionHandle = regionHandle;
Client.Log("Requesting teleport to region handle " + regionHandle.ToString(), Helpers.LogLevel.Info);
Client.Network.SendPacket(teleport);
}
else
{
teleportMessage = "CAPS event queue is not running";
teleportEvent.Set();
teleportStat = TeleportStatus.Failed;
}
}
///
/// Teleport agent to a landmark
///
/// of the landmark to teleport agent to
public void RequestTeleport(LLUUID landmark)
{
TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket();
p.Info = new TeleportLandmarkRequestPacket.InfoBlock();
p.Info.AgentID = Client.Self.AgentID;
p.Info.SessionID = Client.Self.SessionID;
p.Info.LandmarkID = landmark;
Client.Network.SendPacket(p);
}
///
/// Send a teleport lure to another avatar with default "Join me in ..." invitation message
///
/// target avatars to lure
public void SendTeleportLure(LLUUID targetID)
{
SendTeleportLure(targetID, "Join me in " + Client.Network.CurrentSim.Name + "!");
}
///
/// Send a teleport lure to another avatar with custom invitation message
///
/// target avatars to lure
/// custom message to send with invitation
public void SendTeleportLure(LLUUID targetID, string message)
{
StartLurePacket p = new StartLurePacket();
p.AgentData.AgentID = Client.Self.id;
p.AgentData.SessionID = Client.Self.SessionID;
p.Info.LureType = 0;
p.Info.Message = Helpers.StringToField(message);
p.TargetData = new StartLurePacket.TargetDataBlock[] { new StartLurePacket.TargetDataBlock() };
p.TargetData[0].TargetID = targetID;
Client.Network.SendPacket(p);
}
///
/// Respond to a teleport lure by either accepting it and initiating
/// the teleport, or denying it
///
/// of the avatar sending the lure
/// true to accept the lure, false to decline it
public void TeleportLureRespond(LLUUID requesterID, bool accept)
{
InstantMessage(Name, requesterID, String.Empty, LLUUID.Random(),
accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport,
InstantMessageOnline.Offline, this.SimPosition, LLUUID.Zero, new byte[0]);
if (accept)
{
TeleportLureRequestPacket lure = new TeleportLureRequestPacket();
lure.Info.AgentID = Client.Self.AgentID;
lure.Info.SessionID = Client.Self.SessionID;
lure.Info.LureID = Client.Self.AgentID;
lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure;
Client.Network.SendPacket(lure);
}
}
#endregion Teleporting
#region Misc
///
/// Update agent profile
///
/// struct containing updated
/// profile information
public void UpdateProfile(Avatar.AvatarProperties profile)
{
AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket();
apup.AgentData.AgentID = id;
apup.AgentData.SessionID = sessionID;
apup.PropertiesData.AboutText = Helpers.StringToField(profile.AboutText);
apup.PropertiesData.AllowPublish = profile.AllowPublish;
apup.PropertiesData.FLAboutText = Helpers.StringToField(profile.FirstLifeText);
apup.PropertiesData.FLImageID = profile.FirstLifeImage;
apup.PropertiesData.ImageID = profile.ProfileImage;
apup.PropertiesData.MaturePublish = profile.MaturePublish;
apup.PropertiesData.ProfileURL = Helpers.StringToField(profile.ProfileURL);
Client.Network.SendPacket(apup);
}
///
/// Update agents profile interests
///
/// selection of interests from struct
public void UpdateInterests(Avatar.Interests interests)
{
AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket();
aiup.AgentData.AgentID = id;
aiup.AgentData.SessionID = sessionID;
aiup.PropertiesData.LanguagesText = Helpers.StringToField(interests.LanguagesText);
aiup.PropertiesData.SkillsMask = interests.SkillsMask;
aiup.PropertiesData.SkillsText = Helpers.StringToField(interests.SkillsText);
aiup.PropertiesData.WantToMask = interests.WantToMask;
aiup.PropertiesData.WantToText = Helpers.StringToField(interests.WantToText);
Client.Network.SendPacket(aiup);
}
///
/// Set the height and the width of the client window. This is used
/// by the server to build a virtual camera frustum for our avatar
///
/// New height of the viewer window
/// New width of the viewer window
public void SetHeightWidth(ushort height, ushort width)
{
AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket();
heightwidth.AgentData.AgentID = Client.Self.AgentID;
heightwidth.AgentData.SessionID = Client.Self.SessionID;
heightwidth.AgentData.CircuitCode = Client.Network.CircuitCode;
heightwidth.HeightWidthBlock.Height = height;
heightwidth.HeightWidthBlock.Width = width;
heightwidth.HeightWidthBlock.GenCounter = heightWidthGenCounter++;
Client.Network.SendPacket(heightwidth);
}
///
/// Request the list of muted objects and avatars for this agent
///
public void RequestMuteList()
{
MuteListRequestPacket mute = new MuteListRequestPacket();
mute.AgentData.AgentID = Client.Self.AgentID;
mute.AgentData.SessionID = Client.Self.SessionID;
mute.MuteData.MuteCRC = 0;
Client.Network.SendPacket(mute);
}
///
/// Sets home location to agents current position
///
/// will fire an AlertMessage () with
/// success or failure message
public void SetHome()
{
SetStartLocationRequestPacket s = new SetStartLocationRequestPacket();
s.AgentData = new SetStartLocationRequestPacket.AgentDataBlock();
s.AgentData.AgentID = Client.Self.AgentID;
s.AgentData.SessionID = Client.Self.SessionID;
s.StartLocationData = new SetStartLocationRequestPacket.StartLocationDataBlock();
s.StartLocationData.LocationPos = Client.Self.SimPosition;
s.StartLocationData.LocationID = 1;
s.StartLocationData.SimName = Helpers.StringToField(String.Empty);
s.StartLocationData.LocationLookAt = Movement.Camera.AtAxis;
Client.Network.SendPacket(s);
}
///
/// Move an agent in to a simulator. This packet is the last packet
/// needed to complete the transition in to a new simulator
///
/// Object
public void CompleteAgentMovement(Simulator simulator)
{
CompleteAgentMovementPacket move = new CompleteAgentMovementPacket();
move.AgentData.AgentID = Client.Self.AgentID;
move.AgentData.SessionID = Client.Self.SessionID;
move.AgentData.CircuitCode = Client.Network.CircuitCode;
Client.Network.SendPacket(move, simulator);
}
///
/// Reply to script permissions request
///
/// Object
/// of the itemID requesting permissions
/// of the taskID requesting permissions
/// list of permissions to allow
public void ScriptQuestionReply(Simulator simulator, LLUUID itemID, LLUUID taskID, ScriptPermission permissions)
{
ScriptAnswerYesPacket yes = new ScriptAnswerYesPacket();
yes.AgentData.AgentID = Client.Self.AgentID;
yes.AgentData.SessionID = Client.Self.SessionID;
yes.Data.ItemID = itemID;
yes.Data.TaskID = taskID;
yes.Data.Questions = (int)permissions;
Client.Network.SendPacket(yes, simulator);
}
///
/// Respond to a group invitation by either accepting or denying it
///
/// UUID of the group (sent in the AgentID field of the invite message)
/// IM Session ID from the group invitation message
/// Accept the group invitation or deny it
public void GroupInviteRespond(LLUUID groupID, LLUUID imSessionID, bool accept)
{
InstantMessage(Name, groupID, String.Empty, imSessionID,
accept ? InstantMessageDialog.GroupInvitationAccept : InstantMessageDialog.GroupInvitationDecline,
InstantMessageOnline.Offline, LLVector3.Zero, LLUUID.Zero, new byte[0]);
}
#endregion Misc
#region Packet Handlers
///
/// Take an incoming ImprovedInstantMessage packet, auto-parse, and if
/// OnInstantMessage is defined call that with the appropriate arguments
///
/// Incoming ImprovedInstantMessagePacket
/// Unused
private void InstantMessageHandler(Packet packet, Simulator simulator)
{
if (packet.Type == PacketType.ImprovedInstantMessage)
{
ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)packet;
if (OnInstantMessage != null)
{
InstantMessage message;
message.FromAgentID = im.AgentData.AgentID;
message.FromAgentName = Helpers.FieldToUTF8String(im.MessageBlock.FromAgentName);
message.ToAgentID = im.MessageBlock.ToAgentID;
message.ParentEstateID = im.MessageBlock.ParentEstateID;
message.RegionID = im.MessageBlock.RegionID;
message.Position = im.MessageBlock.Position;
message.Dialog = (InstantMessageDialog)im.MessageBlock.Dialog;
message.GroupIM = im.MessageBlock.FromGroup;
message.IMSessionID = im.MessageBlock.ID;
message.Timestamp = new DateTime(im.MessageBlock.Timestamp);
message.Message = Helpers.FieldToUTF8String(im.MessageBlock.Message);
message.Offline = (InstantMessageOnline)im.MessageBlock.Offline;
message.BinaryBucket = im.MessageBlock.BinaryBucket;
try { OnInstantMessage(message, simulator); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
///
/// Take an incoming Chat packet, auto-parse, and if OnChat is defined call
/// that with the appropriate arguments.
///
/// Incoming ChatFromSimulatorPacket
/// Unused
private void ChatHandler(Packet packet, Simulator simulator)
{
if (OnChat != null)
{
ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet;
OnChat(Helpers.FieldToUTF8String(chat.ChatData.Message)
, (ChatAudibleLevel)chat.ChatData.Audible
, (ChatType)chat.ChatData.ChatType
, (ChatSourceType)chat.ChatData.SourceType
, Helpers.FieldToUTF8String(chat.ChatData.FromName)
, chat.ChatData.SourceID
, chat.ChatData.OwnerID
, chat.ChatData.Position
);
}
}
///
/// Used for parsing llDialogs
///
/// Incoming ScriptDialog packet
/// Unused
private void ScriptDialogHandler(Packet packet, Simulator simulator)
{
if (OnScriptDialog != null)
{
ScriptDialogPacket dialog = (ScriptDialogPacket)packet;
List buttons = new List();
foreach (ScriptDialogPacket.ButtonsBlock button in dialog.Buttons)
{
buttons.Add(Helpers.FieldToUTF8String(button.ButtonLabel));
}
OnScriptDialog(Helpers.FieldToUTF8String(dialog.Data.Message),
Helpers.FieldToUTF8String(dialog.Data.ObjectName),
dialog.Data.ImageID,
dialog.Data.ObjectID,
Helpers.FieldToUTF8String(dialog.Data.FirstName),
Helpers.FieldToUTF8String(dialog.Data.LastName),
dialog.Data.ChatChannel,
buttons);
}
}
///
/// Used for parsing llRequestPermissions dialogs
///
/// Incoming ScriptDialog packet
/// Unused
private void ScriptQuestionHandler(Packet packet, Simulator simulator)
{
if (OnScriptQuestion != null)
{
ScriptQuestionPacket question = (ScriptQuestionPacket)packet;
try
{
OnScriptQuestion(simulator,
question.Data.TaskID,
question.Data.ItemID,
Helpers.FieldToUTF8String(question.Data.ObjectName),
Helpers.FieldToUTF8String(question.Data.ObjectOwner),
(ScriptPermission)question.Data.Questions);
}
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
///
/// Handles Script Control changes when Script with permissions releases or takes a control
///
///
///
private void ScriptControlChangeHandler(Packet packet, Simulator simulator)
{
if (OnScriptControlChange != null)
{
ScriptControlChangePacket change = (ScriptControlChangePacket)packet;
for (int i = 0; i < change.Data.Length; i++)
{
try
{
OnScriptControlChange((ScriptControlChange)change.Data[i].Controls,
change.Data[i].PassToAgent,
change.Data[i].TakeControls);
}
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
///
/// Used for parsing llLoadURL Dialogs
///
///
///
private void LoadURLHandler(Packet packet, Simulator simulator)
{
LoadURLPacket loadURL = (LoadURLPacket)packet;
if (OnLoadURL != null)
{
try {
OnLoadURL(
Helpers.FieldToUTF8String(loadURL.Data.ObjectName),
loadURL.Data.ObjectID,
loadURL.Data.OwnerID,
loadURL.Data.OwnerIsGroup,
Helpers.FieldToUTF8String(loadURL.Data.Message),
Helpers.FieldToUTF8String(loadURL.Data.URL)
);
}
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
///
/// Update client's Position, LookAt and region handle from incoming packet
///
/// Incoming AgentMovementCompletePacket
/// Unused
private void MovementCompleteHandler(Packet packet, Simulator simulator)
{
AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet;
relativePosition = movement.Data.Position;
Movement.Camera.LookDirection(movement.Data.LookAt);
simulator.Handle = movement.Data.RegionHandle;
}
///
/// Update Client Avatar's health via incoming packet
///
/// Incoming HealthMessagePacket
/// Unused
private void HealthHandler(Packet packet, Simulator simulator)
{
health = ((HealthMessagePacket)packet).HealthData.Health;
}
private void AgentDataUpdateHandler(Packet packet, Simulator simulator)
{
AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet;
if (p.AgentData.AgentID == simulator.Client.Self.AgentID)
{
firstName = Helpers.FieldToUTF8String(p.AgentData.FirstName);
lastName = Helpers.FieldToUTF8String(p.AgentData.LastName);
activeGroup = p.AgentData.ActiveGroupID;
if (OnAgentDataUpdated != null)
{
string groupTitle = Helpers.FieldToUTF8String(p.AgentData.GroupTitle);
string groupName = Helpers.FieldToUTF8String(p.AgentData.GroupName);
try { OnAgentDataUpdated(firstName, lastName, activeGroup, groupTitle, (GroupPowers)p.AgentData.GroupPowers, groupName); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
else
{
Client.Log("Got an AgentDataUpdate packet for avatar " + p.AgentData.AgentID.ToString() +
" instead of " + Client.Self.AgentID.ToString() + ", this shouldn't happen", Helpers.LogLevel.Error);
}
}
///
/// Update Client Avatar's L$ balance from incoming packet
///
/// Incoming MoneyBalanceReplyPacket
/// Unused
private void BalanceHandler(Packet packet, Simulator simulator)
{
if (packet.Type == PacketType.MoneyBalanceReply)
{
MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet;
balance = mbrp.MoneyData.MoneyBalance;
if (OnMoneyBalanceReplyReceived != null)
{
try { OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID,
mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance,
mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted,
Helpers.FieldToUTF8String(mbrp.MoneyData.Description)); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
if (OnBalanceUpdated != null)
{
try { OnBalanceUpdated(balance); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
private void EstablishAgentCommunicationEventHandler(string message, LLSD llsd, Simulator simulator)
{
StructuredData.LLSDMap body = (StructuredData.LLSDMap)llsd;
if (Client.Settings.MULTIPLE_SIMS && body.ContainsKey("sim-ip-and-port"))
{
string ipAndPort = body["sim-ip-and-port"].AsString();
string[] pieces = ipAndPort.Split(':');
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(pieces[0]), Convert.ToInt32(pieces[1]));
Simulator sim = Client.Network.FindSimulator(endPoint);
if (sim == null)
{
Client.Log("Got EstablishAgentCommunication for unknown sim " + ipAndPort,
Helpers.LogLevel.Error);
// FIXME: Should we use this opportunity to connect to the simulator?
}
else
{
Client.Log("Got EstablishAgentCommunication for " + sim.ToString(),
Helpers.LogLevel.Info);
sim.SetSeedCaps(body["seed-capability"].AsString());
}
}
}
///
/// Handler for teleport Requests
///
/// Incoming TeleportHandler packet
/// Simulator sending teleport information
private void TeleportHandler(Packet packet, Simulator simulator)
{
bool finished = false;
TeleportFlags flags = TeleportFlags.Default;
if (packet.Type == PacketType.TeleportStart)
{
TeleportStartPacket start = (TeleportStartPacket)packet;
teleportMessage = "Teleport started";
flags = (TeleportFlags)start.Info.TeleportFlags;
teleportStat = TeleportStatus.Start;
Client.DebugLog("TeleportStart received, Flags: " + flags.ToString());
}
else if (packet.Type == PacketType.TeleportProgress)
{
TeleportProgressPacket progress = (TeleportProgressPacket)packet;
teleportMessage = Helpers.FieldToUTF8String(progress.Info.Message);
flags = (TeleportFlags)progress.Info.TeleportFlags;
teleportStat = TeleportStatus.Progress;
Client.DebugLog("TeleportProgress received, Message: " + teleportMessage + ", Flags: " + flags.ToString());
}
else if (packet.Type == PacketType.TeleportFailed)
{
TeleportFailedPacket failed = (TeleportFailedPacket)packet;
teleportMessage = Helpers.FieldToUTF8String(failed.Info.Reason);
teleportStat = TeleportStatus.Failed;
finished = true;
Client.DebugLog("TeleportFailed received, Reason: " + teleportMessage);
}
else if (packet.Type == PacketType.TeleportFinish)
{
TeleportFinishPacket finish = (TeleportFinishPacket)packet;
flags = (TeleportFlags)finish.Info.TeleportFlags;
string seedcaps = Helpers.FieldToUTF8String(finish.Info.SeedCapability);
finished = true;
Client.DebugLog("TeleportFinish received, Flags: " + flags.ToString());
// Connect to the new sim
Simulator newSimulator = Client.Network.Connect(new IPAddress(finish.Info.SimIP),
finish.Info.SimPort, finish.Info.RegionHandle, true, seedcaps);
if (newSimulator != null)
{
teleportMessage = "Teleport finished";
teleportStat = TeleportStatus.Finished;
// Disconnect from the previous sim
Client.Network.DisconnectSim(simulator, true);
Client.Log("Moved to new sim " + newSimulator.ToString(), Helpers.LogLevel.Info);
}
else
{
teleportMessage = "Failed to connect to the new sim after a teleport";
teleportStat = TeleportStatus.Failed;
// We're going to get disconnected now
Client.Log(teleportMessage, Helpers.LogLevel.Error);
}
}
else if (packet.Type == PacketType.TeleportCancel)
{
//TeleportCancelPacket cancel = (TeleportCancelPacket)packet;
teleportMessage = "Cancelled";
teleportStat = TeleportStatus.Cancelled;
finished = true;
Client.DebugLog("TeleportCancel received from " + simulator.ToString());
}
else if (packet.Type == PacketType.TeleportLocal)
{
TeleportLocalPacket local = (TeleportLocalPacket)packet;
teleportMessage = "Teleport finished";
flags = (TeleportFlags)local.Info.TeleportFlags;
teleportStat = TeleportStatus.Finished;
relativePosition = local.Info.Position;
Movement.Camera.LookDirection(local.Info.LookAt);
// This field is apparently not used for anything
//local.Info.LocationID;
finished = true;
Client.DebugLog("TeleportLocal received, Flags: " + flags.ToString());
}
if (OnTeleport != null)
{
try { OnTeleport(teleportMessage, teleportStat, flags); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
if (finished) teleportEvent.Set();
}
///
///
///
///
///
private void AvatarAnimationHandler(Packet packet, Simulator sim)
{
AvatarAnimationPacket animation = (AvatarAnimationPacket)packet;
if (animation.Sender.ID == Client.Self.AgentID)
{
lock (SignaledAnimations.Dictionary)
{
// Reset the signaled animation list
SignaledAnimations.Dictionary.Clear();
for (int i = 0; i < animation.AnimationList.Length; i++)
{
LLUUID animID = animation.AnimationList[i].AnimID;
int sequenceID = animation.AnimationList[i].AnimSequenceID;
// Add this animation to the list of currently signaled animations
SignaledAnimations.Dictionary[animID] = sequenceID;
if (i < animation.AnimationSourceList.Length)
{
// FIXME: The server tells us which objects triggered our animations,
// we should store this info
//animation.AnimationSourceList[i].ObjectID
}
if (i < animation.PhysicalAvatarEventList.Length)
{
// FIXME: What is this?
}
if (Client.Settings.SEND_AGENT_UPDATES)
{
// We have to manually tell the server to stop playing some animations
if (animID == Animations.STANDUP ||
animID == Animations.PRE_JUMP ||
animID == Animations.LAND ||
animID == Animations.MEDIUM_LAND)
{
Movement.FinishAnim = true;
Movement.SendUpdate(true);
}
}
}
}
if (OnAnimationsChanged != null)
{
try { OnAnimationsChanged(SignaledAnimations); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
private void MeanCollisionAlertHandler(Packet packet, Simulator sim)
{
if (OnMeanCollision != null)
{
MeanCollisionAlertPacket collision = (MeanCollisionAlertPacket)packet;
for (int i = 0; i < collision.MeanCollision.Length; i++)
{
MeanCollisionAlertPacket.MeanCollisionBlock block = collision.MeanCollision[i];
DateTime time = Helpers.UnixTimeToDateTime(block.Time);
MeanCollisionType type = (MeanCollisionType)block.Type;
try { OnMeanCollision(type, block.Perp, block.Victim, block.Mag, time); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason,
LoginResponseData reply)
{
id = reply.AgentID;
sessionID = reply.SessionID;
secureSessionID = reply.SecureSessionID;
firstName = reply.FirstName;
lastName = reply.LastName;
startLocation = reply.StartLocation;
agentAccess = reply.AgentAccess;
Movement.Camera.LookDirection(reply.LookAt);
homePosition = reply.HomePosition;
homeLookAt = reply.HomeLookAt;
}
private void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message)
{
// Null out the cached fullName since it can change after logging
// in again (with a different account name or different login
// server but using the same SecondLife object
fullName = null;
}
///
/// Allows agent to cross over (walk, fly, vehicle) in to neighboring
/// simulators
///
private void CrossedRegionHandler(Packet packet, Simulator sim)
{
CrossedRegionPacket crossing = (CrossedRegionPacket)packet;
string seedCap = Helpers.FieldToUTF8String(crossing.RegionData.SeedCapability);
IPEndPoint endPoint = new IPEndPoint(crossing.RegionData.SimIP, crossing.RegionData.SimPort);
Client.DebugLog("Crossed in to new region area, attempting to connect to " + endPoint.ToString());
Simulator oldSim = Client.Network.CurrentSim;
Simulator newSim = Client.Network.Connect(endPoint, crossing.RegionData.RegionHandle, true, seedCap);
if (newSim != null)
{
Client.Log("Finished crossing over in to region " + newSim.ToString(), Helpers.LogLevel.Info);
if (OnRegionCrossed != null)
{
try { OnRegionCrossed(oldSim, newSim); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
else
{
// The old simulator will (poorly) handle our movement still, so the connection isn't
// completely shot yet
Client.Log("Failed to connect to new region " + endPoint.ToString() + " after crossing over",
Helpers.LogLevel.Warning);
}
}
///
/// Group Chat event handler
///
///
///
///
private void ChatterBoxSessionEventHandler(string capsKey, LLSD llsd, Simulator simulator)
{
// TODO: this appears to occur when you try and initiate group chat with an unopened session
//
// Key=ChatterBoxSessionEventReply
// llsd={
// ("error": "generic")
// ("event": "message")
// ("session_id": "3dafea18-cda1-9813-d5f1-fd3de6b13f8c") // group uuid
// ("success": "0")}
//LLSDMap map = (LLSDMap)llsd;
//LLUUID groupUUID = map["session_id"].AsUUID();
//Console.WriteLine("SessionEvent: Key={0} llsd={1}", capsKey, llsd.ToString());
}
///
/// Response from request to join a group chat
///
///
///
///
private void ChatterBoxSessionStartReplyHandler(string capsKey, LLSD llsd, Simulator simulator)
{
LLSDMap map = (LLSDMap)llsd;
LLUUID sessionID = map["session_id"].AsUUID();
LLUUID tmpSessionID = map["temp_session_id"].AsUUID();
bool success = map["success"].AsBoolean();
if (success)
{
LLSDArray agentlist = (LLSDArray)map["agents"];
List agents = new List();
foreach (LLSD id in agentlist)
agents.Add(id.AsUUID());
lock (GroupChatSessions.Dictionary)
{
if (GroupChatSessions.ContainsKey(sessionID))
GroupChatSessions.Dictionary[sessionID] = agents;
else
GroupChatSessions.Add(sessionID, agents);
}
}
if (OnGroupChatJoin != null)
{
try { OnGroupChatJoin(sessionID, tmpSessionID, success); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
///
/// Someone joined or left group chat
///
///
///
///
private void ChatterBoxSessionAgentListReplyHandler(string capsKey, LLSD llsd, Simulator simulator)
{
LLSDMap map = (LLSDMap)llsd;
LLUUID sessionID = map["session_id"].AsUUID();
LLSDMap update = (LLSDMap)map["updates"];
string errormsg = map["error"].AsString();
//if (errormsg.Equals("already in session"))
// return;
foreach (KeyValuePair kvp in update)
{
if (kvp.Value.Equals("ENTER"))
{
lock (GroupChatSessions.Dictionary)
{
if (!GroupChatSessions.Dictionary[sessionID].Contains((LLUUID)kvp.Key))
GroupChatSessions.Dictionary[sessionID].Add((LLUUID)kvp.Key);
}
}
else if (kvp.Value.Equals("LEAVE"))
{
lock (GroupChatSessions.Dictionary)
{
if (GroupChatSessions.Dictionary[sessionID].Contains((LLUUID)kvp.Key))
GroupChatSessions.Dictionary[sessionID].Remove((LLUUID)kvp.Key);
// we left session, remove from dictionary
if (kvp.Key.Equals(Client.Self.id) && OnGroupChatLeft != null)
{
GroupChatSessions.Dictionary.Remove(sessionID);
OnGroupChatLeft(sessionID);
}
}
}
}
}
///
/// Group Chat Request
///
/// Caps Key
/// LLSD Map containing invitation
/// Originating Simulator
private void ChatterBoxInvitationHandler(string capsKey, LLSD llsd, Simulator simulator)
{
if (OnInstantMessage != null)
{
LLSDMap map = (LLSDMap)llsd;
LLSDMap im = (LLSDMap)map["instantmessage"];
LLSDMap agent = (LLSDMap)im["agent_params"];
LLSDMap msg = (LLSDMap)im["message_params"];
LLSDMap msgdata = (LLSDMap)msg["data"];
InstantMessage message = new InstantMessage();
message.FromAgentID = map["from_id"].AsUUID();
message.FromAgentName = map["from_name"].AsString();
message.ToAgentID = msg["to_id"].AsString();
message.ParentEstateID = (uint)msg["parent_estate_id"].AsInteger();
message.RegionID = msg["region_id"].AsUUID();
message.Position.FromLLSD(msg["position"]);
message.Dialog = (InstantMessageDialog)msgdata["type"].AsInteger();
message.GroupIM = true;
message.IMSessionID = map["session_id"].AsUUID();
message.Timestamp = new DateTime(msgdata["timestamp"].AsInteger());
message.Message = msg["message"].AsString();
message.Offline = (InstantMessageOnline)msg["offline"].AsInteger();
message.BinaryBucket = msg["binary_bucket"].AsBinary();
try { OnInstantMessage(message, simulator); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
///
/// Alert Message packet handler
///
/// AlertMessagePacket
/// not used
private void AlertMessageHandler(Packet packet, Simulator simulator)
{
AlertMessagePacket alert = (AlertMessagePacket)packet;
string message = Helpers.FieldToUTF8String(alert.AlertData.Message);
if (OnAlertMessage != null)
{
try { OnAlertMessage(message); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
///
/// detects camera constraint collisions
///
///
///
private void CameraConstraintHandler(Packet packet, Simulator simulator)
{
if (OnCameraConstraint != null)
{
CameraConstraintPacket camera = (CameraConstraintPacket)packet;
try { OnCameraConstraint(camera.CameraCollidePlane.Plane); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
#endregion Packet Handlers
}
}