diff --git a/SLProxy/ChatConsole.cs b/SLProxy/ChatConsole.cs index 55fa76c2..b4af315b 100644 --- a/SLProxy/ChatConsole.cs +++ b/SLProxy/ChatConsole.cs @@ -1,83 +1,81 @@ -/* - * ChatConsole.cs: sample SLProxy appliation that writes chat to the console. - * Typing on the console will send chat to Second Life. - * - * Copyright (c) 2006 Austin Jennings - * Modified by Andrew Ortman ("qode") on Decemeber 21, 2006 to work with the new pregen proxy. - * 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 SLProxy; +/* + * ChatConsole.cs: sample SLProxy appliation that writes chat to the console. + * Typing on the console will send chat to Second Life. + * + * Copyright (c) 2006 Austin Jennings + * Modified by Andrew Ortman ("qode") on Decemeber 21, 2006 to work with the new pregen proxy. + * 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 SLProxy; using libsecondlife; -using libsecondlife.Packets; -using Nwc.XmlRpc; - -using System; -using System.Collections; -using System.Net; -using System.Threading; - -public class ChatConsole { - private static SecondLife client; - private static ProtocolManager protocolManager; - private static Proxy proxy; - private static LLUUID agentID; - private static LLUUID sessionID; - - public static void Main(string[] args) { - // configure the proxy - ProxyConfig proxyConfig = new ProxyConfig("ChatConsole V2", "Austin Jennings / Andrew Ortman", args); - proxy = new Proxy(proxyConfig); - - // set a delegate for when the client logs in - proxy.SetLoginResponseDelegate(new XmlRpcResponseDelegate(Login)); - - // add a delegate for incoming chat - proxy.AddDelegate(PacketType.ChatFromSimulator, Direction.Incoming, new PacketDelegate(ChatFromSimulator)); - - // start the proxy - proxy.Start(); - } - - private static void Login(XmlRpcResponse response) { - Hashtable values = (Hashtable)response.Value; - if (values.Contains("agent_id") && values.Contains("session_id")) { - // remember our agentID and sessionID - agentID = new LLUUID((string)values["agent_id"]); - sessionID = new LLUUID((string)values["session_id"]); - - // start a new thread that reads lines from the console - (new Thread(new ThreadStart(ReadFromConsole))).Start(); - } - } - - private static void ReadFromConsole() { - // send text from the console in an infinite loop - for (;;) { - // read a line from the console - string message = Console.ReadLine(); - +using libsecondlife.Packets; +using Nwc.XmlRpc; + +using System; +using System.Collections; +using System.Net; +using System.Threading; + +public class ChatConsole { + private static Proxy proxy; + private static LLUUID agentID; + private static LLUUID sessionID; + + public static void Main(string[] args) { + // configure the proxy + ProxyConfig proxyConfig = new ProxyConfig("ChatConsole V2", "Austin Jennings / Andrew Ortman", args); + proxy = new Proxy(proxyConfig); + + // set a delegate for when the client logs in + proxy.SetLoginResponseDelegate(new XmlRpcResponseDelegate(Login)); + + // add a delegate for incoming chat + proxy.AddDelegate(PacketType.ChatFromSimulator, Direction.Incoming, new PacketDelegate(ChatFromSimulator)); + + // start the proxy + proxy.Start(); + } + + private static void Login(XmlRpcResponse response) { + Hashtable values = (Hashtable)response.Value; + if (values.Contains("agent_id") && values.Contains("session_id")) { + // remember our agentID and sessionID + agentID = new LLUUID((string)values["agent_id"]); + sessionID = new LLUUID((string)values["session_id"]); + + // start a new thread that reads lines from the console + (new Thread(new ThreadStart(ReadFromConsole))).Start(); + } + } + + private static void ReadFromConsole() { + // send text from the console in an infinite loop + for (;;) { + // read a line from the console + string message = Console.ReadLine(); + // construct a ChatFromViewer packet ChatFromViewerPacket chat = new ChatFromViewerPacket(); chat.ChatData.Channel = 0; @@ -85,25 +83,25 @@ public class ChatConsole { chat.ChatData.Type = (byte)1; chat.AgentData.AgentID = agentID; - chat.AgentData.SessionID = sessionID; - // inject the packet - proxy.InjectPacket((Packet)chat, Direction.Outgoing); - } - } - - private static Packet ChatFromSimulator(Packet packet, IPEndPoint sim) { + chat.AgentData.SessionID = sessionID; + // inject the packet + proxy.InjectPacket((Packet)chat, Direction.Outgoing); + } + } + + private static Packet ChatFromSimulator(Packet packet, IPEndPoint sim) { // deconstruct the packet ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet; string message = Helpers.FieldToString(chat.ChatData.Message); string name = Helpers.FieldToString(chat.ChatData.FromName); byte audible = chat.ChatData.Audible; - byte type = chat.ChatData.ChatType; - - // if this was a normal, audible message, write it to the console - if (audible != 0 && (type == 0 || type == 1 || type == 2)) - Console.WriteLine(name + ": " + message); - - // return the packet unmodified - return packet; - } -} + byte type = chat.ChatData.ChatType; + + // if this was a normal, audible message, write it to the console + if (audible != 0 && (type == 0 || type == 1 || type == 2)) + Console.WriteLine(name + ": " + message); + + // return the packet unmodified + return packet; + } +} diff --git a/applications/SLIRC/frmSLIRC.cs b/applications/SLIRC/frmSLIRC.cs index 3a21d3c5..29d9ff6f 100644 --- a/applications/SLIRC/frmSLIRC.cs +++ b/applications/SLIRC/frmSLIRC.cs @@ -89,7 +89,7 @@ namespace SLIRC try { client = new SecondLife(); - client.Self.OnChat += new ChatCallback(Avatar_OnChat); + client.Self.OnChat += new MainAvatar.ChatCallback(Avatar_OnChat); grpLogin.Enabled = true; } catch (Exception error) diff --git a/libsecondlife-cs/AssetSystem/ImageManager.cs b/libsecondlife-cs/AssetSystem/ImageManager.cs index e1dbbee7..218f77d3 100644 --- a/libsecondlife-cs/AssetSystem/ImageManager.cs +++ b/libsecondlife-cs/AssetSystem/ImageManager.cs @@ -42,14 +42,14 @@ namespace libsecondlife.AssetSystem public delegate void ImageRetrievedCallback(LLUUID id, byte[] data, bool cached, string statusmsg); //this delegate is called when an image completed. /// - /// Manages the uploading and downloading of Images from SecondLife - /// - public class ImageManager - { + /// Manages the uploading and downloading of Images from SecondLife + /// + public class ImageManager + { private SecondLife slClient; - public enum CacheTypes {None, Memory, Disk}; + public enum CacheTypes { None, Memory, Disk }; private CacheTypes CacheType; private string CacheDirectory = "ImageCache"; private Dictionary CacheTable = new Dictionary(); @@ -57,40 +57,40 @@ namespace libsecondlife.AssetSystem private ImagePacketHelpers ImagePacketHelper; - private Dictionary htDownloadRequests = new Dictionary(); + private Dictionary htDownloadRequests = new Dictionary(); public ImageRetrievedCallback OnImageRetrieved; - private class TransferRequest - { + private class TransferRequest + { public ManualResetEvent ReceivedHeaderPacket = new ManualResetEvent(false); public ManualResetEvent Completed = new ManualResetEvent(false); - public bool Status; - public string StatusMsg; + public bool Status; + public string StatusMsg; - public uint Size; - public uint Received; - public uint LastPacket; - public byte[] AssetData; + public uint Size; + public uint Received; + public uint LastPacket; + public byte[] AssetData; public int BaseDataReceived; - public TransferRequest() - { - Status = false; - StatusMsg = ""; + public TransferRequest() + { + Status = false; + StatusMsg = ""; - AssetData = null; + AssetData = null; BaseDataReceived = 0; - } - } + } + } /// /// /// public ImageManager(SecondLife client) - { + { Init(client, CacheTypes.None, null); } @@ -251,7 +251,7 @@ namespace libsecondlife.AssetSystem /// /// The Image's AssetID public byte[] RequestImage(LLUUID ImageID) - { + { byte[] imgData = CachedImage(ImageID); if (imgData != null) { @@ -280,17 +280,17 @@ namespace libsecondlife.AssetSystem } // Wait for transfer to complete. - tr.Completed.WaitOne(); + tr.Completed.WaitOne(20000, false); - if( tr.Status == true ) - { - return tr.AssetData; - } - else - { - throw new Exception( "RequestImage: " + tr.StatusMsg ); - } - } + if (tr.Status == true) + { + return tr.AssetData; + } + else + { + throw new Exception("RequestImage: " + tr.StatusMsg); + } + } /// /// Requests an image from SecondLife. @@ -335,17 +335,17 @@ namespace libsecondlife.AssetSystem /// /// public void ImageDataCallbackHandler(Packet packet, Simulator simulator) - { - #if DEBUG_PACKETS + { +#if DEBUG_PACKETS slClient.DebugLog(packet); - #endif +#endif ImageDataPacket reply = (ImageDataPacket)packet; - LLUUID ImageID = reply.ImageID.ID; -// unused? ushort Packets = reply.ImageID.Packets; - uint Size = reply.ImageID.Size; - byte[] Data = reply.ImageData.Data; + LLUUID ImageID = reply.ImageID.ID; + // unused? ushort Packets = reply.ImageID.Packets; + uint Size = reply.ImageID.Size; + byte[] Data = reply.ImageData.Data; // Lookup the request that this packet is for TransferRequest tr; @@ -363,28 +363,28 @@ namespace libsecondlife.AssetSystem } // Initialize the request so that it's data buffer is the right size for the image - tr.Size = Size; - tr.AssetData = new byte[tr.Size]; + tr.Size = Size; + tr.AssetData = new byte[tr.Size]; tr.BaseDataReceived = Data.Length; // Copy the first block of image data into the request. - Array.Copy(Data, 0, tr.AssetData, tr.Received, Data.Length); - tr.Received += (uint)Data.Length; + Array.Copy(Data, 0, tr.AssetData, tr.Received, Data.Length); + tr.Received += (uint)Data.Length; // Mark that the TransferRequest has received this header packet tr.ReceivedHeaderPacket.Set(); - // If we've gotten all the data, mark it completed. - if( tr.Received >= tr.Size ) - { - tr.Status = true; + // If we've gotten all the data, mark it completed. + if (tr.Received >= tr.Size) + { + tr.Status = true; tr.Completed.Set(); // Fire off image downloaded event CacheImage(ImageID, tr.AssetData); FireImageRetrieved(ImageID, tr.AssetData, false); - } - } + } + } /// /// Handles the remaining Image data that did not fit in the initial ImageData packet @@ -392,13 +392,13 @@ namespace libsecondlife.AssetSystem /// /// public void ImagePacketCallbackHandler(Packet packet, Simulator simulator) - { - #if DEBUG_PACKETS + { +#if DEBUG_PACKETS slClient.DebugLog(packet); - #endif +#endif ImagePacketPacket reply = (ImagePacketPacket)packet; - + LLUUID ImageID = reply.ImageID.ID; // Lookup the request for this packet @@ -407,11 +407,11 @@ namespace libsecondlife.AssetSystem { tr = (TransferRequest)htDownloadRequests[ImageID]; } - if( tr == null ) - { + if (tr == null) + { // Received a packet that doesn't belong to any requests in our queue, strange... - return; - } + return; + } // TODO: Received data should probably be put into a temporary collection that's indected by ImageID.Packet @@ -428,42 +428,42 @@ namespace libsecondlife.AssetSystem Array.Copy(reply.ImageData.Data, 0, tr.AssetData, tr.BaseDataReceived + (1000 * (reply.ImageID.Packet - 1)), reply.ImageData.Data.Length); tr.Received += (uint)reply.ImageData.Data.Length; - // If we've gotten all the data, mark it completed. - if( tr.Received >= tr.Size ) - { + // If we've gotten all the data, mark it completed. + if (tr.Received >= tr.Size) + { tr.Status = true; tr.Completed.Set(); // Fire off image downloaded event CacheImage(ImageID, tr.AssetData); FireImageRetrieved(ImageID, tr.AssetData, false); - } - } + } + } /// /// /// public void ImageNotInDatabaseCallbackHandler(Packet packet, Simulator simulator) { - #if DEBUG_PACKETS +#if DEBUG_PACKETS slClient.DebugLog(packet); - #endif +#endif ImageNotInDatabasePacket reply = (ImageNotInDatabasePacket)packet; LLUUID ImageID = reply.ImageID.ID; // Lookup the request for this packet - TransferRequest tr; + TransferRequest tr; lock (htDownloadRequests) { tr = (TransferRequest)htDownloadRequests[ImageID]; } - if( tr == null ) - { + if (tr == null) + { // Received a packet that doesn't belong to any requests in our queue, strange... - return; - } + return; + } tr.Status = false; tr.StatusMsg = "Image not in database"; diff --git a/libsecondlife-cs/Avatar.cs b/libsecondlife-cs/Avatar.cs index f6fb8bfb..6181061d 100644 --- a/libsecondlife-cs/Avatar.cs +++ b/libsecondlife-cs/Avatar.cs @@ -71,17 +71,17 @@ namespace libsecondlife 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, @@ -112,64 +112,104 @@ namespace libsecondlife AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX } - /// The Avatar's UUID, asset server - public LLUUID ID; - /// Avatar ID in Region (sim) it is in - public uint LocalID; - /// Full Name of Avatar - public string Name; - /// Active Group of Avatar - public string GroupName; - /// Online Status of Avatar - public bool Online; - /// Location of Avatar (x,y,z probably) - public LLVector3 Position; - /// Rotational Position of Avatar - public LLQuaternion Rotation; - /// Region (aka sim) the Avatar is in - public Region CurrentRegion; - /// Date the Avatar was Born into Second Life - public string BornOn; - /// Key pointing to the Profile Image - public LLUUID ProfileImage; - /// Key of their Partner - public LLUUID PartnerID; - /// Text from the About field in the Profile - public string AboutText; - /// Bitmask representing Want To checkboxes - public uint WantToMask; - /// Text field for Want To - public string WantToText; - /// Bitmask representing Skills checkboxes - public uint SkillsMask; - /// Text field for Skills - public string SkillsText; - /// Text from the First Life field in the Profile - public string FirstLifeText; - /// Key pointing to the First Life picture - public LLUUID FirstLifeImage; - /// - public bool Identified; - /// - public bool Transacted; - /// - public bool AllowPublish; - /// - public bool MaturePublish; - /// Charter Member type, if applicable - public string CharterMember; - /// Rating for Behavior - public float Behavior; - /// Rating for Appearance - public float Appearance; - /// Rating for Building - public float Building; - /// Text from the Languages field in the Profile - public string LanguagesText; - /// - public TextureEntry Textures; - /// URL to load in Web Profile - public string ProfileURL; + /// + /// Positive and negative ratings + /// + public struct Statistics + { + /// Positive ratings for Behavior + public int BehaviorPositive; + /// Negative ratings for Behavior + public int BehaviorNegative; + /// Positive ratings for Appearance + public int AppearancePositive; + /// Negative ratings for Appearance + public int AppearanceNegative; + /// Positive ratings for Building + public int BuildingPositive; + /// Negative ratings for Building + public int BuildingNegative; + } + + /// + /// Avatar properties including about text, profile URL, image IDs and + /// publishing settings + /// + public struct Properties + { + /// Should this profile be published on the web + public bool AllowPublish; + /// First Life about text + public string FirstLifeText; + /// First Life image ID + public LLUUID FirstLifeImage; + /// + public LLUUID Partner; + /// + public string AboutText; + /// + public string BornOn; + /// + public string CharterMember; + /// Profile image ID + public LLUUID ProfileImage; + /// Is this a mature profile + public bool MaturePublish; + /// + public bool Identified; + /// + public bool Transacted; + /// Web URL for this profile + public string ProfileURL; + } + + /// + /// Avatar interests including spoken languages, skills, and "want to" + /// choices + /// + public struct Interests + { + /// Languages profile field + public string LanguagesText; + /// + public uint SkillsMask; + /// + public string SkillsText; + /// + public uint WantToMask; + /// + public string WantToText; + } + + + /// UUID for this avatar + public LLUUID ID = LLUUID.Zero; + /// Temporary ID for this avatar, local to the current region + public uint LocalID = 0; + /// Full name + public string Name = String.Empty; + /// Active group + public string GroupName = String.Empty; + /// Online status + public bool Online = false; + /// Positive and negative ratings + public Statistics ProfileStatistics = new Statistics(); + /// Avatar properties including about text, profile URL, image IDs and + /// publishing settings + public Properties ProfileProperties = new Properties(); + /// Avatar interests including spoken languages, skills, and "want to" + /// choices + public Interests ProfileInterests = new Interests(); + /// Local location, relative to the sim or what the avatar is + /// sitting on + public LLVector3 Position = LLVector3.Zero; + /// Rotational position, relative to the sim or what the avatar + /// is sitting on + public LLQuaternion Rotation = LLQuaternion.Identity; + /// Region the avatar is in + public Region CurrentRegion = null; + /// Textures for this avatars clothing + public TextureEntry Textures = new TextureEntry(); /// Gets the local ID of the prim the avatar is sitting on, /// zero if the avatar is not currently sitting @@ -180,39 +220,42 @@ namespace libsecondlife internal uint sittingOn = 0; - protected const int CONTROL_AT_POS_INDEX = 0; - protected const int CONTROL_AT_NEG_INDEX = 1; - protected const int CONTROL_LEFT_POS_INDEX = 2; - protected const int CONTROL_LEFT_NEG_INDEX = 3; - protected const int CONTROL_UP_POS_INDEX = 4; - protected const int CONTROL_UP_NEG_INDEX = 5; - protected const int CONTROL_PITCH_POS_INDEX = 6; - protected const int CONTROL_PITCH_NEG_INDEX = 7; - protected const int CONTROL_YAW_POS_INDEX = 8; - protected const int CONTROL_YAW_NEG_INDEX = 9; - protected const int CONTROL_FAST_AT_INDEX = 10; - protected const int CONTROL_FAST_LEFT_INDEX = 11; - protected const int CONTROL_FAST_UP_INDEX = 12; - protected const int CONTROL_FLY_INDEX = 13; - protected const int CONTROL_STOP_INDEX = 14; - protected const int CONTROL_FINISH_ANIM_INDEX = 15; - protected const int CONTROL_STAND_UP_INDEX = 16; - protected const int CONTROL_SIT_ON_GROUND_INDEX = 17; - protected const int CONTROL_MOUSELOOK_INDEX = 18; - protected const int CONTROL_NUDGE_AT_POS_INDEX = 19; - protected const int CONTROL_NUDGE_AT_NEG_INDEX = 20; - protected const int CONTROL_NUDGE_LEFT_POS_INDEX = 21; - protected const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22; - protected const int CONTROL_NUDGE_UP_POS_INDEX = 23; - protected const int CONTROL_NUDGE_UP_NEG_INDEX = 24; - protected const int CONTROL_TURN_LEFT_INDEX = 25; - protected const int CONTROL_TURN_RIGHT_INDEX = 26; - protected const int CONTROL_AWAY_INDEX = 27; - protected const int CONTROL_LBUTTON_DOWN_INDEX = 28; - protected const int CONTROL_LBUTTON_UP_INDEX = 29; - protected const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30; - protected const int CONTROL_ML_LBUTTON_UP_INDEX = 31; - protected const int TOTAL_CONTROLS = 32; - } + 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; + public Avatar() + { + } + } } diff --git a/libsecondlife-cs/AvatarManager.cs b/libsecondlife-cs/AvatarManager.cs index b7d274a7..fc5f93b4 100644 --- a/libsecondlife-cs/AvatarManager.cs +++ b/libsecondlife-cs/AvatarManager.cs @@ -32,52 +32,63 @@ using libsecondlife.Packets; namespace libsecondlife { /// - /// Class to manage multiple Avatars + /// Retrieve friend status notifications, and retrieve avatar names and + /// profiles /// public class AvatarManager { /// /// Triggered after friend request packet is sent out /// - /// - /// + /// + /// public delegate void FriendNotificationCallback(LLUUID agentID, bool online); /// /// Triggered when a UUIDNameReply is received /// /// - public delegate void AgentNamesCallback(Dictionary names); + public delegate void AvatarNamesCallback(Dictionary names); /// - /// Triggered when Avatar properties are received (AvatarPropertiesReply) + /// Triggered when a response for avatar statistics (ratings) is returned /// - /// - public delegate void AvatarPropertiesCallback(Avatar avatar); + /// + /// + public delegate void AvatarStatisticsCallback(LLUUID avatarID, Avatar.Statistics statistics); /// - /// + /// Triggered when a response for avatar interests is returned /// - /// - public delegate void AvatarNameCallback(Avatar avatar); + /// + /// + public delegate void AvatarInterestsCallback(LLUUID avatarID, Avatar.Interests interests); /// - /// + /// Triggered when avatar properties are received (AvatarPropertiesReply) /// - /// - public delegate void AvatarStatisticsCallback(Avatar avatar); + /// + /// + public delegate void AvatarPropertiesCallback(LLUUID avatarID, Avatar.Properties properties); /// - /// Triggered when a response for Avatar Interests is returned + /// Triggered when a name search reply is received (AvatarPickerReply) /// - /// - public delegate void AvatarInterestsCallback(Avatar avatar); + /// + /// + public delegate void AvatarNameSearchCallback(LLUUID queryID, Dictionary avatars); + /// Triggered whenever a friend comes online or goes offline public event FriendNotificationCallback OnFriendNotification; + /// + public event AvatarNamesCallback OnAvatarNames; + /// + public event AvatarStatisticsCallback OnAvatarStatistics; + /// + public event AvatarInterestsCallback OnAvatarInterests; + /// + public event AvatarPropertiesCallback OnAvatarProperties; + /// + public event AvatarNameSearchCallback OnAvatarNameSearch; - private Dictionary Avatars; private SecondLife Client; - private AgentNamesCallback OnAgentNames; - private Dictionary AvatarPropertiesCallbacks; - private Dictionary AvatarStatisticsCallbacks; - private Dictionary AvatarInterestsCallbacks; - private Dictionary ManualResetEvents; + /// /// Represents other avatars /// @@ -85,386 +96,54 @@ namespace libsecondlife public AvatarManager(SecondLife client) { Client = client; - Avatars = new Dictionary(); - //Callback Dictionaries - AvatarPropertiesCallbacks = new Dictionary(); - AvatarStatisticsCallbacks = new Dictionary(); - AvatarInterestsCallbacks = new Dictionary(); - //ManualResetEvent Dictionary - ManualResetEvents = new Dictionary(); + // Friend notification callback NetworkManager.PacketCallback callback = new NetworkManager.PacketCallback(FriendNotificationHandler); Client.Network.RegisterCallback(PacketType.OnlineNotification, callback); Client.Network.RegisterCallback(PacketType.OfflineNotification, callback); - Client.Network.RegisterCallback(PacketType.UUIDNameReply, new NetworkManager.PacketCallback(GetAgentNameHandler)); + + // Avatar profile callbacks Client.Network.RegisterCallback(PacketType.AvatarPropertiesReply, new NetworkManager.PacketCallback(AvatarPropertiesHandler)); Client.Network.RegisterCallback(PacketType.AvatarStatisticsReply, new NetworkManager.PacketCallback(AvatarStatisticsHandler)); Client.Network.RegisterCallback(PacketType.AvatarInterestsReply, new NetworkManager.PacketCallback(AvatarInterestsHandler)); + + // Other callbacks + Client.Network.RegisterCallback(PacketType.UUIDNameReply, new NetworkManager.PacketCallback(AvatarNameHandler)); + Client.Network.RegisterCallback(PacketType.AvatarPickerReply, new NetworkManager.PacketCallback(AvatarPickerReplyHandler)); } - /// - /// Add an Avatar into the Avatars Dictionary + /// Request a single avatar name /// - /// Filled-out Avatar class to insert - public void AddAvatar(Avatar avatar) + /// The avatar key to retrieve a name for + public void RequestAvatarName(LLUUID id) { - lock (Avatars) - { - Avatars[avatar.ID] = avatar; - } + UUIDNameRequestPacket request = new UUIDNameRequestPacket(); + request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[1]; + request.UUIDNameBlock[0] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); + request.UUIDNameBlock[0].ID = id; + + Client.Network.SendPacket(request); } /// - /// Used to search all known Avatars for a particular Avatar Key + /// Request a list of avatar names /// - /// - /// - public bool Contains(LLUUID id) + /// The avatar keys to retrieve names for + public void RequestAvatarNames(List ids) { - return Avatars.ContainsKey(id); + UUIDNameRequestPacket request = new UUIDNameRequestPacket(); + request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[ids.Count]; + + for (int i = 0; i < ids.Count; i++) + { + request.UUIDNameBlock[i] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); + request.UUIDNameBlock[i].ID = ids[i]; + } + + Client.Network.SendPacket(request); } - /// - /// Refresh Avatar Profile information - /// - /// - public void UpdateAvatar(Avatar a) - { - //Basic profile properties - AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket(); - AvatarPropertiesUpdatePacket.AgentDataBlock adb = new AvatarPropertiesUpdatePacket.AgentDataBlock(); - adb.AgentID = a.ID; - adb.SessionID = Client.Network.SessionID; - apup.AgentData = adb; - AvatarPropertiesUpdatePacket.PropertiesDataBlock pdb = new AvatarPropertiesUpdatePacket.PropertiesDataBlock(); - pdb.AllowPublish = a.AllowPublish; - pdb.FLAboutText = Helpers.StringToField(a.FirstLifeText); - pdb.FLImageID = a.FirstLifeImage; - pdb.ImageID = a.ProfileImage; - pdb.MaturePublish = a.MaturePublish; - pdb.ProfileURL = Helpers.StringToField(a.ProfileURL); - apup.PropertiesData = pdb; - //Intrests - AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket(); - AvatarInterestsUpdatePacket.AgentDataBlock iadb = new AvatarInterestsUpdatePacket.AgentDataBlock(); - iadb.AgentID = a.ID; - iadb.SessionID = Client.Network.SessionID; - aiup.AgentData = iadb; - AvatarInterestsUpdatePacket.PropertiesDataBlock ipdb = new AvatarInterestsUpdatePacket.PropertiesDataBlock(); - ipdb.LanguagesText = Helpers.StringToField(a.LanguagesText); - ipdb.SkillsMask = a.SkillsMask; - ipdb.SkillsText = Helpers.StringToField(a.SkillsText); - ipdb.WantToMask = a.WantToMask; - ipdb.WantToText = Helpers.StringToField(a.WantToText); - aiup.PropertiesData = ipdb; - //Send packets - Client.Network.SendPacket(apup); - Client.Network.SendPacket(aiup); - } - - /// - /// This function will only check if the avatar name exists locally, - /// it will not do any networking calls to fetch the name - /// - /// The avatar name, or an empty string if it's not found - public string LocalAvatarNameLookup(LLUUID id) - { - string name = ""; - - lock (Avatars) - { - if (Avatars.ContainsKey(id)) - { - name = Avatars[id].Name; - } - } - - return name; - } - /// - /// Get an avatar's name, either from the cache or request it. - /// This function does block. - /// - /// Key to look up - /// - public string GetAvatarName(LLUUID key) - { - //Short circuit the cache lookup in GetAvatarNames - string name = LocalAvatarNameLookup(key); - if (name != "") return name; - - //Add to the dictionary - ManualResetEvents.Add(key, new ManualResetEvent(false)); - - //Call function - BeginGetAvatarName(key, null); - - //Start the blocking - ManualResetEvents[key].WaitOne(); - - //Clean up - ManualResetEvents[key] = null; - - //Return - return Avatars[key].Name; - } - - /// - /// - /// - /// - public void BeginGetAvatarName(LLUUID id, AgentNamesCallback anc) - { - // TODO: BeginGetAvatarNames is pretty bulky, rewrite a simple version here - - List ids = new List(); - ids.Add(id); - BeginGetAvatarNames(ids, anc); - } - - /// - /// - /// - /// - public void BeginGetAvatarNames(List ids, AgentNamesCallback anc) - { - if (anc != null) - { - OnAgentNames = anc; - } - - Dictionary havenames = new Dictionary(); - List neednames = new List(); - - // Fire callbacks for the ones we already have cached - foreach (LLUUID id in ids) - { - if (Avatars.ContainsKey(id)) - { - havenames[id] = Avatars[id].Name; - //Short circuit the lookup process - if (ManualResetEvents.ContainsKey(id)) - { - ManualResetEvents[id].Set(); - return; - } - } - else - { - neednames.Add(id); - } - } - - if (havenames.Count > 0 && OnAgentNames != null) - { - OnAgentNames(havenames); - - } - - if (neednames.Count > 0) - { - UUIDNameRequestPacket request = new UUIDNameRequestPacket(); - - request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[neednames.Count]; - - for (int i = 0; i < neednames.Count; i++) - { - request.UUIDNameBlock[i] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); - request.UUIDNameBlock[i].ID = neednames[i]; - } - - Client.Network.SendPacket(request); - } - } - - /// - /// Process an incoming UUIDNameReply Packet and insert Full Names into the Avatars Dictionary - /// - /// Incoming Packet to process - /// Unused - private void GetAgentNameHandler(Packet packet, Simulator simulator) - { - Dictionary names = new Dictionary(); - UUIDNameReplyPacket reply = (UUIDNameReplyPacket)packet; - - lock (Avatars) - { - foreach (UUIDNameReplyPacket.UUIDNameBlockBlock block in reply.UUIDNameBlock) - { - if (!Avatars.ContainsKey(block.ID)) - { - Avatars[block.ID] = new Avatar(); - Avatars[block.ID].ID = block.ID; - } - - Avatars[block.ID].Name = Helpers.FieldToString(block.FirstName) + - " " + Helpers.FieldToString(block.LastName); - - names[block.ID] = Avatars[block.ID].Name; - if (ManualResetEvents.ContainsKey(block.ID)) - { - //Stop Blocking - ManualResetEvents[block.ID].Set(); - } - } - } - - if (OnAgentNames != null) - { - OnAgentNames(names); - } - } - /// - /// Handle incoming friend notifications - /// - /// - /// - private void FriendNotificationHandler(Packet packet, Simulator simulator) - { - List requestids = new List(); - - if (packet.Type == PacketType.OnlineNotification) - { - // If the agent is online... - foreach (OnlineNotificationPacket.AgentBlockBlock block in ((OnlineNotificationPacket)packet).AgentBlock) - { - lock (Avatars) - { - if (!Avatars.ContainsKey(block.AgentID)) - { - // Mark this avatar for a name request - requestids.Add(block.AgentID); - - Avatars[block.AgentID] = new Avatar(); - Avatars[block.AgentID].ID = block.AgentID; - } - - Avatars[block.AgentID].Online = true; - } - - if (OnFriendNotification != null) - { - OnFriendNotification(block.AgentID, true); - } - } - } - else if (packet.Type == PacketType.OfflineNotification) - { - // If the agent is Offline... - foreach (OfflineNotificationPacket.AgentBlockBlock block in ((OfflineNotificationPacket)packet).AgentBlock) - { - lock (Avatars) - { - if (!Avatars.ContainsKey(block.AgentID)) - { - // Mark this avatar for a name request - requestids.Add(block.AgentID); - - Avatars[block.AgentID] = new Avatar(); - Avatars[block.AgentID].ID = block.AgentID; - } - - Avatars[block.AgentID].Online = false; - } - - if (OnFriendNotification != null) - { - OnFriendNotification(block.AgentID, true); - } - } - } - - if (requestids.Count > 0) - { - BeginGetAvatarNames(requestids, null); - } - } - /// - /// Handles incoming avatar statistics. What are those ? - /// - /// - /// - private void AvatarStatisticsHandler(Packet packet, Simulator simulator) - { - AvatarStatisticsReplyPacket asr = (AvatarStatisticsReplyPacket)packet; - lock(Avatars) - { - Avatar av; - if (!Avatars.ContainsKey(asr.AvatarData.AvatarID)) - { - av = new Avatar(); - av.ID = asr.AvatarData.AvatarID; - } - else - { - av = Avatars[asr.AvatarData.AvatarID]; - } - - foreach(AvatarStatisticsReplyPacket.StatisticsDataBlock b in asr.StatisticsData) - { - string n = Helpers.FieldToString(b.Name); - if(n.Equals("Behavior")) - { - av.Behavior = b.Positive; - } - else if(n.Equals("Appearance")) - { - av.Appearance = b.Positive; - } - else if(n.Equals("Building")) - { - av.Building = b.Positive; - } - } - - //Call it - if (AvatarStatisticsCallbacks.ContainsKey(av.ID) && AvatarStatisticsCallbacks[av.ID] != null) - AvatarStatisticsCallbacks[av.ID](av); - } - } - /// - /// Process incoming Avatar properties (profile data) - /// - /// - /// - private void AvatarPropertiesHandler(Packet packet, Simulator sim) - { - Avatar av; - AvatarPropertiesReplyPacket reply = (AvatarPropertiesReplyPacket)packet; - lock(Avatars) - { - if (!Avatars.ContainsKey(reply.AgentData.AvatarID)) - { - //not in our "cache", create a new object - av = new Avatar(); - } - else - { - //Cache hit, modify existing avatar - av = Avatars[reply.AgentData.AvatarID]; - } - av.ID = reply.AgentData.AvatarID; - av.ProfileImage = reply.PropertiesData.ImageID; - av.FirstLifeImage = reply.PropertiesData.FLImageID; - av.PartnerID = reply.PropertiesData.PartnerID; - av.AboutText = Helpers.FieldToString(reply.PropertiesData.AboutText); - - av.FirstLifeText = Helpers.FieldToString(reply.PropertiesData.FLAboutText); - av.BornOn = Helpers.FieldToString(reply.PropertiesData.BornOn); - av.CharterMember = Helpers.FieldToString(reply.PropertiesData.CharterMember); - av.AllowPublish = reply.PropertiesData.AllowPublish; - av.MaturePublish = reply.PropertiesData.MaturePublish; - av.Identified = reply.PropertiesData.Identified; - av.Transacted = reply.PropertiesData.Transacted; - av.ProfileURL = Helpers.FieldToString(reply.PropertiesData.ProfileURL); - //reassign in the cache - Avatars[av.ID] = av; - //Heaven forbid that we actually get a packet we didn't ask for. - if (AvatarPropertiesCallbacks.ContainsKey(av.ID) && AvatarPropertiesCallbacks[av.ID] != null) - AvatarPropertiesCallbacks[av.ID](av); - } - } /// /// Start a request for Avatar Properties /// @@ -472,54 +151,184 @@ namespace libsecondlife /// /// /// - public void BeginAvatarPropertiesRequest(LLUUID avatarid, AvatarPropertiesCallback apc, AvatarStatisticsCallback asc, AvatarInterestsCallback aic) + public void RequestAvatarProperties(LLUUID avatarid) { - //Set teh callback! - AvatarPropertiesCallbacks[avatarid] = apc; - AvatarStatisticsCallbacks[avatarid] = asc; - AvatarInterestsCallbacks[avatarid] = aic; - //Oh noes - //Packet construction, good times AvatarPropertiesRequestPacket aprp = new AvatarPropertiesRequestPacket(); - AvatarPropertiesRequestPacket.AgentDataBlock adb = new AvatarPropertiesRequestPacket.AgentDataBlock(); - adb.AgentID = Client.Network.AgentID; - adb.SessionID = Client.Network.SessionID; - adb.AvatarID = avatarid; - aprp.AgentData = adb; - //send the packet! + + aprp.AgentData.AgentID = Client.Network.AgentID; + aprp.AgentData.SessionID = Client.Network.SessionID; + aprp.AgentData.AvatarID = avatarid; + Client.Network.SendPacket(aprp); } + /// - /// Process incoming Avatar Interests information + /// Search for an avatar (first name, last name, and uuid) + /// + /// The name to search for + /// An ID to associate with this query + public void RequestAvatarNameSearch(string name, LLUUID queryID) + { + AvatarPickerRequestPacket aprp = new AvatarPickerRequestPacket(); + + aprp.AgentData.AgentID = Client.Network.AgentID; + aprp.AgentData.SessionID = Client.Network.SessionID; + aprp.AgentData.QueryID = queryID; + aprp.Data.Name = Helpers.StringToField(name); + + Client.Network.SendPacket(aprp); + } + + /// + /// Process an incoming UUIDNameReply Packet and insert Full Names into the Avatars Dictionary + /// + /// Incoming Packet to process + /// Unused + private void AvatarNameHandler(Packet packet, Simulator simulator) + { + if (OnAvatarNames != null) + { + Dictionary names = new Dictionary(); + UUIDNameReplyPacket reply = (UUIDNameReplyPacket)packet; + + foreach (UUIDNameReplyPacket.UUIDNameBlockBlock block in reply.UUIDNameBlock) + { + names[block.ID] = Helpers.FieldToString(block.FirstName) + + " " + Helpers.FieldToString(block.LastName); + } + + OnAvatarNames(names); + } + } + + /// + /// Handle incoming friend notifications /// /// /// + private void FriendNotificationHandler(Packet packet, Simulator simulator) + { + if (OnFriendNotification != null) + { + if (packet.Type == PacketType.OnlineNotification) + { + // If the agent is online + foreach (OnlineNotificationPacket.AgentBlockBlock block in ((OnlineNotificationPacket)packet).AgentBlock) + OnFriendNotification(block.AgentID, true); + } + else if (packet.Type == PacketType.OfflineNotification) + { + // If the agent is offline + foreach (OfflineNotificationPacket.AgentBlockBlock block in ((OfflineNotificationPacket)packet).AgentBlock) + OnFriendNotification(block.AgentID, true); + } + } + } + + /// + /// Handles incoming avatar statistics, such as ratings + /// + /// + /// + private void AvatarStatisticsHandler(Packet packet, Simulator simulator) + { + if (OnAvatarStatistics != null) + { + AvatarStatisticsReplyPacket asr = (AvatarStatisticsReplyPacket)packet; + Avatar.Statistics stats = new Avatar.Statistics(); + + foreach (AvatarStatisticsReplyPacket.StatisticsDataBlock b in asr.StatisticsData) + { + string n = Helpers.FieldToString(b.Name); + + switch (n) + { + case "Behavior": + stats.BehaviorPositive = b.Positive; + stats.BehaviorNegative = b.Negative; + break; + case "Appearance": + stats.AppearancePositive = b.Positive; + stats.AppearanceNegative = b.Negative; + break; + case "Building": + stats.AppearancePositive = b.Positive; + stats.AppearanceNegative = b.Negative; + break; + default: + Client.Log("Got an AvatarStatistics block with the name " + n, Helpers.LogLevel.Warning); + break; + } + } + + OnAvatarStatistics(asr.AvatarData.AvatarID, stats); + } + } + + /// + /// Process incoming avatar properties (profile data) + /// + /// + /// + private void AvatarPropertiesHandler(Packet packet, Simulator sim) + { + if (OnAvatarProperties != null) + { + AvatarPropertiesReplyPacket reply = (AvatarPropertiesReplyPacket)packet; + Avatar.Properties properties = new Avatar.Properties(); + + properties.ProfileImage = reply.PropertiesData.ImageID; + properties.FirstLifeImage = reply.PropertiesData.FLImageID; + properties.Partner = reply.PropertiesData.PartnerID; + properties.AboutText = Helpers.FieldToString(reply.PropertiesData.AboutText); + properties.FirstLifeText = Helpers.FieldToString(reply.PropertiesData.FLAboutText); + properties.BornOn = Helpers.FieldToString(reply.PropertiesData.BornOn); + properties.CharterMember = Helpers.FieldToString(reply.PropertiesData.CharterMember); + properties.AllowPublish = reply.PropertiesData.AllowPublish; + properties.MaturePublish = reply.PropertiesData.MaturePublish; + properties.Identified = reply.PropertiesData.Identified; + properties.Transacted = reply.PropertiesData.Transacted; + properties.ProfileURL = Helpers.FieldToString(reply.PropertiesData.ProfileURL); + + OnAvatarProperties(reply.AgentData.AvatarID, properties); + } + } + + /// + /// Process incoming Avatar Interests information + /// private void AvatarInterestsHandler(Packet packet, Simulator simulator) { - AvatarInterestsReplyPacket airp = (AvatarInterestsReplyPacket)packet; - Avatar av; - lock (Avatars) + if (OnAvatarInterests != null) { - if (!Avatars.ContainsKey(airp.AgentData.AvatarID)) - { - //not in our "cache", create a new object - av = new Avatar(); - av.ID = airp.AgentData.AvatarID; - } - else - { - //Cache hit, modify existing avatar - av = Avatars[airp.AgentData.AvatarID]; - } - //The rest of the properties, thanks LL. - av.WantToMask = airp.PropertiesData.WantToMask; - av.WantToText = Helpers.FieldToString(airp.PropertiesData.WantToText); - av.SkillsMask = airp.PropertiesData.SkillsMask; - av.SkillsText = Helpers.FieldToString(airp.PropertiesData.SkillsText); - av.LanguagesText = Helpers.FieldToString(airp.PropertiesData.LanguagesText); + AvatarInterestsReplyPacket airp = (AvatarInterestsReplyPacket)packet; + Avatar.Interests interests = new Avatar.Interests(); + + interests.WantToMask = airp.PropertiesData.WantToMask; + interests.WantToText = Helpers.FieldToString(airp.PropertiesData.WantToText); + interests.SkillsMask = airp.PropertiesData.SkillsMask; + interests.SkillsText = Helpers.FieldToString(airp.PropertiesData.SkillsText); + interests.LanguagesText = Helpers.FieldToString(airp.PropertiesData.LanguagesText); + + OnAvatarInterests(airp.AgentData.AvatarID, interests); + } + } + + private void AvatarPickerReplyHandler(Packet packet, Simulator simulator) + { + if (OnAvatarNameSearch != null) + { + AvatarPickerReplyPacket reply = (AvatarPickerReplyPacket)packet; + Dictionary avatars = new Dictionary(); + + foreach (AvatarPickerReplyPacket.DataBlock block in reply.Data) + { + avatars[block.AvatarID] = Helpers.FieldToString(block.FirstName) + + " " + Helpers.FieldToString(block.LastName); + } + + OnAvatarNameSearch(reply.AgentData.QueryID, avatars); } - if (AvatarInterestsCallbacks.ContainsKey(airp.AgentData.AvatarID) && AvatarInterestsCallbacks[airp.AgentData.AvatarID] != null) - AvatarInterestsCallbacks[airp.AgentData.AvatarID](av); } } } diff --git a/libsecondlife-cs/MainAvatar.cs b/libsecondlife-cs/MainAvatar.cs index 6945c251..42d1c738 100644 --- a/libsecondlife-cs/MainAvatar.cs +++ b/libsecondlife-cs/MainAvatar.cs @@ -32,114 +32,136 @@ using libsecondlife.Packets; namespace libsecondlife { - /// - /// Triggered on incoming chat messages - /// - /// Text of chat message - /// Is this normal audible chat or not. - /// Type of chat (whisper,shout,status,etc) - /// Type of source (Agent / Object / ???) - /// Text name of sending Avatar/Object - /// - public delegate void ChatCallback(string message, byte audible, byte type, byte 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 - /// - /// - public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName, - LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, - byte dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, - byte offline, byte[] binaryBucket); - - /// - /// Triggered for any status updates of a teleport (progress, failed, succeeded) - /// - /// The simulator the avatar is currently residing in - /// A message about the current teleport status - /// The current status of the teleport - public delegate void TeleportCallback(Simulator currentSim, string message, TeleportStatus status); - - /// - /// Current teleport status - /// - public enum TeleportStatus - { - /// - None, - /// Teleport Start - Start, - /// Teleport in Progress - Progress, - /// Teleport Failed - Failed, - /// Teleport Completed - Finished - } - - /// - /// Special commands used in Instant Messages - /// - public enum InstantMessageDialog - { - /// Indicates a regular IM from another agent - MessageFromAgent = 0, - /// Indicates that someone has given the user an object - GiveInventory = 4, - /// Indicates that someone has given the user a notecard - GiveNotecard = 9, - /// Indicates that the IM is from an object - MessageFromObject = 19, - /// Indicates that the IM is a teleport invitation - 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, - /// Indicates that a user has started typing - StartTyping = 41, - /// Indicates that a user has stopped typing - StopTyping = 42 - } - /// /// Class to hold Client Avatar's data /// public partial class MainAvatar { + /// + /// Current teleport status + /// + public enum TeleportStatus + { + /// + None, + /// Teleport Start + Start, + /// Teleport in Progress + Progress, + /// Teleport Failed + Failed, + /// Teleport Completed + Finished + } + + /// + /// Special commands used in Instant Messages + /// + public enum InstantMessageDialog + { + /// Indicates a regular IM from another agent + MessageFromAgent = 0, + /// Indicates that someone has given the user an object + GiveInventory = 4, + /// Indicates that someone has given the user a notecard + GiveNotecard = 9, + /// Indicates that the IM is from an object + MessageFromObject = 19, + /// Indicates that the IM is a teleport invitation + 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, + /// Indicates that a user has started typing + StartTyping = 41, + /// Indicates that a user has stopped typing + StopTyping = 42 + } + + /// + /// Conversion type to denote Chat Packet types in an easier-to-understand format + /// + public enum ChatType + { + /// Whispers (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" + Say = 3, + /// Event message when an Avatar has begun to type + StartTyping = 4, + /// Event message when an Avatar has stopped typing + StopTyping = 5 + } + + + /// + /// Triggered on incoming chat messages + /// + /// Text of chat message + /// Is this normal audible chat or not. + /// Type of chat (whisper,shout,status,etc) + /// Type of source (Agent / Object / ???) + /// Text name of sending Avatar/Object + /// + public delegate void ChatCallback(string message, byte audible, byte type, byte 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 + /// + /// + public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName, + LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, + byte dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, + byte offline, byte[] binaryBucket); + + /// + /// Triggered for any status updates of a teleport (progress, failed, succeeded) + /// + /// The simulator the avatar is currently residing in + /// A message about the current teleport status + /// The current status of the teleport + public delegate void TeleportCallback(Simulator currentSim, string message, TeleportStatus status); + + /// Callback for incoming chat packets public event ChatCallback OnChat; /// Callback for pop-up dialogs from scripts @@ -156,11 +178,23 @@ namespace libsecondlife /// Your (client) Avatar ID, local to Region/sim public uint LocalID; /// Avatar First Name (i.e. Philip) - public string FirstName = ""; + public string FirstName = String.Empty; /// Avatar Last Name (i.e. Linden) - public string LastName = ""; - /// - public string TeleportMessage; + 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.Properties ProfileProperties = new Avatar.Properties(); + /// 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 @@ -198,6 +232,9 @@ namespace libsecondlife get { return sittingOn; } } + internal uint sittingOn = 0; + internal string teleportMessage = String.Empty; + private SecondLife Client; private TeleportCallback OnBeginTeleport; private TeleportStatus TeleportStat; @@ -207,17 +244,14 @@ namespace libsecondlife private float health = 0.0f; private int balance = 0; - internal uint sittingOn = 0; - /// - /// Constructor, aka 'CallBack Central' - Setup callbacks for packets related to our avatar + /// Constructor, setup callbacks for packets related to our avatar /// /// public MainAvatar(SecondLife client) { NetworkManager.PacketCallback callback; Client = client; - TeleportMessage = ""; Status = new MainAvatarStatus(Client); @@ -240,7 +274,8 @@ namespace libsecondlife // Script dialog callback Client.Network.RegisterCallback(PacketType.ScriptDialog, new NetworkManager.PacketCallback(ScriptDialogHandler)); - TeleportTimer = new Timer(18000); + // Teleport timeout timer + TeleportTimer = new Timer(Client.Settings.TELEPORT_TIMEOUT); TeleportTimer.Elapsed += new ElapsedEventHandler(TeleportTimerEvent); TeleportTimeout = false; @@ -292,17 +327,14 @@ namespace libsecondlife } /// - /// Generate an Instant Message (full arguments) + /// Send an Instant Message /// - /// Client's Avatar - /// SessionID of current connection to grid + /// The name this IM will show up as being from + /// Session ID of current connection to grid /// Key of Avatar - /// Text Message being sent. + /// Text message being sent /// - /// IM Session ID - /// - /// TODO: Have fromName grabbed from elsewhere and remove argument, to prevent inadvertant spoofing. - /// + /// IM session ID (to differentiate between IM windows) public void InstantMessage(string fromName, LLUUID sessionID, LLUUID target, string message, LLUUID[] conferenceIDs, LLUUID IMSessionID) { @@ -335,33 +367,51 @@ namespace libsecondlife //TODO: Allow region id to be correctly set by caller or fetched from Client.* im.MessageBlock.RegionID = LLUUID.Zero; - // Send the message Client.Network.SendPacket(im); } /// - /// Conversion type to denote Chat Packet types in an easier-to-understand format + /// Synchronize the local profile and interests information to the server /// - public enum ChatType + public void SetAvatarInformation() { - /// Whispers (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" - Say = 3, - /// Event message when an Avatar has begun to type - StartTyping = 4, - /// Event message when an Avatar has stopped typing - StopTyping = 5 + // Basic profile properties + AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket(); + + apup.AgentData = new AvatarPropertiesUpdatePacket.AgentDataBlock(); + apup.AgentData.AgentID = this.ID; + apup.AgentData.SessionID = Client.Network.SessionID; + + apup.PropertiesData = new AvatarPropertiesUpdatePacket.PropertiesDataBlock(); + 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 = new AvatarInterestsUpdatePacket.AgentDataBlock(); + aiup.AgentData.AgentID = this.ID; + aiup.AgentData.SessionID = Client.Network.SessionID; + + aiup.PropertiesData = new AvatarInterestsUpdatePacket.PropertiesDataBlock(); + 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. + /// Send a chat message /// /// The Message you're sending out. /// Channel number (0 would be default 'Say' message, other numbers @@ -655,14 +705,14 @@ namespace libsecondlife if (TeleportTimeout) { - TeleportMessage = "Teleport timed out."; + teleportMessage = "Teleport timed out."; TeleportStat = TeleportStatus.Failed; - if (OnTeleport != null) { OnTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); } + if (OnTeleport != null) { OnTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } } else { - if (OnTeleport != null) { OnTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); } + if (OnTeleport != null) { OnTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } } return (TeleportStat == TeleportStatus.Finished); @@ -722,9 +772,9 @@ namespace libsecondlife if (OnTeleport != null) { - TeleportMessage = "Unable to resolve name: " + simName; + teleportMessage = "Unable to resolve name: " + simName; TeleportStat = TeleportStatus.Failed; - OnTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); + OnTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } return false; @@ -1035,36 +1085,36 @@ namespace libsecondlife { Client.DebugLog("TeleportStart received from " + simulator.ToString()); - TeleportMessage = "Teleport started"; + teleportMessage = "Teleport started"; TeleportStat = TeleportStatus.Start; if (OnBeginTeleport != null) { - OnBeginTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); + OnBeginTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } } else if (packet.Type == PacketType.TeleportProgress) { Client.DebugLog("TeleportProgress received from " + simulator.ToString()); - TeleportMessage = Helpers.FieldToString(((TeleportProgressPacket)packet).Info.Message); + teleportMessage = Helpers.FieldToString(((TeleportProgressPacket)packet).Info.Message); TeleportStat = TeleportStatus.Progress; if (OnBeginTeleport != null) { - OnBeginTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); + OnBeginTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } } else if (packet.Type == PacketType.TeleportFailed) { Client.DebugLog("TeleportFailed received from " + simulator.ToString()); - TeleportMessage = Helpers.FieldToString(((TeleportFailedPacket)packet).Info.Reason); + teleportMessage = Helpers.FieldToString(((TeleportFailedPacket)packet).Info.Reason); TeleportStat = TeleportStatus.Failed; if (OnBeginTeleport != null) { - OnBeginTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); + OnBeginTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } OnBeginTeleport = null; @@ -1082,7 +1132,7 @@ namespace libsecondlife if (sim != null) { - TeleportMessage = "Teleport finished"; + teleportMessage = "Teleport finished"; TeleportStat = TeleportStatus.Finished; // Move the avatar in to the new sim @@ -1099,7 +1149,7 @@ namespace libsecondlife if (OnBeginTeleport != null) { - OnBeginTeleport(sim, TeleportMessage, TeleportStat); + OnBeginTeleport(sim, teleportMessage, TeleportStat); } else { @@ -1110,16 +1160,16 @@ namespace libsecondlife } else { - TeleportMessage = "Failed to connect to the new sim after a teleport"; + teleportMessage = "Failed to connect to the new sim after a teleport"; TeleportStat = TeleportStatus.Failed; // FIXME: Set the previous CurrentSim to the current simulator again - Client.Log(TeleportMessage, Helpers.LogLevel.Warning); + Client.Log(teleportMessage, Helpers.LogLevel.Warning); if (OnBeginTeleport != null) { - OnBeginTeleport(Client.Network.CurrentSim, TeleportMessage, TeleportStat); + OnBeginTeleport(Client.Network.CurrentSim, teleportMessage, TeleportStat); } } diff --git a/libsecondlife-cs/SecondLife.cs b/libsecondlife-cs/SecondLife.cs index 7564068b..c166705f 100644 --- a/libsecondlife-cs/SecondLife.cs +++ b/libsecondlife-cs/SecondLife.cs @@ -32,14 +32,6 @@ using libsecondlife.InventorySystem; namespace libsecondlife { - /// - /// Callback used for client apps to receive log messages from - /// libsecondlife - /// - /// - /// - public delegate void LogCallback(string message, Helpers.LogLevel level); - /// /// Main class to expose Second Life functionality to clients. All of the /// classes needed for sending and receiving data are accessible through @@ -47,6 +39,15 @@ namespace libsecondlife /// public class SecondLife { + /// + /// Callback used for client apps to receive log messages from + /// libsecondlife + /// + /// + /// + public delegate void LogCallback(string message, Helpers.LogLevel level); + + /// Networking Subsystem public NetworkManager Network; /// Parcel (subdivided simulator lots) Subsystem @@ -61,14 +62,12 @@ namespace libsecondlife public ObjectManager Objects; /// Group Subsystem public GroupManager Groups; - /// Asset Subsystem public AssetManager Assets; /// Inventory Subsystem public InventoryManager Inventory; /// Image Subsystem public ImageManager Images; - /// Throttling Subsystem public AgentThrottle Throttle; /// Settings Subsystem @@ -86,20 +85,19 @@ namespace libsecondlife /// public SecondLife() { + // These are order-dependant Network = new NetworkManager(this); + Settings = new Settings(this); Parcels = new ParcelManager(this); Self = new MainAvatar(this); Avatars = new AvatarManager(this); Grid = new GridManager(this); Objects = new ObjectManager(this); Groups = new GroupManager(this); - Assets = new AssetManager(this); Images = new ImageManager(this); Inventory = new InventoryManager(this); - Throttle = new AgentThrottle(this); - Settings = new Settings(this); } /// diff --git a/libsecondlife-cs/Settings.cs b/libsecondlife-cs/Settings.cs index 361cc325..2d5f7d82 100644 --- a/libsecondlife-cs/Settings.cs +++ b/libsecondlife-cs/Settings.cs @@ -47,6 +47,9 @@ namespace libsecondlife /// we assume the sequence number just rolls over? Or maybe the /// protocol isn't able to sustain a connection past that public readonly int MAX_SEQUENCE = 0xFFFFFF; + /// Number of milliseconds before a teleport attempt will time + /// out + public readonly int TELEPORT_TIMEOUT = 18 * 1000; /// The maximum size of the sequence number inbox, used to /// check for resent and/or duplicate packets diff --git a/libsecondlife-cs/examples/IA_InventoryManager/iManager.cs b/libsecondlife-cs/examples/IA_InventoryManager/iManager.cs index 1bb409e8..32b7de0d 100644 --- a/libsecondlife-cs/examples/IA_InventoryManager/iManager.cs +++ b/libsecondlife-cs/examples/IA_InventoryManager/iManager.cs @@ -60,7 +60,7 @@ namespace IA_InventoryManager { _Client = new SecondLife(); _Client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected); - _Client.Self.OnTeleport += new TeleportCallback(Self_OnTeleport); + _Client.Self.OnTeleport += new MainAvatar.TeleportCallback(Self_OnTeleport); } catch (Exception e) { @@ -212,7 +212,7 @@ namespace IA_InventoryManager } - void Self_OnTeleport(Simulator currentSim, string message, TeleportStatus status) + void Self_OnTeleport(Simulator currentSim, string message, MainAvatar.TeleportStatus status) { Console.WriteLine("Teleport Completed"); StandUpStraight(); diff --git a/libsecondlife-cs/examples/IA_TestAsyncImage/IA_TestAsyncImage.cs b/libsecondlife-cs/examples/IA_TestAsyncImage/IA_TestAsyncImage.cs index 1d32e162..87828249 100644 --- a/libsecondlife-cs/examples/IA_TestAsyncImage/IA_TestAsyncImage.cs +++ b/libsecondlife-cs/examples/IA_TestAsyncImage/IA_TestAsyncImage.cs @@ -57,19 +57,19 @@ namespace IA_TestAsyncImage private void Objects_OnNewAvatar(Simulator simulator, Avatar avatar, ulong regionHandle, ushort timeDilation) { - if (avatar.FirstLifeImage != null) + if (avatar.ProfileProperties.FirstLifeImage != null) { - if (_Client.Images.isCachedImage(avatar.FirstLifeImage) == false) + if (_Client.Images.isCachedImage(avatar.ProfileProperties.FirstLifeImage) == false) { - _Client.Images.RequestImageAsync(avatar.FirstLifeImage); + _Client.Images.RequestImageAsync(avatar.ProfileProperties.FirstLifeImage); } } - if (avatar.ProfileImage != null) + if (avatar.ProfileProperties.ProfileImage != null) { - if (_Client.Images.isCachedImage(avatar.FirstLifeImage) == false) + if (_Client.Images.isCachedImage(avatar.ProfileProperties.FirstLifeImage) == false) { - _Client.Images.RequestImageAsync(avatar.ProfileImage); + _Client.Images.RequestImageAsync(avatar.ProfileProperties.ProfileImage); } } diff --git a/libsecondlife-cs/examples/Teleport/Teleport.cs b/libsecondlife-cs/examples/Teleport/Teleport.cs index 2fa19810..2fd9ef8d 100644 --- a/libsecondlife-cs/examples/Teleport/Teleport.cs +++ b/libsecondlife-cs/examples/Teleport/Teleport.cs @@ -136,7 +136,7 @@ namespace Teleport } } - Client.Self.OnTeleport += new TeleportCallback(Self_OnTeleport); + Client.Self.OnTeleport += new MainAvatar.TeleportCallback(Self_OnTeleport); DoneTeleporting = false; Client.Self.Teleport(RegionHandle, coords); @@ -147,11 +147,11 @@ namespace Teleport } } - void Self_OnTeleport(Simulator currentSim, string message, TeleportStatus status) + void Self_OnTeleport(Simulator currentSim, string message, MainAvatar.TeleportStatus status) { Console.WriteLine(message); - if (status == TeleportStatus.Finished || status == TeleportStatus.Failed) + if (status == MainAvatar.TeleportStatus.Finished || status == MainAvatar.TeleportStatus.Failed) { DoneTeleporting = true; } diff --git a/libsecondlife-cs/examples/TestClient/Commands/EchoMasterCommand.cs b/libsecondlife-cs/examples/TestClient/Commands/EchoMasterCommand.cs index 319d2cbe..adfee52b 100644 --- a/libsecondlife-cs/examples/TestClient/Commands/EchoMasterCommand.cs +++ b/libsecondlife-cs/examples/TestClient/Commands/EchoMasterCommand.cs @@ -24,13 +24,13 @@ namespace libsecondlife.TestClient if (!Active) { Active = true; - Client.Self.OnChat += new ChatCallback(Self_OnChat); + Client.Self.OnChat += new MainAvatar.ChatCallback(Self_OnChat); return "Echoing is now on."; } else { Active = false; - Client.Self.OnChat -= new ChatCallback(Self_OnChat); + Client.Self.OnChat -= new MainAvatar.ChatCallback(Self_OnChat); return "Echoing is now off."; } } diff --git a/libsecondlife-cs/examples/TestClient/Commands/ExportCommand.cs b/libsecondlife-cs/examples/TestClient/Commands/ExportCommand.cs index b3e06ff4..f165446d 100644 --- a/libsecondlife-cs/examples/TestClient/Commands/ExportCommand.cs +++ b/libsecondlife-cs/examples/TestClient/Commands/ExportCommand.cs @@ -99,6 +99,7 @@ namespace libsecondlife.TestClient XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; XmlWriter writer = XmlWriter.Create(file, settings); + try { List prims = new List(); diff --git a/libsecondlife-cs/examples/TestClient/Commands/ImportCommand.cs b/libsecondlife-cs/examples/TestClient/Commands/ImportCommand.cs index 6632d334..75bdedbe 100644 --- a/libsecondlife-cs/examples/TestClient/Commands/ImportCommand.cs +++ b/libsecondlife-cs/examples/TestClient/Commands/ImportCommand.cs @@ -74,9 +74,9 @@ namespace libsecondlife.TestClient prims.Add(prim.LocalID, prim); } } - catch (Exception ex) + catch (Exception) { - return "Deserialize failed: " + ex.ToString(); + return "Failed to import the object XML file, maybe it doesn't exist or is in the wrong format?"; } if (!registeredCreateEvent) diff --git a/libsecondlife-cs/examples/TestClient/Commands/ImportOutfitCommand.cs b/libsecondlife-cs/examples/TestClient/Commands/ImportOutfitCommand.cs index 922d7d65..aee1ee15 100644 --- a/libsecondlife-cs/examples/TestClient/Commands/ImportOutfitCommand.cs +++ b/libsecondlife-cs/examples/TestClient/Commands/ImportOutfitCommand.cs @@ -32,6 +32,7 @@ namespace libsecondlife.TestClient XmlReader reader = XmlReader.Create(args[0]); XmlSerializer serializer = new XmlSerializer(typeof(Packet)); AvatarAppearancePacket appearance = (AvatarAppearancePacket)serializer.Deserialize(reader); + reader.Close(); AgentSetAppearancePacket set = new AgentSetAppearancePacket(); @@ -58,9 +59,9 @@ namespace libsecondlife.TestClient Client.Network.SendPacket(set); } - catch (Exception e) + catch (Exception) { - return e.ToString(); + return "Failed to import the appearance XML file, maybe it doesn't exist or is in the wrong format?"; } return "Imported " + args[0] + " and sent an AgentSetAppearance packet"; diff --git a/libsecondlife-cs/examples/TestClient/TestClient.cs b/libsecondlife-cs/examples/TestClient/TestClient.cs index 0ce32a6e..11ce5c0b 100644 --- a/libsecondlife-cs/examples/TestClient/TestClient.cs +++ b/libsecondlife-cs/examples/TestClient/TestClient.cs @@ -54,7 +54,7 @@ namespace libsecondlife.TestClient Objects.OnObjectKilled += new ObjectManager.KillObjectCallback(Objects_OnObjectKilled); Objects.OnNewAvatar += new ObjectManager.NewAvatarCallback(Objects_OnNewAvatar); Objects.OnAvatarMoved += new ObjectManager.AvatarMovedCallback(Objects_OnAvatarMoved); - Self.OnInstantMessage += new InstantMessageCallback(Self_OnInstantMessage); + Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(Self_OnInstantMessage); Network.RegisterCallback(PacketType.AvatarAppearance, new NetworkManager.PacketCallback(AvatarAppearanceHandler)); diff --git a/libsecondlife-cs/examples/groupmanager/frmGroupInfo.Designer.cs b/libsecondlife-cs/examples/groupmanager/frmGroupInfo.Designer.cs index e09fe0a3..89bb4ee9 100644 --- a/libsecondlife-cs/examples/groupmanager/frmGroupInfo.Designer.cs +++ b/libsecondlife-cs/examples/groupmanager/frmGroupInfo.Designer.cs @@ -731,7 +731,6 @@ namespace groupmanager this.ShowInTaskbar = false; this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; this.Text = "Group Information"; - this.Load += new System.EventHandler(this.frmGroupInfo_Load); this.tabs.ResumeLayout(false); this.tabGeneral.ResumeLayout(false); this.tabGeneral.PerformLayout(); diff --git a/libsecondlife-cs/examples/groupmanager/frmGroupInfo.cs b/libsecondlife-cs/examples/groupmanager/frmGroupInfo.cs index 054cfa21..b80da985 100644 --- a/libsecondlife-cs/examples/groupmanager/frmGroupInfo.cs +++ b/libsecondlife-cs/examples/groupmanager/frmGroupInfo.cs @@ -11,29 +11,44 @@ using libsecondlife.AssetSystem; namespace groupmanager { + public class GroupMemberData + { + public LLUUID ID; + public string Name; + public string Title; + public string LastOnline; + public ulong Powers; + public bool IsOwner; + public int Contribution; + } + public partial class frmGroupInfo : Form { Group Group; SecondLife Client; - GroupProfile Profile; - Dictionary Members; - Dictionary Titles; - Dictionary MemberData; - Dictionary Names; + GroupProfile Profile = new GroupProfile(); + Dictionary Members = new Dictionary(); + Dictionary Titles = new Dictionary(); + Dictionary MemberData = new Dictionary(); + Dictionary Names = new Dictionary(); public frmGroupInfo(Group group, SecondLife client) { + InitializeComponent(); + + while (!IsHandleCreated) + { + // Force handle creation + IntPtr temp = Handle; + } + Group = group; Client = client; - Profile = new GroupProfile(); - MemberData = new Dictionary(); - Names = new Dictionary(); - InitializeComponent(); - } + Client.Avatars.OnAvatarNames += new AvatarManager.AvatarNamesCallback(AvatarNamesHandler); + + // Request the group information - private void frmGroupInfo_Load(object sender, EventArgs e) - { Client.Groups.BeginGetGroupProfile(Group.ID, new GroupManager.GroupProfileCallback(GroupProfileHandler)); @@ -50,8 +65,6 @@ namespace groupmanager Invoke(new MethodInvoker(UpdateProfile)); - // Waterdrop: new LLUUID("c77a1c21-e604-7d2c-2c89-5539ce853466") - byte[] j2cdata; if (Group.InsigniaID != null) { @@ -59,7 +72,7 @@ namespace groupmanager } else { - // TODO: Add somekind of + // ??? j2cdata = Client.Images.RequestImage("c77a1c21-e604-7d2c-2c89-5539ce853466"); } @@ -84,10 +97,10 @@ namespace groupmanager numFee.Value = Profile.MembershipFee; chkMature.Checked = Profile.MaturePublish; - Client.Avatars.BeginGetAvatarName(Profile.FounderID, new AvatarManager.AgentNamesCallback(AgentNamesHandler)); + Client.Avatars.RequestAvatarName(Profile.FounderID); } - private void AgentNamesHandler(Dictionary names) + private void AvatarNamesHandler(Dictionary names) { lock (Names) { @@ -102,8 +115,6 @@ namespace groupmanager private void UpdateNames() { - GroupMemberData member; - lock (Names) { if (Profile.FounderID != null && Names.ContainsKey(Profile.FounderID)) @@ -120,8 +131,7 @@ namespace groupmanager MemberData[name.Key] = new GroupMemberData(); } - member = MemberData[name.Key]; - member.Name = name.Value; + MemberData[name.Key].Name = name.Value; } } } @@ -209,7 +219,7 @@ namespace groupmanager } } - Client.Avatars.BeginGetAvatarNames(requestids, new AvatarManager.AgentNamesCallback(AgentNamesHandler)); + Client.Avatars.RequestAvatarNames(requestids); } private void GroupTitlesHandler(Dictionary titles) @@ -223,25 +233,5 @@ namespace groupmanager { ; } - - //private void BytesToFile(byte[] bytes, string filename) - //{ - // FileStream filestream = new FileStream(filename, FileMode.Create); - // BinaryWriter writer = new BinaryWriter(filestream); - // writer.Write(bytes); - // writer.Close(); - // filestream.Close(); - //} - } - - public class GroupMemberData - { - public LLUUID ID; - public string Name; - public string Title; - public string LastOnline; - public ulong Powers; - public bool IsOwner; - public int Contribution; } } diff --git a/libsecondlife-cs/examples/primexport/frmPrimExport.cs b/libsecondlife-cs/examples/primexport/frmPrimExport.cs index 4a1cc851..f03b9deb 100644 --- a/libsecondlife-cs/examples/primexport/frmPrimExport.cs +++ b/libsecondlife-cs/examples/primexport/frmPrimExport.cs @@ -299,7 +299,7 @@ namespace primexport InitializeComponent(); client = new SecondLife(); - client.OnLogMessage += new LogCallback(client_OnLogMessage); + client.OnLogMessage += new SecondLife.LogCallback(client_OnLogMessage); client.Objects.RequestAllObjects = true; client.Objects.OnNewPrim += new ObjectManager.NewPrimCallback(PrimSeen); client.Objects.OnNewAvatar += new ObjectManager.NewAvatarCallback(AvatarSeen); diff --git a/libsecondlife-cs/libsecondlife.Tests/Tests.cs b/libsecondlife-cs/libsecondlife.Tests/NetworkTests.cs similarity index 76% rename from libsecondlife-cs/libsecondlife.Tests/Tests.cs rename to libsecondlife-cs/libsecondlife.Tests/NetworkTests.cs index 1b0ba5b5..2dc5791a 100644 --- a/libsecondlife-cs/libsecondlife.Tests/Tests.cs +++ b/libsecondlife-cs/libsecondlife.Tests/NetworkTests.cs @@ -1,209 +1,225 @@ -using System; -using System.Collections.Generic; -using System.Net; -using libsecondlife; -using libsecondlife.Packets; -using NUnit.Framework; - -namespace libsecondlife.Tests -{ - [TestFixture] - public class NetworkTests : Assert - { - SecondLife Client; - ulong CurrentRegionHandle = 0; - ulong AhernRegionHandle = 1096213093149184; - ulong MorrisRegionHandle = 1096213093149183; - bool DetectedObject = false; - bool DoneTeleporting = false; - TeleportStatus tpStatus = TeleportStatus.None; - string tpMessage = ""; - - public NetworkTests() - { - Client = new SecondLife(); - - //string startLoc = NetworkManager.StartLocation("hooper", 128, 128, 32); - - // Register callbacks - Client.Network.RegisterCallback(PacketType.ObjectUpdate, new NetworkManager.PacketCallback(ObjectUpdateHandler)); - Client.Self.OnTeleport += new TeleportCallback(OnTeleportHandler); - - Client.Network.Login("Testing", "Anvil", "testinganvil", "Unit Test Framework", //startLoc, - "contact@libsecondlife.org"/*, false*/); - } - - ~NetworkTests() - { - Client.Network.Logout(); - } - - [SetUp] - public void Init() - { - Assert.IsTrue(Client.Network.Connected, "Client is not connected to the grid: " + Client.Network.LoginError); - - int start = Environment.TickCount; - while (Client.Network.CurrentSim.Region.Name == "") - { - if (Environment.TickCount - start > 5000) - { - Assert.Fail("Timeout waiting for a RegionHandshake packet"); - } - } - - //Assert.AreEqual("ahern", Client.Network.CurrentSim.Region.Name.ToLower(), "Logged in to sim " + - // Client.Network.CurrentSim.Region.Name + " instead of Ahern"); - } - - [Test] - public void DetectObjects() - { - int start = Environment.TickCount; - while (!DetectedObject) - { - if (Environment.TickCount - start > 10000) - { - Assert.Fail("Timeout waiting for an ObjectUpdate packet"); - return; - } - } - } - - [Test] - public void U64Receive() - { - int start = Environment.TickCount; - while (CurrentRegionHandle == 0) - { - if (Environment.TickCount - start > 10000) - { - Assert.Fail("Timeout waiting for an ObjectUpdate packet"); - return; - } - } - - Assert.IsTrue(CurrentRegionHandle == AhernRegionHandle, "Current region is " + - CurrentRegionHandle + " when we were expecting " + AhernRegionHandle + ", possible endian issue"); - } - - [Test] - public void Teleport() - { - DoneTeleporting = false; - tpStatus = TeleportStatus.None; - - Client.Self.Teleport(MorrisRegionHandle, new LLVector3(128, 128, 32)); - - int start = Environment.TickCount; - - while (!DoneTeleporting) - { - System.Threading.Thread.Sleep(100); - - if (Environment.TickCount - start > 10000) - { - Assert.Fail("Timeout waiting for the first teleport to finish"); - return; - } - } - - Assert.IsTrue(tpStatus == TeleportStatus.Finished, "Teleport status is " + tpStatus.ToString() + - ", message=" + tpMessage); - - // Wait for the region information to come in - start = Environment.TickCount; - while (Client.Network.CurrentSim.Region.Name == "") - { - if (Environment.TickCount - start > 5000) - { - Assert.Fail("Timeout waiting for a RegionHandshake packet"); - } - } - - // Assert that we really did make it to our scheduled destination - Assert.AreEqual("morris", Client.Network.CurrentSim.Region.Name.ToLower(), - "Expected to teleport to Morris, ended up in " + Client.Network.CurrentSim.Region.Name + - ". Possibly region full or offline?"); - - /////////////////////////////////////////////////////////////////// - - // TODO: Add a local region teleport - - /////////////////////////////////////////////////////////////////// - - DoneTeleporting = false; - tpStatus = TeleportStatus.None; - - Client.Self.Teleport(AhernRegionHandle, new LLVector3(128, 128, 32)); - - start = Environment.TickCount; - - while (!DoneTeleporting) - { - System.Threading.Thread.Sleep(100); - - if (Environment.TickCount - start > 10000) - { - Assert.Fail("Timeout waiting for the second teleport to finish"); - return; - } - } - - Assert.IsTrue(tpStatus == TeleportStatus.Finished, "Teleport status is " + tpStatus.ToString() + - ", message=" + tpMessage); - - // Wait for the region information to come in - start = Environment.TickCount; - while (Client.Network.CurrentSim.Region.Name == "") - { - if (Environment.TickCount - start > 5000) - { - Assert.Fail("Timeout waiting for a RegionHandshake packet"); - } - } - - // Assert that we really did make it to our scheduled destination - Assert.AreEqual("ahern", Client.Network.CurrentSim.Region.Name.ToLower(), - "Expected to teleport to Ahern, ended up in " + Client.Network.CurrentSim.Region.Name + - ". Possibly region full or offline?"); - } - - private void ObjectUpdateHandler(Packet packet, Simulator sim) - { - ObjectUpdatePacket update = (ObjectUpdatePacket)packet; - - DetectedObject = true; - CurrentRegionHandle = update.RegionData.RegionHandle; - } - - private void OnTeleportHandler(Simulator currentSim, string message, TeleportStatus status) - { - switch (status) - { - case TeleportStatus.None: - break; - case TeleportStatus.Start: - break; - case TeleportStatus.Progress: - break; - case TeleportStatus.Failed: - DoneTeleporting = true; - break; - case TeleportStatus.Finished: - DoneTeleporting = true; - break; - } - - tpMessage = message; - tpStatus = status; - } - - [TearDown] - public void Shutdown() - { - //Client.Network.Logout(); - //Client = null; - } - } -} +using System; +using System.Collections.Generic; +using System.Net; +using libsecondlife; +using libsecondlife.Packets; +using libsecondlife.Utilities; +using NUnit.Framework; + +namespace libsecondlife.Tests +{ + [TestFixture] + public class NetworkTests : Assert + { + SecondLife Client; + + ulong CurrentRegionHandle = 0; + ulong AhernRegionHandle = 1096213093149184; + ulong MorrisRegionHandle = 1096213093149183; + bool DetectedObject = false; + bool DoneTeleporting = false; + MainAvatar.TeleportStatus tpStatus = MainAvatar.TeleportStatus.None; + string tpMessage = ""; + + LLUUID LookupKey1 = new LLUUID("25472683cb324516904a6cd0ecabf128"); + string LookupName1 = "Bot Ringo"; + + public NetworkTests() + { + Client = new SecondLife(); + + string startLoc = NetworkManager.StartLocation("hooper", 128, 128, 32); + + // Register callbacks + Client.Network.RegisterCallback(PacketType.ObjectUpdate, new NetworkManager.PacketCallback(ObjectUpdateHandler)); + Client.Self.OnTeleport += new MainAvatar.TeleportCallback(OnTeleportHandler); + + Client.Network.Login("Testing", "Anvil", "testinganvil", "Unit Test Framework", startLoc, + "contact@libsecondlife.org", false); + } + + ~NetworkTests() + { + Client.Network.Logout(); + } + + [SetUp] + public void Init() + { + Assert.IsTrue(Client.Network.Connected, "Client is not connected to the grid: " + Client.Network.LoginError); + + int start = Environment.TickCount; + while (Client.Network.CurrentSim.Region.Name == "") + { + if (Environment.TickCount - start > 5000) + { + Assert.Fail("Timeout waiting for a RegionHandshake packet"); + } + } + + //Assert.AreEqual("ahern", Client.Network.CurrentSim.Region.Name.ToLower(), "Logged in to sim " + + // Client.Network.CurrentSim.Region.Name + " instead of Ahern"); + } + + [Test] + public void DetectObjects() + { + int start = Environment.TickCount; + while (!DetectedObject) + { + if (Environment.TickCount - start > 10000) + { + Assert.Fail("Timeout waiting for an ObjectUpdate packet"); + return; + } + } + } + + [Test] + public void U64Receive() + { + int start = Environment.TickCount; + while (CurrentRegionHandle == 0) + { + if (Environment.TickCount - start > 10000) + { + Assert.Fail("Timeout waiting for an ObjectUpdate packet"); + return; + } + } + + Assert.IsTrue(CurrentRegionHandle == AhernRegionHandle, "Current region is " + + CurrentRegionHandle + " when we were expecting " + AhernRegionHandle + ", possible endian issue"); + } + + [Test] + public void NameLookup() + { + AvatarTracker tracker = new AvatarTracker(Client); + + string name = tracker.GetAvatarName(LookupKey1); + + Assert.IsTrue(name == LookupName1, "AvatarTracker.GetAvatarName() returned " + name + + " instead of " + LookupName1); + } + + [Test] + public void Teleport() + { + DoneTeleporting = false; + tpStatus = MainAvatar.TeleportStatus.None; + + Client.Self.Teleport(MorrisRegionHandle, new LLVector3(128, 128, 32)); + + int start = Environment.TickCount; + + while (!DoneTeleporting) + { + System.Threading.Thread.Sleep(100); + + if (Environment.TickCount - start > 10000) + { + Assert.Fail("Timeout waiting for the first teleport to finish"); + return; + } + } + + Assert.IsTrue(tpStatus == MainAvatar.TeleportStatus.Finished, + "Teleport status is " + tpStatus.ToString() + ", message=" + tpMessage); + + // Wait for the region information to come in + start = Environment.TickCount; + while (Client.Network.CurrentSim.Region.Name == "") + { + if (Environment.TickCount - start > 5000) + { + Assert.Fail("Timeout waiting for a RegionHandshake packet"); + } + } + + // Assert that we really did make it to our scheduled destination + Assert.AreEqual("morris", Client.Network.CurrentSim.Region.Name.ToLower(), + "Expected to teleport to Morris, ended up in " + Client.Network.CurrentSim.Region.Name + + ". Possibly region full or offline?"); + + /////////////////////////////////////////////////////////////////// + + // TODO: Add a local region teleport + + /////////////////////////////////////////////////////////////////// + + DoneTeleporting = false; + tpStatus = MainAvatar.TeleportStatus.None; + + Client.Self.Teleport(AhernRegionHandle, new LLVector3(128, 128, 32)); + + start = Environment.TickCount; + + while (!DoneTeleporting) + { + System.Threading.Thread.Sleep(100); + + if (Environment.TickCount - start > 10000) + { + Assert.Fail("Timeout waiting for the second teleport to finish"); + return; + } + } + + Assert.IsTrue(tpStatus == MainAvatar.TeleportStatus.Finished, "Teleport status is " + + tpStatus.ToString() + ", message=" + tpMessage); + + // Wait for the region information to come in + start = Environment.TickCount; + while (Client.Network.CurrentSim.Region.Name == "") + { + if (Environment.TickCount - start > 5000) + { + Assert.Fail("Timeout waiting for a RegionHandshake packet"); + } + } + + // Assert that we really did make it to our scheduled destination + Assert.AreEqual("ahern", Client.Network.CurrentSim.Region.Name.ToLower(), + "Expected to teleport to Ahern, ended up in " + Client.Network.CurrentSim.Region.Name + + ". Possibly region full or offline?"); + } + + private void ObjectUpdateHandler(Packet packet, Simulator sim) + { + ObjectUpdatePacket update = (ObjectUpdatePacket)packet; + + DetectedObject = true; + CurrentRegionHandle = update.RegionData.RegionHandle; + } + + private void OnTeleportHandler(Simulator currentSim, string message, MainAvatar.TeleportStatus status) + { + switch (status) + { + case MainAvatar.TeleportStatus.None: + break; + case MainAvatar.TeleportStatus.Start: + break; + case MainAvatar.TeleportStatus.Progress: + break; + case MainAvatar.TeleportStatus.Failed: + DoneTeleporting = true; + break; + case MainAvatar.TeleportStatus.Finished: + DoneTeleporting = true; + break; + } + + tpMessage = message; + tpStatus = status; + } + + [TearDown] + public void Shutdown() + { + //Client.Network.Logout(); + //Client = null; + } + } +} diff --git a/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.csproj b/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.csproj index c6efde47..cecef19d 100644 --- a/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.csproj +++ b/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.csproj @@ -40,13 +40,17 @@ - + {D9CDEDFB-8169-4B03-B57F-0DF638F044EC} libsecondlife + + {CE5E06C2-2428-416B-ADC1-F1FE16A0FB27} + libsecondlife.Utilities + diff --git a/libsecondlife-cs/libsecondlife.Utilities/Utilities.cs b/libsecondlife-cs/libsecondlife.Utilities/Utilities.cs index c0c79d61..f9705f69 100644 --- a/libsecondlife-cs/libsecondlife.Utilities/Utilities.cs +++ b/libsecondlife-cs/libsecondlife.Utilities/Utilities.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Threading; using libsecondlife; +using libsecondlife.Packets; namespace libsecondlife.Utilities { @@ -8,19 +10,174 @@ namespace libsecondlife.Utilities /// Keeps an up to date inventory of the currently seen objects in each /// simulator /// - public class ObjectTracker - { - private SecondLife Client; - private Dictionary> SimPrims = new Dictionary>(); + //public class ObjectTracker + //{ + // private SecondLife Client; + // private Dictionary> SimPrims = new Dictionary>(); - /// - /// Default constructor - /// - /// A reference to the SecondLife client to track - /// objects for - public ObjectTracker(SecondLife client) + // /// + // /// Default constructor + // /// + // /// A reference to the SecondLife client to track + // /// objects for + // public ObjectTracker(SecondLife client) + // { + // Client = client; + // } + //} + + /// + /// Maintains a cache of avatars and does blocking lookups for avatar data + /// + public class AvatarTracker + { + protected SecondLife Client; + protected Dictionary avatars = new Dictionary(); + protected Dictionary NameLookupEvents = new Dictionary(); + + public AvatarTracker(SecondLife client) { Client = client; + + Client.Avatars.OnAvatarNames += new AvatarManager.AvatarNamesCallback(Avatars_OnAvatarNames); + } + + /// + /// Check if a particular avatar is in the local cache + /// + /// + /// + public bool Contains(LLUUID id) + { + return avatars.ContainsKey(id); + } + + /// + /// Get an avatar's name, either from the cache or request it. + /// This function is blocking + /// + /// Avatar key to look up + /// The avatar name, or String.Empty if the lookup failed + public string GetAvatarName(LLUUID id) + { + // Short circuit the cache lookup in GetAvatarNames + if (Contains(id)) + return LocalAvatarNameLookup(id); + + // Add to the dictionary + lock (NameLookupEvents) + NameLookupEvents.Add(id, new ManualResetEvent(false)); + + // Call function + Client.Avatars.RequestAvatarName(id); + + // Start blocking while we wait for this name to be fetched + NameLookupEvents[id].WaitOne(5000, false); + + // Clean up + lock (NameLookupEvents) + NameLookupEvents.Remove(id); + + // Return + return LocalAvatarNameLookup(id); + } + + /// + /// + /// + /// + //public void BeginGetAvatarName(LLUUID id) + //{ + // // TODO: BeginGetAvatarNames is pretty bulky, rewrite a simple version here + + // List ids = new List(); + // ids.Add(id); + // BeginGetAvatarNames(ids); + //} + + /// + /// + /// + /// + //public void BeginGetAvatarNames(List ids) + //{ + // Dictionary havenames = new Dictionary(); + // List neednames = new List(); + + // // Fire callbacks for the ones we already have cached + // foreach (LLUUID id in ids) + // { + // if (Avatars.ContainsKey(id)) + // { + // havenames[id] = Avatars[id].Name; + // //Short circuit the lookup process + // if (ManualResetEvents.ContainsKey(id)) + // { + // ManualResetEvents[id].Set(); + // return; + // } + // } + // else + // { + // neednames.Add(id); + // } + // } + + // if (havenames.Count > 0 && OnAgentNames != null) + // { + // OnAgentNames(havenames); + // } + + // if (neednames.Count > 0) + // { + // UUIDNameRequestPacket request = new UUIDNameRequestPacket(); + + // request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[neednames.Count]; + + // for (int i = 0; i < neednames.Count; i++) + // { + // request.UUIDNameBlock[i] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); + // request.UUIDNameBlock[i].ID = neednames[i]; + // } + + // Client.Network.SendPacket(request); + // } + //} + + /// + /// This function will only check if the avatar name exists locally, + /// it will not do any networking calls to fetch the name + /// + /// The avatar name, or an empty string if it's not found + protected string LocalAvatarNameLookup(LLUUID id) + { + lock (avatars) + { + if (avatars.ContainsKey(id)) + return avatars[id].Name; + else + return String.Empty; + } + } + + private void Avatars_OnAvatarNames(Dictionary names) + { + lock (avatars) + { + foreach (KeyValuePair kvp in names) + { + if (!avatars.ContainsKey(kvp.Key) || avatars[kvp.Key] == null) + avatars[kvp.Key] = new Avatar(); + + avatars[kvp.Key].Name = kvp.Value; + + lock (NameLookupEvents) + { + if (NameLookupEvents.ContainsKey(kvp.Key)) + NameLookupEvents[kvp.Key].Set(); + } + } + } } } }