/* * Copyright (c) 2006-2016, openmetaverse.co * 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 openmetaverse.co nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Net; using System.Text.RegularExpressions; using System.Threading; using System.Collections.Generic; using System.Linq; using OpenMetaverse.StructuredData; using OpenMetaverse.Http; using OpenMetaverse.Assets; using OpenMetaverse.Packets; using OpenMetaverse.Interfaces; using OpenMetaverse.Messages.Linden; namespace OpenMetaverse { #region Enums /// /// Permission request flags, asked when a script wants to control an Avatar /// [Flags] public enum ScriptPermission : int { /// Placeholder for empty values, shouldn't ever see this None = 0, /// Script wants ability to take money from you Debit = 1 << 1, /// Script wants to take camera controls for you TakeControls = 1 << 2, /// Script wants to remap avatars controls RemapControls = 1 << 3, /// Script wants to trigger avatar animations /// This function is not implemented on the grid TriggerAnimation = 1 << 4, /// Script wants to attach or detach the prim or primset to your avatar Attach = 1 << 5, /// Script wants permission to release ownership /// This function is not implemented on the grid /// The concept of "public" objects does not exist anymore. ReleaseOwnership = 1 << 6, /// Script wants ability to link/delink with other prims ChangeLinks = 1 << 7, /// Script wants permission to change joints /// This function is not implemented on the grid ChangeJoints = 1 << 8, /// Script wants permissions to change permissions /// This function is not implemented on the grid ChangePermissions = 1 << 9, /// Script wants to track avatars camera position and rotation TrackCamera = 1 << 10, /// Script wants to control your camera ControlCamera = 1 << 11, /// Script wants the ability to teleport you Teleport = 1 << 12 } /// /// Special commands used in Instant Messages /// public enum InstantMessageDialog : byte { /// Indicates a regular IM from another agent MessageFromAgent = 0, /// Simple notification box with an OK button MessageBox = 1, // Used to show a countdown notification with an OK // button, deprecated now //[Obsolete] //MessageBoxCountdown = 2, /// You've been invited to join a group. GroupInvitation = 3, /// Inventory offer InventoryOffered = 4, /// Accepted inventory offer InventoryAccepted = 5, /// Declined inventory offer InventoryDeclined = 6, /// Group vote GroupVote = 7, // A message to everyone in the agent's group, no longer // used //[Obsolete] //DeprecatedGroupMessage = 8, /// An object is offering its inventory TaskInventoryOffered = 9, /// Accept an inventory offer from an object TaskInventoryAccepted = 10, /// Decline an inventory offer from an object TaskInventoryDeclined = 11, /// Unknown NewUserDefault = 12, /// Start a session, or add users to a session SessionAdd = 13, /// Start a session, but don't prune offline users SessionOfflineAdd = 14, /// Start a session with your group SessionGroupStart = 15, /// Start a session without a calling card (finder or objects) SessionCardlessStart = 16, /// Send a message to a session SessionSend = 17, /// Leave a session SessionDrop = 18, /// Indicates that the IM is from an object MessageFromObject = 19, /// Sent an IM to a busy user, this is the auto response BusyAutoResponse = 20, /// Shows the message in the console and chat history ConsoleAndChatHistory = 21, /// Send a teleport lure RequestTeleport = 22, /// Response sent to the agent which inititiated a teleport invitation AcceptTeleport = 23, /// Response sent to the agent which inititiated a teleport invitation DenyTeleport = 24, /// Only useful if you have Linden permissions GodLikeRequestTeleport = 25, /// Request a teleport lure RequestLure = 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, /// Send the message to the debug channel Debug = 6, /// Event message when an object uses llOwnerSay OwnerSay = 8, /// Event message when an object uses llRegionSayTo RegionSayTo = 9, /// Special value to support llRegionSay, never sent to the client RegionSay = byte.MaxValue, } /// /// Identifies the source of a chat message /// public enum ChatSourceType : byte { /// Chat from the grid or simulator System = 0, /// Chat from another avatar Agent = 1, /// Chat from an object Object = 2 } /// /// /// public enum ChatAudibleLevel : sbyte { /// Not = -1, /// Barely = 0, /// Fully = 1 } /// /// Effect type used in ViewerEffect packets /// public enum EffectType : byte { /// Text = 0, /// Icon, /// Connector, /// FlexibleObject, /// AnimalControls, /// AnimationObject, /// Cloth, /// Project a beam from a source to a destination, such as /// the one used when editing an object Beam, /// Glow, /// Point, /// Trail, /// Create a swirl of particles around an object Sphere, /// Spiral, /// Edit, /// Cause an avatar to look at an object LookAt, /// Cause an avatar to point at an object PointAt } /// /// The action an avatar is doing when looking at something, used in /// ViewerEffect packets for the LookAt effect /// public enum LookAtType : byte { /// None, /// Idle, /// AutoListen, /// FreeLook, /// Respond, /// Hover, /// Deprecated [Obsolete] Conversation, /// Select, /// Focus, /// Mouselook, /// Clear } /// /// The action an avatar is doing when pointing at something, used in /// ViewerEffect packets for the PointAt effect /// public enum PointAtType : byte { /// None, /// Select, /// Grab, /// Clear } /// /// Money transaction types /// public enum MoneyTransactionType : int { /// None = 0, /// FailSimulatorTimeout = 1, /// FailDataserverTimeout = 2, /// ObjectClaim = 1000, /// LandClaim = 1001, /// GroupCreate = 1002, /// ObjectPublicClaim = 1003, /// GroupJoin = 1004, /// TeleportCharge = 1100, /// UploadCharge = 1101, /// LandAuction = 1102, /// ClassifiedCharge = 1103, /// ObjectTax = 2000, /// LandTax = 2001, /// LightTax = 2002, /// ParcelDirFee = 2003, /// GroupTax = 2004, /// ClassifiedRenew = 2005, /// GiveInventory = 3000, /// ObjectSale = 5000, /// Gift = 5001, /// LandSale = 5002, /// ReferBonus = 5003, /// InventorySale = 5004, /// RefundPurchase = 5005, /// LandPassSale = 5006, /// DwellBonus = 5007, /// PayObject = 5008, /// ObjectPays = 5009, /// GroupLandDeed = 6001, /// GroupObjectDeed = 6002, /// GroupLiability = 6003, /// GroupDividend = 6004, /// GroupMembershipDues = 6005, /// ObjectRelease = 8000, /// LandRelease = 8001, /// ObjectDelete = 8002, /// ObjectPublicDecay = 8003, /// ObjectPublicDelete = 8004, /// LindenAdjustment = 9000, /// LindenGrant = 9001, /// LindenPenalty = 9002, /// EventFee = 9003, /// EventPrize = 9004, /// StipendBasic = 10000, /// StipendDeveloper = 10001, /// StipendAlways = 10002, /// StipendDaily = 10003, /// StipendRating = 10004, /// StipendDelta = 10005 } /// /// /// [Flags] public enum TransactionFlags : byte { /// None = 0, /// SourceGroup = 1, /// DestGroup = 2, /// OwnerGroup = 4, /// SimultaneousContribution = 8, /// ContributionRemoval = 16 } /// /// /// public enum MeanCollisionType : byte { /// None, /// Bump, /// LLPushObject, /// SelectedObjectCollide, /// ScriptedObjectCollide, /// PhysicalObjectCollide } /// /// Flags sent when a script takes or releases a control /// /// NOTE: (need to verify) These might be a subset of the ControlFlags enum in Movement, [Flags] public enum ScriptControlChange : uint { /// No Flags set None = 0, /// Forward (W or up Arrow) Forward = 1, /// Back (S or down arrow) Back = 2, /// Move left (shift+A or left arrow) Left = 4, /// Move right (shift+D or right arrow) Right = 8, /// Up (E or PgUp) Up = 16, /// Down (C or PgDown) Down = 32, /// Rotate left (A or left arrow) RotateLeft = 256, /// Rotate right (D or right arrow) RotateRight = 512, /// Left Mouse Button LeftButton = 268435456, /// Left Mouse button in MouseLook MouseLookLeftButton = 1073741824 } /// /// Currently only used to hide your group title /// [Flags] public enum AgentFlags : byte { /// No flags set None = 0, /// Hide your group title HideTitle = 0x01, } /// /// Action state of the avatar, which can currently be typing and /// editing /// [Flags] public enum AgentState : byte { /// None = 0x00, /// Typing = 0x04, /// Editing = 0x10 } /// /// Current teleport status /// public enum TeleportStatus { /// Unknown status None, /// Teleport initialized Start, /// Teleport in progress Progress, /// Teleport failed Failed, /// Teleport completed Finished, /// Teleport cancelled Cancelled } /// /// /// [Flags] public enum TeleportFlags : uint { /// No flags set, or teleport failed Default = 0, /// Set when newbie leaves help island for first time SetHomeToTarget = 1 << 0, /// SetLastToTarget = 1 << 1, /// Via Lure ViaLure = 1 << 2, /// Via Landmark ViaLandmark = 1 << 3, /// Via Location ViaLocation = 1 << 4, /// Via Home ViaHome = 1 << 5, /// Via Telehub ViaTelehub = 1 << 6, /// Via Login ViaLogin = 1 << 7, /// Linden Summoned ViaGodlikeLure = 1 << 8, /// Linden Forced me Godlike = 1 << 9, /// NineOneOne = 1 << 10, /// Agent Teleported Home via Script DisableCancel = 1 << 11, /// ViaRegionID = 1 << 12, /// IsFlying = 1 << 13, /// ResetHome = 1 << 14, /// forced to new location for example when avatar is banned or ejected ForceRedirect = 1 << 15, /// Teleport Finished via a Lure FinishedViaLure = 1 << 26, /// Finished, Sim Changed FinishedViaNewSim = 1 << 28, /// Finished, Same Sim FinishedViaSameSim = 1 << 29 } /// /// /// [Flags] public enum TeleportLureFlags { /// NormalLure = 0, /// GodlikeLure = 1, /// GodlikePursuit = 2 } /// /// /// [Flags] public enum ScriptSensorTypeFlags { /// Agent = 1, /// Active = 2, /// Passive = 4, /// Scripted = 8, } /// /// Type of mute entry /// public enum MuteType { /// Object muted by name ByName = 0, /// Muted residet Resident = 1, /// Object muted by UUID Object = 2, /// Muted group Group = 3, /// Muted external entry External = 4 } /// /// Flags of mute entry /// [Flags] public enum MuteFlags : int { /// No exceptions Default = 0x0, /// Don't mute text chat TextChat = 0x1, /// Don't mute voice chat VoiceChat = 0x2, /// Don't mute particles Particles = 0x4, /// Don't mute sounds ObjectSounds = 0x8, /// Don't mute All = 0xf } #endregion Enums #region Structs /// /// Instant Message /// public struct InstantMessage { /// Key of sender public UUID FromAgentID; /// Name of sender public string FromAgentName; /// Key of destination avatar public UUID ToAgentID; /// ID of originating estate public uint ParentEstateID; /// Key of originating region public UUID RegionID; /// Coordinates in originating region public Vector3 Position; /// Instant message type public InstantMessageDialog Dialog; /// Group IM session toggle public bool GroupIM; /// Key of IM session, for Group Messages, the groups UUID public UUID IMSessionID; /// Timestamp of the instant message public DateTime Timestamp; /// Instant message text public string Message; /// Whether this message is held for offline avatars public InstantMessageOnline Offline; /// Context specific packed data public byte[] BinaryBucket; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// Represents muted object or resident public class MuteEntry { /// Type of the mute entry public MuteType Type; /// UUID of the mute etnry public UUID ID; /// Mute entry name public string Name; /// Mute flags public MuteFlags Flags; } /// Transaction detail sent with MoneyBalanceReply message public class TransactionInfo { /// Type of the transaction public int TransactionType; // FIXME: this should be an enum /// UUID of the transaction source public UUID SourceID; /// Is the transaction source a group public bool IsSourceGroup; /// UUID of the transaction destination public UUID DestID; /// Is transaction destination a group public bool IsDestGroup; /// Transaction amount public int Amount; /// Transaction description public string ItemDescription; } #endregion Structs /// /// Manager class for our own avatar /// public partial class AgentManager { #region Delegates /// /// Called once attachment resource usage information has been collected /// /// Indicates if operation was successfull /// Attachment resource usage information public delegate void AttachmentResourcesCallback(bool success, AttachmentResourcesMessage info); #endregion Delegates #region Event Delegates /// The event subscribers. null if no subcribers private EventHandler m_Chat; /// Raises the ChatFromSimulator event /// A ChatEventArgs object containing the /// data returned from the data server protected virtual void OnChat(ChatEventArgs e) { EventHandler handler = m_Chat; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ChatLock = new object(); /// Raised when a scripted object or agent within range sends a public message public event EventHandler ChatFromSimulator { add { lock (m_ChatLock) { m_Chat += value; } } remove { lock (m_ChatLock) { m_Chat -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ScriptDialog; /// Raises the ScriptDialog event /// A SctriptDialogEventArgs object containing the /// data returned from the data server protected virtual void OnScriptDialog(ScriptDialogEventArgs e) { EventHandler handler = m_ScriptDialog; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ScriptDialogLock = new object(); /// Raised when a scripted object sends a dialog box containing possible /// options an agent can respond to public event EventHandler ScriptDialog { add { lock (m_ScriptDialogLock) { m_ScriptDialog += value; } } remove { lock (m_ScriptDialogLock) { m_ScriptDialog -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ScriptQuestion; /// Raises the ScriptQuestion event /// A ScriptQuestionEventArgs object containing the /// data returned from the data server protected virtual void OnScriptQuestion(ScriptQuestionEventArgs e) { EventHandler handler = m_ScriptQuestion; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ScriptQuestionLock = new object(); /// Raised when an object requests a change in the permissions an agent has permitted public event EventHandler ScriptQuestion { add { lock (m_ScriptQuestionLock) { m_ScriptQuestion += value; } } remove { lock (m_ScriptQuestionLock) { m_ScriptQuestion -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_LoadURL; /// Raises the LoadURL event /// A LoadUrlEventArgs object containing the /// data returned from the data server protected virtual void OnLoadURL(LoadUrlEventArgs e) { EventHandler handler = m_LoadURL; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_LoadUrlLock = new object(); /// Raised when a script requests an agent open the specified URL public event EventHandler LoadURL { add { lock (m_LoadUrlLock) { m_LoadURL += value; } } remove { lock (m_LoadUrlLock) { m_LoadURL -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_Balance; /// Raises the MoneyBalance event /// A BalanceEventArgs object containing the /// data returned from the data server protected virtual void OnBalance(BalanceEventArgs e) { EventHandler handler = m_Balance; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_BalanceLock = new object(); /// Raised when an agents currency balance is updated public event EventHandler MoneyBalance { add { lock (m_BalanceLock) { m_Balance += value; } } remove { lock (m_BalanceLock) { m_Balance -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_MoneyBalance; /// Raises the MoneyBalanceReply event /// A MoneyBalanceReplyEventArgs object containing the /// data returned from the data server protected virtual void OnMoneyBalanceReply(MoneyBalanceReplyEventArgs e) { EventHandler handler = m_MoneyBalance; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_MoneyBalanceReplyLock = new object(); /// Raised when a transaction occurs involving currency such as a land purchase public event EventHandler MoneyBalanceReply { add { lock (m_MoneyBalanceReplyLock) { m_MoneyBalance += value; } } remove { lock (m_MoneyBalanceReplyLock) { m_MoneyBalance -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_InstantMessage; /// Raises the IM event /// A InstantMessageEventArgs object containing the /// data returned from the data server protected virtual void OnInstantMessage(InstantMessageEventArgs e) { EventHandler handler = m_InstantMessage; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_InstantMessageLock = new object(); /// Raised when an ImprovedInstantMessage packet is recieved from the simulator, this is used for everything from /// private messaging to friendship offers. The Dialog field defines what type of message has arrived public event EventHandler IM { add { lock (m_InstantMessageLock) { m_InstantMessage += value; } } remove { lock (m_InstantMessageLock) { m_InstantMessage -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_Teleport; /// Raises the TeleportProgress event /// A TeleportEventArgs object containing the /// data returned from the data server protected virtual void OnTeleport(TeleportEventArgs e) { EventHandler handler = m_Teleport; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_TeleportLock = new object(); /// Raised when an agent has requested a teleport to another location, or when responding to a lure. Raised multiple times /// for each teleport indicating the progress of the request public event EventHandler TeleportProgress { add { lock (m_TeleportLock) { m_Teleport += value; } } remove { lock (m_TeleportLock) { m_Teleport += value; } } } /// The event subscribers. null if no subcribers private EventHandler m_AgentData; /// Raises the AgentDataReply event /// A AgentDataReplyEventArgs object containing the /// data returned from the data server protected virtual void OnAgentData(AgentDataReplyEventArgs e) { EventHandler handler = m_AgentData; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_AgentDataLock = new object(); /// Raised when a simulator sends agent specific information for our avatar. public event EventHandler AgentDataReply { add { lock (m_AgentDataLock) { m_AgentData += value; } } remove { lock (m_AgentDataLock) { m_AgentData -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_AnimationsChanged; /// Raises the AnimationsChanged event /// A AnimationsChangedEventArgs object containing the /// data returned from the data server protected virtual void OnAnimationsChanged(AnimationsChangedEventArgs e) { EventHandler handler = m_AnimationsChanged; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_AnimationsChangedLock = new object(); /// Raised when our agents animation playlist changes public event EventHandler AnimationsChanged { add { lock (m_AnimationsChangedLock) { m_AnimationsChanged += value; } } remove { lock (m_AnimationsChangedLock) { m_AnimationsChanged -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_MeanCollision; /// Raises the MeanCollision event /// A MeanCollisionEventArgs object containing the /// data returned from the data server protected virtual void OnMeanCollision(MeanCollisionEventArgs e) { EventHandler handler = m_MeanCollision; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_MeanCollisionLock = new object(); /// Raised when an object or avatar forcefully collides with our agent public event EventHandler MeanCollision { add { lock (m_MeanCollisionLock) { m_MeanCollision += value; } } remove { lock (m_MeanCollisionLock) { m_MeanCollision -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_RegionCrossed; /// Raises the RegionCrossed event /// A RegionCrossedEventArgs object containing the /// data returned from the data server protected virtual void OnRegionCrossed(RegionCrossedEventArgs e) { EventHandler handler = m_RegionCrossed; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_RegionCrossedLock = new object(); /// Raised when our agent crosses a region border into another region public event EventHandler RegionCrossed { add { lock (m_RegionCrossedLock) { m_RegionCrossed += value; } } remove { lock (m_RegionCrossedLock) { m_RegionCrossed -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_GroupChatJoined; /// Raises the GroupChatJoined event /// A GroupChatJoinedEventArgs object containing the /// data returned from the data server protected virtual void OnGroupChatJoined(GroupChatJoinedEventArgs e) { EventHandler handler = m_GroupChatJoined; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_GroupChatJoinedLock = new object(); /// Raised when our agent succeeds or fails to join a group chat session public event EventHandler GroupChatJoined { add { lock (m_GroupChatJoinedLock) { m_GroupChatJoined += value; } } remove { lock (m_GroupChatJoinedLock) { m_GroupChatJoined -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_AlertMessage; /// Raises the AlertMessage event /// A AlertMessageEventArgs object containing the /// data returned from the data server protected virtual void OnAlertMessage(AlertMessageEventArgs e) { EventHandler handler = m_AlertMessage; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_AlertMessageLock = new object(); /// Raised when a simulator sends an urgent message usually indication the recent failure of /// another action we have attempted to take such as an attempt to enter a parcel where we are denied access public event EventHandler AlertMessage { add { lock (m_AlertMessageLock) { m_AlertMessage += value; } } remove { lock (m_AlertMessageLock) { m_AlertMessage -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ScriptControl; /// Raises the ScriptControlChange event /// A ScriptControlEventArgs object containing the /// data returned from the data server protected virtual void OnScriptControlChange(ScriptControlEventArgs e) { EventHandler handler = m_ScriptControl; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ScriptControlLock = new object(); /// Raised when a script attempts to take or release specified controls for our agent public event EventHandler ScriptControlChange { add { lock (m_ScriptControlLock) { m_ScriptControl += value; } } remove { lock (m_ScriptControlLock) { m_ScriptControl -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_CameraConstraint; /// Raises the CameraConstraint event /// A CameraConstraintEventArgs object containing the /// data returned from the data server protected virtual void OnCameraConstraint(CameraConstraintEventArgs e) { EventHandler handler = m_CameraConstraint; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_CameraConstraintLock = new object(); /// Raised when the simulator detects our agent is trying to view something /// beyond its limits public event EventHandler CameraConstraint { add { lock (m_CameraConstraintLock) { m_CameraConstraint += value; } } remove { lock (m_CameraConstraintLock) { m_CameraConstraint -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ScriptSensorReply; /// Raises the ScriptSensorReply event /// A ScriptSensorReplyEventArgs object containing the /// data returned from the data server protected virtual void OnScriptSensorReply(ScriptSensorReplyEventArgs e) { EventHandler handler = m_ScriptSensorReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ScriptSensorReplyLock = new object(); /// Raised when a script sensor reply is received from a simulator public event EventHandler ScriptSensorReply { add { lock (m_ScriptSensorReplyLock) { m_ScriptSensorReply += value; } } remove { lock (m_ScriptSensorReplyLock) { m_ScriptSensorReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_AvatarSitResponse; /// Raises the AvatarSitResponse event /// A AvatarSitResponseEventArgs object containing the /// data returned from the data server protected virtual void OnAvatarSitResponse(AvatarSitResponseEventArgs e) { EventHandler handler = m_AvatarSitResponse; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_AvatarSitResponseLock = new object(); /// Raised in response to a request public event EventHandler AvatarSitResponse { add { lock (m_AvatarSitResponseLock) { m_AvatarSitResponse += value; } } remove { lock (m_AvatarSitResponseLock) { m_AvatarSitResponse -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ChatSessionMemberAdded; /// Raises the ChatSessionMemberAdded event /// A ChatSessionMemberAddedEventArgs object containing the /// data returned from the data server protected virtual void OnChatSessionMemberAdded(ChatSessionMemberAddedEventArgs e) { EventHandler handler = m_ChatSessionMemberAdded; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ChatSessionMemberAddedLock = new object(); /// Raised when an avatar enters a group chat session we are participating in public event EventHandler ChatSessionMemberAdded { add { lock (m_ChatSessionMemberAddedLock) { m_ChatSessionMemberAdded += value; } } remove { lock (m_ChatSessionMemberAddedLock) { m_ChatSessionMemberAdded -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ChatSessionMemberLeft; /// Raises the ChatSessionMemberLeft event /// A ChatSessionMemberLeftEventArgs object containing the /// data returned from the data server protected virtual void OnChatSessionMemberLeft(ChatSessionMemberLeftEventArgs e) { EventHandler handler = m_ChatSessionMemberLeft; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_ChatSessionMemberLeftLock = new object(); /// Raised when an agent exits a group chat session we are participating in public event EventHandler ChatSessionMemberLeft { add { lock (m_ChatSessionMemberLeftLock) { m_ChatSessionMemberLeft += value; } } remove { lock (m_ChatSessionMemberLeftLock) { m_ChatSessionMemberLeft -= value; } } } /// The event subscribers, null of no subscribers private EventHandler m_SetDisplayNameReply; ///Raises the SetDisplayNameReply Event /// A SetDisplayNameReplyEventArgs object containing /// the data sent from the simulator protected virtual void OnSetDisplayNameReply(SetDisplayNameReplyEventArgs e) { EventHandler handler = m_SetDisplayNameReply; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_SetDisplayNameReplyLock = new object(); /// Raised when the simulator sends us data containing /// the details of display name change public event EventHandler SetDisplayNameReply { add { lock (m_SetDisplayNameReplyLock) { m_SetDisplayNameReply += value; } } remove { lock (m_SetDisplayNameReplyLock) { m_SetDisplayNameReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_MuteListUpdated; /// Raises the MuteListUpdated event /// A EventArgs object containing the /// data returned from the data server protected virtual void OnMuteListUpdated(EventArgs e) { EventHandler handler = m_MuteListUpdated; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_MuteListUpdatedLock = new object(); /// Raised when a scripted object or agent within range sends a public message public event EventHandler MuteListUpdated { add { lock (m_MuteListUpdatedLock) { m_MuteListUpdated += value; } } remove { lock (m_MuteListUpdatedLock) { m_MuteListUpdated -= value; } } } #endregion Callbacks /// Reference to the GridClient instance private readonly GridClient Client; /// Used for movement and camera tracking public readonly AgentMovement Movement; /// Currently playing animations for the agent. Can be used to /// check the current movement status such as walking, hovering, aiming, /// etc. by checking against system animations found in the Animations class public InternalDictionary SignaledAnimations = new InternalDictionary(); /// Dictionary containing current Group Chat sessions and members public InternalDictionary> GroupChatSessions = new InternalDictionary>(); /// Dictionary containing mute list keyead on mute name and key public InternalDictionary MuteList = new InternalDictionary(); public InternalDictionary ActiveGestures { get; } = new InternalDictionary(); #region Properties /// Your (client) avatars /// "client", "agent", and "avatar" all represent the same thing public UUID AgentID => id; /// Temporary assigned to this session, used for /// verifying our identity in packets public UUID SessionID => sessionID; /// Shared secret that is never sent over the wire public UUID SecureSessionID => secureSessionID; /// Your (client) avatar ID, local to the current region/sim public uint LocalID => localID; /// Where the avatar started at login. Can be "last", "home" /// or a login public string StartLocation => startLocation; /// The access level of this agent, usually M, PG or A public string AgentAccess => agentAccess; /// The CollisionPlane of Agent public Vector4 CollisionPlane => collisionPlane; /// An representing the velocity of our agent public Vector3 Velocity => velocity; /// An representing the acceleration of our agent public Vector3 Acceleration => acceleration; /// A which specifies the angular speed, and axis about which an Avatar is rotating. public Vector3 AngularVelocity => angularVelocity; /// Position avatar client will goto when login to 'home' or during /// teleport request to 'home' region. public Vector3 HomePosition => homePosition; /// LookAt point saved/restored with HomePosition public Vector3 HomeLookAt => homeLookAt; /// Avatar First Name (i.e. Philip) public string FirstName => firstName; /// Avatar Last Name (i.e. Linden) public string LastName => lastName; /// LookAt point received with the login response message public Vector3 LookAt => lookAt; /// Avatar Full Name (i.e. Philip Linden) public string Name { get { // This is a fairly common request, so assume the name doesn't // change mid-session and cache the result if (fullName == null || fullName.Length < 2) fullName = $"{firstName} {lastName}"; return fullName; } } /// Gets the health of the agent public float Health => health; /// Gets the current balance of the agent public int Balance => balance; /// Gets the local ID of the prim the agent is sitting on, /// zero if the avatar is not currently sitting public uint SittingOn => sittingOn; /// Gets the of the agents active group. public UUID ActiveGroup => activeGroup; /// Gets the Agents powers in the currently active group public GroupPowers ActiveGroupPowers => activeGroupPowers; /// Current status message for teleporting public string TeleportMessage => teleportMessage; /// Current position of the agent as a relative offset from /// the simulator, or the parent object if we are sitting on something public Vector3 RelativePosition { get { return relativePosition; } set { relativePosition = value; } } /// Current rotation of the agent as a relative rotation from /// the simulator, or the parent object if we are sitting on something public Quaternion RelativeRotation { get { return relativeRotation; } set { relativeRotation = value; } } /// Current position of the agent in the simulator public Vector3 SimPosition { get { // simple case, agent not seated if (sittingOn == 0) { return relativePosition; } // a bit more complicatated, agent sitting on a prim Primitive p; Vector3 fullPosition = relativePosition; if (Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(sittingOn, out p)) { fullPosition = p.Position + relativePosition * p.Rotation; } // go up the hiearchy trying to find the root prim while (p != null && p.ParentID != 0) { Avatar av; if (Client.Network.CurrentSim.ObjectsAvatars.TryGetValue(p.ParentID, out av)) { p = av; fullPosition += p.Position; } else { if (Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(p.ParentID, out p)) { fullPosition += p.Position; } } } if (p != null) // we found the root prim { return fullPosition; } // Didn't find the seat's root prim, try returning coarse loaction if (Client.Network.CurrentSim.avatarPositions.TryGetValue(AgentID, out fullPosition)) { return fullPosition; } Logger.Log("Failed to determine agents sim position", Helpers.LogLevel.Warning, Client); return relativePosition; } } /// /// A representing the agents current rotation /// public Quaternion SimRotation { get { if (sittingOn != 0) { Primitive parent; if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(sittingOn, out parent)) { return relativeRotation * parent.Rotation; } Logger.Log( $"Currently sitting on object {sittingOn} which is not tracked, SimRotation will be inaccurate", Helpers.LogLevel.Warning, Client); return relativeRotation; } return relativeRotation; } } /// Returns the global grid position of the avatar public Vector3d GlobalPosition { get { if (Client.Network.CurrentSim != null) { uint globalX, globalY; Utils.LongToUInts(Client.Network.CurrentSim.Handle, out globalX, out globalY); Vector3 pos = SimPosition; return new Vector3d( globalX + pos.X, globalY + pos.Y, pos.Z); } return Vector3d.Zero; } } /// Various abilities and preferences sent by the grid public AgentStateUpdateMessage AgentStateStatus; #endregion Properties internal uint localID; internal Vector3 relativePosition; internal Quaternion relativeRotation = Quaternion.Identity; internal Vector4 collisionPlane; internal Vector3 velocity; internal Vector3 acceleration; internal Vector3 angularVelocity; internal uint sittingOn; internal int lastInterpolation; #region Private Members private UUID id; private UUID sessionID; private UUID secureSessionID; private string startLocation = string.Empty; private string agentAccess = string.Empty; private Vector3 homePosition; private Vector3 homeLookAt; private Vector3 lookAt; private string firstName = string.Empty; private string lastName = string.Empty; private string fullName; private string teleportMessage = string.Empty; private TeleportStatus teleportStat = TeleportStatus.None; private ManualResetEvent teleportEvent = new ManualResetEvent(false); private uint heightWidthGenCounter; private float health; private int balance; private UUID activeGroup; private GroupPowers activeGroupPowers; private Dictionary gestureCache = new Dictionary(); #endregion Private Members /// /// Constructor, setup callbacks for packets related to our avatar /// /// A reference to the Class public AgentManager(GridClient client) { Client = client; Movement = new AgentMovement(Client); Client.Network.Disconnected += Network_OnDisconnected; // Teleport callbacks Client.Network.RegisterCallback(PacketType.TeleportStart, TeleportHandler); Client.Network.RegisterCallback(PacketType.TeleportProgress, TeleportHandler); Client.Network.RegisterCallback(PacketType.TeleportFailed, TeleportHandler); Client.Network.RegisterCallback(PacketType.TeleportCancel, TeleportHandler); Client.Network.RegisterCallback(PacketType.TeleportLocal, TeleportHandler); // these come in via the EventQueue Client.Network.RegisterEventCallback("TeleportFailed", new Caps.EventQueueCallback(TeleportFailedEventHandler)); Client.Network.RegisterEventCallback("TeleportFinish", new Caps.EventQueueCallback(TeleportFinishEventHandler)); // Instant message callback Client.Network.RegisterCallback(PacketType.ImprovedInstantMessage, InstantMessageHandler); // Chat callback Client.Network.RegisterCallback(PacketType.ChatFromSimulator, ChatHandler); // Script dialog callback Client.Network.RegisterCallback(PacketType.ScriptDialog, ScriptDialogHandler); // Script question callback Client.Network.RegisterCallback(PacketType.ScriptQuestion, ScriptQuestionHandler); // Script URL callback Client.Network.RegisterCallback(PacketType.LoadURL, LoadURLHandler); // Movement complete callback Client.Network.RegisterCallback(PacketType.AgentMovementComplete, MovementCompleteHandler); // Health callback Client.Network.RegisterCallback(PacketType.HealthMessage, HealthHandler); // Money callback Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, MoneyBalanceReplyHandler); //Agent update callback Client.Network.RegisterCallback(PacketType.AgentDataUpdate, AgentDataUpdateHandler); // Animation callback Client.Network.RegisterCallback(PacketType.AvatarAnimation, AvatarAnimationHandler, false); // Object colliding into our agent callback Client.Network.RegisterCallback(PacketType.MeanCollisionAlert, MeanCollisionAlertHandler); // Region Crossing Client.Network.RegisterCallback(PacketType.CrossedRegion, CrossedRegionHandler); Client.Network.RegisterEventCallback("CrossedRegion", CrossedRegionEventHandler); // CAPS callbacks Client.Network.RegisterEventCallback("EstablishAgentCommunication", EstablishAgentCommunicationEventHandler); Client.Network.RegisterEventCallback("SetDisplayNameReply", SetDisplayNameReplyEventHandler); Client.Network.RegisterEventCallback("AgentStateUpdate", AgentStateUpdateEventHandler); // Incoming Group Chat Client.Network.RegisterEventCallback("ChatterBoxInvitation", ChatterBoxInvitationEventHandler); // Outgoing Group Chat Reply Client.Network.RegisterEventCallback("ChatterBoxSessionEventReply", ChatterBoxSessionEventReplyEventHandler); Client.Network.RegisterEventCallback("ChatterBoxSessionStartReply", ChatterBoxSessionStartReplyEventHandler); Client.Network.RegisterEventCallback("ChatterBoxSessionAgentListUpdates", ChatterBoxSessionAgentListUpdatesEventHandler); // Login Client.Network.RegisterLoginResponseCallback(Network_OnLoginResponse); // Alert Messages Client.Network.RegisterCallback(PacketType.AlertMessage, AlertMessageHandler); // script control change messages, ie: when an in-world LSL script wants to take control of your agent. Client.Network.RegisterCallback(PacketType.ScriptControlChange, ScriptControlChangeHandler); // Camera Constraint (probably needs to move to AgentManagerCamera TODO: Client.Network.RegisterCallback(PacketType.CameraConstraint, CameraConstraintHandler); Client.Network.RegisterCallback(PacketType.ScriptSensorReply, ScriptSensorReplyHandler); Client.Network.RegisterCallback(PacketType.AvatarSitResponse, AvatarSitResponseHandler); // Process mute list update message Client.Network.RegisterCallback(PacketType.MuteListUpdate, MuteListUpdateHander); } #region Chat and instant messages /// /// Send a text message from the Agent to the Simulator /// /// A containing the message /// The channel to send the message on, 0 is the public channel. Channels above 0 /// can be used however only scripts listening on the specified channel will see the message /// Denotes the type of message being sent, shout, whisper, etc. public void Chat(string message, int channel, ChatType type) { ChatFromViewerPacket chat = new ChatFromViewerPacket { AgentData = { AgentID = id, SessionID = Client.Self.SessionID }, ChatData = { Channel = channel, Message = Utils.StringToBytes(message), Type = (byte) type } }; Client.Network.SendPacket(chat); } /// /// Request any instant messages sent while the client was offline to be resent. /// public void RetrieveInstantMessages() { RetrieveInstantMessagesPacket p = new RetrieveInstantMessagesPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID } }; Client.Network.SendPacket(p); } /// /// Send an Instant Message to another Avatar /// /// The recipients /// A containing the message to send public void InstantMessage(UUID target, string message) { InstantMessage(Name, target, message, AgentID.Equals(target) ? AgentID : target ^ AgentID, InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, SimPosition, UUID.Zero, Utils.EmptyBytes); } /// /// Send an Instant Message to an existing group chat or conference chat /// /// The recipients /// A containing the message to send /// IM session ID (to differentiate between IM windows) public void InstantMessage(UUID target, string message, UUID imSessionID) { InstantMessage(Name, target, message, imSessionID, InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, SimPosition, UUID.Zero, Utils.EmptyBytes); } /// /// Send an Instant Message /// /// The name this IM will show up as being from /// Key of Avatar /// Text message being sent /// IM session ID (to differentiate between IM windows) /// IDs of sessions for a conference public void InstantMessage(string fromName, UUID target, string message, UUID imSessionID, UUID[] conferenceIDs) { byte[] binaryBucket; if (conferenceIDs != null && conferenceIDs.Length > 0) { binaryBucket = new byte[16 * conferenceIDs.Length]; for (int i = 0; i < conferenceIDs.Length; ++i) Buffer.BlockCopy(conferenceIDs[i].GetBytes(), 0, binaryBucket, i * 16, 16); } else { binaryBucket = Utils.EmptyBytes; } InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, Vector3.Zero, UUID.Zero, binaryBucket); } /// /// Send an Instant Message /// /// The name this IM will show up as being from /// Key of Avatar /// Text message being sent /// IM session ID (to differentiate between IM windows) /// Type of instant message to send /// Whether to IM offline avatars as well /// Senders Position /// RegionID Sender is In /// Packed binary data that is specific to /// the dialog type public void InstantMessage(string fromName, UUID target, string message, UUID imSessionID, InstantMessageDialog dialog, InstantMessageOnline offline, Vector3 position, UUID regionID, byte[] binaryBucket) { if (target != UUID.Zero) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); if (imSessionID.Equals(UUID.Zero) || imSessionID.Equals(AgentID)) imSessionID = AgentID.Equals(target) ? AgentID : target ^ AgentID; im.AgentData.AgentID = Client.Self.AgentID; im.AgentData.SessionID = Client.Self.SessionID; im.MessageBlock.Dialog = (byte)dialog; im.MessageBlock.FromAgentName = Utils.StringToBytes(fromName); im.MessageBlock.FromGroup = false; im.MessageBlock.ID = imSessionID; im.MessageBlock.Message = Utils.StringToBytes(message); im.MessageBlock.Offline = (byte)offline; im.MessageBlock.ToAgentID = target; im.MessageBlock.BinaryBucket = binaryBucket ?? Utils.EmptyBytes; // These fields are mandatory, even if we don't have valid values for them im.MessageBlock.Position = Vector3.Zero; //TODO: Allow region id to be correctly set by caller or fetched from Client.* im.MessageBlock.RegionID = regionID; // Send the message Client.Network.SendPacket(im); } else { Logger.Log($"Suppressing instant message \"{message}\" to UUID.Zero", Helpers.LogLevel.Error, Client); } } /// /// Send an Instant Message to a group /// /// of the group to send message to /// Text Message being sent. public void InstantMessageGroup(UUID groupID, string message) { InstantMessageGroup(Name, groupID, message); } /// /// Send an Instant Message to a group the agent is a member of /// /// The name this IM will show up as being from /// of the group to send message to /// Text message being sent public void InstantMessageGroup(string fromName, UUID groupID, string message) { lock (GroupChatSessions.Dictionary) if (GroupChatSessions.ContainsKey(groupID)) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MessageBlock = { Dialog = (byte) InstantMessageDialog.SessionSend, FromAgentName = Utils.StringToBytes(fromName), FromGroup = false, Message = Utils.StringToBytes(message), Offline = 0, ID = groupID, ToAgentID = groupID, Position = Vector3.Zero, RegionID = UUID.Zero, BinaryBucket = Utils.StringToBytes("\0") } }; Client.Network.SendPacket(im); } else { Logger.Log("No Active group chat session appears to exist, use RequestJoinGroupChat() to join one", Helpers.LogLevel.Error, Client); } } /// /// Send a request to join a group chat session /// /// of Group to leave public void RequestJoinGroupChat(UUID groupID) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MessageBlock = { Dialog = (byte) InstantMessageDialog.SessionGroupStart, FromAgentName = Utils.StringToBytes(Client.Self.Name), FromGroup = false, Message = Utils.EmptyBytes, ParentEstateID = 0, Offline = 0, ID = groupID, ToAgentID = groupID, BinaryBucket = Utils.EmptyBytes, Position = Client.Self.SimPosition, RegionID = UUID.Zero } }; Client.Network.SendPacket(im); } /// /// Exit a group chat session. This will stop further Group chat messages /// from being sent until session is rejoined. /// /// of Group chat session to leave public void RequestLeaveGroupChat(UUID groupID) { ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MessageBlock = { Dialog = (byte) InstantMessageDialog.SessionDrop, FromAgentName = Utils.StringToBytes(Client.Self.Name), FromGroup = false, Message = Utils.EmptyBytes, Offline = 0, ID = groupID, ToAgentID = groupID, BinaryBucket = Utils.EmptyBytes, Position = Vector3.Zero, RegionID = UUID.Zero } }; Client.Network.SendPacket(im); lock (GroupChatSessions.Dictionary) if (GroupChatSessions.ContainsKey(groupID)) GroupChatSessions.Remove(groupID); } /// /// Reply to script dialog questions. /// /// Channel initial request came on /// Index of button you're "clicking" /// Label of button you're "clicking" /// of Object that sent the dialog request /// public void ReplyToScriptDialog(int channel, int buttonIndex, string buttonlabel, UUID objectID) { ScriptDialogReplyPacket reply = new ScriptDialogReplyPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Data = { ButtonIndex = buttonIndex, ButtonLabel = Utils.StringToBytes(buttonlabel), ChatChannel = channel, ObjectID = objectID } }; Client.Network.SendPacket(reply); } /// /// Accept invite for to a chatterbox session /// /// of session to accept invite to public void ChatterBoxAcceptInvite(UUID session_id) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) { throw new Exception("ChatSessionRequest capability is not currently available"); } CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("ChatSessionRequest"); if (request != null) { ChatSessionAcceptInvitation acceptInvite = new ChatSessionAcceptInvitation {SessionID = session_id}; request.BeginGetResponse(acceptInvite.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); lock (GroupChatSessions.Dictionary) if (!GroupChatSessions.ContainsKey(session_id)) GroupChatSessions.Add(session_id, new List()); } else { throw new Exception("ChatSessionRequest capability is not currently available"); } } /// /// Start a friends conference /// /// List of UUIDs to start a conference with /// the temporary session ID returned in the callback> public void StartIMConference(List participants, UUID tmp_session_id) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) { throw new Exception("ChatSessionRequest capability is not currently available"); } CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("ChatSessionRequest"); if (request != null) { ChatSessionRequestStartConference startConference = new ChatSessionRequestStartConference { AgentsBlock = new UUID[participants.Count] }; for (int i = 0; i < participants.Count; i++) { startConference.AgentsBlock[i] = participants[i]; } startConference.SessionID = tmp_session_id; request.BeginGetResponse(startConference.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { throw new Exception("ChatSessionRequest capability is not currently available"); } } #endregion Chat and instant messages #region Viewer Effects /// /// Start a particle stream between an agent and an object /// /// Key of the source agent /// Key of the target object /// /// The type from the enum /// A unique for this effect public void PointAtEffect(UUID sourceAvatar, UUID targetObject, Vector3d globalOffset, PointAtType type, UUID effectID) { ViewerEffectPacket effect = new ViewerEffectPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Effect = new ViewerEffectPacket.EffectBlock[1] }; effect.Effect[0] = new ViewerEffectPacket.EffectBlock { AgentID = Client.Self.AgentID, Color = new byte[4], Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f, ID = effectID, Type = (byte) EffectType.PointAt }; byte[] typeData = new byte[57]; if (sourceAvatar != UUID.Zero) Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); if (targetObject != UUID.Zero) Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16); Buffer.BlockCopy(globalOffset.GetBytes(), 0, typeData, 32, 24); typeData[56] = (byte)type; effect.Effect[0].TypeData = typeData; Client.Network.SendPacket(effect); } /// /// Start a particle stream between an agent and an object /// /// Key of the source agent /// Key of the target object /// A representing the beams offset from the source /// A which sets the avatars lookat animation /// of the Effect public void LookAtEffect(UUID sourceAvatar, UUID targetObject, Vector3d globalOffset, LookAtType type, UUID effectID) { ViewerEffectPacket effect = new ViewerEffectPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID } }; float duration; switch (type) { case LookAtType.Clear: duration = 2.0f; break; case LookAtType.Hover: duration = 1.0f; break; case LookAtType.FreeLook: duration = 2.0f; break; case LookAtType.Idle: duration = 3.0f; break; case LookAtType.AutoListen: case LookAtType.Respond: duration = 4.0f; break; case LookAtType.None: case LookAtType.Select: case LookAtType.Focus: case LookAtType.Mouselook: duration = Single.MaxValue / 2.0f; break; default: duration = 0.0f; break; } effect.Effect = new ViewerEffectPacket.EffectBlock[1]; effect.Effect[0] = new ViewerEffectPacket.EffectBlock { AgentID = Client.Self.AgentID, Color = new byte[4], Duration = duration, ID = effectID, Type = (byte) EffectType.LookAt }; byte[] typeData = new byte[57]; Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); 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); } /// /// Create a particle beam between an avatar and an primitive /// /// The ID of source avatar /// The ID of the target primitive /// global offset /// A object containing the combined red, green, blue and alpha /// color values of particle beam /// a float representing the duration the parcicle beam will last /// A Unique ID for the beam /// public void BeamEffect(UUID sourceAvatar, UUID targetObject, Vector3d globalOffset, Color4 color, float duration, UUID effectID) { ViewerEffectPacket effect = new ViewerEffectPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Effect = new ViewerEffectPacket.EffectBlock[1] }; effect.Effect[0] = new ViewerEffectPacket.EffectBlock { AgentID = Client.Self.AgentID, Color = color.GetBytes(), Duration = duration, ID = effectID, Type = (byte) EffectType.Beam }; byte[] typeData = new byte[56]; Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16); Buffer.BlockCopy(globalOffset.GetBytes(), 0, typeData, 32, 24); effect.Effect[0].TypeData = typeData; Client.Network.SendPacket(effect); } /// /// Create a particle swirl around a target position using a packet /// /// global offset /// A object containing the combined red, green, blue and alpha /// color values of particle beam /// a float representing the duration the parcicle beam will last /// A Unique ID for the beam public void SphereEffect(Vector3d globalOffset, Color4 color, float duration, UUID effectID) { ViewerEffectPacket effect = new ViewerEffectPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Effect = new ViewerEffectPacket.EffectBlock[1] }; effect.Effect[0] = new ViewerEffectPacket.EffectBlock { AgentID = Client.Self.AgentID, Color = color.GetBytes(), Duration = duration, ID = effectID, Type = (byte) EffectType.Sphere }; byte[] typeData = new byte[56]; Buffer.BlockCopy(UUID.Zero.GetBytes(), 0, typeData, 0, 16); Buffer.BlockCopy(UUID.Zero.GetBytes(), 0, typeData, 16, 16); Buffer.BlockCopy(globalOffset.GetBytes(), 0, typeData, 32, 24); effect.Effect[0].TypeData = typeData; Client.Network.SendPacket(effect); } #endregion Viewer Effects #region Movement Actions /// /// Sends a request to sit on the specified object /// /// of the object to sit on /// Sit at offset public void RequestSit(UUID targetID, Vector3 offset) { AgentRequestSitPacket requestSit = new AgentRequestSitPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, TargetObject = { TargetID = targetID, Offset = offset } }; Client.Network.SendPacket(requestSit); } /// /// Follows a call to to actually sit on the object /// public void Sit() { AgentSitPacket sit = new AgentSitPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID } }; Client.Network.SendPacket(sit); } /// Stands up from sitting on a prim or the ground /// true of AgentUpdate was sent public bool Stand() { if (Client.Settings.SEND_AGENT_UPDATES) { Movement.SitOnGround = false; Movement.StandUp = true; Movement.SendUpdate(); Movement.StandUp = false; Movement.SendUpdate(); return true; } else { Logger.Log("Attempted to Stand() but agent updates are disabled", Helpers.LogLevel.Warning, Client); return false; } } /// /// Does a "ground sit" at the avatar's current position /// public void SitOnGround() { Movement.SitOnGround = true; Movement.SendUpdate(true); } /// /// Starts or stops flying /// /// True to start flying, false to stop flying public void Fly(bool start) { Movement.Fly = start; Movement.SendUpdate(true); } /// /// Starts or stops crouching /// /// True to start crouching, false to stop crouching public void Crouch(bool crouching) { Movement.UpNeg = crouching; Movement.SendUpdate(true); } /// /// Starts a jump (begin holding the jump key) /// public void Jump(bool jumping) { Movement.UpPos = jumping; Movement.FastUp = jumping; Movement.SendUpdate(true); } /// /// Use the autopilot sim function to move the avatar to a new /// position. Uses double precision to get precise movements /// /// The z value is currently not handled properly by the simulator /// Global X coordinate to move to /// Global Y coordinate to move to /// Z coordinate to move to public void AutoPilot(double globalX, double globalY, double z) { GenericMessagePacket autopilot = new GenericMessagePacket(); autopilot.AgentData.AgentID = Client.Self.AgentID; autopilot.AgentData.SessionID = Client.Self.SessionID; autopilot.AgentData.TransactionID = UUID.Zero; autopilot.MethodData.Invoice = UUID.Zero; autopilot.MethodData.Method = Utils.StringToBytes("autopilot"); autopilot.ParamList = new GenericMessagePacket.ParamListBlock[3]; autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock { Parameter = Utils.StringToBytes(globalX.ToString()) }; autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock { Parameter = Utils.StringToBytes(globalY.ToString()) }; autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock { Parameter = Utils.StringToBytes(z.ToString()) }; Client.Network.SendPacket(autopilot); } /// /// Use the autopilot sim function to move the avatar to a new position /// /// The z value is currently not handled properly by the simulator /// Integer value for the global X coordinate to move to /// Integer value for the global Y coordinate to move to /// Floating-point value for the Z coordinate to move to public void AutoPilot(ulong globalX, ulong globalY, float z) { GenericMessagePacket autopilot = new GenericMessagePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, TransactionID = UUID.Zero }, MethodData = { Invoice = UUID.Zero, Method = Utils.StringToBytes("autopilot") }, ParamList = new GenericMessagePacket.ParamListBlock[3] }; autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock { Parameter = Utils.StringToBytes(globalX.ToString()) }; autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock { Parameter = Utils.StringToBytes(globalY.ToString()) }; autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock { Parameter = Utils.StringToBytes(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; Utils.LongToUInts(Client.Network.CurrentSim.Handle, out x, out y); AutoPilot((ulong)(x + localX), (ulong)(y + localY), z); } /// Macro to cancel autopilot sim function /// Not certain if this is how it is really done /// true if control flags were set and AgentUpdate was sent to the simulator public bool AutoPilotCancel() { if (Client.Settings.SEND_AGENT_UPDATES) { Movement.AtPos = true; Movement.SendUpdate(); Movement.AtPos = false; Movement.SendUpdate(); return true; } else { Logger.Log("Attempted to AutoPilotCancel() but agent updates are disabled", Helpers.LogLevel.Warning, Client); return false; } } #endregion Movement actions #region Touch and grab public static readonly Vector3 TOUCH_INVALID_TEXCOORD = new Vector3(-1.0f, -1.0f, 0.0f); public static readonly Vector3 TOUCH_INVALID_VECTOR = Vector3.Zero; /// /// Grabs an object /// /// an unsigned integer of the objects ID within the simulator /// public void Grab(uint objectLocalID) { Grab(objectLocalID, Vector3.Zero, TOUCH_INVALID_TEXCOORD, TOUCH_INVALID_TEXCOORD, 0, TOUCH_INVALID_VECTOR, TOUCH_INVALID_VECTOR, TOUCH_INVALID_VECTOR); } /// /// Overload: Grab a simulated object /// /// an unsigned integer of the objects ID within the simulator /// /// The texture coordinates to grab /// The surface coordinates to grab /// The face of the position to grab /// The region coordinates of the position to grab /// The surface normal of the position to grab (A normal is a vector perpendicular to the surface) /// The surface bi-normal of the position to grab (A bi-normal is a vector tangent to the surface /// pointing along the U direction of the tangent space public void Grab(uint objectLocalID, Vector3 grabOffset, Vector3 uvCoord, Vector3 stCoord, int faceIndex, Vector3 position, Vector3 normal, Vector3 binormal) { ObjectGrabPacket grab = new ObjectGrabPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = { LocalID = objectLocalID, GrabOffset = grabOffset }, SurfaceInfo = new ObjectGrabPacket.SurfaceInfoBlock[1] }; grab.SurfaceInfo[0] = new ObjectGrabPacket.SurfaceInfoBlock { UVCoord = uvCoord, STCoord = stCoord, FaceIndex = faceIndex, Position = position, Normal = normal, Binormal = binormal }; Client.Network.SendPacket(grab); } /// /// Drag an object /// /// of the object to drag /// Drag target in region coordinates public void GrabUpdate(UUID objectID, Vector3 grabPosition) { GrabUpdate(objectID, grabPosition, Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, Vector3.Zero, Vector3.Zero, Vector3.Zero); } /// /// Overload: Drag an object /// /// of the object to drag /// Drag target in region coordinates /// /// The texture coordinates to grab /// The surface coordinates to grab /// The face of the position to grab /// The region coordinates of the position to grab /// The surface normal of the position to grab (A normal is a vector perpendicular to the surface) /// The surface bi-normal of the position to grab (A bi-normal is a vector tangent to the surface /// pointing along the U direction of the tangent space public void GrabUpdate(UUID objectID, Vector3 grabPosition, Vector3 grabOffset, Vector3 uvCoord, Vector3 stCoord, int faceIndex, Vector3 position, Vector3 normal, Vector3 binormal) { ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = { ObjectID = objectID, GrabOffsetInitial = grabOffset, GrabPosition = grabPosition, TimeSinceLast = 0 }, SurfaceInfo = new ObjectGrabUpdatePacket.SurfaceInfoBlock[1] }; grab.SurfaceInfo[0] = new ObjectGrabUpdatePacket.SurfaceInfoBlock { UVCoord = uvCoord, STCoord = stCoord, FaceIndex = faceIndex, Position = position, Normal = normal, Binormal = binormal }; Client.Network.SendPacket(grab); } /// /// Release a grabbed object /// /// The Objects Simulator Local ID /// /// /// public void DeGrab(uint objectLocalID) { DeGrab(objectLocalID, TOUCH_INVALID_TEXCOORD, TOUCH_INVALID_TEXCOORD, 0, TOUCH_INVALID_VECTOR, TOUCH_INVALID_VECTOR, TOUCH_INVALID_VECTOR); } /// /// Release a grabbed object /// /// The Objects Simulator Local ID /// The texture coordinates to grab /// The surface coordinates to grab /// The face of the position to grab /// The region coordinates of the position to grab /// The surface normal of the position to grab (A normal is a vector perpendicular to the surface) /// The surface bi-normal of the position to grab (A bi-normal is a vector tangent to the surface /// pointing along the U direction of the tangent space public void DeGrab(uint objectLocalID, Vector3 uvCoord, Vector3 stCoord, int faceIndex, Vector3 position, Vector3 normal, Vector3 binormal) { ObjectDeGrabPacket degrab = new ObjectDeGrabPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, ObjectData = {LocalID = objectLocalID}, SurfaceInfo = new ObjectDeGrabPacket.SurfaceInfoBlock[1] }; degrab.SurfaceInfo[0] = new ObjectDeGrabPacket.SurfaceInfoBlock { UVCoord = uvCoord, STCoord = stCoord, FaceIndex = faceIndex, Position = position, Normal = normal, Binormal = binormal }; Client.Network.SendPacket(degrab); } /// /// Touches an object /// /// an unsigned integer of the objects ID within the simulator /// public void Touch(uint objectLocalID) { Client.Self.Grab(objectLocalID); Client.Self.DeGrab(objectLocalID); } #endregion Touch and grab #region Money /// /// Request the current L$ balance /// public void RequestBalance() { MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MoneyData = {TransactionID = UUID.Zero} }; Client.Network.SendPacket(money); } /// /// Give Money to destination Avatar /// /// UUID of the Target Avatar /// Amount in L$ public void GiveAvatarMoney(UUID target, int amount) { GiveMoney(target, amount, string.Empty, MoneyTransactionType.Gift, TransactionFlags.None); } /// /// Give Money to destination Avatar /// /// UUID of the Target Avatar /// Amount in L$ /// Description that will show up in the /// recipients transaction history public void GiveAvatarMoney(UUID target, int amount, string description) { GiveMoney(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.None); } /// /// Give L$ to an object /// /// object to give money to /// amount of L$ to give /// name of object public void GiveObjectMoney(UUID target, int amount, string objectName) { GiveMoney(target, amount, objectName, MoneyTransactionType.PayObject, TransactionFlags.None); } /// /// Give L$ to a group /// /// group to give money to /// amount of L$ to give public void GiveGroupMoney(UUID target, int amount) { GiveMoney(target, amount, string.Empty, MoneyTransactionType.Gift, TransactionFlags.DestGroup); } /// /// Give L$ to a group /// /// group to give money to /// amount of L$ to give /// description of transaction public void GiveGroupMoney(UUID target, int amount, string description) { GiveMoney(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.DestGroup); } /// /// Pay texture/animation upload fee /// public void PayUploadFee() { GiveMoney(UUID.Zero, Client.Settings.UPLOAD_COST, string.Empty, MoneyTransactionType.UploadCharge, TransactionFlags.None); } /// /// Pay texture/animation upload fee /// /// description of the transaction public void PayUploadFee(string description) { GiveMoney(UUID.Zero, Client.Settings.UPLOAD_COST, description, MoneyTransactionType.UploadCharge, TransactionFlags.None); } /// /// Give Money to destination Object or Avatar /// /// UUID of the Target Object/Avatar /// Amount in L$ /// Reason (Optional normally) /// The type of transaction /// Transaction flags, mostly for identifying group /// transactions public void GiveMoney(UUID target, int amount, string description, MoneyTransactionType type, TransactionFlags flags) { MoneyTransferRequestPacket money = new MoneyTransferRequestPacket { AgentData = { AgentID = id, SessionID = Client.Self.SessionID }, MoneyData = { Description = Utils.StringToBytes(description), DestID = target, SourceID = id, TransactionType = (int) type, AggregatePermInventory = 0, AggregatePermNextOwner = 0, Flags = (byte) flags, Amount = amount } }; // This is weird, apparently always set to zero though // This is weird, apparently always set to zero though Client.Network.SendPacket(money); } #endregion Money #region Gestures /// /// Plays a gesture /// /// Asset of the gesture public void PlayGesture(UUID gestureID) { ThreadPool.QueueUserWorkItem((_) => { // First fetch the guesture AssetGesture gesture = null; if (gestureCache.ContainsKey(gestureID)) { gesture = gestureCache[gestureID]; } else { AutoResetEvent gotAsset = new AutoResetEvent(false); Client.Assets.RequestAsset(gestureID, AssetType.Gesture, true, delegate(AssetDownload transfer, Asset asset) { if (transfer.Success) { gesture = (AssetGesture) asset; } gotAsset.Set(); } ); gotAsset.WaitOne(30 * 1000, false); if (gesture != null && gesture.Decode()) { lock (gestureCache) { if (!gestureCache.ContainsKey(gestureID)) { gestureCache[gestureID] = gesture; } } } } // We got it, now we play it if (gesture == null) return; foreach (GestureStep step in gesture.Sequence) { switch (step.GestureStepType) { case GestureStepType.Chat: string text = ((GestureStepChat) step).Text; int channel = 0; Match m; if ( (m = Regex.Match(text, @"^/(?-?[0-9]+)\s*(?.*)", RegexOptions.CultureInvariant)).Success) { if (int.TryParse(m.Groups["channel"].Value, out channel)) { text = m.Groups["text"].Value; } } Chat(text, channel, ChatType.Normal); break; case GestureStepType.Animation: GestureStepAnimation anim = (GestureStepAnimation) step; if (anim.AnimationStart) { if (SignaledAnimations.ContainsKey(anim.ID)) { AnimationStop(anim.ID, true); } AnimationStart(anim.ID, true); } else { AnimationStop(anim.ID, true); } break; case GestureStepType.Sound: Client.Sound.PlaySound(((GestureStepSound) step).ID); break; case GestureStepType.Wait: GestureStepWait wait = (GestureStepWait) step; if (wait.WaitForTime) { Thread.Sleep((int) (1000f * wait.WaitTime)); } if (wait.WaitForAnimation) { // TODO: implement waiting for all animations to end that were triggered // during playing of this guesture sequence } break; } } }); } /// /// Mark gesture active /// /// Inventory of the gesture /// Asset of the gesture public void ActivateGesture(UUID invID, UUID assetID) { ActivateGesturesPacket packet = new ActivateGesturesPacket { AgentData = { AgentID = AgentID, SessionID = SessionID, Flags = 0x00 } }; ActivateGesturesPacket.DataBlock block = new ActivateGesturesPacket.DataBlock { ItemID = invID, AssetID = assetID, GestureFlags = 0x00 }; packet.Data = new ActivateGesturesPacket.DataBlock[1]; packet.Data[0] = block; Client.Network.SendPacket(packet); ActiveGestures[invID] = assetID; } /// /// Mark gesture inactive /// /// Inventory of the gesture public void DeactivateGesture(UUID invID) { DeactivateGesturesPacket p = new DeactivateGesturesPacket { AgentData = { AgentID = AgentID, SessionID = SessionID, Flags = 0x00 } }; DeactivateGesturesPacket.DataBlock b = new DeactivateGesturesPacket.DataBlock { ItemID = invID, GestureFlags = 0x00 }; p.Data = new DeactivateGesturesPacket.DataBlock[1]; p.Data[0] = b; Client.Network.SendPacket(p); ActiveGestures.Remove(invID); } #endregion #region Animations /// /// Send an AgentAnimation packet that toggles a single animation on /// /// The of the animation to start playing /// Whether to ensure delivery of this packet or not public void AnimationStart(UUID animation, bool reliable) { var animations = new Dictionary {[animation] = true}; Animate(animations, reliable); } /// /// Send an AgentAnimation packet that toggles a single animation off /// /// The of a /// currently playing animation to stop playing /// Whether to ensure delivery of this packet or not public void AnimationStop(UUID animation, bool reliable) { var animations = new Dictionary {[animation] = false}; Animate(animations, reliable); } /// /// Send an AgentAnimation packet that will toggle animations on or off /// /// A list of animation s, and whether to /// turn that animation on or off /// Whether to ensure delivery of this packet or not public void Animate(Dictionary animations, bool reliable) { AgentAnimationPacket animate = new AgentAnimationPacket { Header = {Reliable = reliable}, AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, AnimationList = new AgentAnimationPacket.AnimationListBlock[animations.Count] }; int i = 0; foreach (var animation in animations) { animate.AnimationList[i] = new AgentAnimationPacket.AnimationListBlock { AnimID = animation.Key, StartAnim = animation.Value }; i++; } // TODO: Implement support for this animate.PhysicalAvatarEventList = new AgentAnimationPacket.PhysicalAvatarEventListBlock[0]; Client.Network.SendPacket(animate); } #endregion Animations #region Teleporting /// /// Teleports agent to their stored home location /// /// true on successful teleport to home location public bool GoHome() { return Teleport(UUID.Zero); } /// /// Teleport agent to a landmark /// /// of the landmark to teleport agent to /// true on success, false on failure public bool Teleport(UUID landmark) { teleportStat = TeleportStatus.None; teleportEvent.Reset(); TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket { Info = new TeleportLandmarkRequestPacket.InfoBlock { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, 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, Vector3 position) { return Teleport(simName, position, new Vector3(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, Vector3 position, Vector3 lookAt) { if (Client.Network.CurrentSim == null) return false; teleportStat = TeleportStatus.None; if (simName != Client.Network.CurrentSim.Name) { // Teleporting to a foreign sim GridRegion region; if (Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out region)) { return Teleport(region.RegionHandle, position, lookAt); } else { teleportMessage = "Unable to resolve name: " + simName; teleportStat = TeleportStatus.Failed; return false; } } else { // Teleporting to the sim we're already in return Teleport(Client.Network.CurrentSim.Handle, position, lookAt); } } /// /// Teleport agent to another region /// /// handle of region to teleport agent to /// position in destination sim to teleport to /// true on success, false on failure /// This call is blocking public bool Teleport(ulong regionHandle, Vector3 position) { return Teleport(regionHandle, position, new Vector3(0.0f, 1.0f, 0.0f)); } /// /// Teleport agent to another region /// /// handle of region to teleport agent to /// position in destination sim to teleport to /// direction in destination sim agent will look at /// true on success, false on failure /// This call is blocking public bool Teleport(ulong regionHandle, Vector3 position, Vector3 lookAt) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null || !Client.Network.CurrentSim.Caps.IsEventQueueRunning) { // Wait a bit to see if the event queue comes online AutoResetEvent queueEvent = new AutoResetEvent(false); EventHandler queueCallback = delegate(object sender, EventQueueRunningEventArgs e) { if (e.Simulator == Client.Network.CurrentSim) queueEvent.Set(); }; Client.Network.EventQueueRunning += queueCallback; queueEvent.WaitOne(10 * 1000, false); Client.Network.EventQueueRunning -= queueCallback; } teleportStat = TeleportStatus.None; teleportEvent.Reset(); RequestTeleport(regionHandle, position, lookAt); teleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false); if (teleportStat == TeleportStatus.None || teleportStat == TeleportStatus.Start || teleportStat == TeleportStatus.Progress) { teleportMessage = "Teleport timed out."; teleportStat = TeleportStatus.Failed; } return (teleportStat == TeleportStatus.Finished); } /// /// Request teleport to a another simulator /// /// handle of region to teleport agent to /// position in destination sim to teleport to public void RequestTeleport(ulong regionHandle, Vector3 position) { RequestTeleport(regionHandle, position, new Vector3(0.0f, 1.0f, 0.0f)); } /// /// Request teleport to a another simulator /// /// handle of region to teleport agent to /// position in destination sim to teleport to /// direction in destination sim agent will look at public void RequestTeleport(ulong regionHandle, Vector3 position, Vector3 lookAt) { if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null && Client.Network.CurrentSim.Caps.IsEventQueueRunning) { TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket(); teleport.AgentData.AgentID = Client.Self.AgentID; teleport.AgentData.SessionID = Client.Self.SessionID; teleport.Info.LookAt = lookAt; teleport.Info.Position = position; teleport.Info.RegionHandle = regionHandle; Logger.Log("Requesting teleport to region handle " + regionHandle.ToString(), Helpers.LogLevel.Info, Client); Client.Network.SendPacket(teleport); } else { teleportMessage = "CAPS event queue is not running"; teleportEvent.Set(); teleportStat = TeleportStatus.Failed; } } /// /// Teleport agent to a landmark /// /// of the landmark to teleport agent to public void RequestTeleport(UUID landmark) { TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket { Info = new TeleportLandmarkRequestPacket.InfoBlock { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, LandmarkID = landmark } }; Client.Network.SendPacket(p); } /// /// Send a teleport lure to another avatar with default "Join me in ..." invitation message /// /// target avatars to lure public void SendTeleportLure(UUID targetID) { SendTeleportLure(targetID, "Join me in " + Client.Network.CurrentSim.Name + "!"); } /// /// Send a teleport lure to another avatar with custom invitation message /// /// target avatars to lure /// custom message to send with invitation public void SendTeleportLure(UUID targetID, string message) { StartLurePacket p = new StartLurePacket { AgentData = { AgentID = Client.Self.id, SessionID = Client.Self.SessionID }, Info = { LureType = 0, Message = Utils.StringToBytes(message) }, TargetData = new[] {new StartLurePacket.TargetDataBlock()} }; p.TargetData[0].TargetID = targetID; Client.Network.SendPacket(p); } /// /// Respond to a teleport lure by either accepting it and initiating /// the teleport, or denying it /// /// of the avatar sending the lure /// IM session of the incoming lure request /// true to accept the lure, false to decline it public void TeleportLureRespond(UUID requesterID, UUID sessionID, bool accept) { if (accept) { TeleportLureRequestPacket lure = new TeleportLureRequestPacket { Info = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, LureID = sessionID, TeleportFlags = (uint) TeleportFlags.ViaLure } }; Client.Network.SendPacket(lure); } else { InstantMessage(Name, requesterID, string.Empty, sessionID, InstantMessageDialog.DenyTeleport, InstantMessageOnline.Offline, SimPosition, UUID.Zero, Utils.EmptyBytes); } } #endregion Teleporting #region Misc /// /// Update agent profile /// /// struct containing updated /// profile information public void UpdateProfile(Avatar.AvatarProperties profile) { AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket { AgentData = { AgentID = id, SessionID = sessionID }, PropertiesData = { AboutText = Utils.StringToBytes(profile.AboutText), AllowPublish = profile.AllowPublish, FLAboutText = Utils.StringToBytes(profile.FirstLifeText), FLImageID = profile.FirstLifeImage, ImageID = profile.ProfileImage, MaturePublish = profile.MaturePublish, ProfileURL = Utils.StringToBytes(profile.ProfileURL) } }; Client.Network.SendPacket(apup); } /// /// Update agents profile interests /// /// selection of interests from struct public void UpdateInterests(Avatar.Interests interests) { AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket { AgentData = { AgentID = id, SessionID = sessionID }, PropertiesData = { LanguagesText = Utils.StringToBytes(interests.LanguagesText), SkillsMask = interests.SkillsMask, SkillsText = Utils.StringToBytes(interests.SkillsText), WantToMask = interests.WantToMask, WantToText = Utils.StringToBytes(interests.WantToText) } }; Client.Network.SendPacket(aiup); } /// /// Set the height and the width of the client window. This is used /// by the server to build a virtual camera frustum for our avatar /// /// New height of the viewer window /// New width of the viewer window public void SetHeightWidth(ushort height, ushort width) { AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, CircuitCode = Client.Network.CircuitCode }, HeightWidthBlock = { Height = height, Width = width, GenCounter = heightWidthGenCounter++ } }; Client.Network.SendPacket(heightwidth); } /// /// Request the list of muted objects and avatars for this agent /// public void RequestMuteList() { MuteListRequestPacket mute = new MuteListRequestPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MuteData = {MuteCRC = 0} }; Client.Network.SendPacket(mute); } /// /// Mute an object, resident, etc. /// /// Mute type /// Mute UUID /// Mute name public void UpdateMuteListEntry(MuteType type, UUID id, string name) { UpdateMuteListEntry(type, id, name, MuteFlags.Default); } /// /// Mute an object, resident, etc. /// /// Mute type /// Mute UUID /// Mute name /// Mute flags public void UpdateMuteListEntry(MuteType type, UUID id, string name, MuteFlags flags) { UpdateMuteListEntryPacket p = new UpdateMuteListEntryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MuteData = { MuteType = (int) type, MuteID = id, MuteName = Utils.StringToBytes(name), MuteFlags = (uint) flags } }; Client.Network.SendPacket(p); MuteEntry me = new MuteEntry { Type = type, ID = id, Name = name, Flags = flags }; lock (MuteList.Dictionary) { MuteList[$"{me.ID}|{me.Name}"] = me; } OnMuteListUpdated(EventArgs.Empty); } /// /// Unmute an object, resident, etc. /// /// Mute UUID /// Mute name public void RemoveMuteListEntry(UUID id, string name) { RemoveMuteListEntryPacket p = new RemoveMuteListEntryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, MuteData = { MuteID = id, MuteName = Utils.StringToBytes(name) } }; Client.Network.SendPacket(p); string listKey = $"{id}|{name}"; if (MuteList.ContainsKey(listKey)) { lock (MuteList.Dictionary) { MuteList.Remove(listKey); } OnMuteListUpdated(EventArgs.Empty); } } /// /// Sets home location to agents current position /// /// will fire an AlertMessage () with /// success or failure message public void SetHome() { SetStartLocationRequestPacket s = new SetStartLocationRequestPacket { AgentData = new SetStartLocationRequestPacket.AgentDataBlock { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID } }; s.StartLocationData = new SetStartLocationRequestPacket.StartLocationDataBlock { LocationPos = Client.Self.SimPosition, LocationID = 1, SimName = Utils.StringToBytes(String.Empty), LocationLookAt = Movement.Camera.AtAxis }; Client.Network.SendPacket(s); } /// /// Move an agent in to a simulator. This packet is the last packet /// needed to complete the transition in to a new simulator /// /// Object public void CompleteAgentMovement(Simulator simulator) { CompleteAgentMovementPacket move = new CompleteAgentMovementPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, CircuitCode = Client.Network.CircuitCode } }; Client.Network.SendPacket(move, simulator); } /// /// Reply to script permissions request /// /// Object /// of the itemID requesting permissions /// of the taskID requesting permissions /// list of permissions to allow public void ScriptQuestionReply(Simulator simulator, UUID itemID, UUID taskID, ScriptPermission permissions) { ScriptAnswerYesPacket yes = new ScriptAnswerYesPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Data = { ItemID = itemID, TaskID = taskID, Questions = (int) permissions } }; Client.Network.SendPacket(yes, simulator); } /// /// Respond to a group invitation by either accepting or denying it /// /// UUID of the group (sent in the AgentID field of the invite message) /// IM Session ID from the group invitation message /// Accept the group invitation or deny it public void GroupInviteRespond(UUID groupID, UUID imSessionID, bool accept) { InstantMessage(Name, groupID, string.Empty, imSessionID, accept ? InstantMessageDialog.GroupInvitationAccept : InstantMessageDialog.GroupInvitationDecline, InstantMessageOnline.Offline, Vector3.Zero, UUID.Zero, Utils.EmptyBytes); } /// /// Requests script detection of objects and avatars /// /// name of the object/avatar to search for /// UUID of the object or avatar to search for /// Type of search from ScriptSensorTypeFlags /// range of scan (96 max?) /// the arc in radians to search within /// an user generated ID to correlate replies with /// Simulator to perform search in public void RequestScriptSensor(string name, UUID searchID, ScriptSensorTypeFlags type, float range, float arc, UUID requestID, Simulator sim) { ScriptSensorRequestPacket request = new ScriptSensorRequestPacket { Requester = { Arc = arc, Range = range, RegionHandle = sim.Handle, RequestID = requestID, SearchDir = Quaternion.Identity, SearchID = searchID, SearchName = Utils.StringToBytes(name), SearchPos = Vector3.Zero, SearchRegions = 0, SourceID = Client.Self.AgentID, Type = (int) type } }; // TODO: this needs to be tested // TODO: ? Client.Network.SendPacket(request, sim); } /// /// Create or update profile pick /// /// UUID of the pick to update, or random UUID to create a new pick /// Is this a top pick? (typically false) /// UUID of the parcel (UUID.Zero for the current parcel) /// Name of the pick /// Global position of the pick landmark /// UUID of the image displayed with the pick /// Long description of the pick public void PickInfoUpdate(UUID pickID, bool topPick, UUID parcelID, string name, Vector3d globalPosition, UUID textureID, string description) { PickInfoUpdatePacket pick = new PickInfoUpdatePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Data = { PickID = pickID, Desc = Utils.StringToBytes(description), CreatorID = Client.Self.AgentID, TopPick = topPick, ParcelID = parcelID, Name = Utils.StringToBytes(name), SnapshotID = textureID, PosGlobal = globalPosition, SortOrder = 0, Enabled = false } }; Client.Network.SendPacket(pick); } /// /// Delete profile pick /// /// UUID of the pick to delete public void PickDelete(UUID pickID) { PickDeletePacket delete = new PickDeletePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.sessionID }, Data = {PickID = pickID} }; Client.Network.SendPacket(delete); } /// /// Create or update profile Classified /// /// UUID of the classified to update, or random UUID to create a new classified /// Defines what category the classified is in /// UUID of the image displayed with the classified /// Price that the classified will cost to place for a week /// Global position of the classified landmark /// Name of the classified /// Long description of the classified /// if true, auto renew classified after expiration public void UpdateClassifiedInfo(UUID classifiedID, DirectoryManager.ClassifiedCategories category, UUID snapshotID, int price, Vector3d position, string name, string desc, bool autoRenew) { ClassifiedInfoUpdatePacket classified = new ClassifiedInfoUpdatePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Data = { ClassifiedID = classifiedID, Category = (uint) category, ParcelID = UUID.Zero, ParentEstate = 0, SnapshotID = snapshotID, PosGlobal = position, ClassifiedFlags = autoRenew ? (byte) 32 : (byte) 0, PriceForListing = price, Name = Utils.StringToBytes(name), Desc = Utils.StringToBytes(desc) } }; Client.Network.SendPacket(classified); } /// /// Create or update profile Classified /// /// UUID of the classified to update, or random UUID to create a new classified /// Defines what category the classified is in /// UUID of the image displayed with the classified /// Price that the classified will cost to place for a week /// Name of the classified /// Long description of the classified /// if true, auto renew classified after expiration public void UpdateClassifiedInfo(UUID classifiedID, DirectoryManager.ClassifiedCategories category, UUID snapshotID, int price, string name, string desc, bool autoRenew) { UpdateClassifiedInfo(classifiedID, category, snapshotID, price, Client.Self.GlobalPosition, name, desc, autoRenew); } /// /// Delete a classified ad /// /// The classified ads ID public void DeleteClassfied(UUID classifiedID) { ClassifiedDeletePacket classified = new ClassifiedDeletePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, Data = {ClassifiedID = classifiedID} }; Client.Network.SendPacket(classified); } /// /// Fetches resource usage by agents attachments /// /// Called when the requested information is collected public void GetAttachmentResources(AttachmentResourcesCallback callback) { try { CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("AttachmentResources"); request.OnComplete += delegate(CapsClient client, OSD result, Exception error) { try { if (result == null || error != null) { callback(false, null); } AttachmentResourcesMessage info = AttachmentResourcesMessage.FromOSD(result); callback(true, info); } catch (Exception ex) { Logger.Log("Failed fetching AttachmentResources", Helpers.LogLevel.Error, Client, ex); callback(false, null); } }; request.BeginGetResponse(Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) { Logger.Log("Failed fetching AttachmentResources", Helpers.LogLevel.Error, Client, ex); callback(false, null); } } /// /// Initiates request to set a new display name /// /// Previous display name /// Desired new display name public void SetDisplayName(string oldName, string newName) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) { Logger.Log("Not connected to simulator. " + "Unable to set display name.", Helpers.LogLevel.Warning, Client); return; } CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("SetDisplayName"); if (request == null) { Logger.Log("Unable to obtain capability. Unable to set display name.", Helpers.LogLevel.Warning, Client); return; } SetDisplayNameMessage msg = new SetDisplayNameMessage { OldDisplayName = oldName, NewDisplayName = newName }; request.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } /// /// Tells the sim what UI language is used, and if it's ok to share that with scripts /// /// Two letter language code /// Share language info with scripts public void UpdateAgentLanguage(string language, bool isPublic) { try { UpdateAgentLanguageMessage msg = new UpdateAgentLanguageMessage { Language = language, LanguagePublic = isPublic }; CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("UpdateAgentLanguage"); request?.BeginGetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) { Logger.Log("Failed to update agent language", Helpers.LogLevel.Error, Client, ex); } } public delegate void AgentAccessCallback(AgentAccessEventArgs e); /// /// Sets agents maturity access level /// /// PG, M or A public void SetAgentAccess(string access) { SetAgentAccess(access, null); } /// /// Sets agents maturity access level /// /// PG, M or A /// Callback function public void SetAgentAccess(string access, AgentAccessCallback callback) { if (Client == null || !Client.Network.Connected || Client.Network.CurrentSim.Caps == null) return; CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("UpdateAgentInformation"); if (request == null) return; request.OnComplete += (client, result, error) => { bool success = true; if (error == null && result is OSDMap) { var map = ((OSDMap)result)["access_prefs"]; agentAccess = ((OSDMap)map)["max"]; Logger.Log($"Max maturity access set to {agentAccess}", Helpers.LogLevel.Info, Client ); } else if (error == null) { Logger.Log($"Max maturity unchanged at {agentAccess}", Helpers.LogLevel.Info, Client); } else { Logger.Log("Failed setting max maturity access.", Helpers.LogLevel.Warning, Client); success = false; } if (callback != null) { try { callback(new AgentAccessEventArgs(success, agentAccess)); } catch { } // *TODO: So gross } }; OSDMap req = new OSDMap(); OSDMap prefs = new OSDMap {["max"] = access}; req["access_prefs"] = prefs; request.BeginGetResponse(req, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } /// /// Sets agents hover height. /// /// Hover height [-2.0, 2.0] public void SetHoverHeight(double hoverHeight) { if (Client == null || !Client.Network.Connected || Client.Network.CurrentSim.Caps == null) { return; } CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("AgentPreferences"); if (request == null) { return; } request.OnComplete += (client, result, error) => { var resultMap = result as OSDMap; if(error != null) { Logger.Log($"Failed to set hover height: {error}.", Helpers.LogLevel.Warning, Client); } else if (resultMap == null) { Logger.Log($"Failed to set hover height: Expected {nameof(OSDMap)} response, but got {result.Type}", Helpers.LogLevel.Warning, Client); } else { var confirmedHeight = resultMap["hover_height"]; Logger.Log($"Hover height set to {confirmedHeight}", Helpers.LogLevel.Info, Client); } }; var postData = new OSDMap { ["hover_height"] = hoverHeight }; request.BeginGetResponse(postData, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } #endregion Misc #region Packet Handlers /// /// Take an incoming ImprovedInstantMessage packet, auto-parse, and if /// OnInstantMessage is defined call that with the appropriate arguments /// /// The sender /// The EventArgs object containing the packet data protected void InstantMessageHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; if (packet.Type != PacketType.ImprovedInstantMessage) return; ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)packet; if (m_InstantMessage != null) { InstantMessage message; message.FromAgentID = im.AgentData.AgentID; message.FromAgentName = Utils.BytesToString(im.MessageBlock.FromAgentName); message.ToAgentID = im.MessageBlock.ToAgentID; message.ParentEstateID = im.MessageBlock.ParentEstateID; message.RegionID = im.MessageBlock.RegionID; message.Position = im.MessageBlock.Position; message.Dialog = (InstantMessageDialog)im.MessageBlock.Dialog; message.GroupIM = im.MessageBlock.FromGroup; message.IMSessionID = im.MessageBlock.ID; message.Timestamp = new DateTime(im.MessageBlock.Timestamp); message.Message = Utils.BytesToString(im.MessageBlock.Message); message.Offline = (InstantMessageOnline)im.MessageBlock.Offline; message.BinaryBucket = im.MessageBlock.BinaryBucket; OnInstantMessage(new InstantMessageEventArgs(message, simulator)); } } /// /// Take an incoming Chat packet, auto-parse, and if OnChat is defined call /// that with the appropriate arguments. /// /// The sender /// The EventArgs object containing the packet data protected void ChatHandler(object sender, PacketReceivedEventArgs e) { if (m_Chat == null) return; Packet packet = e.Packet; ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet; OnChat(new ChatEventArgs(e.Simulator, Utils.BytesToString(chat.ChatData.Message), (ChatAudibleLevel)chat.ChatData.Audible, (ChatType)chat.ChatData.ChatType, (ChatSourceType)chat.ChatData.SourceType, Utils.BytesToString(chat.ChatData.FromName), chat.ChatData.SourceID, chat.ChatData.OwnerID, chat.ChatData.Position)); } /// /// Used for parsing llDialogs /// /// The sender /// The EventArgs object containing the packet data protected void ScriptDialogHandler(object sender, PacketReceivedEventArgs e) { if (m_ScriptDialog == null) return; Packet packet = e.Packet; ScriptDialogPacket dialog = (ScriptDialogPacket)packet; List buttons = dialog.Buttons.Select(button => Utils.BytesToString(button.ButtonLabel)).ToList(); UUID ownerID = UUID.Zero; if (dialog.OwnerData != null && dialog.OwnerData.Length > 0) { ownerID = dialog.OwnerData[0].OwnerID; } OnScriptDialog(new ScriptDialogEventArgs(Utils.BytesToString(dialog.Data.Message), Utils.BytesToString(dialog.Data.ObjectName), dialog.Data.ImageID, dialog.Data.ObjectID, Utils.BytesToString(dialog.Data.FirstName), Utils.BytesToString(dialog.Data.LastName), dialog.Data.ChatChannel, buttons, ownerID)); } /// /// Used for parsing llRequestPermissions dialogs /// /// The sender /// The EventArgs object containing the packet data protected void ScriptQuestionHandler(object sender, PacketReceivedEventArgs e) { if (m_ScriptQuestion == null) return; Packet packet = e.Packet; Simulator simulator = e.Simulator; ScriptQuestionPacket question = (ScriptQuestionPacket)packet; OnScriptQuestion(new ScriptQuestionEventArgs(simulator, question.Data.TaskID, question.Data.ItemID, Utils.BytesToString(question.Data.ObjectName), Utils.BytesToString(question.Data.ObjectOwner), (ScriptPermission)question.Data.Questions)); } /// /// Handles Script Control changes when Script with permissions releases or takes a control /// /// The sender /// The EventArgs object containing the packet data private void ScriptControlChangeHandler(object sender, PacketReceivedEventArgs e) { if (m_ScriptControl == null) return; Packet packet = e.Packet; ScriptControlChangePacket change = (ScriptControlChangePacket)packet; foreach (ScriptControlChangePacket.DataBlock data in change.Data) { OnScriptControlChange(new ScriptControlEventArgs((ScriptControlChange)data.Controls, data.PassToAgent, data.TakeControls)); } } /// /// Used for parsing llLoadURL Dialogs /// /// The sender /// The EventArgs object containing the packet data protected void LoadURLHandler(object sender, PacketReceivedEventArgs e) { if (m_LoadURL == null) return; Packet packet = e.Packet; LoadURLPacket loadURL = (LoadURLPacket)packet; OnLoadURL(new LoadUrlEventArgs( Utils.BytesToString(loadURL.Data.ObjectName), loadURL.Data.ObjectID, loadURL.Data.OwnerID, loadURL.Data.OwnerIsGroup, Utils.BytesToString(loadURL.Data.Message), Utils.BytesToString(loadURL.Data.URL) )); } /// /// Update client's Position, LookAt and region handle from incoming packet /// /// The sender /// The EventArgs object containing the packet data /// This occurs when after an avatar moves into a new sim private void MovementCompleteHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet; relativePosition = movement.Data.Position; Movement.Camera.LookDirection(movement.Data.LookAt); simulator.Handle = movement.Data.RegionHandle; simulator.SimVersion = Utils.BytesToString(movement.SimData.ChannelVersion); simulator.AgentMovementComplete = true; } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void HealthHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; health = ((HealthMessagePacket)packet).HealthData.Health; } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void AgentDataUpdateHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.Simulator; AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet; if (p.AgentData.AgentID == simulator.Client.Self.AgentID) { firstName = Utils.BytesToString(p.AgentData.FirstName); lastName = Utils.BytesToString(p.AgentData.LastName); activeGroup = p.AgentData.ActiveGroupID; activeGroupPowers = (GroupPowers)p.AgentData.GroupPowers; if (m_AgentData == null) return; string groupTitle = Utils.BytesToString(p.AgentData.GroupTitle); string groupName = Utils.BytesToString(p.AgentData.GroupName); OnAgentData(new AgentDataReplyEventArgs(firstName, lastName, activeGroup, groupTitle, activeGroupPowers, groupName)); } else { Logger.Log("Got an AgentDataUpdate packet for avatar " + p.AgentData.AgentID.ToString() + " instead of " + Client.Self.AgentID.ToString() + ", this shouldn't happen", Helpers.LogLevel.Error, Client); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void MoneyBalanceReplyHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; if (packet.Type == PacketType.MoneyBalanceReply) { MoneyBalanceReplyPacket reply = (MoneyBalanceReplyPacket)packet; this.balance = reply.MoneyData.MoneyBalance; if (m_MoneyBalance != null) { TransactionInfo transactionInfo = new TransactionInfo { TransactionType = reply.TransactionInfo.TransactionType, SourceID = reply.TransactionInfo.SourceID, IsSourceGroup = reply.TransactionInfo.IsSourceGroup, DestID = reply.TransactionInfo.DestID, IsDestGroup = reply.TransactionInfo.IsDestGroup, Amount = reply.TransactionInfo.Amount, ItemDescription = Utils.BytesToString(reply.TransactionInfo.ItemDescription) }; OnMoneyBalanceReply(new MoneyBalanceReplyEventArgs(reply.MoneyData.TransactionID, reply.MoneyData.TransactionSuccess, reply.MoneyData.MoneyBalance, reply.MoneyData.SquareMetersCredit, reply.MoneyData.SquareMetersCommitted, Utils.BytesToString(reply.MoneyData.Description), transactionInfo)); } } if (m_Balance != null) { OnBalance(new BalanceEventArgs(balance)); } } /// /// EQ Message fired with the result of SetDisplayName request /// /// The message key /// the IMessage object containing the deserialized data sent from the simulator /// The which originated the packet protected void SetDisplayNameReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { if (m_SetDisplayNameReply == null) return; SetDisplayNameReplyMessage msg = (SetDisplayNameReplyMessage)message; OnSetDisplayNameReply(new SetDisplayNameReplyEventArgs(msg.Status, msg.Reason, msg.DisplayName)); } protected void AgentStateUpdateEventHandler(string capsKey, IMessage message, Simulator simulator) { if (message is AgentStateUpdateMessage updateMessage) { AgentStateStatus = updateMessage; } } protected void EstablishAgentCommunicationEventHandler(string capsKey, IMessage message, Simulator simulator) { EstablishAgentCommunicationMessage msg = (EstablishAgentCommunicationMessage)message; if (!Client.Settings.MULTIPLE_SIMS) return; IPEndPoint endPoint = new IPEndPoint(msg.Address, msg.Port); Simulator sim = Client.Network.FindSimulator(endPoint); if (sim == null) { Logger.Log($"Got EstablishAgentCommunication for unknown sim {msg.Address}:{msg.Port}", Helpers.LogLevel.Error, Client); // FIXME: Should we use this opportunity to connect to the simulator? } else { Logger.Log("Got EstablishAgentCommunication for " + sim, Helpers.LogLevel.Info, Client); sim.SetSeedCaps(msg.SeedCapability.ToString()); } } /// /// Process TeleportFailed message sent via EventQueue, informs agent its last teleport has failed and why. /// /// The Message Key /// An IMessage object Deserialized from the recieved message event /// The simulator originating the event message public void TeleportFailedEventHandler(string messageKey, IMessage message, Simulator simulator) { TeleportFailedMessage msg = (TeleportFailedMessage)message; TeleportFailedPacket failedPacket = new TeleportFailedPacket { Info = { AgentID = msg.AgentID, Reason = Utils.StringToBytes(msg.Reason) } }; TeleportHandler(this, new PacketReceivedEventArgs(failedPacket, simulator)); } /// /// Process TeleportFinish from Event Queue and pass it onto our TeleportHandler /// /// The message system key for this event /// IMessage object containing decoded data from OSD /// The simulator originating the event message private void TeleportFinishEventHandler(string capsKey, IMessage message, Simulator simulator) { TeleportFinishMessage msg = (TeleportFinishMessage)message; TeleportFinishPacket p = new TeleportFinishPacket { Info = { AgentID = msg.AgentID, LocationID = (uint) msg.LocationID, RegionHandle = msg.RegionHandle, SeedCapability = Utils.StringToBytes(msg.SeedCapability.ToString()), SimAccess = (byte) msg.SimAccess, SimIP = Utils.IPToUInt(msg.IP), SimPort = (ushort) msg.Port, TeleportFlags = (uint) msg.Flags } }; // FIXME: Check This // pass the packet onto the teleport handler TeleportHandler(this, new PacketReceivedEventArgs(p, simulator)); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void TeleportHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; Simulator simulator = e.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; Logger.DebugLog($"TeleportStart received, Flags: {flags}", Client); } else if (packet.Type == PacketType.TeleportProgress) { TeleportProgressPacket progress = (TeleportProgressPacket)packet; teleportMessage = Utils.BytesToString(progress.Info.Message); flags = (TeleportFlags)progress.Info.TeleportFlags; teleportStat = TeleportStatus.Progress; Logger.DebugLog($"TeleportProgress received, Message: {teleportMessage}, Flags: {flags}", Client); } else if (packet.Type == PacketType.TeleportFailed) { TeleportFailedPacket failed = (TeleportFailedPacket)packet; teleportMessage = Utils.BytesToString(failed.Info.Reason); teleportStat = TeleportStatus.Failed; finished = true; Logger.DebugLog($"TeleportFailed received, Reason: {teleportMessage}", Client); } else if (packet.Type == PacketType.TeleportFinish) { TeleportFinishPacket finish = (TeleportFinishPacket)packet; flags = (TeleportFlags)finish.Info.TeleportFlags; string seedcaps = Utils.BytesToString(finish.Info.SeedCapability); finished = true; Logger.DebugLog($"TeleportFinish received, Flags: {flags}", Client); // Connect to the new sim Client.Network.CurrentSim.AgentMovementComplete = false; // we're not there anymore 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; Logger.Log($"Moved to new sim {newSimulator}", Helpers.LogLevel.Info, Client); } else { teleportMessage = "Failed to connect to the new sim after a teleport"; teleportStat = TeleportStatus.Failed; // We're going to get disconnected now Logger.Log(teleportMessage, Helpers.LogLevel.Error, Client); } } else if (packet.Type == PacketType.TeleportCancel) { //TeleportCancelPacket cancel = (TeleportCancelPacket)packet; teleportMessage = "Cancelled"; teleportStat = TeleportStatus.Cancelled; finished = true; Logger.DebugLog($"TeleportCancel received from {simulator}", Client); } else if (packet.Type == PacketType.TeleportLocal) { TeleportLocalPacket local = (TeleportLocalPacket)packet; teleportMessage = "Teleport finished"; flags = (TeleportFlags)local.Info.TeleportFlags; teleportStat = TeleportStatus.Finished; relativePosition = local.Info.Position; Movement.Camera.LookDirection(local.Info.LookAt); // This field is apparently not used for anything //local.Info.LocationID; finished = true; Logger.DebugLog($"TeleportLocal received, Flags: {flags}", Client); } if (m_Teleport != null) { OnTeleport(new TeleportEventArgs(teleportMessage, teleportStat, flags)); } if (finished) teleportEvent.Set(); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void AvatarAnimationHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; AvatarAnimationPacket animation = (AvatarAnimationPacket)packet; if (animation.Sender.ID == Client.Self.AgentID) { lock (SignaledAnimations.Dictionary) { // Reset the signaled animation list SignaledAnimations.Dictionary.Clear(); for (int i = 0; i < animation.AnimationList.Length; i++) { UUID animID = animation.AnimationList[i].AnimID; int sequenceID = animation.AnimationList[i].AnimSequenceID; // Add this animation to the list of currently signaled animations SignaledAnimations.Dictionary[animID] = sequenceID; if (i < animation.AnimationSourceList.Length) { // FIXME: The server tells us which objects triggered our animations, // we should store this info //animation.AnimationSourceList[i].ObjectID } if (i < animation.PhysicalAvatarEventList.Length) { // FIXME: What is this? } if (!Client.Settings.SEND_AGENT_UPDATES) continue; // We have to manually tell the server to stop playing some animations if (animID == Animations.STANDUP || animID == Animations.PRE_JUMP || animID == Animations.LAND || animID == Animations.MEDIUM_LAND) { Movement.FinishAnim = true; Movement.SendUpdate(true); Movement.FinishAnim = false; } } } } if (m_AnimationsChanged != null) { ThreadPool.QueueUserWorkItem(delegate(object o) { OnAnimationsChanged(new AnimationsChangedEventArgs(this.SignaledAnimations)); }); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void MeanCollisionAlertHandler(object sender, PacketReceivedEventArgs e) { if (m_MeanCollision == null) return; Packet packet = e.Packet; MeanCollisionAlertPacket collision = (MeanCollisionAlertPacket)packet; foreach (MeanCollisionAlertPacket.MeanCollisionBlock block in collision.MeanCollision) { DateTime time = Utils.UnixTimeToDateTime(block.Time); MeanCollisionType type = (MeanCollisionType)block.Type; OnMeanCollision(new MeanCollisionEventArgs(type, block.Perp, block.Victim, block.Mag, time)); } } private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData reply) { id = reply.AgentID; sessionID = reply.SessionID; secureSessionID = reply.SecureSessionID; firstName = reply.FirstName; lastName = reply.LastName; startLocation = reply.StartLocation; agentAccess = reply.AgentAccess; Movement.Camera.LookDirection(reply.LookAt); homePosition = reply.HomePosition; homeLookAt = reply.HomeLookAt; lookAt = reply.LookAt; if (reply.Gestures != null) { foreach (var gesture in reply.Gestures) { ActiveGestures.Add(gesture.Key, gesture.Value); } } } private void Network_OnDisconnected(object sender, DisconnectedEventArgs e) { // Null out the cached fullName since it can change after logging // in again (with a different account name or different login // server but using the same GridClient object fullName = null; } /// /// Crossed region handler for message that comes across the EventQueue. Sent to an agent /// when the agent crosses a sim border into a new region. /// /// The message key /// the IMessage object containing the deserialized data sent from the simulator /// The which originated the packet private void CrossedRegionEventHandler(string capsKey, IMessage message, Simulator simulator) { CrossedRegionMessage crossed = (CrossedRegionMessage)message; IPEndPoint endPoint = new IPEndPoint(crossed.IP, crossed.Port); Logger.DebugLog($"Crossed in to new region area, attempting to connect to {endPoint}", Client); Simulator oldSim = Client.Network.CurrentSim; Simulator newSim = Client.Network.Connect(endPoint, crossed.RegionHandle, true, crossed.SeedCapability.ToString()); if (newSim != null) { Logger.Log($"Finished crossing over in to region {newSim}", Helpers.LogLevel.Info, Client); oldSim.AgentMovementComplete = false; // We're no longer there if (m_RegionCrossed != null) { OnRegionCrossed(new RegionCrossedEventArgs(oldSim, newSim)); } } else { // The old simulator will (poorly) handle our movement still, so the connection isn't // completely shot yet Logger.Log($"Failed to connect to new region {endPoint} after crossing over", Helpers.LogLevel.Warning, Client); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// This packet is now being sent via the EventQueue protected void CrossedRegionHandler(object sender, PacketReceivedEventArgs e) { Packet packet = e.Packet; CrossedRegionPacket crossing = (CrossedRegionPacket)packet; string seedCap = Utils.BytesToString(crossing.RegionData.SeedCapability); IPEndPoint endPoint = new IPEndPoint(crossing.RegionData.SimIP, crossing.RegionData.SimPort); Logger.DebugLog($"Crossed in to new region area, attempting to connect to {endPoint}", Client); Simulator oldSim = Client.Network.CurrentSim; Simulator newSim = Client.Network.Connect(endPoint, crossing.RegionData.RegionHandle, true, seedCap); if (newSim != null) { Logger.Log($"Finished crossing over in to region {newSim}", Helpers.LogLevel.Info, Client); if (m_RegionCrossed != null) { OnRegionCrossed(new RegionCrossedEventArgs(oldSim, newSim)); } } else { // The old simulator will (poorly) handle our movement still, so the connection isn't // completely shot yet Logger.Log($"Failed to connect to new region {endPoint} after crossing over", Helpers.LogLevel.Warning, Client); } } /// /// Group Chat event handler /// /// The capability Key /// IMessage object containing decoded data from OSD /// protected void ChatterBoxSessionEventReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { ChatterboxSessionEventReplyMessage msg = (ChatterboxSessionEventReplyMessage)message; if (msg.Success) return; RequestJoinGroupChat(msg.SessionID); Logger.Log($"Attempt to send group chat to non-existant session for group {msg.SessionID}", Helpers.LogLevel.Info, Client); } /// /// Response from request to join a group chat /// /// /// IMessage object containing decoded data from OSD /// protected void ChatterBoxSessionStartReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { ChatterBoxSessionStartReplyMessage msg = (ChatterBoxSessionStartReplyMessage)message; if (msg.Success) { lock (GroupChatSessions.Dictionary) if (!GroupChatSessions.ContainsKey(msg.SessionID)) GroupChatSessions.Add(msg.SessionID, new List()); } OnGroupChatJoined(new GroupChatJoinedEventArgs(msg.SessionID, msg.SessionName, msg.TempSessionID, msg.Success)); } /// /// Someone joined or left group chat /// /// /// IMessage object containing decoded data from OSD /// private void ChatterBoxSessionAgentListUpdatesEventHandler(string capsKey, IMessage message, Simulator simulator) { ChatterBoxSessionAgentListUpdatesMessage msg = (ChatterBoxSessionAgentListUpdatesMessage)message; lock (GroupChatSessions.Dictionary) if (!GroupChatSessions.ContainsKey(msg.SessionID)) GroupChatSessions.Add(msg.SessionID, new List()); foreach (ChatterBoxSessionAgentListUpdatesMessage.AgentUpdatesBlock t in msg.Updates) { ChatSessionMember fndMbr; lock (GroupChatSessions.Dictionary) { fndMbr = GroupChatSessions[msg.SessionID].Find(member => member.AvatarKey == t.AgentID); } if (t.Transition != null) { if (t.Transition.Equals("ENTER")) { if (fndMbr.AvatarKey == UUID.Zero) { fndMbr = new ChatSessionMember {AvatarKey = t.AgentID}; lock (GroupChatSessions.Dictionary) GroupChatSessions[msg.SessionID].Add(fndMbr); if (m_ChatSessionMemberAdded != null) { OnChatSessionMemberAdded(new ChatSessionMemberAddedEventArgs(msg.SessionID, fndMbr.AvatarKey)); } } } else if (t.Transition.Equals("LEAVE")) { if (fndMbr.AvatarKey != UUID.Zero) lock (GroupChatSessions.Dictionary) GroupChatSessions[msg.SessionID].Remove(fndMbr); if (m_ChatSessionMemberLeft != null) { OnChatSessionMemberLeft(new ChatSessionMemberLeftEventArgs(msg.SessionID, t.AgentID)); } } } // handle updates ChatSessionMember update_member = GroupChatSessions.Dictionary[msg.SessionID].Find( m => m.AvatarKey == t.AgentID); update_member.MuteText = t.MuteText; update_member.MuteVoice = t.MuteVoice; update_member.CanVoiceChat = t.CanVoiceChat; update_member.IsModerator = t.IsModerator; // replace existing member record lock (GroupChatSessions.Dictionary) { int found = GroupChatSessions.Dictionary[msg.SessionID].FindIndex(m => m.AvatarKey == t.AgentID); if (found >= 0) GroupChatSessions.Dictionary[msg.SessionID][found] = update_member; } } } /// /// Handle a group chat Invitation /// /// Caps Key /// IMessage object containing decoded data from OSD /// Originating Simulator private void ChatterBoxInvitationEventHandler(string capsKey, IMessage message, Simulator simulator) { if (m_InstantMessage == null) return; ChatterBoxInvitationMessage msg = (ChatterBoxInvitationMessage)message; //TODO: do something about invitations to voice group chat/friends conference //Skip for now if (msg.Voice) return; InstantMessage im = new InstantMessage { FromAgentID = msg.FromAgentID, FromAgentName = msg.FromAgentName, ToAgentID = msg.ToAgentID, ParentEstateID = msg.ParentEstateID, RegionID = msg.RegionID, Position = msg.Position, Dialog = msg.Dialog, GroupIM = msg.GroupIM, IMSessionID = msg.IMSessionID, Timestamp = msg.Timestamp, Message = msg.Message, Offline = msg.Offline, BinaryBucket = msg.BinaryBucket }; try { ChatterBoxAcceptInvite(msg.IMSessionID); } catch (Exception ex) { Logger.Log("Failed joining IM:", Helpers.LogLevel.Warning, Client, ex); } OnInstantMessage(new InstantMessageEventArgs(im, simulator)); } /// /// Moderate a chat session /// /// the of the session to moderate, for group chats this will be the groups UUID /// the of the avatar to moderate /// Either "voice" to moderate users voice, or "text" to moderate users text session /// true to moderate (silence user), false to allow avatar to speak public void ModerateChatSessions(UUID sessionID, UUID memberID, string key, bool moderate) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) throw new Exception("ChatSessionRequest capability is not currently available"); CapsClient request = Client.Network.CurrentSim.Caps.CreateCapsClient("ChatSessionRequest"); if (request != null) { ChatSessionRequestMuteUpdate req = new ChatSessionRequestMuteUpdate { RequestKey = key, RequestValue = moderate, SessionID = sessionID, AgentID = memberID }; request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { throw new Exception("ChatSessionRequest capability is not currently available"); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void AlertMessageHandler(object sender, PacketReceivedEventArgs e) { if (m_AlertMessage == null) return; Packet packet = e.Packet; AlertMessagePacket alert = (AlertMessagePacket)packet; OnAlertMessage(new AlertMessageEventArgs(Utils.BytesToString(alert.AlertData.Message))); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void CameraConstraintHandler(object sender, PacketReceivedEventArgs e) { if (m_CameraConstraint == null) return; Packet packet = e.Packet; CameraConstraintPacket camera = (CameraConstraintPacket)packet; OnCameraConstraint(new CameraConstraintEventArgs(camera.CameraCollidePlane.Plane)); } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ScriptSensorReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_ScriptSensorReply == null) return; Packet packet = e.Packet; ScriptSensorReplyPacket reply = (ScriptSensorReplyPacket)packet; foreach (ScriptSensorReplyPacket.SensedDataBlock block in reply.SensedData) { ScriptSensorReplyPacket.RequesterBlock requestor = reply.Requester; OnScriptSensorReply(new ScriptSensorReplyEventArgs(requestor.SourceID, block.GroupID, Utils.BytesToString(block.Name), block.ObjectID, block.OwnerID, block.Position, block.Range, block.Rotation, (ScriptSensorTypeFlags)block.Type, block.Velocity)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void AvatarSitResponseHandler(object sender, PacketReceivedEventArgs e) { if (m_AvatarSitResponse == null) return; Packet packet = e.Packet; AvatarSitResponsePacket sit = (AvatarSitResponsePacket)packet; OnAvatarSitResponse(new AvatarSitResponseEventArgs(sit.SitObject.ID, sit.SitTransform.AutoPilot, sit.SitTransform.CameraAtOffset, sit.SitTransform.CameraEyeOffset, sit.SitTransform.ForceMouselook, sit.SitTransform.SitPosition, sit.SitTransform.SitRotation)); } protected void MuteListUpdateHander(object sender, PacketReceivedEventArgs e) { MuteListUpdatePacket packet = (MuteListUpdatePacket)e.Packet; if (packet.MuteData.AgentID != Client.Self.AgentID) { return; } ThreadPool.QueueUserWorkItem(sync => { using (AutoResetEvent gotMuteList = new AutoResetEvent(false)) { string fileName = Utils.BytesToString(packet.MuteData.Filename); string muteList = string.Empty; ulong xferID = 0; byte[] assetData = null; EventHandler xferCallback = (object xsender, XferReceivedEventArgs xe) => { if (xe.Xfer.XferID != xferID) return; assetData = xe.Xfer.AssetData; gotMuteList.Set(); }; Client.Assets.XferReceived += xferCallback; xferID = Client.Assets.RequestAssetXfer(fileName, true, false, UUID.Zero, AssetType.Unknown, true); if (gotMuteList.WaitOne(60 * 1000, false)) { muteList = Utils.BytesToString(assetData); lock (MuteList.Dictionary) { MuteList.Dictionary.Clear(); foreach (var line in muteList.Split('\n')) { if (line.Trim() == string.Empty) continue; try { Match m; if ((m = Regex.Match(line, @"(?\d+)\s+(?[a-zA-Z0-9-]+)\s+(?[^|]+)|(?.+)", RegexOptions.CultureInvariant)).Success) { MuteEntry me = new MuteEntry { Type = (MuteType) int.Parse(m.Groups["MyteType"].Value), ID = new UUID(m.Groups["Key"].Value), Name = m.Groups["Name"].Value }; int flags = 0; int.TryParse(m.Groups["Flags"].Value, out flags); me.Flags = (MuteFlags)flags; MuteList[$"{me.ID}|{me.Name}"] = me; } else { throw new ArgumentException("Invalid mutelist entry line"); } } catch (Exception ex) { Logger.Log("Failed to parse the mute list line: " + line, Helpers.LogLevel.Warning, Client, ex); } } } OnMuteListUpdated(EventArgs.Empty); } else { Logger.Log("Timed out waiting for mute list download", Helpers.LogLevel.Warning, Client); } Client.Assets.XferReceived -= xferCallback; } }); } #endregion Packet Handlers } #region Event Argument Classes /// /// Class for sending info on the success of the opration /// of setting the maturity access level /// public class AgentAccessEventArgs : EventArgs { /// /// New maturity accesss level returned from the sim /// public string NewLevel { get; } /// /// True if setting the new maturity access level has succedded /// public bool Success { get; } /// /// Creates new instance of the EventArgs class /// /// Has setting new maturty access level succeeded /// New maturity access level as returned by the simulator public AgentAccessEventArgs(bool success, string newLevel) { NewLevel = newLevel; Success = success; } } /// /// /// public class ChatEventArgs : EventArgs { /// Get the simulator sending the message public Simulator Simulator { get; } /// Get the message sent public string Message { get; } /// Get the audible level of the message public ChatAudibleLevel AudibleLevel { get; } /// Get the type of message sent: whisper, shout, etc public ChatType Type { get; } /// Get the source type of the message sender public ChatSourceType SourceType { get; } /// Get the name of the agent or object sending the message public string FromName { get; } /// Get the ID of the agent or object sending the message public UUID SourceID { get; } /// Get the ID of the object owner, or the agent ID sending the message public UUID OwnerID { get; } /// Get the position of the agent or object sending the message public Vector3 Position { get; } /// /// Construct a new instance of the ChatEventArgs object /// /// Sim from which the message originates /// The message sent /// The audible level of the message /// The type of message sent: whisper, shout, etc /// The source type of the message sender /// The name of the agent or object sending the message /// The ID of the agent or object sending the message /// The ID of the object owner, or the agent ID sending the message /// The position of the agent or object sending the message public ChatEventArgs(Simulator simulator, string message, ChatAudibleLevel audible, ChatType type, ChatSourceType sourceType, string fromName, UUID sourceId, UUID ownerid, Vector3 position) { Simulator = simulator; Message = message; AudibleLevel = audible; Type = type; SourceType = sourceType; FromName = fromName; SourceID = sourceId; Position = position; OwnerID = ownerid; } } /// Contains the data sent when a primitive opens a dialog with this agent public class ScriptDialogEventArgs : EventArgs { /// Get the dialog message public string Message { get; } /// Get the name of the object that sent the dialog request public string ObjectName { get; } /// Get the ID of the image to be displayed public UUID ImageID { get; } /// Get the ID of the primitive sending the dialog public UUID ObjectID { get; } /// Get the first name of the senders owner public string FirstName { get; } /// Get the last name of the senders owner public string LastName { get; } /// Get the communication channel the dialog was sent on, responses /// should also send responses on this same channel public int Channel { get; } /// Get the string labels containing the options presented in this dialog public List ButtonLabels { get; } /// UUID of the scritped object owner public UUID OwnerID { get; } /// /// Construct a new instance of the ScriptDialogEventArgs /// /// The dialog message /// The name of the object that sent the dialog request /// The ID of the image to be displayed /// The ID of the primitive sending the dialog /// The first name of the senders owner /// The last name of the senders owner /// The communication channel the dialog was sent on /// The string labels containing the options presented in this dialog /// UUID of the scritped object owner public ScriptDialogEventArgs(string message, string objectName, UUID imageID, UUID objectID, string firstName, string lastName, int chatChannel, List buttons, UUID ownerID) { Message = message; ObjectName = objectName; ImageID = imageID; ObjectID = objectID; FirstName = firstName; LastName = lastName; Channel = chatChannel; ButtonLabels = buttons; OwnerID = ownerID; } } /// Contains the data sent when a primitive requests debit or other permissions /// requesting a YES or NO answer public class ScriptQuestionEventArgs : EventArgs { /// Get the simulator containing the object sending the request public Simulator Simulator { get; } /// Get the ID of the script making the request public UUID TaskID { get; } /// Get the ID of the primitive containing the script making the request public UUID ItemID { get; } /// Get the name of the primitive making the request public string ObjectName { get; } /// Get the name of the owner of the object making the request public string ObjectOwnerName { get; } /// Get the permissions being requested public ScriptPermission Questions { get; } /// /// Construct a new instance of the ScriptQuestionEventArgs /// /// The simulator containing the object sending the request /// The ID of the script making the request /// The ID of the primitive containing the script making the request /// The name of the primitive making the request /// The name of the owner of the object making the request /// The permissions being requested public ScriptQuestionEventArgs(Simulator simulator, UUID taskID, UUID itemID, string objectName, string objectOwner, ScriptPermission questions) { Simulator = simulator; TaskID = taskID; ItemID = itemID; ObjectName = objectName; ObjectOwnerName = objectOwner; Questions = questions; } } /// Contains the data sent when a primitive sends a request /// to an agent to open the specified URL public class LoadUrlEventArgs : EventArgs { /// Get the name of the object sending the request public string ObjectName { get; } /// Get the ID of the object sending the request public UUID ObjectID { get; } /// Get the ID of the owner of the object sending the request public UUID OwnerID { get; } /// True if the object is owned by a group public bool OwnerIsGroup { get; } /// Get the message sent with the request public string Message { get; } /// Get the URL the object sent public string URL { get; } /// /// Construct a new instance of the LoadUrlEventArgs /// /// The name of the object sending the request /// The ID of the object sending the request /// The ID of the owner of the object sending the request /// True if the object is owned by a group /// The message sent with the request /// The URL the object sent public LoadUrlEventArgs(string objectName, UUID objectID, UUID ownerID, bool ownerIsGroup, string message, string url) { ObjectName = objectName; ObjectID = objectID; OwnerID = ownerID; OwnerIsGroup = ownerIsGroup; Message = message; URL = url; } } /// The date received from an ImprovedInstantMessage public class InstantMessageEventArgs : EventArgs { /// Get the InstantMessage object public InstantMessage IM { get; } /// Get the simulator where the InstantMessage origniated public Simulator Simulator { get; } /// /// Construct a new instance of the InstantMessageEventArgs object /// /// the InstantMessage object /// the simulator where the InstantMessage origniated public InstantMessageEventArgs(InstantMessage im, Simulator simulator) { IM = im; Simulator = simulator; } } /// Contains the currency balance public class BalanceEventArgs : EventArgs { /// /// Get the currenct balance /// public int Balance { get; } /// /// Construct a new BalanceEventArgs object /// /// The currenct balance public BalanceEventArgs(int balance) { Balance = balance; } } /// Contains the transaction summary when an item is purchased, /// money is given, or land is purchased public class MoneyBalanceReplyEventArgs : EventArgs { /// Get the ID of the transaction public UUID TransactionID { get; } /// True of the transaction was successful public bool Success { get; } /// Get the remaining currency balance public int Balance { get; } /// Get the meters credited public int MetersCredit { get; } /// Get the meters comitted public int MetersCommitted { get; } /// Get the description of the transaction public string Description { get; } /// Detailed transaction information public TransactionInfo TransactionInfo { get; } /// /// Construct a new instance of the MoneyBalanceReplyEventArgs object /// /// The ID of the transaction /// True of the transaction was successful /// The current currency balance /// The meters credited /// The meters comitted /// A brief description of the transaction /// Transaction info public MoneyBalanceReplyEventArgs(UUID transactionID, bool transactionSuccess, int balance, int metersCredit, int metersCommitted, string description, TransactionInfo transactionInfo) { TransactionID = transactionID; Success = transactionSuccess; Balance = balance; MetersCredit = metersCredit; MetersCommitted = metersCommitted; Description = description; TransactionInfo = transactionInfo; } } // string message, TeleportStatus status, TeleportFlags flags public class TeleportEventArgs : EventArgs { public string Message { get; } public TeleportStatus Status { get; } public TeleportFlags Flags { get; } public TeleportEventArgs(string message, TeleportStatus status, TeleportFlags flags) { Message = message; Status = status; Flags = flags; } } /// Data sent from the simulator containing information about your agent and active group information public class AgentDataReplyEventArgs : EventArgs { /// Get the agents first name public string FirstName { get; } /// Get the agents last name public string LastName { get; } /// Get the active group ID of your agent public UUID ActiveGroupID { get; } /// Get the active groups title of your agent public string GroupTitle { get; } /// Get the combined group powers of your agent public GroupPowers GroupPowers { get; } /// Get the active group name of your agent public string GroupName { get; } /// /// Construct a new instance of the AgentDataReplyEventArgs object /// /// The agents first name /// The agents last name /// The agents active group ID /// The group title of the agents active group /// The combined group powers the agent has in the active group /// The name of the group the agent has currently active public AgentDataReplyEventArgs(string firstName, string lastName, UUID activeGroupID, string groupTitle, GroupPowers groupPowers, string groupName) { FirstName = firstName; LastName = lastName; ActiveGroupID = activeGroupID; GroupTitle = groupTitle; GroupPowers = groupPowers; GroupName = groupName; } } /// Data sent by the simulator to indicate the active/changed animations /// applied to your agent public class AnimationsChangedEventArgs : EventArgs { /// Get the dictionary that contains the changed animations public InternalDictionary Animations { get; } /// /// Construct a new instance of the AnimationsChangedEventArgs class /// /// The dictionary that contains the changed animations public AnimationsChangedEventArgs(InternalDictionary agentAnimations) { Animations = agentAnimations; } } /// /// Data sent from a simulator indicating a collision with your agent /// public class MeanCollisionEventArgs : EventArgs { /// Get the Type of collision public MeanCollisionType Type { get; } /// Get the ID of the agent or object that collided with your agent public UUID Aggressor { get; } /// Get the ID of the agent that was attacked public UUID Victim { get; } /// A value indicating the strength of the collision public float Magnitude { get; } /// Get the time the collision occurred public DateTime Time { get; } /// /// Construct a new instance of the MeanCollisionEventArgs class /// /// The type of collision that occurred /// The ID of the agent or object that perpetrated the agression /// The ID of the Victim /// The strength of the collision /// The Time the collision occurred public MeanCollisionEventArgs(MeanCollisionType type, UUID perp, UUID victim, float magnitude, DateTime time) { Type = type; Aggressor = perp; Victim = victim; Magnitude = magnitude; Time = time; } } /// Data sent to your agent when it crosses region boundaries public class RegionCrossedEventArgs : EventArgs { /// Get the simulator your agent just left public Simulator OldSimulator { get; } /// Get the simulator your agent is now in public Simulator NewSimulator { get; } /// /// Construct a new instance of the RegionCrossedEventArgs class /// /// The simulator your agent just left /// The simulator your agent is now in public RegionCrossedEventArgs(Simulator oldSim, Simulator newSim) { OldSimulator = oldSim; NewSimulator = newSim; } } /// Data sent from the simulator when your agent joins a group chat session public class GroupChatJoinedEventArgs : EventArgs { /// Get the ID of the group chat session public UUID SessionID { get; } /// Get the name of the session public string SessionName { get; } /// Get the temporary session ID used for establishing new sessions public UUID TmpSessionID { get; } /// True if your agent successfully joined the session public bool Success { get; } /// /// Construct a new instance of the GroupChatJoinedEventArgs class /// /// The ID of the session /// The name of the session /// A temporary session id used for establishing new sessions /// True of your agent successfully joined the session public GroupChatJoinedEventArgs(UUID groupChatSessionID, string sessionName, UUID tmpSessionID, bool success) { SessionID = groupChatSessionID; SessionName = sessionName; TmpSessionID = tmpSessionID; Success = success; } } /// Data sent by the simulator containing urgent messages public class AlertMessageEventArgs : EventArgs { /// Get the alert message public string Message { get; } /// /// Construct a new instance of the AlertMessageEventArgs class /// /// The alert message public AlertMessageEventArgs(string message) { Message = message; } } /// Data sent by a script requesting to take or release specified controls to your agent public class ScriptControlEventArgs : EventArgs { /// Get the controls the script is attempting to take or release to the agent public ScriptControlChange Controls { get; } /// True if the script is passing controls back to the agent public bool Pass { get; } /// True if the script is requesting controls be released to the script public bool Take { get; } /// /// Construct a new instance of the ScriptControlEventArgs class /// /// The controls the script is attempting to take or release to the agent /// True if the script is passing controls back to the agent /// True if the script is requesting controls be released to the script public ScriptControlEventArgs(ScriptControlChange controls, bool pass, bool take) { Controls = controls; Pass = pass; Take = take; } } /// /// Data sent from the simulator to an agent to indicate its view limits /// public class CameraConstraintEventArgs : EventArgs { /// Get the collision plane public Vector4 CollidePlane { get; } /// /// Construct a new instance of the CameraConstraintEventArgs class /// /// The collision plane public CameraConstraintEventArgs(Vector4 collidePlane) { CollidePlane = collidePlane; } } /// /// Data containing script sensor requests which allow an agent to know the specific details /// of a primitive sending script sensor requests /// public class ScriptSensorReplyEventArgs : EventArgs { /// Get the ID of the primitive sending the sensor public UUID RequestorID { get; } /// Get the ID of the group associated with the primitive public UUID GroupID { get; } /// Get the name of the primitive sending the sensor public string Name { get; } /// Get the ID of the primitive sending the sensor public UUID ObjectID { get; } /// Get the ID of the owner of the primitive sending the sensor public UUID OwnerID { get; } /// Get the position of the primitive sending the sensor public Vector3 Position { get; } /// Get the range the primitive specified to scan public float Range { get; } /// Get the rotation of the primitive sending the sensor public Quaternion Rotation { get; } /// Get the type of sensor the primitive sent public ScriptSensorTypeFlags Type { get; } /// Get the velocity of the primitive sending the sensor public Vector3 Velocity { get; } /// /// Construct a new instance of the ScriptSensorReplyEventArgs /// /// The ID of the primitive sending the sensor /// The ID of the group associated with the primitive /// The name of the primitive sending the sensor /// The ID of the primitive sending the sensor /// The ID of the owner of the primitive sending the sensor /// The position of the primitive sending the sensor /// The range the primitive specified to scan /// The rotation of the primitive sending the sensor /// The type of sensor the primitive sent /// The velocity of the primitive sending the sensor public ScriptSensorReplyEventArgs(UUID requestorID, UUID groupID, string name, UUID objectID, UUID ownerID, Vector3 position, float range, Quaternion rotation, ScriptSensorTypeFlags type, Vector3 velocity) { RequestorID = requestorID; GroupID = groupID; Name = name; ObjectID = objectID; OwnerID = ownerID; Position = position; Range = range; Rotation = rotation; Type = type; Velocity = velocity; } } /// Contains the response data returned from the simulator in response to a public class AvatarSitResponseEventArgs : EventArgs { /// Get the ID of the primitive the agent will be sitting on public UUID ObjectID { get; } /// True if the simulator Autopilot functions were involved public bool Autopilot { get; } /// Get the camera offset of the agent when seated public Vector3 CameraAtOffset { get; } /// Get the camera eye offset of the agent when seated public Vector3 CameraEyeOffset { get; } /// True of the agent will be in mouselook mode when seated public bool ForceMouselook { get; } /// Get the position of the agent when seated public Vector3 SitPosition { get; } /// Get the rotation of the agent when seated public Quaternion SitRotation { get; } /// Construct a new instance of the AvatarSitResponseEventArgs object public AvatarSitResponseEventArgs(UUID objectID, bool autoPilot, Vector3 cameraAtOffset, Vector3 cameraEyeOffset, bool forceMouselook, Vector3 sitPosition, Quaternion sitRotation) { ObjectID = objectID; Autopilot = autoPilot; CameraAtOffset = cameraAtOffset; CameraEyeOffset = cameraEyeOffset; ForceMouselook = forceMouselook; SitPosition = sitPosition; SitRotation = sitRotation; } } /// Data sent when an agent joins a chat session your agent is currently participating in public class ChatSessionMemberAddedEventArgs : EventArgs { /// Get the ID of the chat session public UUID SessionID { get; } /// Get the ID of the agent that joined public UUID AgentID { get; } /// /// Construct a new instance of the ChatSessionMemberAddedEventArgs object /// /// The ID of the chat session /// The ID of the agent joining public ChatSessionMemberAddedEventArgs(UUID sessionID, UUID agentID) { SessionID = sessionID; AgentID = agentID; } } /// Data sent when an agent exits a chat session your agent is currently participating in public class ChatSessionMemberLeftEventArgs : EventArgs { /// Get the ID of the chat session public UUID SessionID { get; } /// Get the ID of the agent that left public UUID AgentID { get; } /// /// Construct a new instance of the ChatSessionMemberLeftEventArgs object /// /// The ID of the chat session /// The ID of the Agent that left public ChatSessionMemberLeftEventArgs(UUID sessionID, UUID agentID) { SessionID = sessionID; AgentID = agentID; } } /// Event arguments with the result of setting display name operation public class SetDisplayNameReplyEventArgs : EventArgs { /// Status code, 200 indicates settign display name was successful public int Status { get; } /// Textual description of the status public string Reason { get; } /// Details of the newly set display name public AgentDisplayName DisplayName { get; } /// Default constructor public SetDisplayNameReplyEventArgs(int status, string reason, AgentDisplayName displayName) { Status = status; Reason = reason; DisplayName = displayName; } } #endregion }