/* * 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 } }