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