/*
* Copyright (c) 2006, Second Life Reverse Engineering Team
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the Second Life Reverse Engineering Team nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Timers;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using libsecondlife.Packets;
namespace libsecondlife
{
///
/// Class to hold Client Avatar's data
///
public partial class MainAvatar
{
#region Enums
///
/// Used to specify movement actions for your agent
///
[Flags]
public enum AgentUpdateFlags
{
/// Empty flag
NONE = 0,
/// Move Forward (SL Keybinding: W/Up Arrow)
AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX,
/// Move Backward (SL Keybinding: S/Down Arrow)
AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX,
/// Move Left (SL Keybinding: Shift-(A/Left Arrow))
AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX,
/// Move Right (SL Keybinding: Shift-(D/Right Arrow))
AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX,
/// Not Flying: Jump/Flying: Move Up (SL Keybinding: E)
AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX,
/// Not Flying: Croutch/Flying: Move Down (SL Keybinding: C)
AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX,
/// Unused
AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX,
/// Unused
AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX,
/// Unused
AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX,
/// Unused
AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX,
/// ORed with AGENT_CONTROL_AT_* if the keyboard is being used
AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX,
/// ORed with AGENT_CONTROL_LEFT_* if the keyboard is being used
AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX,
/// ORed with AGENT_CONTROL_UP_* if the keyboard is being used
AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX,
/// Fly
AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX,
///
AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX,
/// Finish our current animation
AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX,
/// Stand up from the ground or a prim seat
AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX,
/// Sit on the ground at our current location
AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX,
/// Whether mouselook is currently enabled
AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX,
/// Legacy, used if a key was pressed for less than a certain amount of time
AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX,
/// Legacy, used if a key was pressed for less than a certain amount of time
AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX,
/// Legacy, used if a key was pressed for less than a certain amount of time
AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX,
/// Legacy, used if a key was pressed for less than a certain amount of time
AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX,
/// Legacy, used if a key was pressed for less than a certain amount of time
AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX,
/// Legacy, used if a key was pressed for less than a certain amount of time
AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX,
///
AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX,
///
AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX,
/// Set when the avatar is idled or set to away. Note that the away animation is
/// activated separately from setting this flag
AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX,
///
AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX,
///
AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX,
///
AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX,
///
AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX
}
///
/// Current teleport status
///
public enum TeleportStatus
{
/// Unknown status
None,
/// Teleport initialized
Start,
/// Teleport in progress
Progress,
/// Teleport failed
Failed,
/// Teleport completed
Finished,
/// Teleport cancelled
Cancelled
}
///
/// Special commands used in Instant Messages
///
public enum InstantMessageDialog : byte
{
/// Indicates a regular IM from another agent
MessageFromAgent = 0,
/// Simple notification box with an OK button
MessageBox = 1,
/// Used to show a countdown notification with an OK
/// button, deprecated now
[Obsolete]
MessageBoxCountdown = 2,
/// You've been invited to join a group.
GroupInvitation = 3,
/// Inventory offer
InventoryOffered = 4,
/// Accepted inventory offer
InventoryAccepted = 5,
/// Declined inventory offer
InventoryDeclined = 6,
/// Group vote
GroupVote = 7,
/// A message to everyone in the agent's group, no longer
/// used
[Obsolete]
DeprecatedGroupMessage = 8,
/// An object is offering its inventory
TaskInventoryOffered = 9,
/// Accept an inventory offer from an object
TaskInventoryAccepted = 10,
/// Decline an inventory offer from an object
TaskInventoryDeclined = 11,
/// Unknown
NewUserDefault = 12,
/// Start a session, or add users to a session
SessionAdd = 13,
/// Start a session, but don't prune offline users
SessionOfflineAdd = 14,
/// Start a session with your group
SessionGroupStart = 15,
/// Start a session without a calling card (finder or objects)
SessionCardlessStart = 16,
/// Send a message to a session
SessionSend = 17,
/// Leave a session
SessionDrop = 18,
/// Indicates that the IM is from an object
MessageFromObject = 19,
/// sent an IM to a busy user, this is the auto response
BusyAutoResponse = 20,
/// Shows the message in the console and chat history
ConsoleAndChatHistory = 21,
/// IM Types used for luring your friends
RequestTeleport = 22,
/// Response sent to the agent which inititiated a teleport invitation
AcceptTeleport = 23,
/// Response sent to the agent which inititiated a teleport invitation
DenyTeleport = 24,
/// Only useful if you have Linden permissions
GodLikeRequestTeleport = 25,
/// A placeholder type for future expansion, currently not
/// used
CurrentlyUnused = 26,
/// Notification of a new group election, this is
/// deprecated
[Obsolete]
DeprecatedGroupElection = 27,
/// IM to tell the user to go to an URL
GotoUrl = 28,
/// IM for help
Session911Start = 29,
/// IM sent automatically on call for help, sends a lure
/// to each Helper reached
Lure911 = 30,
/// Like an IM but won't go to email
FromTaskAsAlert = 31,
/// IM from a group officer to all group members
GroupNotice = 32,
/// Unknown
GroupNoticeInventoryAccepted = 33,
/// Unknown
GroupNoticeInventoryDeclined = 34,
/// Accept a group invitation
GroupInvitationAccept = 35,
/// Decline a group invitation
GroupInvitationDecline = 36,
/// Unknown
GroupNoticeRequested = 37,
/// An avatar is offering you friendship
FriendshipOffered = 38,
/// An avatar has accepted your friendship offer
FriendshipAccepted = 39,
/// An avatar has declined your friendship offer
FriendshipDeclined = 40,
/// Indicates that a user has started typing
StartTyping = 41,
/// Indicates that a user has stopped typing
StopTyping = 42
}
///
/// Flag in Instant Messages, whether the IM should be delivered to
/// offline avatars as well
///
public enum InstantMessageOnline
{
/// Only deliver to online avatars
Online = 0,
/// If the avatar is offline the message will be held until
/// they login next, and possibly forwarded to their e-mail account
Offline = 1
}
///
/// Conversion type to denote Chat Packet types in an easier-to-understand format
///
public enum ChatType : byte
{
/// Whisper (5m radius)
Whisper = 0,
/// Normal chat (10/20m radius), what the official viewer typically sends
Normal = 1,
/// Shouting! (100m radius)
Shout = 2,
/// Say chat (10/20m radius) - The official viewer will
/// print "[4:15] You say, hey" instead of "[4:15] You: hey"
[Obsolete]
Say = 3,
/// Event message when an Avatar has begun to type
StartTyping = 4,
/// Event message when an Avatar has stopped typing
StopTyping = 5,
/// Unknown
Debug = 6
}
///
/// Identifies the source of a chat message
///
public enum ChatSourceType : byte
{
/// Chat from the grid or simulator
System = 0,
/// Chat from another avatar
Agent = 1,
/// Chat from an object
Object = 2
}
///
///
///
public enum ChatAudibleLevel : sbyte
{
///
Not = -1,
///
Barely = 0,
///
Fully = 1
}
///
/// Effect type used in ViewerEffect packets
///
public enum EffectType : byte
{
/// Place floating text above an object
Text = 0,
/// Unknown, probably places an icon above an object
Icon,
/// Unknown
Connector,
/// Unknown
FlexibleObject,
/// Unknown
AnimalControls,
/// Unknown
AnimationObject,
/// Unknown
Cloth,
/// Project a beam from a source to a destination, such as
/// the one used when editing an object
Beam,
/// Not implemented yet
Glow,
/// Unknown
Point,
/// Unknown
Trail,
/// Create a swirl of particles around an object
Sphere,
/// Unknown
Spiral,
/// Unknown
Edit,
/// Cause an avatar to look at an object
LookAt,
/// Cause an avatar to point at an object
PointAt
}
///
/// The action an avatar is doing when looking at something, used in
/// ViewerEffect packets for the LookAt effect
///
public enum LookAtTarget : byte
{
///
None,
///
Idle,
///
AutoListen,
///
FreeLook,
///
Respond,
///
Hover,
/// Deprecated
Conversation,
///
Select,
///
Focus,
///
Mouselook,
///
Clear
}
///
/// The action an avatar is doing when pointing at something, used in
/// ViewerEffect packets for the PointAt effect
///
public enum PointAtType : byte
{
///
None,
///
Select,
///
Grab,
///
Clear
}
///
///
///
[Flags]
public enum TeleportFlags : uint
{
///
Default = 0,
///
SetHomeToTarget = 1 << 0,
///
SetLastToTarget = 1 << 1,
///
ViaLure = 1 << 2,
///
ViaLandmark = 1 << 3,
///
ViaLocation = 1 << 4,
///
ViaHome = 1 << 5,
///
ViaTelehub = 1 << 6,
///
ViaLogin = 1 << 7,
///
ViaGodlikeLure = 1 << 8,
///
Godlike = 1 << 9,
///
NineOneOne = 1 << 10,
///
DisableCancel = 1 << 11,
///
ViaRegionID = 1 << 12,
///
IsFlying = 1 << 13
}
///
///
///
[Flags]
public enum TeleportLureFlags
{
///
NormalLure = 0,
///
GodlikeLure = 1,
///
GodlikePursuit = 2
}
#endregion
#region Callbacks & Events
///
/// Triggered on incoming chat messages
///
/// Text of chat message
/// Audible level of this chat message
/// Type of chat (whisper, shout, status, etc.)
/// Source of the chat message
/// Name of the sending object
///
///
///
public delegate void ChatCallback(string message, ChatAudibleLevel audible, ChatType type,
ChatSourceType sourceType, string fromName, LLUUID id, LLUUID ownerid, LLVector3 position);
///
/// Triggered when a script pops up a dialog box
///
/// The dialog box message
/// Name of the object that sent the dialog
/// Image to be displayed in the dialog
/// ID of the object that sent the dialog
/// First name of the object owner
/// Last name of the object owner
/// Chat channel that the object is communicating on
/// List of button labels
public delegate void ScriptDialogCallback(string message, string objectName, LLUUID imageID,
LLUUID objectID, string firstName, string lastName, int chatChannel, List buttons);
///
/// Triggered when the L$ account balance for this avatar changes
///
/// The new account balance
public delegate void BalanceCallback(int balance);
///
/// Tiggered on incoming instant messages
///
/// Key of sender
/// Name of sender
/// Key of destination Avatar
/// ID of originating Estate
/// Key of originating Region
/// Coordinates in originating Region
///
/// Group IM session toggle
/// Key of IM Session
/// Timestamp of message
/// Text of message
/// Enum of whether this message is held for
/// offline avatars
///
public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName,
LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position,
InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message,
InstantMessageOnline offline, byte[] binaryBucket);
///
/// Triggered for any status updates of a teleport (progress, failed, succeeded)
///
/// A message about the current teleport status
/// The current status of the teleport
/// Various flags describing the teleport
public delegate void TeleportCallback(string message, TeleportStatus status, TeleportFlags flags);
///
/// Reply to a request to join a group, informs whether it was successful or not
///
/// The group we attempted to join
/// Whether we joined the group or not
public delegate void JoinGroupCallback(LLUUID groupID, bool success);
///
/// Reply to a request to leave a group, informs whether it was successful or not
///
/// The group we attempted to leave
/// Whether we left the group or not
public delegate void LeaveGroupCallback(LLUUID groupID, bool success);
///
/// Informs the avatar that it is no longer a member of a group
///
/// The group we are no longer a member of
public delegate void GroupDroppedCallback(LLUUID groupID);
/// Callback for incoming chat packets
public event ChatCallback OnChat;
/// Callback for pop-up dialogs from scripts
public event ScriptDialogCallback OnScriptDialog;
/// Callback for incoming IMs
public event InstantMessageCallback OnInstantMessage;
/// Callback for Teleport request update
public event TeleportCallback OnTeleport;
/// Callback for incoming change in L$ balance
public event BalanceCallback OnBalanceUpdated;
/// Callback reply for an attempt to join a group
public event JoinGroupCallback OnJoinGroup;
/// Callback reply for an attempt to leave a group
public event LeaveGroupCallback OnLeaveGroup;
/// Callback for informing the avatar that it is no longer a member of a group
public event GroupDroppedCallback OnGroupDropped;
#endregion
#region Public Members
/// Your (client) avatar UUID
public LLUUID ID = LLUUID.Zero;
/// Your (client) avatar ID, local to the current region/sim
public uint LocalID = 0;
/// Avatar First Name (i.e. Philip)
public string FirstName = String.Empty;
/// Avatar Last Name (i.e. Linden)
public string LastName = String.Empty;
/// Positive and negative ratings
/// This information is read-only and any changes will not be
/// reflected on the server
public Avatar.Statistics ProfileStatistics = new Avatar.Statistics();
/// Avatar properties including about text, profile URL, image IDs and
/// publishing settings
/// If you change fields in this struct, the changes will not
/// be reflected on the server until you call SetAvatarInformation
public Avatar.AvatarProperties ProfileProperties = new Avatar.AvatarProperties();
/// Avatar interests including spoken languages, skills, and "want to"
/// choices
/// If you change fields in this struct, the changes will not
/// be reflected on the server until you call SetAvatarInformation
public Avatar.Interests ProfileInterests = new Avatar.Interests();
/// Current position of avatar
public LLVector3 Position = LLVector3.Zero;
/// Current rotation of avatar
public LLQuaternion Rotation = LLQuaternion.Identity;
///
public LLVector4 CollisionPlane = LLVector4.Zero;
///
public LLVector3 Velocity = LLVector3.Zero;
///
public LLVector3 Acceleration = LLVector3.Zero;
///
public LLVector3 AngularVelocity = LLVector3.Zero;
/// The point the avatar is currently looking at
/// (may not stay updated)
public LLVector3 LookAt = LLVector3.Zero;
/// Position avatar client will goto when login to 'home' or during
/// teleport request to 'home' region.
public LLVector3 HomePosition = LLVector3.Zero;
/// LookAt point saved/restored with HomePosition
public LLVector3 HomeLookAt = LLVector3.Zero;
/// Used for camera and control key state tracking
public MainAvatarStatus Status;
/// The UUID of your root inventory folder
public LLUUID InventoryRootFolderUUID = LLUUID.Zero;
/// Gets the health of the agent
public float Health { get { return health; } }
/// Gets the current balance of the agent
public int Balance { get { return balance; } }
/// Gets the local ID of the prim the avatar is sitting on,
/// zero if the avatar is not currently sitting
public uint SittingOn { get { return sittingOn; } }
/// Gets the UUID of the active group.
public LLUUID ActiveGroup { get { return activeGroup; } }
#endregion Public Members
internal uint sittingOn = 0;
internal string teleportMessage = String.Empty;
private SecondLife Client;
private TeleportStatus TeleportStat = TeleportStatus.None;
private ManualResetEvent TeleportEvent = new ManualResetEvent(false);
private uint HeightWidthGenCounter = 0;
private float health = 0.0f;
private int balance = 0;
private LLUUID activeGroup = LLUUID.Zero;
#region AgentUpdate Constants
private const int CONTROL_AT_POS_INDEX = 0;
private const int CONTROL_AT_NEG_INDEX = 1;
private const int CONTROL_LEFT_POS_INDEX = 2;
private const int CONTROL_LEFT_NEG_INDEX = 3;
private const int CONTROL_UP_POS_INDEX = 4;
private const int CONTROL_UP_NEG_INDEX = 5;
private const int CONTROL_PITCH_POS_INDEX = 6;
private const int CONTROL_PITCH_NEG_INDEX = 7;
private const int CONTROL_YAW_POS_INDEX = 8;
private const int CONTROL_YAW_NEG_INDEX = 9;
private const int CONTROL_FAST_AT_INDEX = 10;
private const int CONTROL_FAST_LEFT_INDEX = 11;
private const int CONTROL_FAST_UP_INDEX = 12;
private const int CONTROL_FLY_INDEX = 13;
private const int CONTROL_STOP_INDEX = 14;
private const int CONTROL_FINISH_ANIM_INDEX = 15;
private const int CONTROL_STAND_UP_INDEX = 16;
private const int CONTROL_SIT_ON_GROUND_INDEX = 17;
private const int CONTROL_MOUSELOOK_INDEX = 18;
private const int CONTROL_NUDGE_AT_POS_INDEX = 19;
private const int CONTROL_NUDGE_AT_NEG_INDEX = 20;
private const int CONTROL_NUDGE_LEFT_POS_INDEX = 21;
private const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22;
private const int CONTROL_NUDGE_UP_POS_INDEX = 23;
private const int CONTROL_NUDGE_UP_NEG_INDEX = 24;
private const int CONTROL_TURN_LEFT_INDEX = 25;
private const int CONTROL_TURN_RIGHT_INDEX = 26;
private const int CONTROL_AWAY_INDEX = 27;
private const int CONTROL_LBUTTON_DOWN_INDEX = 28;
private const int CONTROL_LBUTTON_UP_INDEX = 29;
private const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30;
private const int CONTROL_ML_LBUTTON_UP_INDEX = 31;
private const int TOTAL_CONTROLS = 32;
#endregion AgentUpdate Constants
///
/// Constructor, setup callbacks for packets related to our avatar
///
///
public MainAvatar(SecondLife client)
{
Client = client;
Status = new MainAvatarStatus(Client);
NetworkManager.PacketCallback callback;
// Coarse location callback
Client.Network.RegisterCallback(PacketType.CoarseLocationUpdate, new NetworkManager.PacketCallback(CoarseLocationHandler));
// Teleport callbacks
callback = new NetworkManager.PacketCallback(TeleportHandler);
Client.Network.RegisterCallback(PacketType.TeleportStart, callback);
Client.Network.RegisterCallback(PacketType.TeleportProgress, callback);
Client.Network.RegisterCallback(PacketType.TeleportFailed, callback);
Client.Network.RegisterCallback(PacketType.TeleportFinish, callback);
Client.Network.RegisterCallback(PacketType.TeleportCancel, callback);
Client.Network.RegisterCallback(PacketType.TeleportLocal, callback);
// Instant Message callback
Client.Network.RegisterCallback(PacketType.ImprovedInstantMessage, new NetworkManager.PacketCallback(InstantMessageHandler));
// Chat callback
Client.Network.RegisterCallback(PacketType.ChatFromSimulator, new NetworkManager.PacketCallback(ChatHandler));
// Script dialog callback
Client.Network.RegisterCallback(PacketType.ScriptDialog, new NetworkManager.PacketCallback(ScriptDialogHandler));
// Movement complete callback
Client.Network.RegisterCallback(PacketType.AgentMovementComplete, new NetworkManager.PacketCallback(MovementCompleteHandler));
// Health callback
Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler));
// Money callbacks
callback = new NetworkManager.PacketCallback(BalanceHandler);
Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, callback);
Client.Network.RegisterCallback(PacketType.MoneySummaryReply, callback);
Client.Network.RegisterCallback(PacketType.AdjustBalance, callback);
// Group callbacks
Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupHandler));
Client.Network.RegisterCallback(PacketType.LeaveGroupReply, new NetworkManager.PacketCallback(LeaveGroupHandler));
Client.Network.RegisterCallback(PacketType.AgentDropGroup, new NetworkManager.PacketCallback(DropGroupHandler));
//Agent Update Callback
Client.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(AgentDataUpdateHandler));
// Event queue callback (used for Caps teleports currently)
Client.Network.RegisterEventCallback(new Caps.EventQueueCallback(EventQueueHandler));
}
///
/// Send an Instant Message
///
/// Target of the Instant Message
/// Text message being sent
public void InstantMessage(LLUUID target, string message)
{
InstantMessage(FirstName + " " + LastName, target, message, LLUUID.Random(),
InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position,
LLUUID.Zero, new byte[0]);
}
///
/// Send an Instant Message
///
/// Target of the Instant Message
/// Text message being sent
/// IM session ID (to differentiate between IM windows)
public void InstantMessage(LLUUID target, string message, LLUUID imSessionID)
{
InstantMessage(FirstName + " " + LastName, target, message, imSessionID,
InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position,
LLUUID.Zero, new byte[0]);
}
///
/// Send an Instant Message
///
/// The name this IM will show up as being from
/// Key of Avatar
/// Text message being sent
/// IM session ID (to differentiate between IM windows)
///
public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID,
LLUUID[] conferenceIDs)
{
byte[] binaryBucket;
if (conferenceIDs != null && conferenceIDs.Length > 0)
{
binaryBucket = new byte[16 * conferenceIDs.Length];
for (int i = 0; i < conferenceIDs.Length; ++i)
Array.Copy(conferenceIDs[i].Data, 0, binaryBucket, i * 16, 16);
}
else
{
binaryBucket = new byte[0];
}
InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent,
InstantMessageOnline.Offline, LLVector3.Zero, LLUUID.Zero, binaryBucket);
}
///
/// Send an Instant Message
///
/// The name this IM will show up as being from
/// Key of Avatar
/// Text message being sent
/// IM session ID (to differentiate between IM windows)
/// Type of instant message to send
/// Whether to IM offline avatars as well
///
///
/// Packed binary data that is specific to
/// the dialog type
public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID,
InstantMessageDialog dialog, InstantMessageOnline offline, LLVector3 position, LLUUID regionID,
byte[] binaryBucket)
{
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
im.AgentData.AgentID = Client.Network.AgentID;
im.AgentData.SessionID = Client.Network.SessionID;
im.MessageBlock.Dialog = (byte)dialog;
im.MessageBlock.FromAgentName = Helpers.StringToField(fromName);
im.MessageBlock.FromGroup = false;
im.MessageBlock.ID = imSessionID;
im.MessageBlock.Message = Helpers.StringToField(message);
im.MessageBlock.Offline = (byte)offline;
im.MessageBlock.ToAgentID = target;
if (binaryBucket != null)
im.MessageBlock.BinaryBucket = binaryBucket;
else
im.MessageBlock.BinaryBucket = new byte[0];
// These fields are mandatory, even if we don't have valid values for them
im.MessageBlock.Position = LLVector3.Zero;
//TODO: Allow region id to be correctly set by caller or fetched from Client.*
im.MessageBlock.RegionID = regionID;
// Send the message
Client.Network.SendPacket(im);
}
///
/// Send an Instant Message to a group
///
/// Key of Group
/// Text Message being sent.
public void InstantMessageGroup(LLUUID groupUUID, string message)
{
InstantMessageGroup(FirstName + " " + LastName, groupUUID, message);
}
///
/// Send an Instant Message to a group
///
/// The name this IM will show up as being from
/// Key of the group
/// Text message being sent
/// This does not appear to function with groups the agent is not in
public void InstantMessageGroup(string fromName, LLUUID groupUUID, string message)
{
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
im.AgentData.AgentID = Client.Network.AgentID;
im.AgentData.SessionID = Client.Network.SessionID;
im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.SessionSend;
im.MessageBlock.FromAgentName = Helpers.StringToField(fromName);
im.MessageBlock.FromGroup = false;
im.MessageBlock.Message = Helpers.StringToField(message);
im.MessageBlock.Offline = 0;
im.MessageBlock.ID = groupUUID;
im.MessageBlock.ToAgentID = groupUUID;
im.MessageBlock.BinaryBucket = new byte[0];
im.MessageBlock.Position = LLVector3.Zero;
im.MessageBlock.RegionID = LLUUID.Zero;
// Send the message
Client.Network.SendPacket(im);
}
///
///
///
///
///
///
///
public void PointAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, PointAtType type)
{
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = Client.Network.AgentID;
effect.AgentData.SessionID = Client.Network.SessionID;
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
effect.Effect[0].AgentID = Client.Network.AgentID;
effect.Effect[0].Color = LLColor.Black.GetBytes();
effect.Effect[0].Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f;
effect.Effect[0].ID = LLUUID.Random();
effect.Effect[0].Type = (byte)EffectType.PointAt;
byte[] typeData = new byte[57];
if (sourceAvatar != null)
Array.Copy(sourceAvatar.GetBytes(), typeData, 16);
if (targetObject != null)
Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16);
Array.Copy(globalOffset.GetBytes(), 0, typeData, 32, 24);
typeData[56] = (byte)type;
effect.Effect[0].TypeData = typeData;
Client.Network.SendPacket(effect);
}
///
///
///
///
///
///
///
public void LookAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LookAtTarget type)
{
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = Client.Network.AgentID;
effect.AgentData.SessionID = Client.Network.SessionID;
float duration;
switch (type)
{
case LookAtTarget.Clear:
duration = 0.0f;
break;
case LookAtTarget.Hover:
duration = 1.0f;
break;
case LookAtTarget.FreeLook:
duration = 2.0f;
break;
case LookAtTarget.Idle:
duration = 3.0f;
break;
case LookAtTarget.AutoListen:
case LookAtTarget.Respond:
duration = 4.0f;
break;
case LookAtTarget.None:
case LookAtTarget.Conversation:
case LookAtTarget.Select:
case LookAtTarget.Focus:
case LookAtTarget.Mouselook:
duration = Single.MaxValue / 2.0f;
break;
default:
duration = 0.0f;
break;
}
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
effect.Effect[0].AgentID = Client.Network.AgentID;
effect.Effect[0].Color = LLColor.Black.GetBytes();
effect.Effect[0].Duration = duration;
effect.Effect[0].ID = LLUUID.Random();
effect.Effect[0].Type = (byte)EffectType.LookAt;
byte[] typeData = new byte[57];
if (sourceAvatar != null)
Array.Copy(sourceAvatar.GetBytes(), typeData, 16);
if (targetObject != null)
Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16);
typeData[56] = (byte)type;
effect.Effect[0].TypeData = typeData;
Client.Network.SendPacket(effect);
}
///
///
///
///
///
///
///
///
public void BeamEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LLColor color,
float duration)
{
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = Client.Network.AgentID;
effect.AgentData.SessionID = Client.Network.SessionID;
effect.Effect = new ViewerEffectPacket.EffectBlock[1];
effect.Effect[0] = new ViewerEffectPacket.EffectBlock();
effect.Effect[0].AgentID = Client.Network.AgentID;
effect.Effect[0].Color = color.GetBytes();
effect.Effect[0].Duration = duration;
effect.Effect[0].ID = LLUUID.Random();
effect.Effect[0].Type = (byte)EffectType.Beam;
byte[] typeData = new byte[56];
if (sourceAvatar != null)
Array.Copy(sourceAvatar.GetBytes(), 0, typeData, 0, 16);
if (targetObject != null)
Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16);
Array.Copy(globalOffset.GetBytes(), 0, typeData, 32, 24);
effect.Effect[0].TypeData = typeData;
Client.Network.SendPacket(effect);
}
///
/// Synchronize the local profile and interests information to the server
///
public void SetAvatarInformation()
{
// Basic profile properties
AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket();
apup.AgentData.AgentID = this.ID;
apup.AgentData.SessionID = Client.Network.SessionID;
apup.PropertiesData.AboutText = Helpers.StringToField(this.ProfileProperties.AboutText);
apup.PropertiesData.AllowPublish = this.ProfileProperties.AllowPublish;
apup.PropertiesData.FLAboutText = Helpers.StringToField(this.ProfileProperties.FirstLifeText);
apup.PropertiesData.FLImageID = this.ProfileProperties.FirstLifeImage;
apup.PropertiesData.ImageID = this.ProfileProperties.ProfileImage;
apup.PropertiesData.MaturePublish = this.ProfileProperties.MaturePublish;
apup.PropertiesData.ProfileURL = Helpers.StringToField(this.ProfileProperties.ProfileURL);
// Interests
AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket();
aiup.AgentData.AgentID = this.ID;
aiup.AgentData.SessionID = Client.Network.SessionID;
aiup.PropertiesData.LanguagesText = Helpers.StringToField(this.ProfileInterests.LanguagesText);
aiup.PropertiesData.SkillsMask = this.ProfileInterests.SkillsMask;
aiup.PropertiesData.SkillsText = Helpers.StringToField(this.ProfileInterests.SkillsText);
aiup.PropertiesData.WantToMask = this.ProfileInterests.WantToMask;
aiup.PropertiesData.WantToText = Helpers.StringToField(this.ProfileInterests.WantToText);
//Send packets
Client.Network.SendPacket(apup);
Client.Network.SendPacket(aiup);
}
///
/// Send a chat message
///
/// The Message you're sending out.
/// Channel number (0 would be default 'Say' message, other numbers
/// denote the equivalent of /# in normal client).
/// Chat Type, see above.
public void Chat(string message, int channel, ChatType type)
{
ChatFromViewerPacket chat = new ChatFromViewerPacket();
chat.AgentData.AgentID = this.ID;
chat.AgentData.SessionID = Client.Network.SessionID;
chat.ChatData.Channel = channel;
chat.ChatData.Message = Helpers.StringToField(message);
chat.ChatData.Type = (byte)type;
Client.Network.SendPacket(chat);
}
///
/// Set the height and the width of the client window. This is used
/// by the server to build a virtual camera frustum for our avatar
///
/// New height of the viewer window
/// New width of the viewer window
public void SetHeightWidth(ushort height, ushort width)
{
AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket();
heightwidth.AgentData.AgentID = Client.Network.AgentID;
heightwidth.AgentData.SessionID = Client.Network.SessionID;
heightwidth.AgentData.CircuitCode = Client.Network.CircuitCode;
heightwidth.HeightWidthBlock.Height = height;
heightwidth.HeightWidthBlock.Width = width;
heightwidth.HeightWidthBlock.GenCounter = HeightWidthGenCounter++;
Client.Network.SendPacket(heightwidth);
}
///
/// Sends a request to sit on the specified object
///
/// LLUUID of the object to sit on
/// Sit at offset
public void RequestSit(LLUUID targetID, LLVector3 offset)
{
AgentRequestSitPacket requestSit = new AgentRequestSitPacket();
requestSit.AgentData.AgentID = Client.Network.AgentID;
requestSit.AgentData.SessionID = Client.Network.SessionID;
requestSit.TargetObject.TargetID = targetID;
requestSit.TargetObject.Offset = offset;
Client.Network.SendPacket(requestSit);
}
///
/// Request the list of muted things for this avatar
///
public void RequestMuteList()
{
MuteListRequestPacket mute = new MuteListRequestPacket();
mute.AgentData.AgentID = Client.Network.AgentID;
mute.AgentData.SessionID = Client.Network.SessionID;
mute.MuteData.MuteCRC = 0;
Client.Network.SendPacket(mute);
}
///
/// Request the current L$ balance
///
public void RequestBalance()
{
MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket();
money.AgentData.AgentID = Client.Network.AgentID;
money.AgentData.SessionID = Client.Network.SessionID;
money.MoneyData.TransactionID = LLUUID.Zero;
Client.Network.SendPacket(money);
}
///
/// Follows a call to RequestSit() to actually sit on the object
///
public void Sit()
{
AgentSitPacket sit = new AgentSitPacket();
sit.AgentData.AgentID = Client.Network.AgentID;
sit.AgentData.SessionID = Client.Network.SessionID;
Client.Network.SendPacket(sit);
}
///
/// Give Money to destination Avatar
///
/// UUID of the Target Avatar
/// Amount in L$
/// Reason (optional normally)
public void GiveMoney(LLUUID target, int amount, string description)
{
// 5001 - transaction type for av to av money transfers
if (amount > 0)
GiveMoney(target, amount, description, 5001);
else
Client.Log("Attempted to pay zero or negative value " + amount, Helpers.LogLevel.Warning);
}
///
/// Give Money to destionation Object or Avatar
///
/// UUID of the Target Object/Avatar
/// Amount in L$
/// Reason (Optional normally)
/// The type of transaction. Currently only 5001 is
/// documented for Av->Av money transfers.
public void GiveMoney(LLUUID target, int amount, string description, int transactiontype)
{
MoneyTransferRequestPacket money = new MoneyTransferRequestPacket();
money.AgentData.AgentID = this.ID;
money.AgentData.SessionID = Client.Network.SessionID;
money.MoneyData.Description = Helpers.StringToField(description);
money.MoneyData.DestID = target;
money.MoneyData.SourceID = this.ID;
money.MoneyData.TransactionType = transactiontype;
money.MoneyData.AggregatePermInventory = 0; //TODO: whats this?
money.MoneyData.AggregatePermNextOwner = 0; //TODO: whats this?
money.MoneyData.Flags = 0; //TODO: whats this?
money.MoneyData.Amount = amount;
Client.Network.SendPacket(money);
}
///
/// Send an AgentAnimation packet that toggles a single animation on
///
/// The animation to start playing
public void AnimationStart(LLUUID animation)
{
Dictionary animations = new Dictionary();
animations[animation] = true;
Animate(animations);
}
///
/// Send an AgentAnimation packet that toggles a single animation off
///
/// The animation to stop playing
public void AnimationStop(LLUUID animation)
{
Dictionary animations = new Dictionary();
animations[animation] = false;
Animate(animations);
}
///
/// Send an AgentAnimation packet that will toggle animations on or off
///
/// A list of animation UUIDs, and whether to
/// turn that animation on or off
public void Animate(Dictionary animations)
{
AgentAnimationPacket animate = new AgentAnimationPacket();
animate.AgentData.AgentID = Client.Network.AgentID;
animate.AgentData.SessionID = Client.Network.SessionID;
animate.AnimationList = new AgentAnimationPacket.AnimationListBlock[animations.Count];
int i = 0;
foreach (KeyValuePair animation in animations)
{
animate.AnimationList[i] = new AgentAnimationPacket.AnimationListBlock();
animate.AnimationList[i].AnimID = animation.Key;
animate.AnimationList[i].StartAnim = animation.Value;
i++;
}
Client.Network.SendPacket(animate);
}
///
/// Use the autopilot sim function to move the avatar to a new position
///
/// The z value is currently not handled properly by the simulator
/// Integer value for the global X coordinate to move to
/// Integer value for the global Y coordinate to move to
/// Floating-point value for the Z coordinate to move to
/// AutoPilot(252620, 247078, 20.2674);
public void AutoPilot(ulong globalX, ulong globalY, float z)
{
GenericMessagePacket autopilot = new GenericMessagePacket();
autopilot.AgentData.AgentID = Client.Network.AgentID;
autopilot.AgentData.SessionID = Client.Network.SessionID;
autopilot.AgentData.TransactionID = LLUUID.Zero;
autopilot.MethodData.Invoice = LLUUID.Zero;
autopilot.MethodData.Method = Helpers.StringToField("autopilot");
autopilot.ParamList = new GenericMessagePacket.ParamListBlock[3];
autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[0].Parameter = Helpers.StringToField(globalX.ToString());
autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock();
autopilot.ParamList[1].Parameter = Helpers.StringToField(globalY.ToString());
autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock();
// TODO: Do we need to prevent z coordinates from being sent in 1.4827e-18 notation?
autopilot.ParamList[2].Parameter = Helpers.StringToField(z.ToString());
Client.Network.SendPacket(autopilot);
}
///
/// Use the autopilot sim function to move the avatar to a new position
///
/// The z value is currently not handled properly by the simulator
/// Integer value for the local X coordinate to move to
/// Integer value for the local Y coordinate to move to
/// Floating-point value for the Z coordinate to move to
public void AutoPilotLocal(int localX, int localY, float z)
{
uint x, y;
Helpers.LongToUInts(Client.Network.CurrentSim.Handle, out x, out y);
AutoPilot((ulong)(x + localX), (ulong)(y + localY), z);
}
///
/// Attempt to look up a simulator name and teleport to the discovered
/// destination
///
/// Region name to look up
/// Position to teleport to
/// True if the lookup and teleport were successful, otherwise
/// false
public bool Teleport(string simName, LLVector3 position)
{
return Teleport(simName, position, new LLVector3(0, 1.0f, 0));
}
///
/// Attempt to look up a simulator name and teleport to the discovered
/// destination
///
/// Region name to look up
/// Position to teleport to
/// Target to look at
/// True if the lookup and teleport were successful, otherwise
/// false
public bool Teleport(string simName, LLVector3 position, LLVector3 lookAt)
{
TeleportStat = TeleportStatus.None;
simName = simName.ToLower();
if (simName != Client.Network.CurrentSim.Name.ToLower())
{
// Teleporting to a foreign sim
GridRegion region = Client.Grid.GetGridRegion(simName);
if (region != null)
{
return Teleport(region.RegionHandle, position, lookAt);
}
else
{
teleportMessage = "Unable to resolve name: " + simName;
TeleportStat = TeleportStatus.Failed;
return false;
}
}
else
{
// Teleporting to the sim we're already in
return Teleport(Client.Network.CurrentSim.Handle, position, lookAt);
}
}
///
/// Start a teleport process
///
///
/// Position for Teleport
///
public bool Teleport(ulong regionHandle, LLVector3 position)
{
return Teleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f));
}
///
/// Start a teleport process
///
///
/// Position for Teleport
/// Target to look at
///
public bool Teleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt)
{
TeleportStat = TeleportStatus.None;
TeleportEvent.Reset();
RequestTeleport(regionHandle, position, lookAt);
TeleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false);
if (TeleportStat == TeleportStatus.None ||
TeleportStat == TeleportStatus.Start ||
TeleportStat == TeleportStatus.Progress)
{
teleportMessage = "Teleport timed out.";
TeleportStat = TeleportStatus.Failed;
}
return (TeleportStat == TeleportStatus.Finished);
}
///
/// Start a teleport process
///
///
/// Position for Teleport
public void RequestTeleport(ulong regionHandle, LLVector3 position)
{
RequestTeleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f));
}
///
/// Start a teleport process
///
///
/// Position for Teleport
/// Target to look at
public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt)
{
TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket();
teleport.AgentData.AgentID = Client.Network.AgentID;
teleport.AgentData.SessionID = Client.Network.SessionID;
teleport.Info.LookAt = lookAt;
teleport.Info.Position = position;
teleport.Info.RegionHandle = regionHandle;
Client.Log("Teleporting to region " + regionHandle.ToString(), Helpers.LogLevel.Info);
Client.Network.SendPacket(teleport);
}
///
/// Respond to a teleport lure by either accepting it and initiating
/// the teleport, or denying it
///
/// UUID of the avatar requesting the teleport
/// Accept the teleport request or deny it
public void TeleportLureRespond(LLUUID requesterID, bool accept)
{
InstantMessage(FirstName + " " + LastName, requesterID, String.Empty, LLUUID.Random(),
accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport,
InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]);
if (accept)
{
TeleportLureRequestPacket lure = new TeleportLureRequestPacket();
lure.Info.AgentID = Client.Network.AgentID;
lure.Info.SessionID = Client.Network.SessionID;
lure.Info.LureID = Client.Network.AgentID;
lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure;
Client.Network.SendPacket(lure);
}
}
///
/// Grabs an object
///
/// Local ID of Object to grab
public void Grab(uint objectLocalID)
{
ObjectGrabPacket grab = new ObjectGrabPacket();
grab.AgentData.AgentID = Client.Network.AgentID;
grab.AgentData.SessionID = Client.Network.SessionID;
grab.ObjectData.LocalID = objectLocalID;
grab.ObjectData.GrabOffset = new LLVector3(0, 0, 0);
Client.Network.SendPacket(grab);
}
///
/// Drags on an object
///
/// Strangely, LLUID instead of local ID
/// Drag target in region coordinates
public void GrabUpdate(LLUUID objectID, LLVector3 grabPosition)
{
ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket();
grab.AgentData.AgentID = Client.Network.AgentID;
grab.AgentData.SessionID = Client.Network.SessionID;
grab.ObjectData.ObjectID = objectID;
grab.ObjectData.GrabOffsetInitial = new LLVector3(0, 0, 0);
grab.ObjectData.GrabPosition = grabPosition;
grab.ObjectData.TimeSinceLast = 0;
Client.Network.SendPacket(grab);
}
///
/// Releases a grabbed object
///
public void DeGrab(uint objectLocalID)
{
ObjectDeGrabPacket degrab = new ObjectDeGrabPacket();
degrab.AgentData.AgentID = Client.Network.AgentID;
degrab.AgentData.SessionID = Client.Network.SessionID;
degrab.ObjectData.LocalID = objectLocalID;
Client.Network.SendPacket(degrab);
}
///
/// Touches an object
///
public void Touch(uint objectLocalID)
{
Client.Self.Grab(objectLocalID);
Client.Self.DeGrab(objectLocalID);
}
///
/// Request to join a group. If there is an enrollment fee it will
/// automatically be deducted from your balance
///
/// The group to attempt to join
public void RequestJoinGroup(LLUUID groupID)
{
JoinGroupRequestPacket join = new JoinGroupRequestPacket();
join.AgentData.AgentID = Client.Network.AgentID;
join.AgentData.SessionID = Client.Network.SessionID;
join.GroupData.GroupID = groupID;
Client.Network.SendPacket(join);
}
///
/// Request to leave a group
///
/// The group to attempt to leave
public void RequestLeaveGroup(LLUUID groupID)
{
LeaveGroupRequestPacket leave = new LeaveGroupRequestPacket();
leave.AgentData.AgentID = Client.Network.AgentID;
leave.AgentData.SessionID = Client.Network.SessionID;
leave.GroupData.GroupID = groupID;
Client.Network.SendPacket(leave);
}
///
/// Set our current active group
///
/// The group we are a member of that we want to
/// activate
public void ActivateGroup(LLUUID groupID)
{
ActivateGroupPacket activate = new ActivateGroupPacket();
activate.AgentData.AgentID = Client.Network.AgentID;
activate.AgentData.SessionID = Client.Network.SessionID;
activate.AgentData.GroupID = groupID;
Client.Network.SendPacket(activate);
}
///
/// Move an agent in to a simulator. This packet is the last packet
/// needed to complete the transition in to a new simulator
///
///
public void CompleteAgentMovement(Simulator simulator)
{
CompleteAgentMovementPacket move = new CompleteAgentMovementPacket();
move.AgentData.AgentID = Client.Network.AgentID;
move.AgentData.SessionID = Client.Network.SessionID;
move.AgentData.CircuitCode = Client.Network.CircuitCode;
Client.Network.SendPacket(move, simulator);
}
///
/// Sends camera and action updates to the server including the
/// position and orientation of our camera, and a ControlFlags field
/// specifying our current movement actions
///
///
///
///
///
///
///
///
///
///
public void UpdateCamera(MainAvatar.AgentUpdateFlags controlFlags, LLVector3 position, LLVector3 forwardAxis,
LLVector3 leftAxis, LLVector3 upAxis, LLQuaternion bodyRotation, LLQuaternion headRotation, float farClip,
bool reliable)
{
AgentUpdatePacket update = new AgentUpdatePacket();
update.AgentData.AgentID = Client.Network.AgentID;
update.AgentData.SessionID = Client.Network.SessionID;
update.AgentData.State = 0;
update.AgentData.BodyRotation = bodyRotation;
update.AgentData.HeadRotation = headRotation;
update.AgentData.CameraCenter = position;
update.AgentData.CameraAtAxis = forwardAxis;
update.AgentData.CameraLeftAxis = leftAxis;
update.AgentData.CameraUpAxis = upAxis;
update.AgentData.Far = farClip;
update.AgentData.ControlFlags = (uint)controlFlags;
update.AgentData.Flags = 0;
update.Header.Reliable = reliable;
Client.Network.SendPacket(update);
}
///
/// [UNUSED - for now]
///
///
///
private void CoarseLocationHandler(Packet packet, Simulator simulator)
{
if (packet.Type == PacketType.CoarseLocationUpdate)
{
CoarseLocationUpdatePacket p = (CoarseLocationUpdatePacket) packet;
if (p.Index.You < 0 || p.Index.You >= p.Location.Length) return;
/* 1.5 and 6 represent a 50% fudge factor (hysteresis) -- bushing */
if (Math.Abs(Position.X-p.Location[p.Index.You].X) > 1.5) Position.X=p.Location[p.Index.You].X;
if (Math.Abs(Position.Y-p.Location[p.Index.You].Y) > 1.5) Position.Y=p.Location[p.Index.You].Y;
if (Math.Abs(Position.Z-(p.Location[p.Index.You].Z*4)) > 6) Position.Z=p.Location[p.Index.You].Z*4;
}
}
///
/// Take an incoming ImprovedInstantMessage packet, auto-parse, and if
/// OnInstantMessage is defined call that with the appropriate arguments.
///
/// Incoming ImprovedInstantMessagePacket
/// Unused
private void InstantMessageHandler(Packet packet, Simulator simulator)
{
if (packet.Type == PacketType.ImprovedInstantMessage)
{
ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)packet;
if (OnInstantMessage != null)
{
OnInstantMessage(
im.AgentData.AgentID
, Helpers.FieldToUTF8String(im.MessageBlock.FromAgentName),
im.MessageBlock.ToAgentID
, im.MessageBlock.ParentEstateID
, im.MessageBlock.RegionID
, im.MessageBlock.Position
, (InstantMessageDialog)im.MessageBlock.Dialog
, im.MessageBlock.FromGroup
, im.MessageBlock.ID
, new DateTime(im.MessageBlock.Timestamp)
, Helpers.FieldToUTF8String(im.MessageBlock.Message)
, (InstantMessageOnline)im.MessageBlock.Offline
, im.MessageBlock.BinaryBucket
);
}
}
}
///
/// Take an incoming Chat packet, auto-parse, and if OnChat is defined call
/// that with the appropriate arguments.
///
/// Incoming ChatFromSimulatorPacket
/// Unused
private void ChatHandler(Packet packet, Simulator simulator)
{
if (OnChat != null)
{
ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet;
OnChat(Helpers.FieldToUTF8String(chat.ChatData.Message)
, (ChatAudibleLevel)chat.ChatData.Audible
, (ChatType)chat.ChatData.ChatType
, (ChatSourceType)chat.ChatData.SourceType
, Helpers.FieldToUTF8String(chat.ChatData.FromName)
, chat.ChatData.SourceID
, chat.ChatData.OwnerID
, chat.ChatData.Position
);
}
}
///
/// Used for parsing llDialog's
///
/// Incoming ScriptDialog packet
/// Unused
private void ScriptDialogHandler(Packet packet, Simulator simulator)
{
if (OnScriptDialog != null)
{
ScriptDialogPacket dialog = (ScriptDialogPacket)packet;
List buttons = new List();
foreach (ScriptDialogPacket.ButtonsBlock button in dialog.Buttons)
{
buttons.Add(Helpers.FieldToString(button.ButtonLabel));
}
OnScriptDialog(Helpers.FieldToUTF8String(dialog.Data.Message),
Helpers.FieldToString(dialog.Data.ObjectName),
dialog.Data.ImageID,
dialog.Data.ObjectID,
Helpers.FieldToUTF8String(dialog.Data.FirstName),
Helpers.FieldToUTF8String(dialog.Data.LastName),
dialog.Data.ChatChannel,
buttons);
}
}
///
/// Update client's Position, LookAt and region handle from incoming packet
///
/// Incoming AgentMovementCompletePacket
/// Unused
private void MovementCompleteHandler(Packet packet, Simulator simulator)
{
AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet;
this.Position = movement.Data.Position;
this.LookAt = movement.Data.LookAt;
simulator.Handle = movement.Data.RegionHandle;
}
///
/// Update Client Avatar's health via incoming packet
///
/// Incoming HealthMessagePacket
/// Unused
private void HealthHandler(Packet packet, Simulator simulator)
{
health = ((HealthMessagePacket)packet).HealthData.Health;
}
private void JoinGroupHandler(Packet packet, Simulator simulator)
{
if (OnJoinGroup != null)
{
JoinGroupReplyPacket reply = (JoinGroupReplyPacket)packet;
OnJoinGroup(reply.GroupData.GroupID, reply.GroupData.Success);
}
}
private void LeaveGroupHandler(Packet packet, Simulator simulator)
{
if (OnLeaveGroup != null)
{
LeaveGroupReplyPacket reply = (LeaveGroupReplyPacket)packet;
OnLeaveGroup(reply.GroupData.GroupID, reply.GroupData.Success);
}
}
public void AgentDataUpdateHandler(Packet packet, Simulator simulator)
{
AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet;
if (p.AgentData.AgentID == simulator.Client.Network.AgentID) {
activeGroup = p.AgentData.ActiveGroupID;
}
}
private void DropGroupHandler(Packet packet, Simulator simulator)
{
if (OnGroupDropped != null)
{
OnGroupDropped(((AgentDropGroupPacket)packet).AgentData.GroupID);
}
}
///
/// Update Client Avatar's L$ balance from incoming packet
///
/// Incoming MoneyBalanceReplyPacket
/// Unused
private void BalanceHandler(Packet packet, Simulator simulator)
{
if (packet.Type == PacketType.MoneyBalanceReply)
{
balance = ((MoneyBalanceReplyPacket)packet).MoneyData.MoneyBalance;
}
else if (packet.Type == PacketType.MoneySummaryReply)
{
balance = ((MoneySummaryReplyPacket)packet).MoneyData.Balance;
}
else if (packet.Type == PacketType.AdjustBalance)
{
balance += ((AdjustBalancePacket)packet).AgentData.Delta;
}
if (OnBalanceUpdated != null)
{
OnBalanceUpdated(balance);
}
}
private void EventQueueHandler(string message, object body)
{
if (message == "TeleportFinish")
{
Hashtable tpt = (Hashtable)body;
Hashtable info = (Hashtable)tpt["Info"];
// Backwards compatibility hack
TeleportFinishPacket packet = new TeleportFinishPacket();
packet.Info.SimIP = Helpers.BytesToUIntBig((byte[])info["SimIP"]);
packet.Info.LocationID = Helpers.BytesToUInt((byte[])info["LocationID"]);
packet.Info.TeleportFlags = Helpers.BytesToUInt((byte[])info["TeleportFlags"]);
packet.Info.AgentID = (LLUUID)info["AgentID"];
packet.Info.RegionHandle = Helpers.BytesToUInt64((byte[])info["RegionHandle"]);
packet.Info.SeedCapability = Helpers.StringToField((string)info["SeedCapability"]);
packet.Info.SimPort = (ushort)(long)info["SimPort"];
packet.Info.SimAccess = (byte)(long)info["SimAccess"];
Client.DebugLog("Received a TeleportFinish event, SimIP: " + new IPAddress(packet.Info.SimIP) +
", LocationID: " + packet.Info.LocationID + ", RegionHandle: " + packet.Info.RegionHandle);
TeleportHandler(packet, Client.Network.CurrentSim);
}
else
{
Client.Log("Received unhandled event " + message + " in the EventQueueHandler",
Helpers.LogLevel.Warning);
}
}
///
/// Handler for teleport Requests
///
/// Incoming TeleportHandler packet
/// Simulator sending teleport information
private void TeleportHandler(Packet packet, Simulator simulator)
{
bool finished = false;
TeleportFlags flags = TeleportFlags.Default;
if (packet.Type == PacketType.TeleportStart)
{
TeleportStartPacket start = (TeleportStartPacket)packet;
teleportMessage = "Teleport started";
flags = (TeleportFlags)start.Info.TeleportFlags;
TeleportStat = TeleportStatus.Start;
Client.DebugLog("TeleportStart received from " + simulator.ToString() + ", Flags: " + flags.ToString());
}
else if (packet.Type == PacketType.TeleportProgress)
{
TeleportProgressPacket progress = (TeleportProgressPacket)packet;
teleportMessage = Helpers.FieldToUTF8String(progress.Info.Message);
flags = (TeleportFlags)progress.Info.TeleportFlags;
TeleportStat = TeleportStatus.Progress;
Client.DebugLog("TeleportProgress received from " + simulator.ToString() + ", Flags: " + flags.ToString());
}
else if (packet.Type == PacketType.TeleportFailed)
{
TeleportFailedPacket failed = (TeleportFailedPacket)packet;
teleportMessage = Helpers.FieldToUTF8String(failed.Info.Reason);
TeleportStat = TeleportStatus.Failed;
finished = true;
Client.DebugLog("TeleportFailed received from " + simulator.ToString() + ", Reason: " + teleportMessage);
}
else if (packet.Type == PacketType.TeleportFinish)
{
TeleportFinishPacket finish = (TeleportFinishPacket)packet;
Simulator previousSim = Client.Network.CurrentSim;
flags = (TeleportFlags)finish.Info.TeleportFlags;
string seedcaps = Helpers.FieldToUTF8String(finish.Info.SeedCapability);
IPAddress simIP = new IPAddress(finish.Info.SimIP);
finished = true;
Client.DebugLog("TeleportFinish received from " + simulator.ToString() + ", Flags: " + flags.ToString());
// Disable CAPS on the current sim since we are moving
if (Client.Network.CurrentCaps != null) Client.Network.CurrentCaps.Dead = true;
// Connect to the new sim
Simulator sim = Client.Network.Connect(simIP, finish.Info.SimPort, true,
seedcaps);
if (sim != null)
{
teleportMessage = "Teleport finished";
TeleportStat = TeleportStatus.Finished;
// Disconnect from the previous sim
Client.Network.DisconnectSim(previousSim);
Client.Log("Moved to new sim " + sim.ToString(), Helpers.LogLevel.Info);
}
else
{
teleportMessage = "Failed to connect to the new sim after a teleport";
TeleportStat = TeleportStatus.Failed;
// Attempt to reconnect to the previous simulator
// TODO: This hasn't been tested at all
Client.Network.Connect(previousSim.IPEndPoint.Address, (ushort)previousSim.IPEndPoint.Port,
true, Client.Network.CurrentCaps.Seedcaps);
Client.Log(teleportMessage, Helpers.LogLevel.Warning);
}
}
else if (packet.Type == PacketType.TeleportCancel)
{
//TeleportCancelPacket cancel = (TeleportCancelPacket)packet;
teleportMessage = "Cancelled.";
TeleportStat = TeleportStatus.Cancelled;
finished = true;
Client.DebugLog("TeleportCancel received from " + simulator.ToString());
}
else if (packet.Type == PacketType.TeleportLocal)
{
TeleportLocalPacket local = (TeleportLocalPacket)packet;
teleportMessage = "Teleport finished";
flags = (TeleportFlags)local.Info.TeleportFlags;
TeleportStat = TeleportStatus.Finished;
LookAt = local.Info.LookAt;
Position = local.Info.Position;
// This field is apparently not used for anything
//local.Info.LocationID;
finished = true;
Client.DebugLog("TeleportLocal received from " + simulator.ToString() + ", Flags: " + flags.ToString());
}
if (OnTeleport != null)
{
try { OnTeleport(teleportMessage, TeleportStat, flags); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
if (finished) TeleportEvent.Set();
}
}
}