/* * 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 { /// /// /// 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 AvatarPropertiesCallback(Avatar avatar); /// /// /// /// public delegate void AvatarNameCallback(Avatar avatar); /// /// /// /// public delegate void AvatarStatisticsCallback(Avatar avatar); /// /// /// /// public delegate void AvatarInterestsCallback(Avatar avatar); /// Triggered whenever a friend comes online or goes offline public event FriendNotificationCallback OnFriendNotification; private SecondLife Client; private Dictionary Avatars; private AgentNamesCallback OnAgentNames; private Dictionary AvatarPropertiesCallbacks; private Dictionary AvatarStatisticsCallbacks; private Dictionary AvatarInterestsCallbacks; /// /// /// /// public AvatarManager(SecondLife client) { Client = client; Avatars = new Dictionary(); //Callback Dictionaries AvatarPropertiesCallbacks = new Dictionary(); AvatarStatisticsCallbacks = new Dictionary(); AvatarInterestsCallbacks = 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)); 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)); } /// /// Add an Avatar into the Avatars Dictionary /// /// Filled-out Avatar class to insert public void AddAvatar(Avatar avatar) { lock (Avatars) { Avatars[avatar.ID] = avatar; } } /// /// /// /// /// public bool Contains(LLUUID id) { return Avatars.ContainsKey(id); } /// /// /// /// 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; } /// /// /// /// 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; } 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 (OnAgentNames != null) { OnAgentNames(names); } } 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); } } 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); } } 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); } } public void BeginAvatarPropertiesRequest(LLUUID avatarid, AvatarPropertiesCallback apc, AvatarStatisticsCallback asc, AvatarInterestsCallback aic) { //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! Client.Network.SendPacket(aprp); } private void AvatarInterestsHandler(Packet packet, Simulator simulator) { AvatarInterestsReplyPacket airp = (AvatarInterestsReplyPacket)packet; Avatar av; lock (Avatars) { 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); } if (AvatarInterestsCallbacks.ContainsKey(airp.AgentData.AvatarID) && AvatarInterestsCallbacks[airp.AgentData.AvatarID] != null) AvatarInterestsCallbacks[airp.AgentData.AvatarID](av); } } }