/* * Copyright (c) 2006-2007, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Threading; using libsecondlife.Packets; namespace libsecondlife { /// /// Retrieve friend status notifications, and retrieve avatar names and /// profiles /// public class AvatarManager { /// /// Triggered when a UUIDNameReply is received /// /// public delegate void AvatarNamesCallback(Dictionary names); /// /// Triggered when a response for avatar statistics (ratings) is returned /// /// /// // public delegate void AvatarStatisticsCallback(LLUUID avatarID, Avatar.Statistics statistics); /// /// Triggered when a response for avatar interests is returned /// /// /// public delegate void AvatarInterestsCallback(LLUUID avatarID, Avatar.Interests interests); /// /// Triggered when avatar properties are received (AvatarPropertiesReply) /// /// /// public delegate void AvatarPropertiesCallback(LLUUID avatarID, Avatar.AvatarProperties properties); /// /// Triggered when an avatar group list is received (AvatarGroupsReply) /// /// /// public delegate void AvatarGroupsCallback(LLUUID avatarID, AvatarGroupsReplyPacket.GroupDataBlock[] groups); /// /// Triggered when a name search reply is received (AvatarPickerReply) /// /// /// public delegate void AvatarNameSearchCallback(LLUUID queryID, Dictionary avatars); /// /// /// /// /// /// /// /// /// public delegate void PointAtCallback(LLUUID sourceID, LLUUID targetID, LLVector3d targetPos, PointAtType pointType, float duration, LLUUID id); /// /// /// /// /// /// /// /// /// public delegate void LookAtCallback(LLUUID sourceID, LLUUID targetID, LLVector3d targetPos, LookAtType lookType, float duration, LLUUID id); /// /// /// /// /// /// /// /// /// public delegate void EffectCallback(EffectType type, LLUUID sourceID, LLUUID targetID, LLVector3d targetPos, float duration, LLUUID id); /// public event AvatarNamesCallback OnAvatarNames; /// // public event AvatarStatisticsCallback OnAvatarStatistics; /// public event AvatarInterestsCallback OnAvatarInterests; /// public event AvatarPropertiesCallback OnAvatarProperties; /// public event AvatarGroupsCallback OnAvatarGroups; /// public event AvatarNameSearchCallback OnAvatarNameSearch; /// public event PointAtCallback OnPointAt; /// public event LookAtCallback OnLookAt; /// public event EffectCallback OnEffect; private SecondLife Client; /// /// Represents other avatars /// /// public AvatarManager(SecondLife client) { Client = client; // 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)); // Avatar group callback Client.Network.RegisterCallback(PacketType.AvatarGroupsReply, new NetworkManager.PacketCallback(AvatarGroupsHandler)); // Viewer effect callback Client.Network.RegisterCallback(PacketType.ViewerEffect, new NetworkManager.PacketCallback(ViewerEffectHandler)); // Other callbacks Client.Network.RegisterCallback(PacketType.UUIDNameReply, new NetworkManager.PacketCallback(AvatarNameHandler)); Client.Network.RegisterCallback(PacketType.AvatarPickerReply, new NetworkManager.PacketCallback(AvatarPickerReplyHandler)); Client.Network.RegisterCallback(PacketType.AvatarAnimation, new NetworkManager.PacketCallback(AvatarAnimationHandler)); } /// Tracks the specified avatar on your map /// Avatar ID to track public void TrackAvatar(LLUUID preyID) { TrackAgentPacket p = new TrackAgentPacket(); p.AgentData.AgentID = Client.Self.AgentID; p.AgentData.SessionID = Client.Self.SessionID; p.TargetData.PreyID = preyID; Client.Network.SendPacket(p); } /// /// Request a single avatar name /// /// The avatar key to retrieve a name for public void RequestAvatarName(LLUUID id) { 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); } /// /// Request a list of avatar names /// /// The avatar keys to retrieve names for public void RequestAvatarNames(List ids) { 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); } /// /// Start a request for Avatar Properties /// /// public void RequestAvatarProperties(LLUUID avatarid) { AvatarPropertiesRequestPacket aprp = new AvatarPropertiesRequestPacket(); aprp.AgentData.AgentID = Client.Self.AgentID; aprp.AgentData.SessionID = Client.Self.SessionID; aprp.AgentData.AvatarID = avatarid; Client.Network.SendPacket(aprp); } /// /// 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.Self.AgentID; aprp.AgentData.SessionID = Client.Self.SessionID; aprp.AgentData.QueryID = queryID; aprp.Data.Name = Helpers.StringToField(name); Client.Network.SendPacket(aprp); } #region Packet Handlers /// /// 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.FieldToUTF8String(block.FirstName) + " " + Helpers.FieldToUTF8String(block.LastName); } OnAvatarNames(names); } } /// /// Process incoming avatar properties (profile data) /// /// /// private void AvatarPropertiesHandler(Packet packet, Simulator sim) { if (OnAvatarProperties != null) { AvatarPropertiesReplyPacket reply = (AvatarPropertiesReplyPacket)packet; Avatar.AvatarProperties properties = new Avatar.AvatarProperties(); properties.ProfileImage = reply.PropertiesData.ImageID; properties.FirstLifeImage = reply.PropertiesData.FLImageID; properties.Partner = reply.PropertiesData.PartnerID; properties.AboutText = Helpers.FieldToUTF8String(reply.PropertiesData.AboutText); properties.FirstLifeText = Helpers.FieldToUTF8String(reply.PropertiesData.FLAboutText); properties.BornOn = Helpers.FieldToUTF8String(reply.PropertiesData.BornOn); //properties.CharterMember = Helpers.FieldToUTF8String(reply.PropertiesData.CharterMember); uint charter = Helpers.BytesToUInt(reply.PropertiesData.CharterMember); if ( charter == 0 ) { properties.CharterMember = "Resident"; } else if ( charter == 2 ) { properties.CharterMember = "Charter"; } else if ( charter == 3 ) { properties.CharterMember = "Linden"; } else { properties.CharterMember = Helpers.FieldToUTF8String(reply.PropertiesData.CharterMember); } properties.Flags = (Avatar.ProfileFlags)reply.PropertiesData.Flags; properties.ProfileURL = Helpers.FieldToUTF8String(reply.PropertiesData.ProfileURL); OnAvatarProperties(reply.AgentData.AvatarID, properties); } } /// /// Process incoming Avatar Interests information /// private void AvatarInterestsHandler(Packet packet, Simulator simulator) { if (OnAvatarInterests != null) { AvatarInterestsReplyPacket airp = (AvatarInterestsReplyPacket)packet; Avatar.Interests interests = new Avatar.Interests(); interests.WantToMask = airp.PropertiesData.WantToMask; interests.WantToText = Helpers.FieldToUTF8String(airp.PropertiesData.WantToText); interests.SkillsMask = airp.PropertiesData.SkillsMask; interests.SkillsText = Helpers.FieldToUTF8String(airp.PropertiesData.SkillsText); interests.LanguagesText = Helpers.FieldToUTF8String(airp.PropertiesData.LanguagesText); OnAvatarInterests(airp.AgentData.AvatarID, interests); } } private void AvatarGroupsHandler(Packet packet, Simulator simulator) { if (OnAvatarGroups != null) { AvatarGroupsReplyPacket groups = (AvatarGroupsReplyPacket)packet; // FIXME: Build a little struct to represent the groups.GroupData blocks so we keep // libsecondlife.Packets abstracted away OnAvatarGroups(groups.AgentData.AvatarID, groups.GroupData); } } 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.FieldToUTF8String(block.FirstName) + " " + Helpers.FieldToUTF8String(block.LastName); } OnAvatarNameSearch(reply.AgentData.QueryID, avatars); } } /// /// Process an incoming effect /// private void ViewerEffectHandler(Packet packet, Simulator simulator) { ViewerEffectPacket effect = (ViewerEffectPacket)packet; foreach (ViewerEffectPacket.EffectBlock block in effect.Effect) { EffectType type = (EffectType)block.Type; //LLColor color; //if (block.Color.Length == 4) //{ // color = new LLColor(block.Color, 0); //} //else //{ // Client.Log("Received a ViewerEffect.EffectBlock.Color array with " + block.Color.Length + // " bytes", Helpers.LogLevel.Warning); // color = LLColor.Black; //} // Each ViewerEffect type uses it's own custom binary format for additional data. Fun eh? switch (type) { case EffectType.Text: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.Icon: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.Connector: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.FlexibleObject: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.AnimalControls: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.AnimationObject: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.Cloth: Client.Log("Received a ViewerEffect of type " + type.ToString() + ", implement me!", Helpers.LogLevel.Warning); break; case EffectType.Glow: Client.Log("Received a Glow ViewerEffect which is not implemented yet", Helpers.LogLevel.Warning); break; case EffectType.Beam: case EffectType.Point: case EffectType.Trail: case EffectType.Sphere: case EffectType.Spiral: case EffectType.Edit: if (OnEffect != null) { if (block.TypeData.Length == 56) { LLUUID sourceAvatar = new LLUUID(block.TypeData, 0); LLUUID targetObject = new LLUUID(block.TypeData, 16); LLVector3d targetPos = new LLVector3d(block.TypeData, 32); try { OnEffect(type, sourceAvatar, targetObject, targetPos, block.Duration, block.ID); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } else { Client.Log("Received a " + type.ToString() + " ViewerEffect with an incorrect TypeData size of " + block.TypeData.Length + " bytes", Helpers.LogLevel.Warning); } } break; case EffectType.LookAt: if (OnLookAt != null) { if (block.TypeData.Length == 57) { LLUUID sourceAvatar = new LLUUID(block.TypeData, 0); LLUUID targetObject = new LLUUID(block.TypeData, 16); LLVector3d targetPos = new LLVector3d(block.TypeData, 32); LookAtType lookAt = (LookAtType)block.TypeData[56]; try { OnLookAt(sourceAvatar, targetObject, targetPos, lookAt, block.Duration, block.ID); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } else { Client.Log("Received a LookAt ViewerEffect with an incorrect TypeData size of " + block.TypeData.Length + " bytes", Helpers.LogLevel.Warning); } } break; case EffectType.PointAt: if (OnPointAt != null) { if (block.TypeData.Length == 57) { LLUUID sourceAvatar = new LLUUID(block.TypeData, 0); LLUUID targetObject = new LLUUID(block.TypeData, 16); LLVector3d targetPos = new LLVector3d(block.TypeData, 32); PointAtType pointAt = (PointAtType)block.TypeData[56]; try { OnPointAt(sourceAvatar, targetObject, targetPos, pointAt, block.Duration, block.ID); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } else { Client.Log("Received a PointAt ViewerEffect with an incorrect TypeData size of " + block.TypeData.Length + " bytes", Helpers.LogLevel.Warning); } } break; default: Client.Log("Received a ViewerEffect with an unknown type " + type, Helpers.LogLevel.Warning); break; } } } protected void AvatarAnimationHandler(Packet packet, Simulator sim) { //FIXME } #endregion Packet Handlers } }