/* * Copyright (c) 2007-2008, openmetaverse.org * 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 openmetaverse.org 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 OpenMetaverse.Packets; namespace OpenMetaverse { /// /// /// [Flags] public enum FriendRights : 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 bitflag properties, /// TheirFriendsRights and MyFriendsRights /// public class FriendInfo { private UUID 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; #region Properties /// /// System ID of the avatar /// public UUID 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 FriendRights TheirFriendRights { get { FriendRights results = FriendRights.None; if (m_canSeeMeOnline) results |= FriendRights.CanSeeOnline; if (m_canSeeMeOnMap) results |= FriendRights.CanSeeOnMap; if (m_canModifyMyObjects) results |= FriendRights.CanModifyObjects; return results; } set { m_canSeeMeOnline = (value & FriendRights.CanSeeOnline) != 0; m_canSeeMeOnMap = (value & FriendRights.CanSeeOnMap) != 0; m_canModifyMyObjects = (value & FriendRights.CanModifyObjects) != 0; } } /// /// My rights represented as bitmapped flags /// public FriendRights MyFriendRights { get { FriendRights results = FriendRights.None; if (m_canSeeThemOnline) results |= FriendRights.CanSeeOnline; if (m_canSeeThemOnMap) results |= FriendRights.CanSeeOnMap; if (m_canModifyTheirObjects) results |= FriendRights.CanModifyObjects; return results; } set { m_canSeeThemOnline = (value & FriendRights.CanSeeOnline) != 0; m_canSeeThemOnMap = (value & FriendRights.CanSeeOnMap) != 0; m_canModifyTheirObjects = (value & FriendRights.CanModifyObjects) != 0; } } #endregion Properties /// /// Used internally when building the initial list of friends at login time /// /// 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 internal FriendInfo(UUID id, FriendRights theirRights, FriendRights myRights) { m_id = id; m_canSeeMeOnline = (theirRights & FriendRights.CanSeeOnline) != 0; m_canSeeMeOnMap = (theirRights & FriendRights.CanSeeOnMap) != 0; m_canModifyMyObjects = (theirRights & FriendRights.CanModifyObjects) != 0; m_canSeeThemOnline = (myRights & FriendRights.CanSeeOnline) != 0; m_canSeeThemOnMap = (myRights & FriendRights.CanSeeOnMap) != 0; m_canModifyTheirObjects = (myRights & FriendRights.CanModifyObjects) != 0; } /// /// FriendInfo represented as a string /// /// A string reprentation of both my rights and my friends rights public override string ToString() { if (!String.IsNullOrEmpty(m_name)) return String.Format("{0} (Their Rights: {1}, My Rights: {2})", m_name, TheirFriendRights, MyFriendRights); else return String.Format("{0} (Their Rights: {1}, My Rights: {2})", m_id, TheirFriendRights, MyFriendRights); } } /// /// This class is used to add and remove avatars from your friends list and to manage their permission. /// public class FriendsManager { #region Delegates /// /// 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 FriendRights() 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 names on the friend list are received after the initial request upon login /// /// public delegate void FriendNamesReceived(Dictionary names); /// /// 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 void FriendshipOfferedEvent(UUID agentID, string agentName, UUID imSessionID); /// /// Trigger when your friendship offer has been accepted or declined /// /// 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(UUID agentID, string agentName, bool accepted); /// /// Trigger when someone terminates your friendship. /// /// System ID of the avatar who terminated your friendship /// Full name of the avatar who terminated your friendship public delegate void FriendshipTerminatedEvent(UUID agentID, string agentName); /// /// Triggered in response to a FindFriend request /// /// Friends Key /// region handle friend is in /// X/Y location of friend public delegate void FriendFoundEvent(UUID agentID, ulong regionHandle, Vector3 location); #endregion Delegates #region Events public event FriendNamesReceived OnFriendNamesReceived; public event FriendOnlineEvent OnFriendOnline; public event FriendOfflineEvent OnFriendOffline; public event FriendRightsEvent OnFriendRights; public event FriendshipOfferedEvent OnFriendshipOffered; public event FriendshipResponseEvent OnFriendshipResponse; public event FriendshipTerminatedEvent OnFriendshipTerminated; public event FriendFoundEvent OnFriendFound; #endregion Events private GridClient Client; /// /// A dictionary of key/value pairs containing known friends of this avatar. /// /// The Key is the of the friend, the value is a /// object that contains detailed information including permissions you have and have given to the friend /// public InternalDictionary FriendList = new InternalDictionary(); /// /// A Dictionary of key/value pairs containing current pending frienship offers. /// /// The key is the of the avatar making the request, /// the value is the of the request which is used to accept /// or decline the friendship offer /// public InternalDictionary FriendRequests = new InternalDictionary(); /// /// Internal constructor /// /// A reference to the GridClient Object internal FriendsManager(GridClient client) { Client = client; Client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnect); Client.Avatars.OnAvatarNames += new AvatarManager.AvatarNamesCallback(Avatars_OnAvatarNames); Client.Self.OnInstantMessage += new AgentManager.InstantMessageCallback(MainAvatar_InstantMessage); Client.Network.RegisterCallback(PacketType.OnlineNotification, OnlineNotificationHandler); Client.Network.RegisterCallback(PacketType.OfflineNotification, OfflineNotificationHandler); Client.Network.RegisterCallback(PacketType.ChangeUserRights, ChangeUserRightsHandler); Client.Network.RegisterCallback(PacketType.TerminateFriendship, TerminateFriendshipHandler); Client.Network.RegisterCallback(PacketType.FindAgent, OnFindAgentReplyHandler); Client.Network.RegisterLoginResponseCallback(new NetworkManager.LoginResponseCallback(Network_OnLoginResponse), new string[] { "buddy-list" }); } #region Public Methods /// /// Accept a friendship request /// /// agentID of avatatar to form friendship with /// imSessionID of the friendship request message public void AcceptFriendship(UUID fromAgentID, UUID imSessionID) { UUID callingCardFolder = Client.Inventory.FindFolderForType(AssetType.CallingCard); AcceptFriendshipPacket request = new AcceptFriendshipPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.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, FriendRights.CanSeeOnline, FriendRights.CanSeeOnline); lock (FriendList) { if(!FriendList.ContainsKey(fromAgentID)) FriendList.Add(friend.UUID, friend); } lock (FriendRequests) { if (FriendRequests.ContainsKey(fromAgentID)) FriendRequests.Remove(fromAgentID); } Client.Avatars.RequestAvatarName(fromAgentID); } /// /// Decline a friendship request /// /// of friend /// imSessionID of the friendship request message public void DeclineFriendship(UUID fromAgentID, UUID imSessionID) { DeclineFriendshipPacket request = new DeclineFriendshipPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.TransactionBlock.TransactionID = imSessionID; Client.Network.SendPacket(request); lock (FriendRequests) { if (FriendRequests.ContainsKey(fromAgentID)) FriendRequests.Remove(fromAgentID); } } /// /// Offer friendship to an avatar. /// /// System ID of the avatar you are offering friendship to public void OfferFriendship(UUID agentID) { // HACK: folder id stored as "message" UUID callingCardFolder = Client.Inventory.FindFolderForType(AssetType.CallingCard); Client.Self.InstantMessage(Client.Self.Name, agentID, callingCardFolder.ToString(), UUID.Random(), InstantMessageDialog.FriendshipOffered, InstantMessageOnline.Online, Client.Self.SimPosition, Client.Network.CurrentSim.ID, Utils.EmptyBytes); } /// /// Terminate a friendship with an avatar /// /// System ID of the avatar you are terminating the friendship with public void TerminateFriendship(UUID agentID) { if (FriendList.ContainsKey(agentID)) { TerminateFriendshipPacket request = new TerminateFriendshipPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ExBlock.OtherID = agentID; Client.Network.SendPacket(request); lock (FriendList) { if (FriendList.ContainsKey(agentID)) FriendList.Remove(agentID); } } } /// /// Fired when another friend terminates friendship. We need to remove them from /// our cached list. /// /// /// private void TerminateFriendshipHandler(Packet packet, Simulator simulator) { TerminateFriendshipPacket itsOver = (TerminateFriendshipPacket)packet; string name = String.Empty; lock (FriendList) { if (FriendList.ContainsKey(itsOver.ExBlock.OtherID)) { name = FriendList[itsOver.ExBlock.OtherID].Name; FriendList.Remove(itsOver.ExBlock.OtherID); } } if (OnFriendshipTerminated != null) { OnFriendshipTerminated(itsOver.ExBlock.OtherID, name); } } /// /// Change the rights of a friend avatar. /// /// the of the friend /// the new rights to give the friend /// This method will implicitly set the rights to those passed in the rights parameter. public void GrantRights(UUID friendID, FriendRights rights) { GrantUserRightsPacket request = new GrantUserRightsPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Rights = new GrantUserRightsPacket.RightsBlock[1]; request.Rights[0] = new GrantUserRightsPacket.RightsBlock(); request.Rights[0].AgentRelated = friendID; request.Rights[0].RelatedRights = (int)rights; Client.Network.SendPacket(request); } /// /// Use to map a friends location on the grid. /// /// Friends UUID to find /// public void MapFriend(UUID friendID) { FindAgentPacket stalk = new FindAgentPacket(); stalk.AgentBlock.Hunter = Client.Self.AgentID; stalk.AgentBlock.Prey = friendID; Client.Network.SendPacket(stalk); } /// /// Use to track a friends movement on the grid /// /// Friends Key public void TrackFriend(UUID friendID) { TrackAgentPacket stalk = new TrackAgentPacket(); stalk.AgentData.AgentID = Client.Self.AgentID; stalk.AgentData.SessionID = Client.Self.SessionID; stalk.TargetData.PreyID = friendID; Client.Network.SendPacket(stalk); } #endregion #region Internal events /// /// 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(); if ( FriendList.Count > 0 ) { lock (FriendList) { foreach (KeyValuePair kvp in FriendList.Dictionary) { 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 (FriendList) { Dictionary newNames = new Dictionary(); foreach (KeyValuePair kvp in names) { if (FriendList.ContainsKey(kvp.Key)) { if (FriendList[kvp.Key].Name == null) newNames.Add(kvp.Key, names[kvp.Key]); FriendList[kvp.Key].Name = names[kvp.Key]; } } if (newNames.Count > 0 && OnFriendNamesReceived != null) { try { OnFriendNamesReceived(newNames); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } #endregion #region Packet Handlers /// /// 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) { FriendInfo friend; lock (FriendList) { if (!FriendList.ContainsKey(block.AgentID)) { friend = new FriendInfo(block.AgentID, FriendRights.CanSeeOnline, FriendRights.CanSeeOnline); FriendList.Add(block.AgentID, friend); } else { friend = FriendList[block.AgentID]; } } bool doNotify = !friend.IsOnline; friend.IsOnline = true; if (OnFriendOnline != null && doNotify) { try { OnFriendOnline(friend); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } } /// /// 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) { FriendInfo friend; lock (FriendList) { if (!FriendList.ContainsKey(block.AgentID)) FriendList.Add(block.AgentID, new FriendInfo(block.AgentID, FriendRights.CanSeeOnline, FriendRights.CanSeeOnline)); friend = FriendList[block.AgentID]; friend.IsOnline = false; } if (OnFriendOffline != null) { try { OnFriendOffline(friend); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } } /// /// 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) { FriendRights newRights = (FriendRights)block.RelatedRights; if (FriendList.TryGetValue(block.AgentRelated, out friend)) { friend.TheirFriendRights = newRights; if (OnFriendRights != null) { try { OnFriendRights(friend); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else if (block.AgentRelated == Client.Self.AgentID) { if (FriendList.TryGetValue(rights.AgentData.AgentID, out friend)) { friend.MyFriendRights = newRights; if (OnFriendRights != null) { try { OnFriendRights(friend); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } } } } /// /// Handle friend location updates /// /// The Packet /// The Simulator public void OnFindAgentReplyHandler(Packet packet, Simulator simulator) { if(OnFriendFound != null) { FindAgentPacket reply = (FindAgentPacket)packet; float x,y; UUID prey = reply.AgentBlock.Prey; ulong regionHandle = Helpers.GlobalPosToRegionHandle((float)reply.LocationBlock[0].GlobalX, (float)reply.LocationBlock[0].GlobalY, out x, out y); Vector3 xyz = new Vector3(x, y, 0f); try { OnFriendFound(prey, regionHandle, xyz); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } #endregion /// /// Handles relevant messages from the server encapsulated in instant messages. /// /// InstantMessage object containing encapsalated instant message /// Originating Simulator private void MainAvatar_InstantMessage(InstantMessage im, Simulator simulator) { if (im.Dialog == InstantMessageDialog.FriendshipOffered) { if (OnFriendshipOffered != null) { lock (FriendRequests) { if (FriendRequests.ContainsKey(im.FromAgentID)) FriendRequests[im.FromAgentID] = im.IMSessionID; else FriendRequests.Add(im.FromAgentID, im.IMSessionID); } try { OnFriendshipOffered(im.FromAgentID, im.FromAgentName, im.IMSessionID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else if (im.Dialog == InstantMessageDialog.FriendshipAccepted) { FriendInfo friend = new FriendInfo(im.FromAgentID, FriendRights.CanSeeOnline, FriendRights.CanSeeOnline); friend.Name = im.FromAgentName; lock (FriendList) FriendList[friend.UUID] = friend; if (OnFriendshipResponse != null) { try { OnFriendshipResponse(im.FromAgentID, im.FromAgentName, true); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else if (im.Dialog == InstantMessageDialog.FriendshipDeclined) { if (OnFriendshipResponse != null) { try { OnFriendshipResponse(im.FromAgentID, im.FromAgentName, false); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason, LoginResponseData replyData) { if (loginSuccess && replyData.BuddyList != null) { lock (FriendList) { for (int i = 0; i < replyData.BuddyList.Length; i++) { FriendInfo friend = replyData.BuddyList[i]; FriendList[friend.UUID] = friend; } } } } } }