/*
* 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;
}
}
}
}
}
}