/* * Copyright (c) 2006, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Threading; using libsecondlife.Packets; namespace libsecondlife { /// /// 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 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); /// 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 AvatarGroupsCallback OnAvatarGroups; /// public event AvatarNameSearchCallback OnAvatarNameSearch; private SecondLife Client; /// /// Represents other avatars /// /// public AvatarManager(SecondLife client) { Client = client; // Friend notification callback NetworkManager.PacketCallback callback = new NetworkManager.PacketCallback(FriendNotificationHandler); Client.Network.RegisterCallback(PacketType.OnlineNotification, callback); Client.Network.RegisterCallback(PacketType.OfflineNotification, callback); // 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)); } /// /// 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.Network.AgentID; aprp.AgentData.SessionID = Client.Network.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.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.FieldToUTF8String(block.FirstName) + " " + Helpers.FieldToUTF8String(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.FieldToUTF8String(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; case "Given": stats.GivenPositive = b.Positive; stats.GivenNegative = 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.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); // FIXME: These have been converted in to a Flags field, build an enum and fix this! //properties.AllowPublish = reply.PropertiesData.AllowPublish; //properties.MaturePublish = reply.PropertiesData.MaturePublish; //properties.Identified = reply.PropertiesData.Identified; //properties.Transacted = reply.PropertiesData.Transacted; 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) { MainAvatar.EffectType type; try { type = (MainAvatar.EffectType)block.Type; } catch (Exception) { Client.Log("Received a ViewerEffect block with an unknown type " + block.Type, Helpers.LogLevel.Warning); continue; } //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 = new LLColor(); //} // Each ViewerEffect type uses it's own custom binary format for additional data. Fun eh? switch (type) { case MainAvatar.EffectType.Text: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Icon: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Connector: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.FlexibleObject: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.AnimalControls: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.AnimationObject: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Cloth: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Beam: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Glow: Client.Log("Received a Glow ViewerEffect which is not implemented yet", Helpers.LogLevel.Warning); break; case MainAvatar.EffectType.Point: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Trail: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Sphere: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Spiral: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.Edit: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.LookAt: //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); break; case MainAvatar.EffectType.PointAt: 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); MainAvatar.PointAtType pointAt; try { pointAt = (MainAvatar.PointAtType)block.TypeData[56]; } catch (Exception) { Client.Log("Unrecognized PointAtType " + block.TypeData[56], Helpers.LogLevel.Warning); pointAt = MainAvatar.PointAtType.Clear; } // TODO: Create a OnAvatarPointAt event and call it here } else { Client.Log("Received a PointAt ViewerEffect with an incorrect TypeData size of " + block.TypeData.Length + " bytes", Helpers.LogLevel.Warning); } break; } } } } }