/* * 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.Timers; using System.Net; using System.Collections; using System.Collections.Generic; using System.Threading; using System.Text; using libsecondlife.Packets; namespace libsecondlife { /// /// Class to hold Client Avatar's data /// public partial class MainAvatar { #region Enums /// /// Used to specify movement actions for your agent /// [Flags] public enum ControlFlags { /// Empty flag NONE = 0, /// Move Forward (SL Keybinding: W/Up Arrow) AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX, /// Move Backward (SL Keybinding: S/Down Arrow) AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX, /// Move Left (SL Keybinding: Shift-(A/Left Arrow)) AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX, /// Move Right (SL Keybinding: Shift-(D/Right Arrow)) AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX, /// Not Flying: Jump/Flying: Move Up (SL Keybinding: E) AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX, /// Not Flying: Croutch/Flying: Move Down (SL Keybinding: C) AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX, /// Unused AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX, /// Unused AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX, /// Unused AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX, /// Unused AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX, /// ORed with AGENT_CONTROL_AT_* if the keyboard is being used AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX, /// ORed with AGENT_CONTROL_LEFT_* if the keyboard is being used AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX, /// ORed with AGENT_CONTROL_UP_* if the keyboard is being used AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX, /// Fly AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX, /// AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX, /// Finish our current animation AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX, /// Stand up from the ground or a prim seat AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX, /// Sit on the ground at our current location AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX, /// Whether mouselook is currently enabled AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX, /// AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX, /// AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX, /// Set when the avatar is idled or set to away. Note that the away animation is /// activated separately from setting this flag AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX, /// AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX, /// AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX, /// AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX, /// AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX } /// /// 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 } /// /// /// [Flags] public enum ScriptPermission : int { /// Placeholder for empty values, shouldn't ever see this None = 0, /// Script wants to take money from you Debit = 1 << 1, /// TakeControls = 1 << 2, /// RemapControls = 1 << 3, /// Script wants to trigger avatar animations TriggerAnimation = 1 << 4, /// Attach = 1 << 5, /// ReleaseOwnership = 1 << 6, /// ChangeLinks = 1 << 7, /// ChangeJoints = 1 << 8, /// ChangePermissions = 1 << 9, /// TrackCamera = 1 << 10, /// Script wants to control your camera ControlCamera = 1 << 11 } /// /// 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 } /// /// 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, /// IM Types used for luring your friends 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 { /// Place floating text above an object Text = 0, /// Unknown, probably places an icon above an object Icon, /// Unknown Connector, /// Unknown FlexibleObject, /// Unknown AnimalControls, /// Unknown AnimationObject, /// Unknown Cloth, /// Project a beam from a source to a destination, such as /// the one used when editing an object Beam, /// Not implemented yet Glow, /// Unknown Point, /// Unknown Trail, /// Create a swirl of particles around an object Sphere, /// Unknown Spiral, /// Unknown 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 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 } /// /// /// [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 /// /// /// 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(LLUUID taskID, LLUUID itemID, string objectName, string objectOwner, ScriptPermission questions); /// /// 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 /// /// /// public delegate void MoneyBalanceReplyCallback(LLUUID transactionID, bool transactionSuccess, int balance, int metersCredit, int metersCommitted, string description); /// /// Triggered on incoming instant messages /// /// Key of sender /// Name of sender /// Key of destination Avatar /// ID of originating Estate /// Key of originating Region /// Coordinates in originating Region /// /// Group IM session toggle /// Key of IM Session /// Timestamp of message /// Text of message /// Enum of whether this message is held for /// offline avatars /// Context specific packed data /// Simulator where this IM was received from public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, InstantMessageOnline offline, byte[] binaryBucket, 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 we are no longer a member of public delegate void GroupDroppedCallback(LLUUID groupID); /// /// Informs the avatar that the active group has changed /// /// The group that is now the active group public delegate void ActiveGroupChangedCallback(LLUUID groupID); /// 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 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 reply for an attempt to join a group public event JoinGroupCallback OnJoinGroup; /// Callback reply for an attempt to leave a group public event LeaveGroupCallback OnLeaveGroup; /// Callback for informing the avatar that it is no longer a member of a group public event GroupDroppedCallback OnGroupDropped; /// Callback reply for the current group changing public event ActiveGroupChangedCallback OnActiveGroupChanged; #endregion #region Public Members /// Your (client) avatar UUID public LLUUID ID = LLUUID.Zero; /// Your (client) avatar ID, local to the current region/sim public uint LocalID = 0; /// Avatar First Name (i.e. Philip) public string FirstName = String.Empty; /// Avatar Last Name (i.e. Linden) public string LastName = String.Empty; /// Where the avatar started at login. Can be "last", "home" /// or a login URI public string StartLocation = String.Empty; /// The access level of this agent, usually M or PG public string AgentAccess = String.Empty; /// Positive and negative ratings /// This information is read-only and any changes will not be /// reflected on the server public Avatar.Statistics ProfileStatistics = new Avatar.Statistics(); /// Avatar properties including about text, profile URL, image IDs and /// publishing settings /// If you change fields in this struct, the changes will not /// be reflected on the server until you call SetAvatarInformation public Avatar.AvatarProperties ProfileProperties = new Avatar.AvatarProperties(); /// Avatar interests including spoken languages, skills, and "want to" /// choices /// If you change fields in this struct, the changes will not /// be reflected on the server until you call SetAvatarInformation public Avatar.Interests ProfileInterests = new Avatar.Interests(); /// Current position of avatar public LLVector3 Position = LLVector3.Zero; /// Current rotation of avatar public LLQuaternion Rotation = LLQuaternion.Identity; /// public LLVector4 CollisionPlane = LLVector4.Zero; /// public LLVector3 Velocity = LLVector3.Zero; /// public LLVector3 Acceleration = LLVector3.Zero; /// public LLVector3 AngularVelocity = LLVector3.Zero; /// The point the avatar is currently looking at /// (may not stay updated) public LLVector3 LookAt = LLVector3.Zero; /// Position avatar client will goto when login to 'home' or during /// teleport request to 'home' region. public LLVector3 HomePosition = LLVector3.Zero; /// LookAt point saved/restored with HomePosition public LLVector3 HomeLookAt = LLVector3.Zero; /// Used for camera and control key state tracking public MainAvatarStatus Status; /// The UUID of your root inventory folder public LLUUID InventoryRootFolderUUID = LLUUID.Zero; /// 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 avatar is sitting on, /// zero if the avatar is not currently sitting public uint SittingOn { get { return sittingOn; } } /// Gets the UUID of the active group. public LLUUID ActiveGroup { get { return activeGroup; } } /// Current status message for teleporting public string TeleportMessage { get { return teleportMessage; } } #endregion Public Members internal string teleportMessage = String.Empty; internal uint sittingOn = 0; internal DateTime lastInterpolation; private SecondLife Client; private TeleportStatus TeleportStat = TeleportStatus.None; private ManualResetEvent TeleportEvent = new ManualResetEvent(false); private uint HeightWidthGenCounter = 0; private float health = 0.0f; private int balance = 0; private LLUUID activeGroup = LLUUID.Zero; #region AgentUpdate Constants private const int CONTROL_AT_POS_INDEX = 0; private const int CONTROL_AT_NEG_INDEX = 1; private const int CONTROL_LEFT_POS_INDEX = 2; private const int CONTROL_LEFT_NEG_INDEX = 3; private const int CONTROL_UP_POS_INDEX = 4; private const int CONTROL_UP_NEG_INDEX = 5; private const int CONTROL_PITCH_POS_INDEX = 6; private const int CONTROL_PITCH_NEG_INDEX = 7; private const int CONTROL_YAW_POS_INDEX = 8; private const int CONTROL_YAW_NEG_INDEX = 9; private const int CONTROL_FAST_AT_INDEX = 10; private const int CONTROL_FAST_LEFT_INDEX = 11; private const int CONTROL_FAST_UP_INDEX = 12; private const int CONTROL_FLY_INDEX = 13; private const int CONTROL_STOP_INDEX = 14; private const int CONTROL_FINISH_ANIM_INDEX = 15; private const int CONTROL_STAND_UP_INDEX = 16; private const int CONTROL_SIT_ON_GROUND_INDEX = 17; private const int CONTROL_MOUSELOOK_INDEX = 18; private const int CONTROL_NUDGE_AT_POS_INDEX = 19; private const int CONTROL_NUDGE_AT_NEG_INDEX = 20; private const int CONTROL_NUDGE_LEFT_POS_INDEX = 21; private const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22; private const int CONTROL_NUDGE_UP_POS_INDEX = 23; private const int CONTROL_NUDGE_UP_NEG_INDEX = 24; private const int CONTROL_TURN_LEFT_INDEX = 25; private const int CONTROL_TURN_RIGHT_INDEX = 26; private const int CONTROL_AWAY_INDEX = 27; private const int CONTROL_LBUTTON_DOWN_INDEX = 28; private const int CONTROL_LBUTTON_UP_INDEX = 29; private const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30; private const int CONTROL_ML_LBUTTON_UP_INDEX = 31; private const int TOTAL_CONTROLS = 32; #endregion AgentUpdate Constants /// /// Constructor, setup callbacks for packets related to our avatar /// /// public MainAvatar(SecondLife client) { Client = client; Status = new MainAvatarStatus(Client); NetworkManager.PacketCallback callback; // 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)); // Movement complete callback Client.Network.RegisterCallback(PacketType.AgentMovementComplete, new NetworkManager.PacketCallback(MovementCompleteHandler)); // Health callback Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler)); // Money callbacks callback = new NetworkManager.PacketCallback(BalanceHandler); Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, callback); // Group callbacks Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupHandler)); Client.Network.RegisterCallback(PacketType.LeaveGroupReply, new NetworkManager.PacketCallback(LeaveGroupHandler)); Client.Network.RegisterCallback(PacketType.AgentDropGroup, new NetworkManager.PacketCallback(DropGroupHandler)); //Agent Update Callback Client.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(AgentDataUpdateHandler)); // Event queue callback (used for Caps teleports currently) Client.Network.RegisterEventCallback(new Caps.EventQueueCallback(EventQueueHandler)); } /// /// Send an Instant Message /// /// Target of the Instant Message /// Text message being sent public void InstantMessage(LLUUID target, string message) { InstantMessage(Client.ToString(), target, message, LLUUID.Random(), InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, 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(Client.ToString(), target, message, imSessionID, InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, 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) /// 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].Data, 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 /// /// /// 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) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); im.AgentData.AgentID = Client.Network.AgentID; im.AgentData.SessionID = Client.Network.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); } /// /// Send an Instant Message to a group /// /// Key of Group /// Text Message being sent. public void InstantMessageGroup(LLUUID groupUUID, string message) { InstantMessageGroup(Client.ToString(), 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) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); im.AgentData.AgentID = Client.Network.AgentID; im.AgentData.SessionID = Client.Network.SessionID; im.MessageBlock.Dialog = (byte)MainAvatar.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.BinaryBucket = new byte[0]; im.MessageBlock.Position = LLVector3.Zero; im.MessageBlock.RegionID = LLUUID.Zero; // Send the message Client.Network.SendPacket(im); } /// /// /// /// /// /// /// public void PointAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, PointAtType type) { ViewerEffectPacket effect = new ViewerEffectPacket(); effect.AgentData.AgentID = Client.Network.AgentID; effect.AgentData.SessionID = Client.Network.SessionID; effect.Effect = new ViewerEffectPacket.EffectBlock[1]; effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); effect.Effect[0].AgentID = Client.Network.AgentID; effect.Effect[0].Color = LLColor.Black.GetBytes(); effect.Effect[0].Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f; effect.Effect[0].ID = LLUUID.Random(); effect.Effect[0].Type = (byte)EffectType.PointAt; byte[] typeData = new byte[57]; if (sourceAvatar != null) Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); if (targetObject != null) 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); } /// /// /// /// /// /// /// public void LookAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LookAtType type) { ViewerEffectPacket effect = new ViewerEffectPacket(); effect.AgentData.AgentID = Client.Network.AgentID; effect.AgentData.SessionID = Client.Network.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.Conversation: 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.Network.AgentID; effect.Effect[0].Color = LLColor.Black.GetBytes(); effect.Effect[0].Duration = duration; effect.Effect[0].ID = LLUUID.Random(); effect.Effect[0].Type = (byte)EffectType.LookAt; byte[] typeData = new byte[57]; if (sourceAvatar != null) Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); if (targetObject != null) 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) { ViewerEffectPacket effect = new ViewerEffectPacket(); effect.AgentData.AgentID = Client.Network.AgentID; effect.AgentData.SessionID = Client.Network.SessionID; effect.Effect = new ViewerEffectPacket.EffectBlock[1]; effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); effect.Effect[0].AgentID = Client.Network.AgentID; effect.Effect[0].Color = color.GetBytes(); effect.Effect[0].Duration = duration; effect.Effect[0].ID = LLUUID.Random(); effect.Effect[0].Type = (byte)EffectType.Beam; byte[] typeData = new byte[56]; if (sourceAvatar != null) Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); if (targetObject != null) 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); } /// /// Synchronize the local profile and interests information to the server /// public void SetAvatarInformation() { // Basic profile properties AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket(); apup.AgentData.AgentID = this.ID; apup.AgentData.SessionID = Client.Network.SessionID; apup.PropertiesData.AboutText = Helpers.StringToField(this.ProfileProperties.AboutText); apup.PropertiesData.AllowPublish = this.ProfileProperties.AllowPublish; apup.PropertiesData.FLAboutText = Helpers.StringToField(this.ProfileProperties.FirstLifeText); apup.PropertiesData.FLImageID = this.ProfileProperties.FirstLifeImage; apup.PropertiesData.ImageID = this.ProfileProperties.ProfileImage; apup.PropertiesData.MaturePublish = this.ProfileProperties.MaturePublish; apup.PropertiesData.ProfileURL = Helpers.StringToField(this.ProfileProperties.ProfileURL); Client.Network.SendPacket(apup); // Interests AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket(); aiup.AgentData.AgentID = this.ID; aiup.AgentData.SessionID = Client.Network.SessionID; aiup.PropertiesData.LanguagesText = Helpers.StringToField(this.ProfileInterests.LanguagesText); aiup.PropertiesData.SkillsMask = this.ProfileInterests.SkillsMask; aiup.PropertiesData.SkillsText = Helpers.StringToField(this.ProfileInterests.SkillsText); aiup.PropertiesData.WantToMask = this.ProfileInterests.WantToMask; aiup.PropertiesData.WantToText = Helpers.StringToField(this.ProfileInterests.WantToText); Client.Network.SendPacket(aiup); } /// /// 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.Network.SessionID; chat.ChatData.Channel = channel; chat.ChatData.Message = Helpers.StringToField(message); chat.ChatData.Type = (byte)type; Client.Network.SendPacket(chat); } /// /// 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.Network.AgentID; heightwidth.AgentData.SessionID = Client.Network.SessionID; heightwidth.AgentData.CircuitCode = Client.Network.CircuitCode; heightwidth.HeightWidthBlock.Height = height; heightwidth.HeightWidthBlock.Width = width; heightwidth.HeightWidthBlock.GenCounter = HeightWidthGenCounter++; Client.Network.SendPacket(heightwidth); } /// /// 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.Network.AgentID; requestSit.AgentData.SessionID = Client.Network.SessionID; requestSit.TargetObject.TargetID = targetID; requestSit.TargetObject.Offset = offset; Client.Network.SendPacket(requestSit); } /// /// Request the list of muted things for this avatar /// public void RequestMuteList() { MuteListRequestPacket mute = new MuteListRequestPacket(); mute.AgentData.AgentID = Client.Network.AgentID; mute.AgentData.SessionID = Client.Network.SessionID; mute.MuteData.MuteCRC = 0; Client.Network.SendPacket(mute); } /// /// Request the current L$ balance /// public void RequestBalance() { MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket(); money.AgentData.AgentID = Client.Network.AgentID; money.AgentData.SessionID = Client.Network.SessionID; money.MoneyData.TransactionID = LLUUID.Zero; Client.Network.SendPacket(money); } /// /// Sets home location /// public void SetHome() { SetStartLocationRequestPacket s = new SetStartLocationRequestPacket(); s.AgentData = new SetStartLocationRequestPacket.AgentDataBlock(); s.AgentData.AgentID = Client.Network.AgentID; s.AgentData.SessionID = Client.Network.SessionID; s.StartLocationData = new SetStartLocationRequestPacket.StartLocationDataBlock(); s.StartLocationData.LocationPos = Client.Self.Position; s.StartLocationData.LocationID = 1; s.StartLocationData.SimName = Helpers.StringToField(""); s.StartLocationData.LocationLookAt = Client.Self.LookAt; Client.Network.SendPacket(s); } /// /// Teleports the avatar home /// public bool GoHome() { return Teleport(new LLUUID()); } /// /// Follows a call to RequestSit() to actually sit on the object /// public void Sit() { AgentSitPacket sit = new AgentSitPacket(); sit.AgentData.AgentID = Client.Network.AgentID; sit.AgentData.SessionID = Client.Network.SessionID; Client.Network.SendPacket(sit); } /// /// Give Money to destination Avatar /// /// UUID of the Target Avatar /// Amount in L$ /// Reason (optional normally) public void GiveMoney(LLUUID target, int amount, string description) { // 5001 - transaction type for av to av money transfers if (amount > 0) GiveMoney(target, amount, description, 5001); else Client.Log("Attempted to pay zero or negative value " + amount, Helpers.LogLevel.Warning); } /// /// Give Money to destionation Object or Avatar /// /// UUID of the Target Object/Avatar /// Amount in L$ /// Reason (Optional normally) /// The type of transaction. Currently only 5001 is /// documented for Av->Av money transfers. public void GiveMoney(LLUUID target, int amount, string description, int transactiontype) { MoneyTransferRequestPacket money = new MoneyTransferRequestPacket(); money.AgentData.AgentID = this.ID; money.AgentData.SessionID = Client.Network.SessionID; money.MoneyData.Description = Helpers.StringToField(description); money.MoneyData.DestID = target; money.MoneyData.SourceID = this.ID; money.MoneyData.TransactionType = transactiontype; money.MoneyData.AggregatePermInventory = 0; //TODO: whats this? money.MoneyData.AggregatePermNextOwner = 0; //TODO: whats this? money.MoneyData.Flags = 0; //TODO: whats this? money.MoneyData.Amount = amount; Client.Network.SendPacket(money); } /// /// Send an AgentAnimation packet that toggles a single animation on /// /// The animation to start playing public void AnimationStart(LLUUID animation) { Dictionary animations = new Dictionary(); animations[animation] = true; Animate(animations); } /// /// Send an AgentAnimation packet that toggles a single animation off /// /// The animation to stop playing public void AnimationStop(LLUUID animation) { Dictionary animations = new Dictionary(); animations[animation] = false; Animate(animations); } /// /// Send an AgentAnimation packet that will toggle animations on or off /// /// A list of animation UUIDs, and whether to /// turn that animation on or off public void Animate(Dictionary animations) { AgentAnimationPacket animate = new AgentAnimationPacket(); animate.AgentData.AgentID = Client.Network.AgentID; animate.AgentData.SessionID = Client.Network.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); } /// /// 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 /// AutoPilot(252620, 247078, 20.2674); public void AutoPilot(ulong globalX, ulong globalY, float z) { GenericMessagePacket autopilot = new GenericMessagePacket(); autopilot.AgentData.AgentID = Client.Network.AgentID; autopilot.AgentData.SessionID = Client.Network.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(); // TODO: Do we need to prevent z coordinates from being sent in 1.4827e-18 notation? 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); } /// Attempt teleport to specified LLUUID public bool Teleport(LLUUID landmark) { TeleportStat = TeleportStatus.None; TeleportEvent.Reset(); TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket(); p.Info = new TeleportLandmarkRequestPacket.InfoBlock(); p.Info.AgentID = Client.Network.AgentID; p.Info.SessionID = Client.Network.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, 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); } } /// /// Start a teleport process /// /// /// Position for Teleport /// public bool Teleport(ulong regionHandle, LLVector3 position) { return Teleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f)); } /// /// Start a teleport process /// /// /// Position for Teleport /// Target to look at /// 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); } /// /// Start a teleport process /// /// /// Position for Teleport public void RequestTeleport(ulong regionHandle, LLVector3 position) { RequestTeleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f)); } /// /// Start a teleport process /// /// /// Position for Teleport /// Target to look at public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt) { if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.SimCaps != null && Client.Network.CurrentSim.SimCaps.IsEventQueueRunning) { TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket(); teleport.AgentData.AgentID = Client.Network.AgentID; teleport.AgentData.SessionID = Client.Network.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; } } public void SendTeleportLure(LLUUID targetID) { SendTeleportLure(targetID, "Join me in " + Client.Network.CurrentSim.Name + "!"); } public void SendTeleportLure(LLUUID targetID, string message) { StartLurePacket p = new StartLurePacket(); p.AgentData.AgentID = Client.Self.ID; p.AgentData.SessionID = Client.Network.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 /// /// UUID of the avatar requesting the teleport /// Accept the teleport request or deny it public void TeleportLureRespond(LLUUID requesterID, bool accept) { InstantMessage(Client.ToString(), requesterID, String.Empty, LLUUID.Random(), accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport, InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]); if (accept) { TeleportLureRequestPacket lure = new TeleportLureRequestPacket(); lure.Info.AgentID = Client.Network.AgentID; lure.Info.SessionID = Client.Network.SessionID; lure.Info.LureID = Client.Network.AgentID; lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure; Client.Network.SendPacket(lure); } } /// /// Grabs an object /// /// Local ID of Object to grab public void Grab(uint objectLocalID) { ObjectGrabPacket grab = new ObjectGrabPacket(); grab.AgentData.AgentID = Client.Network.AgentID; grab.AgentData.SessionID = Client.Network.SessionID; grab.ObjectData.LocalID = objectLocalID; grab.ObjectData.GrabOffset = new LLVector3(0, 0, 0); Client.Network.SendPacket(grab); } /// /// Drags on an object /// /// Strangely, LLUID instead of local ID /// Drag target in region coordinates public void GrabUpdate(LLUUID objectID, LLVector3 grabPosition) { ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket(); grab.AgentData.AgentID = Client.Network.AgentID; grab.AgentData.SessionID = Client.Network.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.Network.AgentID; degrab.AgentData.SessionID = Client.Network.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); } /// /// Rotates body toward target position /// /// Region coordinates to turn toward public bool TurnToward(LLVector3 target) { if (Client.Settings.SEND_AGENT_UPDATES) { LLQuaternion newRot = Helpers.RotBetween(new LLVector3(1, 0, 0), Helpers.VecNorm(target - Client.Self.Position)); Client.Self.Status.Camera.BodyRotation = newRot; Client.Self.Status.SendUpdate(); return true; } else { Client.Log("Attempted TurnToward but agent updates are disabled", Helpers.LogLevel.Warning); return false; } } /// /// Request to join a group. If there is an enrollment fee it will /// automatically be deducted from your balance /// /// The group to attempt to join public void RequestJoinGroup(LLUUID groupID) { JoinGroupRequestPacket join = new JoinGroupRequestPacket(); join.AgentData.AgentID = Client.Network.AgentID; join.AgentData.SessionID = Client.Network.SessionID; join.GroupData.GroupID = groupID; Client.Network.SendPacket(join); } /// /// Request to leave a group /// /// The group to attempt to leave public void RequestLeaveGroup(LLUUID groupID) { LeaveGroupRequestPacket leave = new LeaveGroupRequestPacket(); leave.AgentData.AgentID = Client.Network.AgentID; leave.AgentData.SessionID = Client.Network.SessionID; leave.GroupData.GroupID = groupID; Client.Network.SendPacket(leave); } /// /// Set our current active group /// /// The group we are a member of that we want to /// activate public void ActivateGroup(LLUUID groupID) { ActivateGroupPacket activate = new ActivateGroupPacket(); activate.AgentData.AgentID = Client.Network.AgentID; activate.AgentData.SessionID = Client.Network.SessionID; activate.AgentData.GroupID = groupID; Client.Network.SendPacket(activate); } /// /// Move an agent in to a simulator. This packet is the last packet /// needed to complete the transition in to a new simulator /// /// public void CompleteAgentMovement(Simulator simulator) { CompleteAgentMovementPacket move = new CompleteAgentMovementPacket(); move.AgentData.AgentID = Client.Network.AgentID; move.AgentData.SessionID = Client.Network.SessionID; move.AgentData.CircuitCode = Client.Network.CircuitCode; Client.Network.SendPacket(move, simulator); } /// /// Set this avatar's tier contribution /// /// Group to change tier in /// amount of tier to donate public void SetGroupContribution(LLUUID group, int contribution) { libsecondlife.Packets.SetGroupContributionPacket sgp = new SetGroupContributionPacket(); sgp.AgentData.AgentID = Client.Network.AgentID; sgp.AgentData.SessionID = Client.Network.SessionID; sgp.Data.GroupID = group; sgp.Data.Contribution = contribution; Client.Network.SendPacket(sgp); } /// /// Change the role that determines your active title /// /// Group to use /// Role to change to public void ChangeTitle(LLUUID group, LLUUID role) { libsecondlife.Packets.GroupTitleUpdatePacket gtu = new GroupTitleUpdatePacket(); gtu.AgentData.AgentID = Client.Network.AgentID; gtu.AgentData.SessionID = Client.Network.SessionID; gtu.AgentData.TitleRoleID = role; gtu.AgentData.GroupID = group; Client.Network.SendPacket(gtu); } /// /// Sends camera and action updates to the server including the /// position and orientation of our camera, and a ControlFlags field /// specifying our current movement actions /// /// /// /// /// /// /// /// /// /// public void UpdateCamera(MainAvatar.ControlFlags controlFlags, LLVector3 position, LLVector3 forwardAxis, LLVector3 leftAxis, LLVector3 upAxis, LLQuaternion bodyRotation, LLQuaternion headRotation, float farClip, AgentFlags flags, AgentState state, bool reliable) { AgentUpdatePacket update = new AgentUpdatePacket(); update.AgentData.AgentID = Client.Network.AgentID; update.AgentData.SessionID = Client.Network.SessionID; update.AgentData.BodyRotation = bodyRotation; update.AgentData.HeadRotation = headRotation; update.AgentData.CameraCenter = position; update.AgentData.CameraAtAxis = forwardAxis; update.AgentData.CameraLeftAxis = leftAxis; update.AgentData.CameraUpAxis = upAxis; update.AgentData.Far = farClip; update.AgentData.ControlFlags = (uint)controlFlags; update.AgentData.Flags = (byte)flags; update.AgentData.State = (byte)state; update.Header.Reliable = reliable; Client.Network.SendPacket(update); } /// /// /// /// /// /// /// public void ScriptQuestionReply(Simulator simulator, LLUUID itemID, LLUUID taskID, ScriptPermission permissions) { ScriptAnswerYesPacket yes = new ScriptAnswerYesPacket(); yes.AgentData.AgentID = Client.Network.AgentID; yes.AgentData.SessionID = Client.Network.SessionID; yes.Data.ItemID = itemID; yes.Data.TaskID = taskID; yes.Data.Questions = (int)permissions; Client.Network.SendPacket(yes, simulator); } #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) { OnInstantMessage( im.AgentData.AgentID , Helpers.FieldToUTF8String(im.MessageBlock.FromAgentName), im.MessageBlock.ToAgentID , im.MessageBlock.ParentEstateID , im.MessageBlock.RegionID , im.MessageBlock.Position , (InstantMessageDialog)im.MessageBlock.Dialog , im.MessageBlock.FromGroup , im.MessageBlock.ID , new DateTime(im.MessageBlock.Timestamp) , Helpers.FieldToUTF8String(im.MessageBlock.Message) , (InstantMessageOnline)im.MessageBlock.Offline , im.MessageBlock.BinaryBucket , simulator ); } } } /// /// 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 llDialog's /// /// 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(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); } } } /// /// 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; this.Position = movement.Data.Position; this.LookAt = 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 JoinGroupHandler(Packet packet, Simulator simulator) { if (OnJoinGroup != null) { JoinGroupReplyPacket reply = (JoinGroupReplyPacket)packet; OnJoinGroup(reply.GroupData.GroupID, reply.GroupData.Success); } } private void LeaveGroupHandler(Packet packet, Simulator simulator) { if (OnLeaveGroup != null) { LeaveGroupReplyPacket reply = (LeaveGroupReplyPacket)packet; OnLeaveGroup(reply.GroupData.GroupID, reply.GroupData.Success); } } public void AgentDataUpdateHandler(Packet packet, Simulator simulator) { AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet; if (p.AgentData.AgentID == simulator.Client.Network.AgentID) { if (activeGroup != p.AgentData.ActiveGroupID) { activeGroup = p.AgentData.ActiveGroupID; if (OnActiveGroupChanged != null) OnActiveGroupChanged(activeGroup); } } } private void DropGroupHandler(Packet packet, Simulator simulator) { if (OnGroupDropped != null) { OnGroupDropped(((AgentDropGroupPacket)packet).AgentData.GroupID); } } /// /// 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 EventQueueHandler(string message, Hashtable body, Caps caps) { if (message == "TeleportFinish" && body.ContainsKey("Info")) { ArrayList infoList = (ArrayList)body["Info"]; Hashtable info = (Hashtable)infoList[0]; // Backwards compatibility hack TeleportFinishPacket packet = new TeleportFinishPacket(); packet.Info.SimIP = Helpers.BytesToUIntBig((byte[])info["SimIP"]); packet.Info.LocationID = Helpers.BytesToUInt((byte[])info["LocationID"]); packet.Info.TeleportFlags = Helpers.BytesToUInt((byte[])info["TeleportFlags"]); packet.Info.AgentID = (LLUUID)info["AgentID"]; packet.Info.RegionHandle = Helpers.BytesToUInt64((byte[])info["RegionHandle"]); packet.Info.SeedCapability = Helpers.StringToField((string)info["SeedCapability"]); packet.Info.SimPort = (ushort)(int)info["SimPort"]; packet.Info.SimAccess = (byte)(int)info["SimAccess"]; Client.DebugLog(String.Format( "Received a TeleportFinish event from {0}, SimIP: {1}, Location: {2}, RegionHandle: {3}", caps.Simulator.ToString(), packet.Info.SimIP, packet.Info.LocationID, packet.Info.RegionHandle)); TeleportHandler(packet, Client.Network.CurrentSim); } else if(message == "EstablishAgentCommunication" && Client.Settings.MULTIPLE_SIMS) { string ipAndPort = (string)body["sim-ip-and-port"]; 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); } else { Client.Log("Got EstablishAgentCommunication for sim " + ipAndPort + ", seed cap " + (string)body["seed-capability"], Helpers.LogLevel.Info); sim.SetSeedCaps((string)body["seed-capability"]); } } else { Client.Log("Received unhandled event " + message + " in the EventQueueHandler", Helpers.LogLevel.Warning); } } /// /// 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 from " + simulator.ToString() + ", 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 from " + simulator.ToString() + "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 from " + simulator.ToString() + ", 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 from " + simulator.ToString() + ", 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); 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; LookAt = local.Info.LookAt; Position = local.Info.Position; // This field is apparently not used for anything //local.Info.LocationID; finished = true; Client.DebugLog("TeleportLocal received from " + simulator.ToString() + ", 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(); } } #endregion Packet Handlers }