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