/* * Copyright (c) 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.Text; using libsecondlife.Packets; namespace libsecondlife { /// /// This class is used to add and remove avatars from your friends list and to manage their permission. /// public class FriendsManager { [Flags] public enum RightsFlags : int { /// The avatar has no rights None = 0, /// The avatar can see the online status of the target avatar CanSeeOnline = 1, /// The avatar can see the location of the target avatar on the map CanSeeOnMap = 2, /// The avatar can modify the ojects of the target avatar CanModifyObjects = 4 } /// /// This class holds information about an avatar in the friends list. There are two ways /// to interface to this class. The first is through the set of boolean properties. This is the typical /// way clients of this class will use it. The second interface is through two bitmap properties. While /// the bitmap interface is public, it is intended for use the libsecondlife framework. /// public class FriendInfo { private LLUUID m_id; private string m_name; private bool m_isOnline; private bool m_canSeeMeOnline; private bool m_canSeeMeOnMap; private bool m_canModifyMyObjects; private bool m_canSeeThemOnline; private bool m_canSeeThemOnMap; private bool m_canModifyTheirObjects; /// /// Used by the libsecondlife framework when building the initial list of friends /// at login time. This constructor should not be called by consummer of this class. /// /// System ID of the avatar being prepesented /// Rights the friend has to see you online and to modify your objects /// Rights you have to see your friend online and to modify their objects public FriendInfo(LLUUID id, RightsFlags theirRights, RightsFlags myRights) { m_id = id; m_canSeeMeOnline = (theirRights & RightsFlags.CanSeeOnline) != 0; m_canSeeMeOnMap = (theirRights & RightsFlags.CanSeeOnMap) != 0; m_canModifyMyObjects = (theirRights & RightsFlags.CanModifyObjects) != 0; m_canSeeThemOnline = (myRights & RightsFlags.CanSeeOnline) != 0; m_canSeeThemOnMap = (myRights & RightsFlags.CanSeeOnMap) != 0; m_canModifyTheirObjects = (myRights & RightsFlags.CanModifyObjects) != 0; } /// /// System ID of the avatar /// public LLUUID UUID { get { return m_id; } } /// /// full name of the avatar /// public string Name { get { return m_name; } set { m_name = value; } } /// /// True if the avatar is online /// public bool IsOnline { get { return m_isOnline; } set { m_isOnline = value; } } /// /// True if the friend can see if I am online /// public bool CanSeeMeOnline { get { return m_canSeeMeOnline; } set { m_canSeeMeOnline = value; // if I can't see them online, then I can't see them on the map if (!m_canSeeMeOnline) m_canSeeMeOnMap = false; } } /// /// True if the friend can see me on the map /// public bool CanSeeMeOnMap { get { return m_canSeeMeOnMap; } set { // if I can't see them online, then I can't see them on the map if (m_canSeeMeOnline) m_canSeeMeOnMap = value; } } /// /// True if the freind can modify my objects /// public bool CanModifyMyObjects { get { return m_canModifyMyObjects; } set { m_canModifyMyObjects = value; } } /// /// True if I can see if my friend is online /// public bool CanSeeThemOnline { get { return m_canSeeThemOnline; } } /// /// True if I can see if my friend is on the map /// public bool CanSeeThemOnMap { get { return m_canSeeThemOnMap; } } /// /// True if I can modify my friend's objects /// public bool CanModifyTheirObjects { get { return m_canModifyTheirObjects; } } /// /// My friend's rights represented as bitmapped flags /// public RightsFlags TheirRightsFlags { get { RightsFlags results = RightsFlags.None; if (m_canSeeMeOnline) results |= RightsFlags.CanSeeOnline; if (m_canSeeMeOnMap) results |= RightsFlags.CanSeeOnMap; if (m_canModifyMyObjects) results |= RightsFlags.CanModifyObjects; return results; } set { m_canSeeMeOnline = (value & RightsFlags.CanSeeOnline) != 0; m_canSeeMeOnMap = (value & RightsFlags.CanSeeOnMap) != 0; m_canModifyMyObjects = (value & RightsFlags.CanModifyObjects) != 0; } } /// /// My rights represented as bitmapped flags /// public RightsFlags MyRightsFlags { get { RightsFlags results = RightsFlags.None; if (m_canSeeThemOnline) results |= RightsFlags.CanSeeOnline; if (m_canSeeThemOnMap) results |= RightsFlags.CanSeeOnMap; if (m_canModifyTheirObjects) results |= RightsFlags.CanModifyObjects; return results; } set { m_canSeeThemOnline = (value & RightsFlags.CanSeeOnline) != 0; m_canSeeThemOnMap = (value & RightsFlags.CanSeeOnMap) != 0; m_canModifyTheirObjects = (value & RightsFlags.CanModifyObjects) != 0; } } /// /// This class represented as a string. /// /// A string reprentation of both my rights and my friend's righs public override string ToString() { return String.Format("{0} (their rights: {1}, my rights: {2})", m_name, TheirRightsFlags, MyRightsFlags); } } /// /// Triggered when an avatar in your friends list comes online /// /// System ID of the avatar public delegate void FriendOnlineEvent(FriendInfo friend); /// /// Triggered when an avatar in your friends list goes offline /// /// System ID of the avatar public delegate void FriendOfflineEvent(FriendInfo friend); /// /// Triggered in response to a call to the GrantRighs() method, or when a friend changes your rights /// /// System ID of the avatar you changed the right of public delegate void FriendRightsEvent(FriendInfo friend); /// /// Triggered when someone offers you friendship /// /// System ID of the agent offering friendship /// full name of the agent offereing friendship /// session ID need when accepting/declining the offer /// Return true to accept the friendship, false to deny it public delegate bool FriendshipOfferedEvent(LLUUID agentID, string agentName, LLUUID imSessionID); /// /// Trigger when your friendship offer has been excepted /// /// System ID of the avatar who accepted your friendship offer /// Full name of the avatar who accepted your friendship offer /// Whether the friendship request was accepted or declined public delegate void FriendshipResponseEvent(LLUUID agentID, string agentName, bool accepted); public event FriendOnlineEvent OnFriendOnline; public event FriendOfflineEvent OnFriendOffline; public event FriendRightsEvent OnFriendRights; public event FriendshipOfferedEvent OnFriendshipOffered; public event FriendshipResponseEvent OnFriendshipResponse; private SecondLife Client; private Dictionary _Friends = new Dictionary(); /// /// This constructor is intened to for use only the the libsecondlife framework /// /// public FriendsManager(SecondLife client) { Client = client; Client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnect); Client.Avatars.OnAvatarNames += new AvatarManager.AvatarNamesCallback(Avatars_OnAvatarNames); Client.Self.OnInstantMessage += new MainAvatar.InstantMessageCallback(MainAvatar_InstantMessage); Client.Network.RegisterCallback(PacketType.OnlineNotification, OnlineNotificationHandler); Client.Network.RegisterCallback(PacketType.OfflineNotification, OfflineNotificationHandler); Client.Network.RegisterCallback(PacketType.ChangeUserRights, ChangeUserRightsHandler); } /// /// Get a list of all the friends we are currently aware of /// /// /// This function performs a shallow copy from the internal dictionary /// in FriendsManager. Avoid calling it multiple times when it is not /// necessary to as it can be expensive memory-wise /// public List GetFriendsList() { List friends = new List(); lock (_Friends) { foreach (FriendInfo info in _Friends.Values) friends.Add(info); } return friends; } /// /// Offer friendship to an avatar. /// /// System ID of the avatar you are offering friendship to public void OfferFriendship(LLUUID agentID) { // HACK: folder id stored as "message" LLUUID callingCardFolder = Client.Inventory.FindFolderForType(AssetType.CallingCard); Client.Self.InstantMessage(Client.ToString(), agentID, callingCardFolder.ToString(), LLUUID.Random(), MainAvatar.InstantMessageDialog.FriendshipOffered, MainAvatar.InstantMessageOnline.Online, Client.Self.Position, Client.Network.CurrentSim.ID, new byte[0]); } /// /// Terminate a friendship with an avatar /// /// System ID of the avatar you are terminating the friendship with public void TerminateFriendship(LLUUID agentID) { if (_Friends.ContainsKey(agentID)) { TerminateFriendshipPacket request = new TerminateFriendshipPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; request.ExBlock.OtherID = agentID; Client.Network.SendPacket(request); } } /// /// Change the rights of a friend avatar. To use this routine, first change the right of the /// avatar stored in the item property. /// /// System ID of the avatar you are changing the rights of public void GrantRights(LLUUID agentID) { GrantUserRightsPacket request = new GrantUserRightsPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; request.Rights = new GrantUserRightsPacket.RightsBlock[1]; request.Rights[0] = new GrantUserRightsPacket.RightsBlock(); request.Rights[0].AgentRelated = agentID; request.Rights[0].RelatedRights = (int)(_Friends[agentID].TheirRightsFlags); Client.Network.SendPacket(request); } /// /// Adds a friend. Intended for use by the libsecondlife framework to build the /// initial list of friends from the buddy-list in the login reply XML /// /// ID of the agent being added to the list of friends /// rights the friend has /// rights you have internal void AddFriend(LLUUID agentID, RightsFlags theirRights, RightsFlags myRights) { if (!_Friends.ContainsKey(agentID)) { FriendInfo info = new FriendInfo(agentID, theirRights, myRights); _Friends.Add(agentID, info); } } /// /// Called when a connection to the SL server is established. The list of friend avatars /// is populated from XML returned by the login server. That list contains the avatar's id /// and right, but no names. Here is where those names are requested. /// /// private void Network_OnConnect(object sender) { List names = new List(); lock (_Friends) { foreach (KeyValuePair kvp in _Friends) { if (String.IsNullOrEmpty(kvp.Value.Name)) names.Add(kvp.Key); } } Client.Avatars.RequestAvatarNames(names); } /// /// This handles the asynchronous response of a RequestAvatarNames call. /// /// names cooresponding to the the list of IDs sent the the RequestAvatarNames call. private void Avatars_OnAvatarNames(Dictionary names) { lock (_Friends) { foreach (KeyValuePair kvp in names) { if (_Friends.ContainsKey(kvp.Key)) { _Friends[kvp.Key].Name = names[kvp.Key]; //Client.DebugLog(_Friends[kvp.Key].ToString()); } } } } /// /// Handle notifications sent when a friends has come online. /// /// /// private void OnlineNotificationHandler(Packet packet, Simulator simulator) { if (packet.Type == PacketType.OnlineNotification) { OnlineNotificationPacket notification = ((OnlineNotificationPacket)packet); foreach (OnlineNotificationPacket.AgentBlockBlock block in notification.AgentBlock) { bool doNotify = !_Friends[block.AgentID].IsOnline; _Friends[block.AgentID].IsOnline = true; if (OnFriendOnline != null && doNotify) { try { OnFriendOnline(_Friends[block.AgentID]); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } } /// /// Handle notifications sent when a friends has gone offline. /// /// /// private void OfflineNotificationHandler(Packet packet, Simulator simulator) { if (packet.Type == PacketType.OfflineNotification) { OfflineNotificationPacket notification = ((OfflineNotificationPacket)packet); foreach (OfflineNotificationPacket.AgentBlockBlock block in notification.AgentBlock) { _Friends[block.AgentID].IsOnline = false; if (OnFriendOffline != null) { try { OnFriendOffline(_Friends[block.AgentID]); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } } /// /// Handle notifications sent when a friend rights change. This notification is also received /// when my own rights change. /// /// /// private void ChangeUserRightsHandler(Packet packet, Simulator simulator) { if (packet.Type == PacketType.ChangeUserRights) { FriendInfo friend; ChangeUserRightsPacket rights = (ChangeUserRightsPacket)packet; foreach (ChangeUserRightsPacket.RightsBlock block in rights.Rights) { RightsFlags newRights = (RightsFlags)block.RelatedRights; if (_Friends.TryGetValue(block.AgentRelated, out friend)) { friend.TheirRightsFlags = newRights; if (OnFriendRights != null) { try { OnFriendRights(friend); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } else if (block.AgentRelated == Client.Self.ID) { if (_Friends.TryGetValue(rights.AgentData.AgentID, out friend)) { friend.MyRightsFlags = newRights; if (OnFriendRights != null) { try { OnFriendRights(friend); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } } } } /// /// Handles relevant messages from the server encapsulated in instant messages. /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// private void MainAvatar_InstantMessage(LLUUID fromAgentID, string fromAgentName, LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, MainAvatar.InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, MainAvatar.InstantMessageOnline offline, byte[] binaryBucket, Simulator simulator) { if (dialog == MainAvatar.InstantMessageDialog.FriendshipOffered) { if (OnFriendshipOffered != null) { try { if (OnFriendshipOffered(fromAgentID, fromAgentName, imSessionID)) { // Accept the offer LLUUID callingCardFolder = Client.Inventory.FindFolderForType(AssetType.CallingCard); AcceptFriendshipPacket request = new AcceptFriendshipPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; request.TransactionBlock.TransactionID = imSessionID; request.FolderData = new AcceptFriendshipPacket.FolderDataBlock[1]; request.FolderData[0] = new AcceptFriendshipPacket.FolderDataBlock(); request.FolderData[0].FolderID = callingCardFolder; Client.Network.SendPacket(request); FriendInfo friend = new FriendInfo(fromAgentID, RightsFlags.CanSeeOnline, RightsFlags.CanSeeOnline); _Friends.Add(friend.UUID, friend); Client.Avatars.RequestAvatarName(fromAgentID); } else { // Decline the offer DeclineFriendshipPacket request = new DeclineFriendshipPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; request.TransactionBlock.TransactionID = imSessionID; Client.Network.SendPacket(request); } } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } else if (dialog == MainAvatar.InstantMessageDialog.FriendshipAccepted) { FriendInfo friend = new FriendInfo(fromAgentID, RightsFlags.CanSeeOnline, RightsFlags.CanSeeOnline); friend.Name = fromAgentName; _Friends.Add(friend.UUID, friend); if (OnFriendshipResponse != null) { try { OnFriendshipResponse(fromAgentID, fromAgentName, true); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } else if (dialog == MainAvatar.InstantMessageDialog.FriendshipDeclined) { if (OnFriendshipResponse != null) { try { OnFriendshipResponse(fromAgentID, fromAgentName, false); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } } }