git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@2340 52acb1d6-8a22-11de-b505-999d5b087335
3451 lines
149 KiB
C#
3451 lines
149 KiB
C#
/*
|
|
* Copyright (c) 2006-2008, openmetaverse.org
|
|
* 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.org nor the names
|
|
* of its contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Net;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Text;
|
|
using System.Reflection;
|
|
using OpenMetaverse.StructuredData;
|
|
using OpenMetaverse.Capabilities;
|
|
using OpenMetaverse.Packets;
|
|
|
|
namespace OpenMetaverse
|
|
{
|
|
#region Enums
|
|
|
|
/// <summary>
|
|
/// Permission request flags, asked when a script wants to control an Avatar
|
|
/// </summary>
|
|
[Flags]
|
|
public enum ScriptPermission : int
|
|
{
|
|
/// <summary>Placeholder for empty values, shouldn't ever see this</summary>
|
|
None = 0,
|
|
/// <summary>Script wants ability to take money from you</summary>
|
|
Debit = 1 << 1,
|
|
/// <summary>Script wants to take camera controls for you</summary>
|
|
TakeControls = 1 << 2,
|
|
/// <summary>Script wants to remap avatars controls</summary>
|
|
RemapControls = 1 << 3,
|
|
/// <summary>Script wants to trigger avatar animations</summary>
|
|
/// <remarks>This function is not implemented on the grid</remarks>
|
|
TriggerAnimation = 1 << 4,
|
|
/// <summary>Script wants to attach or detach the prim or primset to your avatar</summary>
|
|
Attach = 1 << 5,
|
|
/// <summary>Script wants permission to release ownership</summary>
|
|
/// <remarks>This function is not implemented on the grid
|
|
/// The concept of "public" objects does not exist anymore.</remarks>
|
|
ReleaseOwnership = 1 << 6,
|
|
/// <summary>Script wants ability to link/delink with other prims</summary>
|
|
ChangeLinks = 1 << 7,
|
|
/// <summary>Script wants permission to change joints</summary>
|
|
/// <remarks>This function is not implemented on the grid</remarks>
|
|
ChangeJoints = 1 << 8,
|
|
/// <summary>Script wants permissions to change permissions</summary>
|
|
/// <remarks>This function is not implemented on the grid</remarks>
|
|
ChangePermissions = 1 << 9,
|
|
/// <summary>Script wants to track avatars camera position and rotation </summary>
|
|
TrackCamera = 1 << 10,
|
|
/// <summary>Script wants to control your camera</summary>
|
|
ControlCamera = 1 << 11
|
|
}
|
|
|
|
/// <summary>
|
|
/// Special commands used in Instant Messages
|
|
/// </summary>
|
|
public enum InstantMessageDialog : byte
|
|
{
|
|
/// <summary>Indicates a regular IM from another agent</summary>
|
|
MessageFromAgent = 0,
|
|
/// <summary>Simple notification box with an OK button</summary>
|
|
MessageBox = 1,
|
|
// <summary>Used to show a countdown notification with an OK
|
|
// button, deprecated now</summary>
|
|
//[Obsolete]
|
|
//MessageBoxCountdown = 2,
|
|
/// <summary>You've been invited to join a group.</summary>
|
|
GroupInvitation = 3,
|
|
/// <summary>Inventory offer</summary>
|
|
InventoryOffered = 4,
|
|
/// <summary>Accepted inventory offer</summary>
|
|
InventoryAccepted = 5,
|
|
/// <summary>Declined inventory offer</summary>
|
|
InventoryDeclined = 6,
|
|
/// <summary>Group vote</summary>
|
|
GroupVote = 7,
|
|
// <summary>A message to everyone in the agent's group, no longer
|
|
// used</summary>
|
|
//[Obsolete]
|
|
//DeprecatedGroupMessage = 8,
|
|
/// <summary>An object is offering its inventory</summary>
|
|
TaskInventoryOffered = 9,
|
|
/// <summary>Accept an inventory offer from an object</summary>
|
|
TaskInventoryAccepted = 10,
|
|
/// <summary>Decline an inventory offer from an object</summary>
|
|
TaskInventoryDeclined = 11,
|
|
/// <summary>Unknown</summary>
|
|
NewUserDefault = 12,
|
|
/// <summary>Start a session, or add users to a session</summary>
|
|
SessionAdd = 13,
|
|
/// <summary>Start a session, but don't prune offline users</summary>
|
|
SessionOfflineAdd = 14,
|
|
/// <summary>Start a session with your group</summary>
|
|
SessionGroupStart = 15,
|
|
/// <summary>Start a session without a calling card (finder or objects)</summary>
|
|
SessionCardlessStart = 16,
|
|
/// <summary>Send a message to a session</summary>
|
|
SessionSend = 17,
|
|
/// <summary>Leave a session</summary>
|
|
SessionDrop = 18,
|
|
/// <summary>Indicates that the IM is from an object</summary>
|
|
MessageFromObject = 19,
|
|
/// <summary>Sent an IM to a busy user, this is the auto response</summary>
|
|
BusyAutoResponse = 20,
|
|
/// <summary>Shows the message in the console and chat history</summary>
|
|
ConsoleAndChatHistory = 21,
|
|
/// <summary>Send a teleport lure</summary>
|
|
RequestTeleport = 22,
|
|
/// <summary>Response sent to the agent which inititiated a teleport invitation</summary>
|
|
AcceptTeleport = 23,
|
|
/// <summary>Response sent to the agent which inititiated a teleport invitation</summary>
|
|
DenyTeleport = 24,
|
|
/// <summary>Only useful if you have Linden permissions</summary>
|
|
GodLikeRequestTeleport = 25,
|
|
/// <summary>A placeholder type for future expansion, currently not
|
|
/// used</summary>
|
|
CurrentlyUnused = 26,
|
|
// <summary>Notification of a new group election, this is
|
|
// deprecated</summary>
|
|
//[Obsolete]
|
|
//DeprecatedGroupElection = 27,
|
|
/// <summary>IM to tell the user to go to an URL</summary>
|
|
GotoUrl = 28,
|
|
/// <summary>IM for help</summary>
|
|
Session911Start = 29,
|
|
/// <summary>IM sent automatically on call for help, sends a lure
|
|
/// to each Helper reached</summary>
|
|
Lure911 = 30,
|
|
/// <summary>Like an IM but won't go to email</summary>
|
|
FromTaskAsAlert = 31,
|
|
/// <summary>IM from a group officer to all group members</summary>
|
|
GroupNotice = 32,
|
|
/// <summary>Unknown</summary>
|
|
GroupNoticeInventoryAccepted = 33,
|
|
/// <summary>Unknown</summary>
|
|
GroupNoticeInventoryDeclined = 34,
|
|
/// <summary>Accept a group invitation</summary>
|
|
GroupInvitationAccept = 35,
|
|
/// <summary>Decline a group invitation</summary>
|
|
GroupInvitationDecline = 36,
|
|
/// <summary>Unknown</summary>
|
|
GroupNoticeRequested = 37,
|
|
/// <summary>An avatar is offering you friendship</summary>
|
|
FriendshipOffered = 38,
|
|
/// <summary>An avatar has accepted your friendship offer</summary>
|
|
FriendshipAccepted = 39,
|
|
/// <summary>An avatar has declined your friendship offer</summary>
|
|
FriendshipDeclined = 40,
|
|
/// <summary>Indicates that a user has started typing</summary>
|
|
StartTyping = 41,
|
|
/// <summary>Indicates that a user has stopped typing</summary>
|
|
StopTyping = 42
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flag in Instant Messages, whether the IM should be delivered to
|
|
/// offline avatars as well
|
|
/// </summary>
|
|
public enum InstantMessageOnline
|
|
{
|
|
/// <summary>Only deliver to online avatars</summary>
|
|
Online = 0,
|
|
/// <summary>If the avatar is offline the message will be held until
|
|
/// they login next, and possibly forwarded to their e-mail account</summary>
|
|
Offline = 1
|
|
}
|
|
|
|
/// <summary>
|
|
/// Conversion type to denote Chat Packet types in an easier-to-understand format
|
|
/// </summary>
|
|
public enum ChatType : byte
|
|
{
|
|
/// <summary>Whisper (5m radius)</summary>
|
|
Whisper = 0,
|
|
/// <summary>Normal chat (10/20m radius), what the official viewer typically sends</summary>
|
|
Normal = 1,
|
|
/// <summary>Shouting! (100m radius)</summary>
|
|
Shout = 2,
|
|
// <summary>Say chat (10/20m radius) - The official viewer will
|
|
// print "[4:15] You say, hey" instead of "[4:15] You: hey"</summary>
|
|
//[Obsolete]
|
|
//Say = 3,
|
|
/// <summary>Event message when an Avatar has begun to type</summary>
|
|
StartTyping = 4,
|
|
/// <summary>Event message when an Avatar has stopped typing</summary>
|
|
StopTyping = 5,
|
|
/// <summary>Unknown</summary>
|
|
Debug = 6,
|
|
/// <summary>Event message when an object uses llOwnerSay</summary>
|
|
OwnerSay = 8
|
|
}
|
|
|
|
/// <summary>
|
|
/// Identifies the source of a chat message
|
|
/// </summary>
|
|
public enum ChatSourceType : byte
|
|
{
|
|
/// <summary>Chat from the grid or simulator</summary>
|
|
System = 0,
|
|
/// <summary>Chat from another avatar</summary>
|
|
Agent = 1,
|
|
/// <summary>Chat from an object</summary>
|
|
Object = 2
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public enum ChatAudibleLevel : sbyte
|
|
{
|
|
/// <summary></summary>
|
|
Not = -1,
|
|
/// <summary></summary>
|
|
Barely = 0,
|
|
/// <summary></summary>
|
|
Fully = 1
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effect type used in ViewerEffect packets
|
|
/// </summary>
|
|
public enum EffectType : byte
|
|
{
|
|
/// <summary></summary>
|
|
Text = 0,
|
|
/// <summary></summary>
|
|
Icon,
|
|
/// <summary></summary>
|
|
Connector,
|
|
/// <summary></summary>
|
|
FlexibleObject,
|
|
/// <summary></summary>
|
|
AnimalControls,
|
|
/// <summary></summary>
|
|
AnimationObject,
|
|
/// <summary></summary>
|
|
Cloth,
|
|
/// <summary>Project a beam from a source to a destination, such as
|
|
/// the one used when editing an object</summary>
|
|
Beam,
|
|
/// <summary></summary>
|
|
Glow,
|
|
/// <summary></summary>
|
|
Point,
|
|
/// <summary></summary>
|
|
Trail,
|
|
/// <summary>Create a swirl of particles around an object</summary>
|
|
Sphere,
|
|
/// <summary></summary>
|
|
Spiral,
|
|
/// <summary></summary>
|
|
Edit,
|
|
/// <summary>Cause an avatar to look at an object</summary>
|
|
LookAt,
|
|
/// <summary>Cause an avatar to point at an object</summary>
|
|
PointAt
|
|
}
|
|
|
|
/// <summary>
|
|
/// The action an avatar is doing when looking at something, used in
|
|
/// ViewerEffect packets for the LookAt effect
|
|
/// </summary>
|
|
public enum LookAtType : byte
|
|
{
|
|
/// <summary></summary>
|
|
None,
|
|
/// <summary></summary>
|
|
Idle,
|
|
/// <summary></summary>
|
|
AutoListen,
|
|
/// <summary></summary>
|
|
FreeLook,
|
|
/// <summary></summary>
|
|
Respond,
|
|
/// <summary></summary>
|
|
Hover,
|
|
/// <summary>Deprecated</summary>
|
|
[Obsolete]
|
|
Conversation,
|
|
/// <summary></summary>
|
|
Select,
|
|
/// <summary></summary>
|
|
Focus,
|
|
/// <summary></summary>
|
|
Mouselook,
|
|
/// <summary></summary>
|
|
Clear
|
|
}
|
|
|
|
/// <summary>
|
|
/// The action an avatar is doing when pointing at something, used in
|
|
/// ViewerEffect packets for the PointAt effect
|
|
/// </summary>
|
|
public enum PointAtType : byte
|
|
{
|
|
/// <summary></summary>
|
|
None,
|
|
/// <summary></summary>
|
|
Select,
|
|
/// <summary></summary>
|
|
Grab,
|
|
/// <summary></summary>
|
|
Clear
|
|
}
|
|
|
|
/// <summary>
|
|
/// Money transaction types
|
|
/// </summary>
|
|
public enum MoneyTransactionType : int
|
|
{
|
|
/// <summary></summary>
|
|
None = 0,
|
|
/// <summary></summary>
|
|
FailSimulatorTimeout = 1,
|
|
/// <summary></summary>
|
|
FailDataserverTimeout = 2,
|
|
/// <summary></summary>
|
|
ObjectClaim = 1000,
|
|
/// <summary></summary>
|
|
LandClaim = 1001,
|
|
/// <summary></summary>
|
|
GroupCreate = 1002,
|
|
/// <summary></summary>
|
|
ObjectPublicClaim = 1003,
|
|
/// <summary></summary>
|
|
GroupJoin = 1004,
|
|
/// <summary></summary>
|
|
TeleportCharge = 1100,
|
|
/// <summary></summary>
|
|
UploadCharge = 1101,
|
|
/// <summary></summary>
|
|
LandAuction = 1102,
|
|
/// <summary></summary>
|
|
ClassifiedCharge = 1103,
|
|
/// <summary></summary>
|
|
ObjectTax = 2000,
|
|
/// <summary></summary>
|
|
LandTax = 2001,
|
|
/// <summary></summary>
|
|
LightTax = 2002,
|
|
/// <summary></summary>
|
|
ParcelDirFee = 2003,
|
|
/// <summary></summary>
|
|
GroupTax = 2004,
|
|
/// <summary></summary>
|
|
ClassifiedRenew = 2005,
|
|
/// <summary></summary>
|
|
GiveInventory = 3000,
|
|
/// <summary></summary>
|
|
ObjectSale = 5000,
|
|
/// <summary></summary>
|
|
Gift = 5001,
|
|
/// <summary></summary>
|
|
LandSale = 5002,
|
|
/// <summary></summary>
|
|
ReferBonus = 5003,
|
|
/// <summary></summary>
|
|
InventorySale = 5004,
|
|
/// <summary></summary>
|
|
RefundPurchase = 5005,
|
|
/// <summary></summary>
|
|
LandPassSale = 5006,
|
|
/// <summary></summary>
|
|
DwellBonus = 5007,
|
|
/// <summary></summary>
|
|
PayObject = 5008,
|
|
/// <summary></summary>
|
|
ObjectPays = 5009,
|
|
/// <summary></summary>
|
|
GroupLandDeed = 6001,
|
|
/// <summary></summary>
|
|
GroupObjectDeed = 6002,
|
|
/// <summary></summary>
|
|
GroupLiability = 6003,
|
|
/// <summary></summary>
|
|
GroupDividend = 6004,
|
|
/// <summary></summary>
|
|
GroupMembershipDues = 6005,
|
|
/// <summary></summary>
|
|
ObjectRelease = 8000,
|
|
/// <summary></summary>
|
|
LandRelease = 8001,
|
|
/// <summary></summary>
|
|
ObjectDelete = 8002,
|
|
/// <summary></summary>
|
|
ObjectPublicDecay = 8003,
|
|
/// <summary></summary>
|
|
ObjectPublicDelete = 8004,
|
|
/// <summary></summary>
|
|
LindenAdjustment = 9000,
|
|
/// <summary></summary>
|
|
LindenGrant = 9001,
|
|
/// <summary></summary>
|
|
LindenPenalty = 9002,
|
|
/// <summary></summary>
|
|
EventFee = 9003,
|
|
/// <summary></summary>
|
|
EventPrize = 9004,
|
|
/// <summary></summary>
|
|
StipendBasic = 10000,
|
|
/// <summary></summary>
|
|
StipendDeveloper = 10001,
|
|
/// <summary></summary>
|
|
StipendAlways = 10002,
|
|
/// <summary></summary>
|
|
StipendDaily = 10003,
|
|
/// <summary></summary>
|
|
StipendRating = 10004,
|
|
/// <summary></summary>
|
|
StipendDelta = 10005
|
|
}
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Flags]
|
|
public enum TransactionFlags : byte
|
|
{
|
|
/// <summary></summary>
|
|
None = 0,
|
|
/// <summary></summary>
|
|
SourceGroup = 1,
|
|
/// <summary></summary>
|
|
DestGroup = 2,
|
|
/// <summary></summary>
|
|
OwnerGroup = 4,
|
|
/// <summary></summary>
|
|
SimultaneousContribution = 8,
|
|
/// <summary></summary>
|
|
ContributionRemoval = 16
|
|
}
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public enum MeanCollisionType : byte
|
|
{
|
|
/// <summary></summary>
|
|
None,
|
|
/// <summary></summary>
|
|
Bump,
|
|
/// <summary></summary>
|
|
LLPushObject,
|
|
/// <summary></summary>
|
|
SelectedObjectCollide,
|
|
/// <summary></summary>
|
|
ScriptedObjectCollide,
|
|
/// <summary></summary>
|
|
PhysicalObjectCollide
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flags sent when a script takes or releases a control
|
|
/// </summary>
|
|
/// <remarks>NOTE: (need to verify) These might be a subset of the ControlFlags enum in Movement,</remarks>
|
|
[Flags]
|
|
public enum ScriptControlChange : uint
|
|
{
|
|
/// <summary>No Flags set</summary>
|
|
None = 0,
|
|
/// <summary>Forward (W or up Arrow)</summary>
|
|
Forward = 1,
|
|
/// <summary>Back (S or down arrow)</summary>
|
|
Back = 2,
|
|
/// <summary>Move left (shift+A or left arrow)</summary>
|
|
Left = 4,
|
|
/// <summary>Move right (shift+D or right arrow)</summary>
|
|
Right = 8,
|
|
/// <summary>Up (E or PgUp)</summary>
|
|
Up = 16,
|
|
/// <summary>Down (C or PgDown</summary>
|
|
Down = 32,
|
|
/// <summary>Rotate left (A or left arrow)</summary>
|
|
RotateLeft = 256,
|
|
/// <summary>Rotate right (D or right arrow)</summary>
|
|
RotateRight = 512,
|
|
/// <summary>Left Mouse Button</summary>
|
|
LeftButton = 268435456,
|
|
/// <summary>Left Mouse button in MouseLook</summary>
|
|
MouseLookLeftButton = 1073741824
|
|
}
|
|
#endregion Enums
|
|
|
|
#region Structs
|
|
|
|
/// <summary>
|
|
/// Instant Message
|
|
/// </summary>
|
|
public struct InstantMessage
|
|
{
|
|
/// <summary>Key of sender</summary>
|
|
public UUID FromAgentID;
|
|
/// <summary>Name of sender</summary>
|
|
public string FromAgentName;
|
|
/// <summary>Key of destination avatar</summary>
|
|
public UUID ToAgentID;
|
|
/// <summary>ID of originating estate</summary>
|
|
public uint ParentEstateID;
|
|
/// <summary>Key of originating region</summary>
|
|
public UUID RegionID;
|
|
/// <summary>Coordinates in originating region</summary>
|
|
public Vector3 Position;
|
|
/// <summary>Instant message type</summary>
|
|
public InstantMessageDialog Dialog;
|
|
/// <summary>Group IM session toggle</summary>
|
|
public bool GroupIM;
|
|
/// <summary>Key of IM session, for Group Messages, the groups UUID</summary>
|
|
public UUID IMSessionID;
|
|
/// <summary>Timestamp of the instant message</summary>
|
|
public DateTime Timestamp;
|
|
/// <summary>Instant message text</summary>
|
|
public string Message;
|
|
/// <summary>Whether this message is held for offline avatars</summary>
|
|
public InstantMessageOnline Offline;
|
|
/// <summary>Context specific packed data</summary>
|
|
public byte[] BinaryBucket;
|
|
//Print the contents of a message
|
|
public override string ToString()
|
|
{
|
|
string result = "";
|
|
Type imType = this.GetType();
|
|
FieldInfo[] fields = imType.GetFields();
|
|
foreach (FieldInfo field in fields)
|
|
{
|
|
result += (field.Name + " = " + field.GetValue(this) + " ");
|
|
}
|
|
return result;
|
|
|
|
}
|
|
}
|
|
|
|
#endregion Structs
|
|
|
|
/// <summary>
|
|
/// Manager class for our own avatar
|
|
/// </summary>
|
|
public partial class AgentManager
|
|
{
|
|
#region Enums
|
|
|
|
/// <summary>
|
|
/// Currently only used to hide your group title
|
|
/// </summary>
|
|
[Flags]
|
|
public enum AgentFlags : byte
|
|
{
|
|
/// <summary>No flags set</summary>
|
|
None = 0,
|
|
/// <summary>Hide your group title</summary>
|
|
HideTitle = 0x01,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Action state of the avatar, which can currently be typing and
|
|
/// editing
|
|
/// </summary>
|
|
[Flags]
|
|
public enum AgentState : byte
|
|
{
|
|
/// <summary></summary>
|
|
None = 0x00,
|
|
/// <summary></summary>
|
|
Typing = 0x04,
|
|
/// <summary></summary>
|
|
Editing = 0x10
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current teleport status
|
|
/// </summary>
|
|
public enum TeleportStatus
|
|
{
|
|
/// <summary>Unknown status</summary>
|
|
None,
|
|
/// <summary>Teleport initialized</summary>
|
|
Start,
|
|
/// <summary>Teleport in progress</summary>
|
|
Progress,
|
|
/// <summary>Teleport failed</summary>
|
|
Failed,
|
|
/// <summary>Teleport completed</summary>
|
|
Finished,
|
|
/// <summary>Teleport cancelled</summary>
|
|
Cancelled
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Flags]
|
|
public enum TeleportFlags : uint
|
|
{
|
|
/// <summary>No flags set, or teleport failed</summary>
|
|
Default = 0,
|
|
/// <summary>Set when newbie leaves help island for first time</summary>
|
|
SetHomeToTarget = 1 << 0,
|
|
/// <summary></summary>
|
|
SetLastToTarget = 1 << 1,
|
|
/// <summary>Via Lure</summary>
|
|
ViaLure = 1 << 2,
|
|
/// <summary>Via Landmark</summary>
|
|
ViaLandmark = 1 << 3,
|
|
/// <summary>Via Location</summary>
|
|
ViaLocation = 1 << 4,
|
|
/// <summary>Via Home</summary>
|
|
ViaHome = 1 << 5,
|
|
/// <summary>Via Telehub</summary>
|
|
ViaTelehub = 1 << 6,
|
|
/// <summary>Via Login</summary>
|
|
ViaLogin = 1 << 7,
|
|
/// <summary>Linden Summoned</summary>
|
|
ViaGodlikeLure = 1 << 8,
|
|
/// <summary>Linden Forced me</summary>
|
|
Godlike = 1 << 9,
|
|
/// <summary></summary>
|
|
NineOneOne = 1 << 10,
|
|
/// <summary>Agent Teleported Home via Script</summary>
|
|
DisableCancel = 1 << 11,
|
|
/// <summary></summary>
|
|
ViaRegionID = 1 << 12,
|
|
/// <summary></summary>
|
|
IsFlying = 1 << 13,
|
|
/// <summary></summary>
|
|
ResetHome = 1 << 14,
|
|
/// <summary>forced to new location for example when avatar is banned or ejected</summary>
|
|
ForceRedirect = 1 << 15,
|
|
/// <summary>Teleport Finished via a Lure</summary>
|
|
FinishedViaLure = 1 << 26,
|
|
/// <summary>Finished, Sim Changed</summary>
|
|
FinishedViaNewSim = 1 << 28,
|
|
/// <summary>Finished, Same Sim</summary>
|
|
FinishedViaSameSim = 1 << 29
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Flags]
|
|
public enum TeleportLureFlags
|
|
{
|
|
/// <summary></summary>
|
|
NormalLure = 0,
|
|
/// <summary></summary>
|
|
GodlikeLure = 1,
|
|
/// <summary></summary>
|
|
GodlikePursuit = 2
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Flags]
|
|
public enum ScriptSensorTypeFlags
|
|
{
|
|
/// <summary></summary>
|
|
Agent = 1,
|
|
/// <summary></summary>
|
|
Active = 2,
|
|
/// <summary></summary>
|
|
Passive = 4,
|
|
/// <summary></summary>
|
|
Scripted = 8,
|
|
}
|
|
|
|
#endregion Enums
|
|
|
|
#region Callbacks
|
|
|
|
/// <summary>
|
|
/// Triggered on incoming chat messages
|
|
/// </summary>
|
|
/// <param name="message">Text of chat message</param>
|
|
/// <param name="audible">Audible level of this chat message</param>
|
|
/// <param name="type">Type of chat (whisper, shout, status, etc.)</param>
|
|
/// <param name="sourceType">Source of the chat message</param>
|
|
/// <param name="fromName">Name of the sending object</param>
|
|
/// <param name="id">Key of source</param>
|
|
/// <param name="ownerid">Key of the sender</param>
|
|
/// <param name="position">Senders position</param>
|
|
public delegate void ChatCallback(string message, ChatAudibleLevel audible, ChatType type,
|
|
ChatSourceType sourceType, string fromName, UUID id, UUID ownerid, Vector3 position);
|
|
|
|
/// <summary>
|
|
/// Triggered when a script pops up a dialog box
|
|
/// </summary>
|
|
/// <param name="message">The dialog box message</param>
|
|
/// <param name="objectName">Name of the object that sent the dialog</param>
|
|
/// <param name="imageID">Image to be displayed in the dialog</param>
|
|
/// <param name="objectID">ID of the object that sent the dialog</param>
|
|
/// <param name="firstName">First name of the object owner</param>
|
|
/// <param name="lastName">Last name of the object owner</param>
|
|
/// <param name="chatChannel">Chat channel that the object is communicating on</param>
|
|
/// <param name="buttons">List of button labels</param>
|
|
public delegate void ScriptDialogCallback(string message, string objectName, UUID imageID,
|
|
UUID objectID, string firstName, string lastName, int chatChannel, List<string> buttons);
|
|
|
|
/// <summary>
|
|
/// Triggered when a script asks for permissions
|
|
/// </summary>
|
|
/// <param name="simulator">Simulator object this request comes from</param>
|
|
/// <param name="taskID">Task ID of the script requesting permissions</param>
|
|
/// <param name="itemID">ID of the object containing the script</param>
|
|
/// <param name="objectName">Name of the object containing the script</param>
|
|
/// <param name="objectOwner">Name of the object's owner</param>
|
|
/// <param name="questions">Bitwise value representing the requested permissions</param>
|
|
public delegate void ScriptQuestionCallback(Simulator simulator, UUID taskID, UUID itemID, string objectName, string objectOwner, ScriptPermission questions);
|
|
|
|
/// <summary>
|
|
/// Triggered when a script displays a URL via llLoadURL
|
|
/// </summary>
|
|
/// <param name="objectName">Name of the scripted object</param>
|
|
/// <param name="objectID">ID of the scripted object</param>
|
|
/// <param name="ownerID">ID of the object's owner</param>
|
|
/// <param name="ownerIsGroup">Whether or not ownerID is a group</param>
|
|
/// <param name="message">Message displayed along with URL</param>
|
|
/// <param name="URL">Offered URL</param>
|
|
public delegate void LoadURLCallback(string objectName, UUID objectID, UUID ownerID, bool ownerIsGroup, string message, string URL);
|
|
|
|
/// <summary>
|
|
/// Triggered when the L$ account balance for this avatar changes
|
|
/// </summary>
|
|
/// <param name="balance">The new account balance</param>
|
|
public delegate void BalanceCallback(int balance);
|
|
|
|
/// <summary>
|
|
/// Triggered on Money Balance Reply
|
|
/// </summary>
|
|
/// <param name="transactionID">ID provided in Request Money Balance, or auto-generated by system events</param>
|
|
/// <param name="transactionSuccess">Was the transaction successful</param>
|
|
/// <param name="balance">Current balance</param>
|
|
/// <param name="metersCredit">Land use credits you have</param>
|
|
/// <param name="metersCommitted">Tier committed to group(s)</param>
|
|
/// <param name="description">Description of the transaction</param>
|
|
public delegate void MoneyBalanceReplyCallback(UUID transactionID, bool transactionSuccess, int balance, int metersCredit, int metersCommitted, string description);
|
|
|
|
/// <summary>
|
|
/// Triggered on incoming instant messages
|
|
/// </summary>
|
|
/// <param name="im">Instant message data structure</param>
|
|
/// <param name="simulator">Simulator where this IM was received from</param>
|
|
public delegate void InstantMessageCallback(InstantMessage im, Simulator simulator);
|
|
|
|
/// <summary>
|
|
/// Triggered for any status updates of a teleport (progress, failed, succeeded)
|
|
/// </summary>
|
|
/// <param name="message">A message about the current teleport status</param>
|
|
/// <param name="status">The current status of the teleport</param>
|
|
/// <param name="flags">Various flags describing the teleport</param>
|
|
public delegate void TeleportCallback(string message, TeleportStatus status, TeleportFlags flags);
|
|
|
|
/// <summary>
|
|
/// Reply to a request to join a group, informs whether it was successful or not
|
|
/// </summary>
|
|
/// <param name="groupID">The group we attempted to join</param>
|
|
/// <param name="success">Whether we joined the group or not</param>
|
|
public delegate void JoinGroupCallback(UUID groupID, bool success);
|
|
|
|
/// <summary>
|
|
/// Reply to a request to leave a group, informs whether it was successful or not
|
|
/// </summary>
|
|
/// <param name="groupID">The group we attempted to leave</param>
|
|
/// <param name="success">Whether we left the group or not</param>
|
|
public delegate void LeaveGroupCallback(UUID groupID, bool success);
|
|
|
|
/// <summary>
|
|
/// Informs the avatar that it is no longer a member of a group
|
|
/// </summary>
|
|
/// <param name="groupID">The group Key we are no longer a member of</param>
|
|
public delegate void GroupDroppedCallback(UUID groupID);
|
|
|
|
/// <summary>
|
|
/// Reply to an AgentData request
|
|
/// </summary>
|
|
/// <param name="firstName">First name of Avatar</param>
|
|
/// <param name="lastName">Last name of Avatar</param>
|
|
/// <param name="activeGroupID">Key of Group Avatar has active</param>
|
|
/// <param name="groupTitle">Avatars Active Title</param>
|
|
/// <param name="groupPowers">Powers Avatar has in group</param>
|
|
/// <param name="groupName">Name of the Group</param>
|
|
public delegate void AgentDataCallback(string firstName, string lastName, UUID activeGroupID,
|
|
string groupTitle, GroupPowers groupPowers, string groupName);
|
|
|
|
/// <summary>
|
|
/// Triggered when the current agent animations change
|
|
/// </summary>
|
|
/// <param name="agentAnimations">A convenience reference to the
|
|
/// SignaledAnimations collection</param>
|
|
public delegate void AnimationsChangedCallback(InternalDictionary<UUID, int> agentAnimations);
|
|
|
|
/// <summary>
|
|
/// Triggered when an object or avatar forcefully collides with our
|
|
/// agent
|
|
/// </summary>
|
|
/// <param name="type">Collision type</param>
|
|
/// <param name="perp">Colliding object or avatar ID</param>
|
|
/// <param name="victim">Victim ID, should be our own AgentID</param>
|
|
/// <param name="magnitude">Velocity or total force of the collision</param>
|
|
/// <param name="time">Time the collision occurred</param>
|
|
public delegate void MeanCollisionCallback(MeanCollisionType type, UUID perp, UUID victim,
|
|
float magnitude, DateTime time);
|
|
|
|
/// <summary>
|
|
/// Triggered when the agent physically moves in to a neighboring region
|
|
/// </summary>
|
|
/// <param name="oldSim">Simulator agent was previously occupying</param>
|
|
/// <param name="newSim">Simulator agent is now currently occupying</param>
|
|
public delegate void RegionCrossedCallback(Simulator oldSim, Simulator newSim);
|
|
|
|
/// <summary>
|
|
/// Fired when group chat session confirmed joined</summary>
|
|
/// <param name="groupChatSessionID">Key of Session (groups UUID)</param>
|
|
/// <param name="tmpSessionID">Temporary session Key</param>
|
|
/// <param name="sessionName">A string representation of the session name</param>
|
|
/// <param name="success"><see langword="true"/> if session start successful,
|
|
/// <see langword="false"/> otherwise</param>
|
|
public delegate void GroupChatJoinedCallback(UUID groupChatSessionID, string sessionName, UUID tmpSessionID, bool success);
|
|
|
|
/// <summary>Fired when agent group chat session terminated</summary>
|
|
/// <param name="groupchatSessionID">Key of Session (groups UUID)</param>
|
|
public delegate void GroupChatLeftCallback(UUID groupchatSessionID);
|
|
|
|
/// <summary>
|
|
/// Fired when alert message received from simulator
|
|
/// </summary>
|
|
/// <param name="message">the message sent from the grid to our avatar.</param>
|
|
public delegate void AlertMessageCallback(string message);
|
|
|
|
/// <summary>
|
|
/// Fired when a script wants to give or release controls.
|
|
/// </summary>
|
|
/// <param name="controls">Control to give or take</param>
|
|
/// <param name="pass">true of passing control to agent</param>
|
|
/// <param name="take">true of taking control from agent</param>
|
|
public delegate void ScriptControlCallback(ScriptControlChange controls, bool pass, bool take);
|
|
|
|
/// <summary>
|
|
/// Fired when camera tries to view beyond its view limits
|
|
/// </summary>
|
|
/// <param name="collidePlane"><seealso cref="Vector4"/> representing plane where constraints were hit</param>
|
|
public delegate void CameraConstraintCallback(Vector4 collidePlane);
|
|
|
|
/// <summary>
|
|
/// Fired when script sensor reply is received
|
|
/// </summary>
|
|
/// <param name="requestorID">requestors UUID</param>
|
|
/// <param name="groupID">Sources Group UUID</param>
|
|
/// <param name="name">Sources Name</param>
|
|
/// <param name="objectID">Objects UUID</param>
|
|
/// <param name="ownerID">Object owners UUID</param>
|
|
/// <param name="position">Position of Object</param>
|
|
/// <param name="range">Range of Object</param>
|
|
/// <param name="rotation">Rotation of object</param>
|
|
/// <param name="type">Objects Type</param>
|
|
/// <param name="velocity"><seealso cref="Vector3"/> representing the velocity of object</param>
|
|
/// TODO: this should probably be a struct, and there should be an enum added for type
|
|
public delegate void ScriptSensorReplyCallback(UUID requestorID, UUID groupID, string name,
|
|
UUID objectID, UUID ownerID, Vector3 position, float range, Quaternion rotation,
|
|
ScriptSensorTypeFlags type, Vector3 velocity);
|
|
|
|
/// <summary>
|
|
/// Fired in response to a RequestSit()
|
|
/// </summary>
|
|
/// <param name="objectID">ID of primitive avatar will be sitting on</param>
|
|
/// <param name="autoPilot">true of avatar autopiloted there</param>
|
|
/// <param name="cameraAtOffset">Camera offset when avatar is seated</param>
|
|
/// <param name="cameraEyeOffset">Camera eye offset when avatar is seated</param>
|
|
/// <param name="forceMouselook">true of sitting on this object will force mouselook</param>
|
|
/// <param name="sitPosition">position avatar will be in when seated</param>
|
|
/// <param name="sitRotation">rotation avatar will be in when seated</param>
|
|
public delegate void AvatarSitResponseCallback(UUID objectID, bool autoPilot, Vector3 cameraAtOffset,
|
|
Vector3 cameraEyeOffset, bool forceMouselook, Vector3 sitPosition, Quaternion sitRotation);
|
|
|
|
/// <summary>
|
|
/// Fired when a new member joins a Group chat session
|
|
/// </summary>
|
|
/// <param name="sessionID">the ID of the session</param>
|
|
/// <param name="agent_key">the ID of the avatar that joined</param>
|
|
public delegate void ChatSessionMemberAddedCallback(UUID sessionID, UUID agent_key);
|
|
/// <summary>
|
|
/// Fired when a member of a Group chat leaves the session
|
|
/// </summary>
|
|
/// <param name="sessionID">the ID of the session</param>
|
|
/// <param name="agent_key">the ID of the avatar that joined</param>
|
|
public delegate void ChatSessionMemberLeftCallback(UUID sessionID, UUID agent_key);
|
|
|
|
#endregion Callbacks
|
|
|
|
#region Events
|
|
|
|
/// <summary>Fired when a <see cref="ChatFromSimulatorPacket"/> is received from the simulator, Contains
|
|
/// Any Whisper, Shout, or Say within range of avatar</summary>
|
|
public event ChatCallback OnChat;
|
|
/// <summary>Fired when a <see cref="ScriptDialogPacket"/> is received, use <seealso cref="ReplyToScriptDialog"/>
|
|
/// to respond to dialog</summary>
|
|
public event ScriptDialogCallback OnScriptDialog;
|
|
/// <summary>Fired when a <seealso cref="ScriptQuestionPacket"/> is received in response to a
|
|
/// scripted object requesting permissions, Use <seealso cref="ScriptQuestionReply"/> to reply</summary>
|
|
public event ScriptQuestionCallback OnScriptQuestion;
|
|
/// <summary>Fired when a <seealso cref="LoadURLPacket"/> is received, contains a URL pasted in Chat</summary>
|
|
public event LoadURLCallback OnLoadURL;
|
|
/// <summary>Fired when a <seealso cref="ImprovedInstantMessagePacket"/> or a ChatterBoxInvitation is received</summary>
|
|
public event InstantMessageCallback OnInstantMessage;
|
|
/// <summary>Fired when a <seealso cref="TeleportLocalPacket"/> is received, occurs when a
|
|
/// <seealso cref="RequestTeleport"/> or <seealso cref="Teleport"/> is called</summary>
|
|
public event TeleportCallback OnTeleport;
|
|
/// <summary>Fired when a <seealso cref="MoneyBalanceReplyPacket"/> indicating the agents
|
|
/// balance has changed by spending, sending, or receiving L$, Contains the Avatars new balance</summary>
|
|
public event BalanceCallback OnBalanceUpdated;
|
|
/// <summary>Fired when a <seealso cref="MoneyBalanceReplyPacket"/> is received, contains L$ balance and additional
|
|
/// details of the transaction</summary>
|
|
public event MoneyBalanceReplyCallback OnMoneyBalanceReplyReceived;
|
|
/// <summary>Fired when a <seealso cref="AgentDataUpdatePacket"/> is received, caused by changing the
|
|
/// Agents active group with <seealso cref="ActivateGroup"/></summary>
|
|
public event AgentDataCallback OnAgentDataUpdated;
|
|
/// <summary>Fired when a <seealso cref="AvatarAnimationPacket"/> is received, will contain a Dictionary
|
|
/// of animations currently being played</summary>
|
|
public event AnimationsChangedCallback OnAnimationsChanged;
|
|
/// <summary>Callback for an object or avatar forcefully colliding
|
|
/// with the agent</summary>
|
|
public event MeanCollisionCallback OnMeanCollision;
|
|
/// <summary>Callback for the agent moving in to a neighboring sim</summary>
|
|
public event RegionCrossedCallback OnRegionCrossed;
|
|
/// <summary>Callback for when agent is confirmed joined group chat session.</summary>
|
|
public event GroupChatJoinedCallback OnGroupChatJoin;
|
|
/// <summary>Callback for when agent is confirmed to have left group chat session.</summary>
|
|
public event GroupChatLeftCallback OnGroupChatLeft;
|
|
/// <summary>Alert messages sent to client from simulator</summary>
|
|
public event AlertMessageCallback OnAlertMessage;
|
|
/// <summary>Fired when a script wants to take or release control of your avatar.</summary>
|
|
public event ScriptControlCallback OnScriptControlChange;
|
|
/// <summary>Fired when our avatar camera reaches the maximum possible point</summary>
|
|
public event CameraConstraintCallback OnCameraConstraint;
|
|
/// <summary>Fired when a script sensor reply is received</summary>
|
|
public event ScriptSensorReplyCallback OnScriptSensorReply;
|
|
/// <summary>Fired in response to a sit request</summary>
|
|
public event AvatarSitResponseCallback OnAvatarSitResponse;
|
|
/// <summary>Fired when a new member joins an active ChatterBoxSession session</summary>
|
|
public event ChatSessionMemberAddedCallback OnChatSessionMemberAdded;
|
|
/// <summary>Fired when a member of an active ChatterBoxSession leaves the session</summary>
|
|
public event ChatSessionMemberLeftCallback OnChatSessionMemberLeft;
|
|
|
|
#endregion Events
|
|
|
|
/// <summary>Reference to the GridClient object</summary>
|
|
public readonly GridClient Client;
|
|
/// <summary>Used for movement and camera tracking</summary>
|
|
public readonly AgentMovement Movement;
|
|
/// <summary>Currently playing animations for the agent. Can be used to
|
|
/// check the current movement status such as walking, hovering, aiming,
|
|
/// etc. by checking for system animations in the Animations class</summary>
|
|
public InternalDictionary<UUID, int> SignaledAnimations = new InternalDictionary<UUID, int>();
|
|
/// <summary>
|
|
/// Dictionary containing current Group Chat sessions and members
|
|
/// </summary>
|
|
public InternalDictionary<UUID, List<ChatSessionMember>> GroupChatSessions = new InternalDictionary<UUID, List<ChatSessionMember>>();
|
|
|
|
#region Properties
|
|
|
|
/// <summary>Your (client) avatars <seealso cref="UUID"/></summary>
|
|
/// <remarks>"client", "agent", and "avatar" all represent the same thing</remarks>
|
|
public UUID AgentID { get { return id; } }
|
|
/// <summary>Temporary <seealso cref="UUID"/> assigned to this session, used for
|
|
/// verifying our identity in packets</summary>
|
|
public UUID SessionID { get { return sessionID; } }
|
|
/// <summary>Shared secret <seealso cref="UUID"/> that is never sent over the wire</summary>
|
|
public UUID SecureSessionID { get { return secureSessionID; } }
|
|
/// <summary>Your (client) avatar ID, local to the current region/sim</summary>
|
|
public uint LocalID { get { return localID; } }
|
|
/// <summary>Where the avatar started at login. Can be "last", "home"
|
|
/// or a login <seealso cref="T:OpenMetaverse.URI"/></summary>
|
|
public string StartLocation { get { return startLocation; } }
|
|
/// <summary>The access level of this agent, usually M or PG</summary>
|
|
public string AgentAccess { get { return agentAccess; } }
|
|
/// <summary>The CollisionPlane of Agent</summary>
|
|
public Vector4 CollisionPlane { get { return collisionPlane; } }
|
|
/// <summary>An <seealso cref="Vector3"/> representing the velocity of our agent</summary>
|
|
public Vector3 Velocity { get { return velocity; } }
|
|
/// <summary>An <seealso cref="Vector3"/> representing the acceleration of our agent</summary>
|
|
public Vector3 Acceleration { get { return acceleration; } }
|
|
/// <summary>A <seealso cref="Vector3"/> which specifies the angular speed, and axis about which an Avatar is rotating.</summary>
|
|
public Vector3 AngularVelocity { get { return angularVelocity; } }
|
|
/// <summary>Position avatar client will goto when login to 'home' or during
|
|
/// teleport request to 'home' region.</summary>
|
|
public Vector3 HomePosition { get { return homePosition; } }
|
|
/// <summary>LookAt point saved/restored with HomePosition</summary>
|
|
public Vector3 HomeLookAt { get { return homeLookAt; } }
|
|
/// <summary>Avatar First Name (i.e. Philip)</summary>
|
|
public string FirstName { get { return firstName; } }
|
|
/// <summary>Avatar Last Name (i.e. Linden)</summary>
|
|
public string LastName { get { return lastName; } }
|
|
/// <summary>Avatar Full Name (i.e. Philip Linden)</summary>
|
|
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 = String.Format("{0} {1}", firstName, lastName);
|
|
return fullName;
|
|
}
|
|
}
|
|
/// <summary>Gets the health of the agent</summary>
|
|
public float Health { get { return health; } }
|
|
/// <summary>Gets the current balance of the agent</summary>
|
|
public int Balance { get { return balance; } }
|
|
/// <summary>Gets the local ID of the prim the agent is sitting on,
|
|
/// zero if the avatar is not currently sitting</summary>
|
|
public uint SittingOn { get { return sittingOn; } }
|
|
/// <summary>Gets the <seealso cref="UUID"/> of the agents active group.</summary>
|
|
public UUID ActiveGroup { get { return activeGroup; } }
|
|
/// <summary>Gets the Agents powers in the currently active group</summary>
|
|
public GroupPowers ActiveGroupPowers { get { return activeGroupPowers; } }
|
|
/// <summary>Current status message for teleporting</summary>
|
|
public string TeleportMessage { get { return teleportMessage; } }
|
|
/// <summary>Current position of the agent as a relative offset from
|
|
/// the simulator, or the parent object if we are sitting on something</summary>
|
|
public Vector3 RelativePosition { get { return relativePosition; } }
|
|
/// <summary>Current rotation of the agent as a relative rotation from
|
|
/// the simulator, or the parent object if we are sitting on something</summary>
|
|
public Quaternion RelativeRotation { get { return relativeRotation; } }
|
|
/// <summary>Current position of the agent in the simulator</summary>
|
|
public Vector3 SimPosition
|
|
{
|
|
get
|
|
{
|
|
if (sittingOn != 0)
|
|
{
|
|
Primitive parent;
|
|
if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(sittingOn, out parent))
|
|
{
|
|
return parent.Position + Vector3.Transform(relativePosition, Matrix4.CreateFromQuaternion(parent.Rotation));
|
|
}
|
|
else
|
|
{
|
|
Logger.Log("Currently sitting on object " + sittingOn + " which is not tracked, SimPosition will be inaccurate",
|
|
Helpers.LogLevel.Warning, Client);
|
|
return relativePosition;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return relativePosition;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// A <seealso cref="Quaternion"/> representing the agents current rotation
|
|
/// </summary>
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
Logger.Log("Currently sitting on object " + sittingOn + " which is not tracked, SimRotation will be inaccurate",
|
|
Helpers.LogLevel.Warning, Client);
|
|
return relativeRotation;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return relativeRotation;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>Returns the global grid position of the avatar</summary>
|
|
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(
|
|
(double)globalX + (double)pos.X,
|
|
(double)globalY + (double)pos.Y,
|
|
(double)pos.Z);
|
|
}
|
|
else
|
|
return Vector3d.Zero;
|
|
}
|
|
}
|
|
|
|
#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 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;
|
|
#endregion Private Members
|
|
|
|
/// <summary>
|
|
/// Constructor, setup callbacks for packets related to our avatar
|
|
/// </summary>
|
|
/// <param name="client">A reference to the <seealso cref="T:OpenMetaverse.GridClient"/> Class</param>
|
|
public AgentManager(GridClient client)
|
|
{
|
|
Client = client;
|
|
Movement = new AgentMovement(Client);
|
|
NetworkManager.PacketCallback callback;
|
|
|
|
Client.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected);
|
|
|
|
// Teleport callbacks
|
|
callback = new NetworkManager.PacketCallback(TeleportHandler);
|
|
Client.Network.RegisterCallback(PacketType.TeleportStart, callback);
|
|
Client.Network.RegisterCallback(PacketType.TeleportProgress, callback);
|
|
Client.Network.RegisterCallback(PacketType.TeleportFailed, callback);
|
|
Client.Network.RegisterEventCallback("TeleportFinish", new Caps.EventQueueCallback(TeleportFinishEventHandler));
|
|
Client.Network.RegisterCallback(PacketType.TeleportCancel, callback);
|
|
Client.Network.RegisterCallback(PacketType.TeleportLocal, callback);
|
|
|
|
// Instant message callback
|
|
Client.Network.RegisterCallback(PacketType.ImprovedInstantMessage, new NetworkManager.PacketCallback(InstantMessageHandler));
|
|
// Chat callback
|
|
Client.Network.RegisterCallback(PacketType.ChatFromSimulator, new NetworkManager.PacketCallback(ChatHandler));
|
|
// Script dialog callback
|
|
Client.Network.RegisterCallback(PacketType.ScriptDialog, new NetworkManager.PacketCallback(ScriptDialogHandler));
|
|
// Script question callback
|
|
Client.Network.RegisterCallback(PacketType.ScriptQuestion, new NetworkManager.PacketCallback(ScriptQuestionHandler));
|
|
// Script URL callback
|
|
Client.Network.RegisterCallback(PacketType.LoadURL, new NetworkManager.PacketCallback(LoadURLHandler));
|
|
// Movement complete callback
|
|
Client.Network.RegisterCallback(PacketType.AgentMovementComplete, new NetworkManager.PacketCallback(MovementCompleteHandler));
|
|
// Health callback
|
|
Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler));
|
|
// Money callback
|
|
Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, new NetworkManager.PacketCallback(BalanceHandler));
|
|
//Agent update callback
|
|
Client.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(AgentDataUpdateHandler));
|
|
// Animation callback
|
|
Client.Network.RegisterCallback(PacketType.AvatarAnimation, new NetworkManager.PacketCallback(AvatarAnimationHandler));
|
|
// Object colliding into our agent callback
|
|
Client.Network.RegisterCallback(PacketType.MeanCollisionAlert, new NetworkManager.PacketCallback(MeanCollisionAlertHandler));
|
|
// Region Crossing
|
|
Client.Network.RegisterCallback(PacketType.CrossedRegion, new NetworkManager.PacketCallback(CrossedRegionHandler));
|
|
// CAPS callbacks
|
|
Client.Network.RegisterEventCallback("EstablishAgentCommunication", new Caps.EventQueueCallback(EstablishAgentCommunicationEventHandler));
|
|
// Incoming Group Chat
|
|
Client.Network.RegisterEventCallback("ChatterBoxInvitation", new Caps.EventQueueCallback(ChatterBoxInvitationHandler));
|
|
// Outgoing Group Chat Reply
|
|
Client.Network.RegisterEventCallback("ChatterBoxSessionEventReply", new Caps.EventQueueCallback(ChatterBoxSessionEventHandler));
|
|
Client.Network.RegisterEventCallback("ChatterBoxSessionStartReply", new Caps.EventQueueCallback(ChatterBoxSessionStartReplyHandler));
|
|
Client.Network.RegisterEventCallback("ChatterBoxSessionAgentListUpdates", new Caps.EventQueueCallback(ChatterBoxSessionAgentListReplyHandler));
|
|
// Login
|
|
Client.Network.RegisterLoginResponseCallback(new NetworkManager.LoginResponseCallback(Network_OnLoginResponse));
|
|
// Alert Messages
|
|
Client.Network.RegisterCallback(PacketType.AlertMessage, new NetworkManager.PacketCallback(AlertMessageHandler));
|
|
// script control change messages, ie: when an in-world LSL script wants to take control of your agent.
|
|
Client.Network.RegisterCallback(PacketType.ScriptControlChange, new NetworkManager.PacketCallback(ScriptControlChangeHandler));
|
|
// Camera Constraint (probably needs to move to AgentManagerCamera TODO:
|
|
Client.Network.RegisterCallback(PacketType.CameraConstraint, new NetworkManager.PacketCallback(CameraConstraintHandler));
|
|
Client.Network.RegisterCallback(PacketType.ScriptSensorReply, new NetworkManager.PacketCallback(ScriptSensorReplyHandler));
|
|
Client.Network.RegisterCallback(PacketType.AvatarSitResponse, new NetworkManager.PacketCallback(AvatarSitResponseHandler));
|
|
|
|
}
|
|
|
|
#region Chat and instant messages
|
|
|
|
/// <summary>
|
|
/// Send a chat message
|
|
/// </summary>
|
|
/// <param name="message">The Message you're sending out.</param>
|
|
/// <param name="channel">Channel number (0 would be default 'Say' message, other numbers
|
|
/// denote the equivalent of /# in normal client).</param>
|
|
/// <param name="type">Chat Type, see above.</param>
|
|
public void Chat(string message, int channel, ChatType type)
|
|
{
|
|
ChatFromViewerPacket chat = new ChatFromViewerPacket();
|
|
chat.AgentData.AgentID = this.id;
|
|
chat.AgentData.SessionID = Client.Self.SessionID;
|
|
chat.ChatData.Channel = channel;
|
|
chat.ChatData.Message = Utils.StringToBytes(message);
|
|
chat.ChatData.Type = (byte)type;
|
|
|
|
Client.Network.SendPacket(chat);
|
|
}
|
|
|
|
/// <summary>Requests missed/offline messages</summary>
|
|
public void RetrieveInstantMessages()
|
|
{
|
|
RetrieveInstantMessagesPacket p = new RetrieveInstantMessagesPacket();
|
|
p.AgentData.AgentID = Client.Self.AgentID;
|
|
p.AgentData.SessionID = Client.Self.SessionID;
|
|
Client.Network.SendPacket(p);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an Instant Message
|
|
/// </summary>
|
|
/// <param name="target">Target of the Instant Message</param>
|
|
/// <param name="message">Text message being sent</param>
|
|
public void InstantMessage(UUID target, string message)
|
|
{
|
|
InstantMessage(Name, target, message, AgentID.Equals(target) ? AgentID : target ^ AgentID,
|
|
InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.SimPosition,
|
|
UUID.Zero, new byte[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an Instant Message
|
|
/// </summary>
|
|
/// <param name="target">Target of the Instant Message</param>
|
|
/// <param name="message">Text message being sent</param>
|
|
/// <param name="imSessionID">IM session ID (to differentiate between IM windows)</param>
|
|
public void InstantMessage(UUID target, string message, UUID imSessionID)
|
|
{
|
|
InstantMessage(Name, target, message, imSessionID,
|
|
InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.SimPosition,
|
|
UUID.Zero, new byte[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an Instant Message
|
|
/// </summary>
|
|
/// <param name="fromName">The name this IM will show up as being from</param>
|
|
/// <param name="target">Key of Avatar</param>
|
|
/// <param name="message">Text message being sent</param>
|
|
/// <param name="imSessionID">IM session ID (to differentiate between IM windows)</param>
|
|
/// <param name="conferenceIDs">IDs of sessions for a conference</param>
|
|
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 = new byte[0];
|
|
}
|
|
|
|
InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent,
|
|
InstantMessageOnline.Offline, Vector3.Zero, UUID.Zero, binaryBucket);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an Instant Message
|
|
/// </summary>
|
|
/// <param name="fromName">The name this IM will show up as being from</param>
|
|
/// <param name="target">Key of Avatar</param>
|
|
/// <param name="message">Text message being sent</param>
|
|
/// <param name="imSessionID">IM session ID (to differentiate between IM windows)</param>
|
|
/// <param name="dialog">Type of instant message to send</param>
|
|
/// <param name="offline">Whether to IM offline avatars as well</param>
|
|
/// <param name="position">Senders Position</param>
|
|
/// <param name="regionID">RegionID Sender is In</param>
|
|
/// <param name="binaryBucket">Packed binary data that is specific to
|
|
/// the dialog type</param>
|
|
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;
|
|
|
|
if (binaryBucket != null)
|
|
im.MessageBlock.BinaryBucket = binaryBucket;
|
|
else
|
|
im.MessageBlock.BinaryBucket = new byte[0];
|
|
|
|
// These fields are mandatory, even if we don't have valid values for them
|
|
im.MessageBlock.Position = 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(String.Format("Suppressing instant message \"{0}\" to UUID.Zero", message),
|
|
Helpers.LogLevel.Error, Client);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an Instant Message to a group
|
|
/// </summary>
|
|
/// <param name="groupID"><seealso cref="UUID"/> of the group to send message to</param>
|
|
/// <param name="message">Text Message being sent.</param>
|
|
public void InstantMessageGroup(UUID groupID, string message)
|
|
{
|
|
InstantMessageGroup(Name, groupID, message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an Instant Message to a group
|
|
/// </summary>
|
|
/// <param name="fromName">The name this IM will show up as being from</param>
|
|
/// <param name="groupID"><seealso cref="UUID"/> of the group to send message to</param>
|
|
/// <param name="message">Text message being sent</param>
|
|
/// <remarks>This does not appear to function with groups the agent is not in</remarks>
|
|
public void InstantMessageGroup(string fromName, UUID groupID, string message)
|
|
{
|
|
lock (GroupChatSessions.Dictionary)
|
|
if (GroupChatSessions.ContainsKey(groupID))
|
|
{
|
|
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
|
|
|
|
im.AgentData.AgentID = Client.Self.AgentID;
|
|
im.AgentData.SessionID = Client.Self.SessionID;
|
|
im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionSend;
|
|
im.MessageBlock.FromAgentName = Utils.StringToBytes(fromName);
|
|
im.MessageBlock.FromGroup = false;
|
|
im.MessageBlock.Message = Utils.StringToBytes(message);
|
|
im.MessageBlock.Offline = 0;
|
|
im.MessageBlock.ID = groupID;
|
|
im.MessageBlock.ToAgentID = groupID;
|
|
im.MessageBlock.Position = Vector3.Zero;
|
|
im.MessageBlock.RegionID = UUID.Zero;
|
|
im.MessageBlock.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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a request to join a group chat session
|
|
/// </summary>
|
|
/// <param name="groupID"><seealso cref="UUID"/> of Group to leave</param>
|
|
public void RequestJoinGroupChat(UUID groupID)
|
|
{
|
|
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
|
|
|
|
im.AgentData.AgentID = Client.Self.AgentID;
|
|
im.AgentData.SessionID = Client.Self.SessionID;
|
|
im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionGroupStart;
|
|
im.MessageBlock.FromAgentName = Utils.StringToBytes(Client.Self.Name);
|
|
im.MessageBlock.FromGroup = false;
|
|
im.MessageBlock.Message = new byte[0];
|
|
im.MessageBlock.ParentEstateID = 0;
|
|
im.MessageBlock.Offline = 0;
|
|
im.MessageBlock.ID = groupID;
|
|
im.MessageBlock.ToAgentID = groupID;
|
|
im.MessageBlock.BinaryBucket = new byte[0];
|
|
im.MessageBlock.Position = Client.Self.SimPosition;
|
|
im.MessageBlock.RegionID = UUID.Zero;
|
|
|
|
Client.Network.SendPacket(im);
|
|
}
|
|
/// <summary>
|
|
/// Request self terminates group chat. This will stop Group IM's from showing up
|
|
/// until session is rejoined or expires.
|
|
/// </summary>
|
|
/// <param name="groupID"><seealso cref="UUID"/> of Group to leave</param>
|
|
public void RequestLeaveGroupChat(UUID groupID)
|
|
{
|
|
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
|
|
|
|
im.AgentData.AgentID = Client.Self.AgentID;
|
|
im.AgentData.SessionID = Client.Self.SessionID;
|
|
im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionDrop;
|
|
im.MessageBlock.FromAgentName = Utils.StringToBytes(Client.Self.Name);
|
|
im.MessageBlock.FromGroup = false;
|
|
im.MessageBlock.Message = new byte[0];
|
|
im.MessageBlock.Offline = 0;
|
|
im.MessageBlock.ID = groupID;
|
|
im.MessageBlock.ToAgentID = groupID;
|
|
im.MessageBlock.BinaryBucket = new byte[0];
|
|
im.MessageBlock.Position = Vector3.Zero;
|
|
im.MessageBlock.RegionID = UUID.Zero;
|
|
|
|
Client.Network.SendPacket(im);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reply to script dialog questions.
|
|
/// </summary>
|
|
/// <param name="channel">Channel initial request came on</param>
|
|
/// <param name="buttonIndex">Index of button you're "clicking"</param>
|
|
/// <param name="buttonlabel">Label of button you're "clicking"</param>
|
|
/// <param name="objectID"><seealso cref="UUID"/> of Object that sent the dialog request</param>
|
|
/// <seealso cref="OnScriptDialog"/>
|
|
public void ReplyToScriptDialog(int channel, int buttonIndex, string buttonlabel, UUID objectID)
|
|
{
|
|
ScriptDialogReplyPacket reply = new ScriptDialogReplyPacket();
|
|
|
|
reply.AgentData.AgentID = Client.Self.AgentID;
|
|
reply.AgentData.SessionID = Client.Self.SessionID;
|
|
|
|
reply.Data.ButtonIndex = buttonIndex;
|
|
reply.Data.ButtonLabel = Utils.StringToBytes(buttonlabel);
|
|
reply.Data.ChatChannel = channel;
|
|
reply.Data.ObjectID = objectID;
|
|
|
|
Client.Network.SendPacket(reply);
|
|
}
|
|
|
|
#endregion Chat and instant messages
|
|
|
|
#region Viewer Effects
|
|
|
|
/// <summary>
|
|
/// Start a particle stream between an agent and an object
|
|
/// </summary>
|
|
/// <param name="sourceAvatar"><seealso cref="UUID"/> Key of the source agent</param>
|
|
/// <param name="targetObject"><seealso cref="UUID"/> Key of the target object</param>
|
|
/// <param name="globalOffset"></param>
|
|
/// <param name="type">The type from the <seealso cref="T:PointAtType"/> enum</param>
|
|
/// <param name="effectID">A unique <seealso cref="UUID"/> for this effect</param>
|
|
public void PointAtEffect(UUID sourceAvatar, UUID targetObject, Vector3d globalOffset, PointAtType type,
|
|
UUID effectID)
|
|
{
|
|
ViewerEffectPacket effect = new ViewerEffectPacket();
|
|
|
|
effect.AgentData.AgentID = Client.Self.AgentID;
|
|
effect.AgentData.SessionID = Client.Self.SessionID;
|
|
|
|
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
|
|
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
|
|
effect.Effect[0].AgentID = Client.Self.AgentID;
|
|
effect.Effect[0].Color = new byte[4];
|
|
effect.Effect[0].Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f;
|
|
effect.Effect[0].ID = effectID;
|
|
effect.Effect[0].Type = (byte)EffectType.PointAt;
|
|
|
|
byte[] typeData = new byte[57];
|
|
if (sourceAvatar != 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a particle stream between an agent and an object
|
|
/// </summary>
|
|
/// <param name="sourceAvatar"><seealso cref="UUID"/> Key of the source agent</param>
|
|
/// <param name="targetObject"><seealso cref="UUID"/> Key of the target object</param>
|
|
/// <param name="globalOffset">A <seealso cref="Vector3d"/> representing the beams offset from the source</param>
|
|
/// <param name="type">A <seealso cref="T:PointAtType"/> which sets the avatars lookat animation</param>
|
|
/// <param name="effectID"><seealso cref="UUID"/> of the Effect</param>
|
|
public void LookAtEffect(UUID sourceAvatar, UUID targetObject, Vector3d globalOffset, LookAtType type,
|
|
UUID effectID)
|
|
{
|
|
ViewerEffectPacket effect = new ViewerEffectPacket();
|
|
|
|
effect.AgentData.AgentID = Client.Self.AgentID;
|
|
effect.AgentData.SessionID = Client.Self.SessionID;
|
|
|
|
float duration;
|
|
|
|
switch (type)
|
|
{
|
|
case LookAtType.Clear:
|
|
duration = 0.0f;
|
|
break;
|
|
case LookAtType.Hover:
|
|
duration = 1.0f;
|
|
break;
|
|
case LookAtType.FreeLook:
|
|
duration = 2.0f;
|
|
break;
|
|
case LookAtType.Idle:
|
|
duration = 3.0f;
|
|
break;
|
|
case LookAtType.AutoListen:
|
|
case LookAtType.Respond:
|
|
duration = 4.0f;
|
|
break;
|
|
case LookAtType.None:
|
|
case LookAtType.Select:
|
|
case LookAtType.Focus:
|
|
case LookAtType.Mouselook:
|
|
duration = Single.MaxValue / 2.0f;
|
|
break;
|
|
default:
|
|
duration = 0.0f;
|
|
break;
|
|
}
|
|
|
|
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
|
|
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
|
|
effect.Effect[0].AgentID = Client.Self.AgentID;
|
|
effect.Effect[0].Color = new byte[4];
|
|
effect.Effect[0].Duration = duration;
|
|
effect.Effect[0].ID = effectID;
|
|
effect.Effect[0].Type = (byte)EffectType.LookAt;
|
|
|
|
byte[] typeData = new byte[57];
|
|
if (sourceAvatar != UUID.Zero)
|
|
Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16);
|
|
if (targetObject != UUID.Zero)
|
|
Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16);
|
|
typeData[56] = (byte)type;
|
|
|
|
effect.Effect[0].TypeData = typeData;
|
|
|
|
Client.Network.SendPacket(effect);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a particle beam between an avatar and an primitive
|
|
/// </summary>
|
|
/// <param name="sourceAvatar"><seealso cref="UUID"/> of sources avatar</param>
|
|
/// <param name="targetObject"><seealso cref="UUID"/> of the target</param>
|
|
/// <param name="globalOffset"><seealso cref="Vector3d"/>global offset</param>
|
|
/// <param name="color"><seealso cref="Color4"/>Color values of beam</param>
|
|
/// <param name="duration">a float representing the duration the beam will last</param>
|
|
/// <param name="effectID"><seealso cref="UUID"/> of the Effect</param>
|
|
public void BeamEffect(UUID sourceAvatar, UUID targetObject, Vector3d globalOffset, Color4 color,
|
|
float duration, UUID effectID)
|
|
{
|
|
ViewerEffectPacket effect = new ViewerEffectPacket();
|
|
|
|
effect.AgentData.AgentID = Client.Self.AgentID;
|
|
effect.AgentData.SessionID = Client.Self.SessionID;
|
|
|
|
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
|
|
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
|
|
effect.Effect[0].AgentID = Client.Self.AgentID;
|
|
effect.Effect[0].Color = color.GetBytes();
|
|
effect.Effect[0].Duration = duration;
|
|
effect.Effect[0].ID = effectID;
|
|
effect.Effect[0].Type = (byte)EffectType.Beam;
|
|
|
|
byte[] typeData = new byte[56];
|
|
Buffer.BlockCopy(sourceAvatar.GetBytes(), 0, typeData, 0, 16);
|
|
Buffer.BlockCopy(targetObject.GetBytes(), 0, typeData, 16, 16);
|
|
Buffer.BlockCopy(globalOffset.GetBytes(), 0, typeData, 32, 24);
|
|
|
|
effect.Effect[0].TypeData = typeData;
|
|
|
|
Client.Network.SendPacket(effect);
|
|
}
|
|
|
|
#endregion Viewer Effects
|
|
|
|
#region Movement Actions
|
|
|
|
/// <summary>
|
|
/// Sends a request to sit on the specified object
|
|
/// </summary>
|
|
/// <param name="targetID"><seealso cref="UUID"/> of the object to sit on</param>
|
|
/// <param name="offset">Sit at offset</param>
|
|
public void RequestSit(UUID targetID, Vector3 offset)
|
|
{
|
|
AgentRequestSitPacket requestSit = new AgentRequestSitPacket();
|
|
requestSit.AgentData.AgentID = Client.Self.AgentID;
|
|
requestSit.AgentData.SessionID = Client.Self.SessionID;
|
|
requestSit.TargetObject.TargetID = targetID;
|
|
requestSit.TargetObject.Offset = offset;
|
|
Client.Network.SendPacket(requestSit);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Follows a call to <seealso cref="RequestSit"/> to actually sit on the object
|
|
/// </summary>
|
|
public void Sit()
|
|
{
|
|
AgentSitPacket sit = new AgentSitPacket();
|
|
sit.AgentData.AgentID = Client.Self.AgentID;
|
|
sit.AgentData.SessionID = Client.Self.SessionID;
|
|
Client.Network.SendPacket(sit);
|
|
}
|
|
|
|
/// <summary>Stands up from sitting on a prim or the ground</summary>
|
|
/// <returns>true of AgentUpdate was sent</returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Does a "ground sit" at the avatar's current position
|
|
/// </summary>
|
|
public void SitOnGround()
|
|
{
|
|
Movement.SitOnGround = true;
|
|
Movement.SendUpdate(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts or stops flying
|
|
/// </summary>
|
|
/// <param name="start">True to start flying, false to stop flying</param>
|
|
public void Fly(bool start)
|
|
{
|
|
if (start)
|
|
Movement.Fly = true;
|
|
else
|
|
Movement.Fly = false;
|
|
|
|
Movement.SendUpdate(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts or stops crouching
|
|
/// </summary>
|
|
/// <param name="crouching">True to start crouching, false to stop crouching</param>
|
|
public void Crouch(bool crouching)
|
|
{
|
|
Movement.UpNeg = crouching;
|
|
Movement.SendUpdate(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts a jump (begin holding the jump key)
|
|
/// </summary>
|
|
public void Jump(bool jumping)
|
|
{
|
|
Movement.UpPos = jumping;
|
|
Movement.FastUp = jumping;
|
|
Movement.SendUpdate(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use the autopilot sim function to move the avatar to a new
|
|
/// position. Uses double precision to get precise movements
|
|
/// </summary>
|
|
/// <remarks>The z value is currently not handled properly by the simulator</remarks>
|
|
/// <param name="globalX">Global X coordinate to move to</param>
|
|
/// <param name="globalY">Global Y coordinate to move to</param>
|
|
/// <param name="z">Z coordinate to move to</param>
|
|
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();
|
|
autopilot.ParamList[0].Parameter = Utils.StringToBytes(globalX.ToString());
|
|
autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock();
|
|
autopilot.ParamList[1].Parameter = Utils.StringToBytes(globalY.ToString());
|
|
autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock();
|
|
autopilot.ParamList[2].Parameter = Utils.StringToBytes(z.ToString());
|
|
|
|
Client.Network.SendPacket(autopilot);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use the autopilot sim function to move the avatar to a new position
|
|
/// </summary>
|
|
/// <remarks>The z value is currently not handled properly by the simulator</remarks>
|
|
/// <param name="globalX">Integer value for the global X coordinate to move to</param>
|
|
/// <param name="globalY">Integer value for the global Y coordinate to move to</param>
|
|
/// <param name="z">Floating-point value for the Z coordinate to move to</param>
|
|
public void AutoPilot(ulong globalX, ulong globalY, float z)
|
|
{
|
|
GenericMessagePacket autopilot = new GenericMessagePacket();
|
|
|
|
autopilot.AgentData.AgentID = Client.Self.AgentID;
|
|
autopilot.AgentData.SessionID = Client.Self.SessionID;
|
|
autopilot.AgentData.TransactionID = 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();
|
|
autopilot.ParamList[0].Parameter = Utils.StringToBytes(globalX.ToString());
|
|
autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock();
|
|
autopilot.ParamList[1].Parameter = Utils.StringToBytes(globalY.ToString());
|
|
autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock();
|
|
autopilot.ParamList[2].Parameter = Utils.StringToBytes(z.ToString());
|
|
|
|
Client.Network.SendPacket(autopilot);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use the autopilot sim function to move the avatar to a new position
|
|
/// </summary>
|
|
/// <remarks>The z value is currently not handled properly by the simulator</remarks>
|
|
/// <param name="localX">Integer value for the local X coordinate to move to</param>
|
|
/// <param name="localY">Integer value for the local Y coordinate to move to</param>
|
|
/// <param name="z">Floating-point value for the Z coordinate to move to</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Macro to cancel autopilot sim function</summary>
|
|
/// <remarks>Not certain if this is how it is really done</remarks>
|
|
/// <returns>true if control flags were set and AgentUpdate was sent to the simulator</returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Grabs an object
|
|
/// </summary>
|
|
/// <param name="objectLocalID">an unsigned integer of the objects ID within the simulator</param>
|
|
/// <seealso cref="T:OpenMetaverse.NetworkManager.CurrentSim.ObjectsPrimitives"/>
|
|
public void Grab(uint objectLocalID)
|
|
{
|
|
ObjectGrabPacket grab = new ObjectGrabPacket();
|
|
grab.AgentData.AgentID = Client.Self.AgentID;
|
|
grab.AgentData.SessionID = Client.Self.SessionID;
|
|
grab.ObjectData.LocalID = objectLocalID;
|
|
grab.ObjectData.GrabOffset = new Vector3(0, 0, 0);
|
|
Client.Network.SendPacket(grab);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drags an object
|
|
/// </summary>
|
|
/// <param name="objectID"><seealso cref="UUID"/> of the object to drag</param>
|
|
/// <param name="grabPosition">Drag target in region coordinates</param>
|
|
public void GrabUpdate(UUID objectID, Vector3 grabPosition)
|
|
{
|
|
ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket();
|
|
grab.AgentData.AgentID = Client.Self.AgentID;
|
|
grab.AgentData.SessionID = Client.Self.SessionID;
|
|
grab.ObjectData.ObjectID = objectID;
|
|
grab.ObjectData.GrabOffsetInitial = new Vector3(0, 0, 0);
|
|
grab.ObjectData.GrabPosition = grabPosition;
|
|
grab.ObjectData.TimeSinceLast = 0;
|
|
Client.Network.SendPacket(grab);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases a grabbed object
|
|
/// </summary>
|
|
/// <param name="objectLocalID">The Objects Simulator Local ID</param>
|
|
/// <seealso cref="T:OpenMetaverse.NetworkManager.CurrentSim.ObjectsPrimitives"/>
|
|
/// <seealso cref="Grab"/>
|
|
/// <seealso cref="GrabUpdate"/>
|
|
public void DeGrab(uint objectLocalID)
|
|
{
|
|
ObjectDeGrabPacket degrab = new ObjectDeGrabPacket();
|
|
degrab.AgentData.AgentID = Client.Self.AgentID;
|
|
degrab.AgentData.SessionID = Client.Self.SessionID;
|
|
degrab.ObjectData.LocalID = objectLocalID;
|
|
Client.Network.SendPacket(degrab);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Touches an object
|
|
/// </summary>
|
|
/// <param name="objectLocalID">an unsigned integer of the objects ID within the simulator</param>
|
|
/// <seealso cref="T:OpenMetaverse.NetworkManager.CurrentSim.ObjectsPrimitives"/>
|
|
public void Touch(uint objectLocalID)
|
|
{
|
|
Client.Self.Grab(objectLocalID);
|
|
Client.Self.DeGrab(objectLocalID);
|
|
}
|
|
|
|
#endregion Touch and grab
|
|
|
|
#region Money
|
|
|
|
/// <summary>
|
|
/// Request the current L$ balance
|
|
/// </summary>
|
|
public void RequestBalance()
|
|
{
|
|
MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket();
|
|
money.AgentData.AgentID = Client.Self.AgentID;
|
|
money.AgentData.SessionID = Client.Self.SessionID;
|
|
money.MoneyData.TransactionID = UUID.Zero;
|
|
|
|
Client.Network.SendPacket(money);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Give Money to destination Avatar
|
|
/// </summary>
|
|
/// <param name="target">UUID of the Target Avatar</param>
|
|
/// <param name="amount">Amount in L$</param>
|
|
public void GiveAvatarMoney(UUID target, int amount)
|
|
{
|
|
GiveMoney(target, amount, String.Empty, MoneyTransactionType.Gift, TransactionFlags.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Give Money to destination Avatar
|
|
/// </summary>
|
|
/// <param name="target">UUID of the Target Avatar</param>
|
|
/// <param name="amount">Amount in L$</param>
|
|
/// <param name="description">Description that will show up in the
|
|
/// recipients transaction history</param>
|
|
public void GiveAvatarMoney(UUID target, int amount, string description)
|
|
{
|
|
GiveMoney(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Give L$ to an object
|
|
/// </summary>
|
|
/// <param name="target">object <seealso cref="UUID"/> to give money to</param>
|
|
/// <param name="amount">amount of L$ to give</param>
|
|
/// <param name="objectName">name of object</param>
|
|
public void GiveObjectMoney(UUID target, int amount, string objectName)
|
|
{
|
|
GiveMoney(target, amount, objectName, MoneyTransactionType.PayObject, TransactionFlags.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Give L$ to a group
|
|
/// </summary>
|
|
/// <param name="target">group <seealso cref="UUID"/> to give money to</param>
|
|
/// <param name="amount">amount of L$ to give</param>
|
|
public void GiveGroupMoney(UUID target, int amount)
|
|
{
|
|
GiveMoney(target, amount, String.Empty, MoneyTransactionType.Gift, TransactionFlags.DestGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Give L$ to a group
|
|
/// </summary>
|
|
/// <param name="target">group <seealso cref="UUID"/> to give money to</param>
|
|
/// <param name="amount">amount of L$ to give</param>
|
|
/// <param name="description">description of transaction</param>
|
|
public void GiveGroupMoney(UUID target, int amount, string description)
|
|
{
|
|
GiveMoney(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.DestGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pay texture/animation upload fee
|
|
/// </summary>
|
|
public void PayUploadFee()
|
|
{
|
|
GiveMoney(UUID.Zero, Client.Settings.UPLOAD_COST, String.Empty, MoneyTransactionType.UploadCharge,
|
|
TransactionFlags.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pay texture/animation upload fee
|
|
/// </summary>
|
|
/// <param name="description">description of the transaction</param>
|
|
public void PayUploadFee(string description)
|
|
{
|
|
GiveMoney(UUID.Zero, Client.Settings.UPLOAD_COST, description, MoneyTransactionType.UploadCharge,
|
|
TransactionFlags.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Give Money to destionation Object or Avatar
|
|
/// </summary>
|
|
/// <param name="target">UUID of the Target Object/Avatar</param>
|
|
/// <param name="amount">Amount in L$</param>
|
|
/// <param name="description">Reason (Optional normally)</param>
|
|
/// <param name="type">The type of transaction</param>
|
|
/// <param name="flags">Transaction flags, mostly for identifying group
|
|
/// transactions</param>
|
|
public void GiveMoney(UUID target, int amount, string description, MoneyTransactionType type, TransactionFlags flags)
|
|
{
|
|
MoneyTransferRequestPacket money = new MoneyTransferRequestPacket();
|
|
money.AgentData.AgentID = this.id;
|
|
money.AgentData.SessionID = Client.Self.SessionID;
|
|
money.MoneyData.Description = Utils.StringToBytes(description);
|
|
money.MoneyData.DestID = target;
|
|
money.MoneyData.SourceID = this.id;
|
|
money.MoneyData.TransactionType = (int)type;
|
|
money.MoneyData.AggregatePermInventory = 0; // This is weird, apparently always set to zero though
|
|
money.MoneyData.AggregatePermNextOwner = 0; // This is weird, apparently always set to zero though
|
|
money.MoneyData.Flags = (byte)flags;
|
|
money.MoneyData.Amount = amount;
|
|
|
|
Client.Network.SendPacket(money);
|
|
}
|
|
|
|
#endregion Money
|
|
|
|
#region Animations
|
|
|
|
/// <summary>
|
|
/// Send an AgentAnimation packet that toggles a single animation on
|
|
/// </summary>
|
|
/// <param name="animation">The <seealso cref="UUID"/> of the animation to start playing</param>
|
|
/// <param name="reliable">Whether to ensure delivery of this packet or not</param>
|
|
public void AnimationStart(UUID animation, bool reliable)
|
|
{
|
|
Dictionary<UUID, bool> animations = new Dictionary<UUID, bool>();
|
|
animations[animation] = true;
|
|
|
|
Animate(animations, reliable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an AgentAnimation packet that toggles a single animation off
|
|
/// </summary>
|
|
/// <param name="animation">The <seealso cref="UUID"/> of a
|
|
/// currently playing animation to stop playing</param>
|
|
/// <param name="reliable">Whether to ensure delivery of this packet or not</param>
|
|
public void AnimationStop(UUID animation, bool reliable)
|
|
{
|
|
Dictionary<UUID, bool> animations = new Dictionary<UUID, bool>();
|
|
animations[animation] = false;
|
|
|
|
Animate(animations, reliable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an AgentAnimation packet that will toggle animations on or off
|
|
/// </summary>
|
|
/// <param name="animations">A list of animation <seealso cref="UUID"/>s, and whether to
|
|
/// turn that animation on or off</param>
|
|
/// <param name="reliable">Whether to ensure delivery of this packet or not</param>
|
|
public void Animate(Dictionary<UUID, bool> animations, bool reliable)
|
|
{
|
|
AgentAnimationPacket animate = new AgentAnimationPacket();
|
|
animate.Header.Reliable = reliable;
|
|
|
|
animate.AgentData.AgentID = Client.Self.AgentID;
|
|
animate.AgentData.SessionID = Client.Self.SessionID;
|
|
animate.AnimationList = new AgentAnimationPacket.AnimationListBlock[animations.Count];
|
|
int i = 0;
|
|
|
|
foreach (KeyValuePair<UUID, bool> animation in animations)
|
|
{
|
|
animate.AnimationList[i] = new AgentAnimationPacket.AnimationListBlock();
|
|
animate.AnimationList[i].AnimID = animation.Key;
|
|
animate.AnimationList[i].StartAnim = animation.Value;
|
|
|
|
i++;
|
|
}
|
|
|
|
Client.Network.SendPacket(animate);
|
|
}
|
|
|
|
#endregion Animations
|
|
|
|
#region Teleporting
|
|
|
|
/// <summary>
|
|
/// Teleports agent to their stored home location
|
|
/// </summary>
|
|
/// <returns>true on successful teleport to home location</returns>
|
|
public bool GoHome()
|
|
{
|
|
return Teleport(UUID.Zero);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Teleport agent to a landmark
|
|
/// </summary>
|
|
/// <param name="landmark"><seealso cref="UUID"/> of the landmark to teleport agent to</param>
|
|
/// <returns>true on success, false on failure</returns>
|
|
public bool Teleport(UUID landmark)
|
|
{
|
|
teleportStat = TeleportStatus.None;
|
|
teleportEvent.Reset();
|
|
TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket();
|
|
p.Info = new TeleportLandmarkRequestPacket.InfoBlock();
|
|
p.Info.AgentID = Client.Self.AgentID;
|
|
p.Info.SessionID = Client.Self.SessionID;
|
|
p.Info.LandmarkID = landmark;
|
|
Client.Network.SendPacket(p);
|
|
|
|
teleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false);
|
|
|
|
if (teleportStat == TeleportStatus.None ||
|
|
teleportStat == TeleportStatus.Start ||
|
|
teleportStat == TeleportStatus.Progress)
|
|
{
|
|
teleportMessage = "Teleport timed out.";
|
|
teleportStat = TeleportStatus.Failed;
|
|
}
|
|
|
|
return (teleportStat == TeleportStatus.Finished);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempt to look up a simulator name and teleport to the discovered
|
|
/// destination
|
|
/// </summary>
|
|
/// <param name="simName">Region name to look up</param>
|
|
/// <param name="position">Position to teleport to</param>
|
|
/// <returns>True if the lookup and teleport were successful, otherwise
|
|
/// false</returns>
|
|
public bool Teleport(string simName, Vector3 position)
|
|
{
|
|
return Teleport(simName, position, new Vector3(0, 1.0f, 0));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempt to look up a simulator name and teleport to the discovered
|
|
/// destination
|
|
/// </summary>
|
|
/// <param name="simName">Region name to look up</param>
|
|
/// <param name="position">Position to teleport to</param>
|
|
/// <param name="lookAt">Target to look at</param>
|
|
/// <returns>True if the lookup and teleport were successful, otherwise
|
|
/// false</returns>
|
|
public bool Teleport(string simName, Vector3 position, Vector3 lookAt)
|
|
{
|
|
teleportStat = TeleportStatus.None;
|
|
simName = simName.ToLower();
|
|
|
|
if (simName != Client.Network.CurrentSim.Name.ToLower())
|
|
{
|
|
// Teleporting to a foreign sim
|
|
GridRegion region;
|
|
|
|
if (Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out region))
|
|
{
|
|
return Teleport(region.RegionHandle, position, lookAt);
|
|
}
|
|
else
|
|
{
|
|
teleportMessage = "Unable to resolve name: " + simName;
|
|
teleportStat = TeleportStatus.Failed;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Teleporting to the sim we're already in
|
|
return Teleport(Client.Network.CurrentSim.Handle, position, lookAt);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Teleport agent to another region
|
|
/// </summary>
|
|
/// <param name="regionHandle">handle of region to teleport agent to</param>
|
|
/// <param name="position"><seealso cref="Vector3"/> position in destination sim to teleport to</param>
|
|
/// <returns>true on success, false on failure</returns>
|
|
/// <remarks>This call is blocking</remarks>
|
|
public bool Teleport(ulong regionHandle, Vector3 position)
|
|
{
|
|
return Teleport(regionHandle, position, new Vector3(0.0f, 1.0f, 0.0f));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Teleport agent to another region
|
|
/// </summary>
|
|
/// <param name="regionHandle">handle of region to teleport agent to</param>
|
|
/// <param name="position"><seealso cref="Vector3"/> position in destination sim to teleport to</param>
|
|
/// <param name="lookAt"><seealso cref="Vector3"/> direction in destination sim agent will look at</param>
|
|
/// <returns>true on success, false on failure</returns>
|
|
/// <remarks>This call is blocking</remarks>
|
|
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);
|
|
NetworkManager.EventQueueRunningCallback queueCallback =
|
|
delegate(Simulator simulator)
|
|
{
|
|
if (simulator == Client.Network.CurrentSim)
|
|
queueEvent.Set();
|
|
};
|
|
|
|
Client.Network.OnEventQueueRunning += queueCallback;
|
|
queueEvent.WaitOne(10 * 1000, false);
|
|
Client.Network.OnEventQueueRunning -= 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request teleport to a another simulator
|
|
/// </summary>
|
|
/// <param name="regionHandle">handle of region to teleport agent to</param>
|
|
/// <param name="position"><seealso cref="Vector3"/> position in destination sim to teleport to</param>
|
|
public void RequestTeleport(ulong regionHandle, Vector3 position)
|
|
{
|
|
RequestTeleport(regionHandle, position, new Vector3(0.0f, 1.0f, 0.0f));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request teleport to a another simulator
|
|
/// </summary>
|
|
/// <param name="regionHandle">handle of region to teleport agent to</param>
|
|
/// <param name="position"><seealso cref="Vector3"/> position in destination sim to teleport to</param>
|
|
/// <param name="lookAt"><seealso cref="Vector3"/> direction in destination sim agent will look at</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Teleport agent to a landmark
|
|
/// </summary>
|
|
/// <param name="landmark"><seealso cref="UUID"/> of the landmark to teleport agent to</param>
|
|
public void RequestTeleport(UUID landmark)
|
|
{
|
|
TeleportLandmarkRequestPacket p = new TeleportLandmarkRequestPacket();
|
|
p.Info = new TeleportLandmarkRequestPacket.InfoBlock();
|
|
p.Info.AgentID = Client.Self.AgentID;
|
|
p.Info.SessionID = Client.Self.SessionID;
|
|
p.Info.LandmarkID = landmark;
|
|
Client.Network.SendPacket(p);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a teleport lure to another avatar with default "Join me in ..." invitation message
|
|
/// </summary>
|
|
/// <param name="targetID">target avatars <seealso cref="UUID"/> to lure</param>
|
|
public void SendTeleportLure(UUID targetID)
|
|
{
|
|
SendTeleportLure(targetID, "Join me in " + Client.Network.CurrentSim.Name + "!");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a teleport lure to another avatar with custom invitation message
|
|
/// </summary>
|
|
/// <param name="targetID">target avatars <seealso cref="UUID"/> to lure</param>
|
|
/// <param name="message">custom message to send with invitation</param>
|
|
public void SendTeleportLure(UUID targetID, string message)
|
|
{
|
|
StartLurePacket p = new StartLurePacket();
|
|
p.AgentData.AgentID = Client.Self.id;
|
|
p.AgentData.SessionID = Client.Self.SessionID;
|
|
p.Info.LureType = 0;
|
|
p.Info.Message = Utils.StringToBytes(message);
|
|
p.TargetData = new StartLurePacket.TargetDataBlock[] { new StartLurePacket.TargetDataBlock() };
|
|
p.TargetData[0].TargetID = targetID;
|
|
Client.Network.SendPacket(p);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Respond to a teleport lure by either accepting it and initiating
|
|
/// the teleport, or denying it
|
|
/// </summary>
|
|
/// <param name="requesterID"><seealso cref="UUID"/> of the avatar sending the lure</param>
|
|
/// <param name="accept">true to accept the lure, false to decline it</param>
|
|
public void TeleportLureRespond(UUID requesterID, bool accept)
|
|
{
|
|
InstantMessage(Name, requesterID, String.Empty, UUID.Random(),
|
|
accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport,
|
|
InstantMessageOnline.Offline, this.SimPosition, UUID.Zero, new byte[0]);
|
|
|
|
if (accept)
|
|
{
|
|
TeleportLureRequestPacket lure = new TeleportLureRequestPacket();
|
|
|
|
lure.Info.AgentID = Client.Self.AgentID;
|
|
lure.Info.SessionID = Client.Self.SessionID;
|
|
lure.Info.LureID = Client.Self.AgentID;
|
|
lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure;
|
|
|
|
Client.Network.SendPacket(lure);
|
|
}
|
|
}
|
|
|
|
#endregion Teleporting
|
|
|
|
#region Misc
|
|
|
|
/// <summary>
|
|
/// Update agent profile
|
|
/// </summary>
|
|
/// <param name="profile"><seealso cref="OpenMetaverse.Avatar.AvatarProperties"/> struct containing updated
|
|
/// profile information</param>
|
|
public void UpdateProfile(Avatar.AvatarProperties profile)
|
|
{
|
|
AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket();
|
|
apup.AgentData.AgentID = id;
|
|
apup.AgentData.SessionID = sessionID;
|
|
apup.PropertiesData.AboutText = Utils.StringToBytes(profile.AboutText);
|
|
apup.PropertiesData.AllowPublish = profile.AllowPublish;
|
|
apup.PropertiesData.FLAboutText = Utils.StringToBytes(profile.FirstLifeText);
|
|
apup.PropertiesData.FLImageID = profile.FirstLifeImage;
|
|
apup.PropertiesData.ImageID = profile.ProfileImage;
|
|
apup.PropertiesData.MaturePublish = profile.MaturePublish;
|
|
apup.PropertiesData.ProfileURL = Utils.StringToBytes(profile.ProfileURL);
|
|
|
|
Client.Network.SendPacket(apup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update agents profile interests
|
|
/// </summary>
|
|
/// <param name="interests">selection of interests from <seealso cref="T:OpenMetaverse.Avatar.Interests"/> struct</param>
|
|
public void UpdateInterests(Avatar.Interests interests)
|
|
{
|
|
AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket();
|
|
aiup.AgentData.AgentID = id;
|
|
aiup.AgentData.SessionID = sessionID;
|
|
aiup.PropertiesData.LanguagesText = Utils.StringToBytes(interests.LanguagesText);
|
|
aiup.PropertiesData.SkillsMask = interests.SkillsMask;
|
|
aiup.PropertiesData.SkillsText = Utils.StringToBytes(interests.SkillsText);
|
|
aiup.PropertiesData.WantToMask = interests.WantToMask;
|
|
aiup.PropertiesData.WantToText = Utils.StringToBytes(interests.WantToText);
|
|
|
|
Client.Network.SendPacket(aiup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="height">New height of the viewer window</param>
|
|
/// <param name="width">New width of the viewer window</param>
|
|
public void SetHeightWidth(ushort height, ushort width)
|
|
{
|
|
AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket();
|
|
heightwidth.AgentData.AgentID = Client.Self.AgentID;
|
|
heightwidth.AgentData.SessionID = Client.Self.SessionID;
|
|
heightwidth.AgentData.CircuitCode = Client.Network.CircuitCode;
|
|
heightwidth.HeightWidthBlock.Height = height;
|
|
heightwidth.HeightWidthBlock.Width = width;
|
|
heightwidth.HeightWidthBlock.GenCounter = heightWidthGenCounter++;
|
|
|
|
Client.Network.SendPacket(heightwidth);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request the list of muted objects and avatars for this agent
|
|
/// </summary>
|
|
public void RequestMuteList()
|
|
{
|
|
MuteListRequestPacket mute = new MuteListRequestPacket();
|
|
mute.AgentData.AgentID = Client.Self.AgentID;
|
|
mute.AgentData.SessionID = Client.Self.SessionID;
|
|
mute.MuteData.MuteCRC = 0;
|
|
|
|
Client.Network.SendPacket(mute);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets home location to agents current position
|
|
/// </summary>
|
|
/// <remarks>will fire an AlertMessage (<seealso cref="E:OpenMetaverse.AgentManager.OnAlertMessage"/>) with
|
|
/// success or failure message</remarks>
|
|
public void SetHome()
|
|
{
|
|
SetStartLocationRequestPacket s = new SetStartLocationRequestPacket();
|
|
s.AgentData = new SetStartLocationRequestPacket.AgentDataBlock();
|
|
s.AgentData.AgentID = Client.Self.AgentID;
|
|
s.AgentData.SessionID = Client.Self.SessionID;
|
|
s.StartLocationData = new SetStartLocationRequestPacket.StartLocationDataBlock();
|
|
s.StartLocationData.LocationPos = Client.Self.SimPosition;
|
|
s.StartLocationData.LocationID = 1;
|
|
s.StartLocationData.SimName = Utils.StringToBytes(String.Empty);
|
|
s.StartLocationData.LocationLookAt = Movement.Camera.AtAxis;
|
|
Client.Network.SendPacket(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move an agent in to a simulator. This packet is the last packet
|
|
/// needed to complete the transition in to a new simulator
|
|
/// </summary>
|
|
/// <param name="simulator"><seealso cref="T:OpenMetaverse.Simulator"/> Object</param>
|
|
public void CompleteAgentMovement(Simulator simulator)
|
|
{
|
|
CompleteAgentMovementPacket move = new CompleteAgentMovementPacket();
|
|
|
|
move.AgentData.AgentID = Client.Self.AgentID;
|
|
move.AgentData.SessionID = Client.Self.SessionID;
|
|
move.AgentData.CircuitCode = Client.Network.CircuitCode;
|
|
|
|
Client.Network.SendPacket(move, simulator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reply to script permissions request
|
|
/// </summary>
|
|
/// <param name="simulator"><seealso cref="T:OpenMetaverse.Simulator"/> Object</param>
|
|
/// <param name="itemID"><seealso cref="UUID"/> of the itemID requesting permissions</param>
|
|
/// <param name="taskID"><seealso cref="UUID"/> of the taskID requesting permissions</param>
|
|
/// <param name="permissions"><seealso cref="OpenMetaverse.ScriptPermission"/> list of permissions to allow</param>
|
|
public void ScriptQuestionReply(Simulator simulator, UUID itemID, UUID taskID, ScriptPermission permissions)
|
|
{
|
|
ScriptAnswerYesPacket yes = new ScriptAnswerYesPacket();
|
|
yes.AgentData.AgentID = Client.Self.AgentID;
|
|
yes.AgentData.SessionID = Client.Self.SessionID;
|
|
yes.Data.ItemID = itemID;
|
|
yes.Data.TaskID = taskID;
|
|
yes.Data.Questions = (int)permissions;
|
|
|
|
Client.Network.SendPacket(yes, simulator);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Respond to a group invitation by either accepting or denying it
|
|
/// </summary>
|
|
/// <param name="groupID">UUID of the group (sent in the AgentID field of the invite message)</param>
|
|
/// <param name="imSessionID">IM Session ID from the group invitation message</param>
|
|
/// <param name="accept">Accept the group invitation or deny it</param>
|
|
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, new byte[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests script detection of objects and avatars
|
|
/// </summary>
|
|
/// <param name="name">name of the object/avatar to search for</param>
|
|
/// <param name="searchID">UUID of the object or avatar to search for</param>
|
|
/// <param name="type">Type of search from ScriptSensorTypeFlags</param>
|
|
/// <param name="range">range of scan (96 max?)</param>
|
|
/// <param name="arc">the arc in radians to search within</param>
|
|
/// <param name="requestID">an user generated ID to correlate replies with</param>
|
|
/// <param name="sim">Simulator to perform search in</param>
|
|
public void RequestScriptSensor(string name, UUID searchID, ScriptSensorTypeFlags type, float range, float arc, UUID requestID, Simulator sim)
|
|
{
|
|
ScriptSensorRequestPacket request = new ScriptSensorRequestPacket();
|
|
request.Requester.Arc = arc;
|
|
request.Requester.Range = range;
|
|
request.Requester.RegionHandle = sim.Handle;
|
|
request.Requester.RequestID = requestID;
|
|
request.Requester.SearchDir = Quaternion.Identity; // TODO: this needs to be tested
|
|
request.Requester.SearchID = searchID;
|
|
request.Requester.SearchName = Utils.StringToBytes(name);
|
|
request.Requester.SearchPos = Vector3.Zero;
|
|
request.Requester.SearchRegions = 0; // TODO: ?
|
|
request.Requester.SourceID = Client.Self.AgentID;
|
|
request.Requester.Type = (int)type;
|
|
|
|
Client.Network.SendPacket(request, sim);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create or update profile pick
|
|
/// </summary>
|
|
/// <param name="pickID">UUID of the pick to update, or random UUID to create a new pick</param>
|
|
/// <param name="topPick">Is this a top pick? (typically false)</param>
|
|
/// <param name="parcelID">UUID of the parcel (UUID.Zero for the current parcel)</param>
|
|
/// <param name="name">Name of the pick</param>
|
|
/// <param name="globalPosition">Global position of the pick landmark</param>
|
|
/// <param name="textureID">UUID of the image displayed with the pick</param>
|
|
/// <param name="description">Long description of the pick</param>
|
|
public void PickInfoUpdate(UUID pickID, bool topPick, UUID parcelID, string name, Vector3d globalPosition, UUID textureID, string description)
|
|
{
|
|
PickInfoUpdatePacket pick = new PickInfoUpdatePacket();
|
|
pick.AgentData.AgentID = Client.Self.AgentID;
|
|
pick.AgentData.SessionID = Client.Self.SessionID;
|
|
pick.Data.PickID = pickID;
|
|
pick.Data.Desc = Utils.StringToBytes(description);
|
|
pick.Data.CreatorID = Client.Self.AgentID;
|
|
pick.Data.TopPick = topPick;
|
|
pick.Data.ParcelID = parcelID;
|
|
pick.Data.Name = Utils.StringToBytes(name);
|
|
pick.Data.SnapshotID = textureID;
|
|
pick.Data.PosGlobal = Client.Self.GlobalPosition;
|
|
pick.Data.SortOrder = 0;
|
|
pick.Data.Enabled = false;
|
|
|
|
Client.Network.SendPacket(pick);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete profile pick
|
|
/// </summary>
|
|
/// <param name="pickID">UUID of the pick to delete</param>
|
|
public void PickDelete(UUID pickID)
|
|
{
|
|
PickDeletePacket delete = new PickDeletePacket();
|
|
delete.AgentData.AgentID = Client.Self.AgentID;
|
|
delete.AgentData.SessionID = Client.Self.sessionID;
|
|
delete.Data.PickID = pickID;
|
|
|
|
Client.Network.SendPacket(delete);
|
|
}
|
|
|
|
#endregion Misc
|
|
|
|
#region Packet Handlers
|
|
|
|
/// <summary>
|
|
/// Take an incoming ImprovedInstantMessage packet, auto-parse, and if
|
|
/// OnInstantMessage is defined call that with the appropriate arguments
|
|
/// </summary>
|
|
/// <param name="packet">Incoming ImprovedInstantMessagePacket</param>
|
|
/// <param name="simulator">Unused</param>
|
|
private void InstantMessageHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (packet.Type == PacketType.ImprovedInstantMessage)
|
|
{
|
|
ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)packet;
|
|
|
|
if (OnInstantMessage != null)
|
|
{
|
|
InstantMessage message;
|
|
message.FromAgentID = im.AgentData.AgentID;
|
|
message.FromAgentName = 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;
|
|
|
|
try { OnInstantMessage(message, simulator); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Take an incoming Chat packet, auto-parse, and if OnChat is defined call
|
|
/// that with the appropriate arguments.
|
|
/// </summary>
|
|
/// <param name="packet">Incoming ChatFromSimulatorPacket</param>
|
|
/// <param name="simulator">Unused</param>
|
|
private void ChatHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnChat != null)
|
|
{
|
|
ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet;
|
|
|
|
OnChat(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
|
|
);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for parsing llDialogs
|
|
/// </summary>
|
|
/// <param name="packet">Incoming ScriptDialog packet</param>
|
|
/// <param name="simulator">Unused</param>
|
|
private void ScriptDialogHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnScriptDialog != null)
|
|
{
|
|
ScriptDialogPacket dialog = (ScriptDialogPacket)packet;
|
|
List<string> buttons = new List<string>();
|
|
|
|
foreach (ScriptDialogPacket.ButtonsBlock button in dialog.Buttons)
|
|
{
|
|
buttons.Add(Utils.BytesToString(button.ButtonLabel));
|
|
}
|
|
|
|
OnScriptDialog(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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for parsing llRequestPermissions dialogs
|
|
/// </summary>
|
|
/// <param name="packet">Incoming ScriptDialog packet</param>
|
|
/// <param name="simulator">Unused</param>
|
|
private void ScriptQuestionHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnScriptQuestion != null)
|
|
{
|
|
ScriptQuestionPacket question = (ScriptQuestionPacket)packet;
|
|
|
|
try
|
|
{
|
|
OnScriptQuestion(simulator,
|
|
question.Data.TaskID,
|
|
question.Data.ItemID,
|
|
Utils.BytesToString(question.Data.ObjectName),
|
|
Utils.BytesToString(question.Data.ObjectOwner),
|
|
(ScriptPermission)question.Data.Questions);
|
|
}
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles Script Control changes when Script with permissions releases or takes a control
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="simulator"></param>
|
|
private void ScriptControlChangeHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnScriptControlChange != null)
|
|
{
|
|
ScriptControlChangePacket change = (ScriptControlChangePacket)packet;
|
|
for (int i = 0; i < change.Data.Length; i++)
|
|
{
|
|
try
|
|
{
|
|
OnScriptControlChange((ScriptControlChange)change.Data[i].Controls,
|
|
change.Data[i].PassToAgent,
|
|
change.Data[i].TakeControls);
|
|
}
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for parsing llLoadURL Dialogs
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="simulator"></param>
|
|
private void LoadURLHandler(Packet packet, Simulator simulator)
|
|
{
|
|
LoadURLPacket loadURL = (LoadURLPacket)packet;
|
|
if (OnLoadURL != null)
|
|
{
|
|
try
|
|
{
|
|
OnLoadURL(
|
|
Utils.BytesToString(loadURL.Data.ObjectName),
|
|
loadURL.Data.ObjectID,
|
|
loadURL.Data.OwnerID,
|
|
loadURL.Data.OwnerIsGroup,
|
|
Utils.BytesToString(loadURL.Data.Message),
|
|
Utils.BytesToString(loadURL.Data.URL)
|
|
);
|
|
}
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update client's Position, LookAt and region handle from incoming packet
|
|
/// </summary>
|
|
/// <param name="packet">Incoming AgentMovementCompletePacket</param>
|
|
/// <param name="simulator">Unused</param>
|
|
/// <remarks>This occurs when after an avatar moves into a new sim</remarks>
|
|
private void MovementCompleteHandler(Packet packet, Simulator simulator)
|
|
{
|
|
AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet;
|
|
|
|
relativePosition = movement.Data.Position;
|
|
Movement.Camera.LookDirection(movement.Data.LookAt);
|
|
simulator.Handle = movement.Data.RegionHandle;
|
|
simulator.SimVersion = Utils.BytesToString(movement.SimData.ChannelVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update Client Avatar's health via incoming packet
|
|
/// </summary>
|
|
/// <param name="packet">Incoming HealthMessagePacket</param>
|
|
/// <param name="simulator">Unused</param>
|
|
private void HealthHandler(Packet packet, Simulator simulator)
|
|
{
|
|
health = ((HealthMessagePacket)packet).HealthData.Health;
|
|
}
|
|
|
|
private void AgentDataUpdateHandler(Packet packet, Simulator simulator)
|
|
{
|
|
AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet;
|
|
|
|
if (p.AgentData.AgentID == simulator.Client.Self.AgentID)
|
|
{
|
|
firstName = Utils.BytesToString(p.AgentData.FirstName);
|
|
lastName = Utils.BytesToString(p.AgentData.LastName);
|
|
activeGroup = p.AgentData.ActiveGroupID;
|
|
activeGroupPowers = (GroupPowers)p.AgentData.GroupPowers;
|
|
|
|
if (OnAgentDataUpdated != null)
|
|
{
|
|
string groupTitle = Utils.BytesToString(p.AgentData.GroupTitle);
|
|
string groupName = Utils.BytesToString(p.AgentData.GroupName);
|
|
|
|
try { OnAgentDataUpdated(firstName, lastName, activeGroup, groupTitle, (GroupPowers)p.AgentData.GroupPowers, groupName); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update Client Avatar's L$ balance from incoming packet
|
|
/// </summary>
|
|
/// <param name="packet">Incoming MoneyBalanceReplyPacket</param>
|
|
/// <param name="simulator">Unused</param>
|
|
private void BalanceHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (packet.Type == PacketType.MoneyBalanceReply)
|
|
{
|
|
MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet;
|
|
balance = mbrp.MoneyData.MoneyBalance;
|
|
|
|
if (OnMoneyBalanceReplyReceived != null)
|
|
{
|
|
try
|
|
{
|
|
OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID,
|
|
mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance,
|
|
mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted,
|
|
Utils.BytesToString(mbrp.MoneyData.Description));
|
|
}
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
if (OnBalanceUpdated != null)
|
|
{
|
|
try { OnBalanceUpdated(balance); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
private void EstablishAgentCommunicationEventHandler(string message, OSD osd, Simulator simulator)
|
|
{
|
|
StructuredData.OSDMap body = (StructuredData.OSDMap)osd;
|
|
|
|
|
|
if (Client.Settings.MULTIPLE_SIMS && body.ContainsKey("sim-ip-and-port"))
|
|
{
|
|
string ipAndPort = body["sim-ip-and-port"].AsString();
|
|
string[] pieces = ipAndPort.Split(':');
|
|
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(pieces[0]), Convert.ToInt32(pieces[1]));
|
|
Simulator sim = Client.Network.FindSimulator(endPoint);
|
|
|
|
if (sim == null)
|
|
{
|
|
Logger.Log("Got EstablishAgentCommunication for unknown sim " + ipAndPort,
|
|
Helpers.LogLevel.Error, Client);
|
|
|
|
// FIXME: Should we use this opportunity to connect to the simulator?
|
|
}
|
|
else
|
|
{
|
|
Logger.Log("Got EstablishAgentCommunication for " + sim.ToString(),
|
|
Helpers.LogLevel.Info, Client);
|
|
|
|
sim.SetSeedCaps(body["seed-capability"].AsString());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process TeleportFinish from Event Queue and pass it onto our TeleportHandler
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
/// <param name="osd"></param>
|
|
/// <param name="simulator"></param>
|
|
private void TeleportFinishEventHandler(string message, OSD osd, Simulator simulator)
|
|
{
|
|
|
|
OSDMap map = (OSDMap)osd;
|
|
OSDArray array = (OSDArray)map["Info"];
|
|
for (int i = 0; i < array.Count; i++)
|
|
{
|
|
TeleportFinishPacket p = new TeleportFinishPacket();
|
|
OSDMap data = (OSDMap)array[i];
|
|
p.Info.AgentID = data["AgentID"].AsUUID();
|
|
p.Info.LocationID = Utils.BytesToUInt(data["LocationID"].AsBinary());
|
|
p.Info.RegionHandle = Utils.BytesToUInt64(data["RegionHandle"].AsBinary());
|
|
p.Info.SeedCapability = data["SeedCapability"].AsBinary();
|
|
p.Info.SimAccess = (byte)data["SimAccess"].AsInteger();
|
|
p.Info.SimIP = Utils.BytesToUInt(data["SimIP"].AsBinary());
|
|
p.Info.SimPort = (ushort)data["SimPort"].AsInteger();
|
|
p.Info.TeleportFlags = Utils.BytesToUInt(data["TeleportFlags"].AsBinary());
|
|
|
|
// pass the packet onto the teleport handler
|
|
TeleportHandler(p, simulator);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for teleport Requests
|
|
/// </summary>
|
|
/// <param name="packet">Incoming TeleportHandler packet</param>
|
|
/// <param name="simulator">Simulator sending teleport information</param>
|
|
private void TeleportHandler(Packet packet, Simulator simulator)
|
|
{
|
|
bool finished = false;
|
|
TeleportFlags flags = TeleportFlags.Default;
|
|
|
|
if (packet.Type == PacketType.TeleportStart)
|
|
{
|
|
TeleportStartPacket start = (TeleportStartPacket)packet;
|
|
|
|
teleportMessage = "Teleport started";
|
|
flags = (TeleportFlags)start.Info.TeleportFlags;
|
|
teleportStat = TeleportStatus.Start;
|
|
|
|
Logger.DebugLog("TeleportStart received, Flags: " + flags.ToString(), 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.ToString(), 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.ToString(), Client);
|
|
|
|
// Connect to the new sim
|
|
Simulator newSimulator = Client.Network.Connect(new IPAddress(finish.Info.SimIP),
|
|
finish.Info.SimPort, finish.Info.RegionHandle, true, seedcaps);
|
|
|
|
if (newSimulator != null)
|
|
{
|
|
teleportMessage = "Teleport finished";
|
|
teleportStat = TeleportStatus.Finished;
|
|
|
|
// Disconnect from the previous sim
|
|
Client.Network.DisconnectSim(simulator, true);
|
|
|
|
Logger.Log("Moved to new sim " + newSimulator.ToString(), 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.ToString(), 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.ToString(), Client);
|
|
}
|
|
|
|
if (OnTeleport != null)
|
|
{
|
|
try { OnTeleport(teleportMessage, teleportStat, flags); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
|
|
if (finished) teleportEvent.Set();
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="sim"></param>
|
|
private void AvatarAnimationHandler(Packet packet, Simulator sim)
|
|
{
|
|
AvatarAnimationPacket animation = (AvatarAnimationPacket)packet;
|
|
|
|
if (animation.Sender.ID == Client.Self.AgentID)
|
|
{
|
|
lock (SignaledAnimations.Dictionary)
|
|
{
|
|
// Reset the signaled animation list
|
|
SignaledAnimations.Dictionary.Clear();
|
|
|
|
for (int i = 0; i < animation.AnimationList.Length; i++)
|
|
{
|
|
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)
|
|
{
|
|
// 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 (OnAnimationsChanged != null)
|
|
{
|
|
try { OnAnimationsChanged(SignaledAnimations); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MeanCollisionAlertHandler(Packet packet, Simulator sim)
|
|
{
|
|
if (OnMeanCollision != null)
|
|
{
|
|
MeanCollisionAlertPacket collision = (MeanCollisionAlertPacket)packet;
|
|
|
|
for (int i = 0; i < collision.MeanCollision.Length; i++)
|
|
{
|
|
MeanCollisionAlertPacket.MeanCollisionBlock block = collision.MeanCollision[i];
|
|
|
|
DateTime time = Utils.UnixTimeToDateTime(block.Time);
|
|
MeanCollisionType type = (MeanCollisionType)block.Type;
|
|
|
|
try { OnMeanCollision(type, block.Perp, block.Victim, block.Mag, time); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason,
|
|
LoginResponseData reply)
|
|
{
|
|
id = reply.AgentID;
|
|
sessionID = reply.SessionID;
|
|
secureSessionID = reply.SecureSessionID;
|
|
firstName = reply.FirstName;
|
|
lastName = reply.LastName;
|
|
startLocation = reply.StartLocation;
|
|
agentAccess = reply.AgentAccess;
|
|
Movement.Camera.LookDirection(reply.LookAt);
|
|
homePosition = reply.HomePosition;
|
|
homeLookAt = reply.HomeLookAt;
|
|
}
|
|
|
|
private void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message)
|
|
{
|
|
// Null out the cached fullName since it can change after logging
|
|
// in again (with a different account name or different login
|
|
// server but using the same GridClient object
|
|
fullName = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows agent to cross over (walk, fly, vehicle) in to neighboring
|
|
/// simulators
|
|
/// </summary>
|
|
private void CrossedRegionHandler(Packet packet, Simulator sim)
|
|
{
|
|
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.ToString(), 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.ToString(), Helpers.LogLevel.Info, Client);
|
|
|
|
if (OnRegionCrossed != null)
|
|
{
|
|
try { OnRegionCrossed(oldSim, newSim); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
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.ToString() + " after crossing over",
|
|
Helpers.LogLevel.Warning, Client);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Group Chat event handler
|
|
/// </summary>
|
|
/// <param name="capsKey">The capability Key</param>
|
|
/// <param name="osd"></param>
|
|
/// <param name="simulator"></param>
|
|
private void ChatterBoxSessionEventHandler(string capsKey, OSD osd, Simulator simulator)
|
|
{
|
|
OSDMap map = (OSDMap)osd;
|
|
if (map["success"].AsBoolean() != true)
|
|
{
|
|
Logger.Log("Attempt to send group chat to non-existant session for group " + map["session_id"].AsString(),
|
|
Helpers.LogLevel.Info, Client);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Response from request to join a group chat
|
|
/// </summary>
|
|
/// <param name="capsKey"></param>
|
|
/// <param name="osd"></param>
|
|
/// <param name="simulator"></param>
|
|
private void ChatterBoxSessionStartReplyHandler(string capsKey, OSD osd, Simulator simulator)
|
|
{
|
|
OSDMap map = (OSDMap)osd;
|
|
UUID sessionID = map["session_id"].AsUUID();
|
|
UUID tmpSessionID = map["temp_session_id"].AsUUID();
|
|
|
|
string sessionName = String.Empty;
|
|
|
|
bool success = map["success"].AsBoolean();
|
|
|
|
if (success)
|
|
{
|
|
OSDMap sessionInfo = (OSDMap)map["session_info"];
|
|
sessionName = sessionInfo["session_name"].AsString();
|
|
|
|
/* Parameters we do not currently use for anything */
|
|
// sessionInfo["type"].AsInteger();
|
|
// sessionInfo["voice_enabled"}.AsBoolean();
|
|
// sessionInfo["moderated_mode"] -> ["voice"].AsBoolean()
|
|
}
|
|
|
|
if (OnGroupChatJoin != null)
|
|
{
|
|
try { OnGroupChatJoin(sessionID, sessionName, tmpSessionID, success); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Someone joined or left group chat
|
|
/// </summary>
|
|
/// <param name="capsKey"></param>
|
|
/// <param name="osd"></param>
|
|
/// <param name="simulator"></param>
|
|
private void ChatterBoxSessionAgentListReplyHandler(string capsKey, OSD osd, Simulator simulator)
|
|
{
|
|
// parse the SD
|
|
OSDMap map = (OSDMap)osd;
|
|
|
|
// verify sessions exists, if not add it
|
|
UUID sessionID;
|
|
if (map.ContainsKey("session_id"))
|
|
{
|
|
sessionID = map["session_id"].AsUUID();
|
|
lock (GroupChatSessions)
|
|
if (!GroupChatSessions.ContainsKey(sessionID))
|
|
GroupChatSessions.Add(sessionID, new List<ChatSessionMember>());
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
//string errormsg = map["error"].AsString();
|
|
//SDMap updates = (SDMap)map["updates"];
|
|
|
|
// Handle any agent data updates
|
|
OSDMap agent_updates = (OSDMap)map["agent_updates"];
|
|
|
|
foreach (KeyValuePair<string, OSD> kvp in agent_updates)
|
|
{
|
|
UUID agent_key = UUID.Parse(kvp.Key);
|
|
OSDMap record = (OSDMap)kvp.Value;
|
|
|
|
// handle joins/parts first
|
|
if (record.ContainsKey("transition"))
|
|
{
|
|
// find existing record if any
|
|
ChatSessionMember fndMbr;
|
|
lock (GroupChatSessions.Dictionary)
|
|
{
|
|
fndMbr = GroupChatSessions[sessionID].Find(delegate(ChatSessionMember member)
|
|
{
|
|
return member.AvatarKey == agent_key;
|
|
});
|
|
}
|
|
|
|
// handle joins
|
|
if (record["transition"].AsString().Equals("ENTER"))
|
|
{
|
|
if (fndMbr.AvatarKey == UUID.Zero)
|
|
{
|
|
fndMbr = new ChatSessionMember();
|
|
fndMbr.AvatarKey = agent_key;
|
|
|
|
lock (GroupChatSessions.Dictionary)
|
|
GroupChatSessions[sessionID].Add(fndMbr);
|
|
|
|
if (OnChatSessionMemberAdded != null)
|
|
{
|
|
try { OnChatSessionMemberAdded(sessionID, agent_key); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
}
|
|
// handle parts
|
|
else if (record["transition"].AsString().Equals("LEAVE"))
|
|
{
|
|
if (fndMbr.AvatarKey != UUID.Zero)
|
|
lock (GroupChatSessions.Dictionary)
|
|
GroupChatSessions[sessionID].Remove(fndMbr);
|
|
|
|
if (OnChatSessionMemberLeft != null)
|
|
{
|
|
try { OnChatSessionMemberLeft(sessionID, agent_key); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
|
|
if (agent_key == Client.Self.AgentID)
|
|
{
|
|
try { OnGroupChatLeft(sessionID); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
|
|
// no need to process anything else in this record
|
|
continue;
|
|
}
|
|
// this should only fire if LL adds a new transition but doesn't tell anyone
|
|
else
|
|
{
|
|
Logger.Log("Unknown transition action " + record["transition"], Helpers.LogLevel.Warning, Client);
|
|
}
|
|
}
|
|
|
|
|
|
// Handle any updates
|
|
|
|
// search for member to update
|
|
ChatSessionMember update_member = GroupChatSessions.Dictionary[sessionID].Find(delegate(ChatSessionMember m)
|
|
{
|
|
return m.AvatarKey == agent_key;
|
|
});
|
|
|
|
OSDMap record_info = (OSDMap)record["info"];
|
|
|
|
lock (GroupChatSessions.Dictionary)
|
|
{
|
|
|
|
if (record_info.ContainsKey("mutes"))
|
|
{
|
|
OSDMap mutes = (OSDMap)record_info["mutes"];
|
|
foreach (KeyValuePair<string, OSD> muteEntry in mutes)
|
|
{
|
|
if (muteEntry.Key == "text")
|
|
{
|
|
update_member.MuteText = muteEntry.Value.AsBoolean();
|
|
}
|
|
else if (muteEntry.Key == "voice")
|
|
{
|
|
update_member.MuteVoice = muteEntry.Value.AsBoolean();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (record_info.ContainsKey("can_voice_chat"))
|
|
{
|
|
update_member.CanVoiceChat = record_info["can_voice_chat"].AsBoolean();
|
|
}
|
|
|
|
if (record_info.ContainsKey("is_moderator"))
|
|
{
|
|
update_member.IsModerator = record_info["is_moderator"].AsBoolean();
|
|
}
|
|
}
|
|
|
|
// replace existing member record
|
|
lock (GroupChatSessions.Dictionary)
|
|
{
|
|
int found = GroupChatSessions.Dictionary[sessionID].FindIndex(delegate(ChatSessionMember m)
|
|
{
|
|
return m.AvatarKey == agent_key;
|
|
});
|
|
|
|
if (found >= 0)
|
|
GroupChatSessions.Dictionary[sessionID][found] = update_member;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//foreach (KeyValuePair<string, SD> kvp in updates)
|
|
//{
|
|
// if (kvp.Value.Equals("ENTER"))
|
|
// {
|
|
// lock (GroupChatSessions.Dictionary)
|
|
// {
|
|
// if (!GroupChatSessions.Dictionary[sessionID].Contains((UUID)kvp.Key))
|
|
// GroupChatSessions.Dictionary[sessionID].Add((UUID)kvp.Key);
|
|
// }
|
|
// }
|
|
// else if (kvp.Value.Equals("LEAVE"))
|
|
// {
|
|
// lock (GroupChatSessions.Dictionary)
|
|
// {
|
|
// if (GroupChatSessions.Dictionary[sessionID].Contains((UUID)kvp.Key))
|
|
// GroupChatSessions.Dictionary[sessionID].Remove((UUID)kvp.Key);
|
|
|
|
// // we left session, remove from dictionary
|
|
// if (kvp.Key.Equals(Client.Self.id) && OnGroupChatLeft != null)
|
|
// {
|
|
// GroupChatSessions.Dictionary.Remove(sessionID);
|
|
// OnGroupChatLeft(sessionID);
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Group Chat Request
|
|
/// </summary>
|
|
/// <param name="capsKey">Caps Key</param>
|
|
/// <param name="osd">SD Map containing invitation</param>
|
|
/// <param name="simulator">Originating Simulator</param>
|
|
private void ChatterBoxInvitationHandler(string capsKey, OSD osd, Simulator simulator)
|
|
{
|
|
if (OnInstantMessage != null)
|
|
{
|
|
OSDMap map = (OSDMap)osd;
|
|
OSDMap im = (OSDMap)map["instantmessage"];
|
|
//SDMap agent = (SDMap)im["agent_params"];
|
|
OSDMap msg = (OSDMap)im["message_params"];
|
|
OSDMap msgdata = (OSDMap)msg["data"];
|
|
|
|
InstantMessage message = new InstantMessage();
|
|
|
|
message.FromAgentID = map["from_id"].AsUUID();
|
|
message.FromAgentName = map["from_name"].AsString();
|
|
message.ToAgentID = msg["to_id"].AsUUID();
|
|
message.ParentEstateID = (uint)msg["parent_estate_id"].AsInteger();
|
|
message.RegionID = msg["region_id"].AsUUID();
|
|
message.Position = ((OSDArray)msg["position"]).AsVector3();
|
|
message.Dialog = (InstantMessageDialog)msgdata["type"].AsInteger();
|
|
message.GroupIM = true;
|
|
message.IMSessionID = map["session_id"].AsUUID();
|
|
message.Timestamp = new DateTime(msgdata["timestamp"].AsInteger());
|
|
message.Message = msg["message"].AsString();
|
|
message.Offline = (InstantMessageOnline)msg["offline"].AsInteger();
|
|
message.BinaryBucket = msgdata["binary_bucket"].AsBinary();
|
|
|
|
try { OnInstantMessage(message, simulator); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Moderate a chat session
|
|
/// </summary>
|
|
/// <param name="sessionID">the <see cref="UUID"/> of the session to moderate, for group chats this will be the groups UUID</param>
|
|
/// <param name="memberID">the <see cref="UUID"/> of the avatar to moderate</param>
|
|
/// <param name="moderateText">true to moderate (silence user), false to allow avatar to speak</param>
|
|
public void ModerateChatSessions(UUID sessionID, UUID memberID, bool moderateText)
|
|
{
|
|
if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null)
|
|
throw new Exception("ChatSessionRequest capability is not currently available");
|
|
|
|
Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("ChatSessionRequest");
|
|
|
|
if (url != null)
|
|
{
|
|
OSDMap req = new OSDMap();
|
|
req.Add("method", OSD.FromString("mute update"));
|
|
|
|
OSDMap mute_info = new OSDMap();
|
|
mute_info.Add("text", OSD.FromBoolean(moderateText));
|
|
|
|
OSDMap parameters = new OSDMap();
|
|
parameters["agent_id"] = OSD.FromUUID(memberID);
|
|
parameters["mute_info"] = mute_info;
|
|
|
|
req["params"] = parameters;
|
|
|
|
req.Add("session-id", OSD.FromUUID(sessionID));
|
|
|
|
byte[] postData = StructuredData.OSDParser.SerializeLLSDXmlBytes(req);
|
|
|
|
CapsClient request = new CapsClient(url);
|
|
request.StartRequest(postData);
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("ChatSessionRequest capability is not currently available");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alert Message packet handler
|
|
/// </summary>
|
|
/// <param name="packet">AlertMessagePacket</param>
|
|
/// <param name="simulator">not used</param>
|
|
private void AlertMessageHandler(Packet packet, Simulator simulator)
|
|
{
|
|
AlertMessagePacket alert = (AlertMessagePacket)packet;
|
|
string message = Utils.BytesToString(alert.AlertData.Message);
|
|
|
|
if (OnAlertMessage != null)
|
|
{
|
|
try { OnAlertMessage(message); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// detects camera constraint collisions
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="simulator"></param>
|
|
private void CameraConstraintHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnCameraConstraint != null)
|
|
{
|
|
CameraConstraintPacket camera = (CameraConstraintPacket)packet;
|
|
|
|
try { OnCameraConstraint(camera.CameraCollidePlane.Plane); }
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packet handler for ScriptSensorReply packet
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="simulator"></param>
|
|
private void ScriptSensorReplyHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnScriptSensorReply != null)
|
|
{
|
|
ScriptSensorReplyPacket reply = (ScriptSensorReplyPacket)packet;
|
|
|
|
for (int i = 0; i < reply.SensedData.Length; i++)
|
|
{
|
|
ScriptSensorReplyPacket.SensedDataBlock block = reply.SensedData[i];
|
|
ScriptSensorReplyPacket.RequesterBlock requestor = reply.Requester;
|
|
|
|
try
|
|
{
|
|
OnScriptSensorReply(requestor.SourceID, block.GroupID, Utils.BytesToString(block.Name),
|
|
block.ObjectID, block.OwnerID, block.Position, block.Range, block.Rotation, (ScriptSensorTypeFlags)block.Type, block.Velocity);
|
|
}
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Packet handler for AvatarSitResponse packet
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="simulator"></param>
|
|
private void AvatarSitResponseHandler(Packet packet, Simulator simulator)
|
|
{
|
|
if (OnAvatarSitResponse != null)
|
|
{
|
|
AvatarSitResponsePacket sit = (AvatarSitResponsePacket)packet;
|
|
|
|
try
|
|
{
|
|
OnAvatarSitResponse(sit.SitObject.ID, sit.SitTransform.AutoPilot, sit.SitTransform.CameraAtOffset,
|
|
sit.SitTransform.CameraEyeOffset, sit.SitTransform.ForceMouselook, sit.SitTransform.SitPosition,
|
|
sit.SitTransform.SitRotation);
|
|
}
|
|
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
|
}
|
|
}
|
|
|
|
#endregion Packet Handlers
|
|
}
|
|
}
|