/* * 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, for Group Messages, the groups UUID 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 } [Flags] public enum ScriptSensorTypeFlags { Agent = 1, Active = 2, Passive = 4, Scripted = 8, } #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); /// /// Fired when script sensor reply is received /// /// requestors UUID /// Sources Group UUID /// Sources Name /// Objects UUID /// Object owners UUID /// Position of Object /// Range of Object /// Rotation of object /// Objects Type /// LLVector3 representing the velocity of object /// TODO: this should probably be a struct, and there should be an enum added for type public delegate void ScriptSensorReplyCallback(LLUUID requestorID, LLUUID groupID, string name, LLUUID objectID, LLUUID ownerID, LLVector3 position, float range, LLQuaternion rotation, ScriptSensorTypeFlags type, LLVector3 velocity); /// /// Fired in response to a RequestSit() /// /// ID of primitive avatar will be sitting on /// true of avatar autopiloted there /// Camera offset when avatar is seated /// Camera eye offset when avatar is seated /// true of sitting on this object will force mouselook /// position avatar will be in when seated /// rotation avatar will be in when seated public delegate void AvatarSitResponseCallback(LLUUID objectID, bool autoPilot, LLVector3 cameraAtOffset, LLVector3 cameraEyeOffset, bool forceMouselook, LLVector3 sitPosition, LLQuaternion sitRotation); public delegate void AgentMovementCallback(LLVector3 agentPosition, ulong regionHandle, LLVector3 agentLookAt, string simVersion); /// 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; /// Fired when a script sensor reply is received public event ScriptSensorReplyCallback OnScriptSensorReply; public event AvatarSitResponseCallback OnAvatarSitResponse; public event AgentMovementCallback OnAgentMovement; #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)); Client.Network.RegisterCallback(PacketType.ScriptSensorReply, new NetworkManager.PacketCallback(ScriptSensorReplyHandler)); Client.Network.RegisterCallback(PacketType.AvatarSitResponse, new NetworkManager.PacketCallback(AvatarSitResponseHandler)); } #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]); } /// /// Requests script detection of objects and avatars /// /// name of the object/avatar to search for /// UUID of the object or avatar to search for /// Type of search from ScriptSensorTypeFlags /// range of scan (96 max?) /// the arc in radians to search within /// an user generated ID to correlate replies with /// Simulator to perform search in public void RequestScriptSensor(string name, LLUUID searchID, ScriptSensorTypeFlags type, float range, float arc, LLUUID requestID, Simulator sim) { ScriptSensorRequestPacket request = new ScriptSensorRequestPacket(); request.Requester.Arc = arc; request.Requester.Range = range; request.Requester.RegionHandle = sim.Handle; request.Requester.RequestID = requestID; request.Requester.SearchDir = LLQuaternion.Identity; // TODO: this needs to be tested request.Requester.SearchID = searchID; request.Requester.SearchName = Helpers.StringToField(name); request.Requester.SearchPos = LLVector3.Zero; request.Requester.SearchRegions = 0; // TODO: ? request.Requester.SourceID = Client.Self.AgentID; request.Requester.Type = (int)type; Client.Network.SendPacket(request, sim); } #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; if (OnAgentMovement != null) { try { OnAgentMovement(movement.Data.Position, movement.Data.RegionHandle, movement.Data.LookAt, Helpers.FieldToUTF8String(movement.SimData.ChannelVersion)); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } /// /// 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); } } } /// /// Packet handler for ScriptSensorReply packet /// /// /// private void ScriptSensorReplyHandler(Packet packet, Simulator simulator) { if (OnScriptSensorReply != null) { ScriptSensorReplyPacket reply = (ScriptSensorReplyPacket)packet; for (int i = 0; i < reply.SensedData.Length; i++) { ScriptSensorReplyPacket.SensedDataBlock block = reply.SensedData[i]; ScriptSensorReplyPacket.RequesterBlock requestor = reply.Requester; try { OnScriptSensorReply(requestor.SourceID, block.GroupID, Helpers.FieldToUTF8String(block.Name), block.ObjectID, block.OwnerID, block.Position, block.Range, block.Rotation, (ScriptSensorTypeFlags)block.Type, block.Velocity); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } /// /// Packet handler for AvatarSitResponse packet /// /// /// private void AvatarSitResponseHandler(Packet packet, Simulator simulator) { if (OnAvatarSitResponse != null) { AvatarSitResponsePacket sit = (AvatarSitResponsePacket)packet; try { OnAvatarSitResponse(sit.SitObject.ID, sit.SitTransform.AutoPilot, sit.SitTransform.CameraAtOffset, sit.SitTransform.CameraEyeOffset, sit.SitTransform.ForceMouselook, sit.SitTransform.SitPosition, sit.SitTransform.SitRotation); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } #endregion Packet Handlers } }