/* * Copyright (c) 2006, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.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 AgentUpdateFlags { /// 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 } /// /// 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 LookAtTarget : 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 the L$ account balance for this avatar changes /// /// The new account balance public delegate void BalanceCallback(int balance); /// /// Tiggered 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 /// 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); /// /// 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); /// Callback for incoming chat packets public event ChatCallback OnChat; /// Callback for pop-up dialogs from scripts public event ScriptDialogCallback OnScriptDialog; /// 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 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; #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; /// 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; } } #endregion Public Members internal uint sittingOn = 0; internal string teleportMessage = String.Empty; 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; // Coarse location callback Client.Network.RegisterCallback(PacketType.CoarseLocationUpdate, new NetworkManager.PacketCallback(CoarseLocationHandler)); // 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)); // 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); Client.Network.RegisterCallback(PacketType.MoneySummaryReply, callback); Client.Network.RegisterCallback(PacketType.AdjustBalance, 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(FirstName + " " + LastName, 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(FirstName + " " + LastName, 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) Array.Copy(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(FirstName + " " + LastName, 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) Array.Copy(sourceAvatar.GetBytes(), typeData, 16); if (targetObject != null) Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); Array.Copy(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, LookAtTarget type) { ViewerEffectPacket effect = new ViewerEffectPacket(); effect.AgentData.AgentID = Client.Network.AgentID; effect.AgentData.SessionID = Client.Network.SessionID; float duration; switch (type) { case LookAtTarget.Clear: duration = 0.0f; break; case LookAtTarget.Hover: duration = 1.0f; break; case LookAtTarget.FreeLook: duration = 2.0f; break; case LookAtTarget.Idle: duration = 3.0f; break; case LookAtTarget.AutoListen: case LookAtTarget.Respond: duration = 4.0f; break; case LookAtTarget.None: case LookAtTarget.Conversation: case LookAtTarget.Select: case LookAtTarget.Focus: case LookAtTarget.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) Array.Copy(sourceAvatar.GetBytes(), typeData, 16); if (targetObject != null) Array.Copy(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) Array.Copy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); if (targetObject != null) Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); Array.Copy(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); // 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); //Send packets Client.Network.SendPacket(apup); 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); } /// /// 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 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 = Client.Grid.GetGridRegion(simName); if (region != null) { 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) { 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("Teleporting to region " + regionHandle.ToString(), Helpers.LogLevel.Info); Client.Network.SendPacket(teleport); } /// /// 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(FirstName + " " + LastName, 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); } /// /// 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); } /// /// 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.AgentUpdateFlags controlFlags, LLVector3 position, LLVector3 forwardAxis, LLVector3 leftAxis, LLVector3 upAxis, LLQuaternion bodyRotation, LLQuaternion headRotation, float farClip, bool reliable) { AgentUpdatePacket update = new AgentUpdatePacket(); update.AgentData.AgentID = Client.Network.AgentID; update.AgentData.SessionID = Client.Network.SessionID; update.AgentData.State = 0; 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 = 0; update.Header.Reliable = reliable; Client.Network.SendPacket(update); } /// /// [UNUSED - for now] /// /// /// private void CoarseLocationHandler(Packet packet, Simulator simulator) { if (packet.Type == PacketType.CoarseLocationUpdate) { CoarseLocationUpdatePacket p = (CoarseLocationUpdatePacket) packet; if (p.Index.You < 0 || p.Index.You >= p.Location.Length) return; /* 1.5 and 6 represent a 50% fudge factor (hysteresis) -- bushing */ if (Math.Abs(Position.X-p.Location[p.Index.You].X) > 1.5) Position.X=p.Location[p.Index.You].X; if (Math.Abs(Position.Y-p.Location[p.Index.You].Y) > 1.5) Position.Y=p.Location[p.Index.You].Y; if (Math.Abs(Position.Z-(p.Location[p.Index.You].Z*4)) > 6) Position.Z=p.Location[p.Index.You].Z*4; } } /// /// 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 ); } } } /// /// 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.FieldToString(button.ButtonLabel)); } OnScriptDialog(Helpers.FieldToUTF8String(dialog.Data.Message), Helpers.FieldToString(dialog.Data.ObjectName), dialog.Data.ImageID, dialog.Data.ObjectID, Helpers.FieldToUTF8String(dialog.Data.FirstName), Helpers.FieldToUTF8String(dialog.Data.LastName), dialog.Data.ChatChannel, buttons); } } /// /// 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) { activeGroup = p.AgentData.ActiveGroupID; } } 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) { balance = ((MoneyBalanceReplyPacket)packet).MoneyData.MoneyBalance; } else if (packet.Type == PacketType.MoneySummaryReply) { balance = ((MoneySummaryReplyPacket)packet).MoneyData.Balance; } else if (packet.Type == PacketType.AdjustBalance) { balance += ((AdjustBalancePacket)packet).AgentData.Delta; } if (OnBalanceUpdated != null) { OnBalanceUpdated(balance); } } private void EventQueueHandler(string message, object body) { if (message == "TeleportFinish") { Hashtable tpt = (Hashtable)body; Hashtable info = (Hashtable)tpt["Info"]; // 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)(long)info["SimPort"]; packet.Info.SimAccess = (byte)(long)info["SimAccess"]; Client.DebugLog("Received a TeleportFinish event, SimIP: " + new IPAddress(packet.Info.SimIP) + ", LocationID: " + packet.Info.LocationID + ", RegionHandle: " + packet.Info.RegionHandle); TeleportHandler(packet, Client.Network.CurrentSim); } 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() + ", 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; Simulator previousSim = Client.Network.CurrentSim; flags = (TeleportFlags)finish.Info.TeleportFlags; string seedcaps = Helpers.FieldToUTF8String(finish.Info.SeedCapability); IPAddress simIP = new IPAddress(finish.Info.SimIP); finished = true; Client.DebugLog("TeleportFinish received from " + simulator.ToString() + ", Flags: " + flags.ToString()); // Disable CAPS on the current sim since we are moving if (Client.Network.CurrentCaps != null) Client.Network.CurrentCaps.Dead = true; // Connect to the new sim Simulator sim = Client.Network.Connect(simIP, finish.Info.SimPort, true, seedcaps); if (sim != null) { teleportMessage = "Teleport finished"; TeleportStat = TeleportStatus.Finished; // Disconnect from the previous sim Client.Network.DisconnectSim(previousSim); Client.Log("Moved to new sim " + sim.ToString(), Helpers.LogLevel.Info); } else { teleportMessage = "Failed to connect to the new sim after a teleport"; TeleportStat = TeleportStatus.Failed; // Attempt to reconnect to the previous simulator // TODO: This hasn't been tested at all Client.Network.Connect(previousSim.IPEndPoint.Address, (ushort)previousSim.IPEndPoint.Port, true, Client.Network.CurrentCaps.Seedcaps); Client.Log(teleportMessage, Helpers.LogLevel.Warning); } } 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(); } } }