FriendsModule.cs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Net;
  31. using System.Reflection;
  32. using log4net;
  33. using Nini.Config;
  34. using Nwc.XmlRpc;
  35. using OpenMetaverse;
  36. using OpenSim.Framework;
  37. using OpenSim.Framework.Communications;
  38. using OpenSim.Framework.Communications.Cache;
  39. using OpenSim.Region.Framework.Interfaces;
  40. using OpenSim.Region.Framework.Scenes;
  41. using OpenSim.Services.Interfaces;
  42. using GridRegion = OpenSim.Services.Interfaces.GridRegion;
  43. namespace OpenSim.Region.CoreModules.Avatar.Friends
  44. {
  45. /*
  46. This module handles adding/removing friends, and the the presence
  47. notification process for login/logoff of friends.
  48. The presence notification works as follows:
  49. - After the user initially connects to a region (so we now have a UDP
  50. connection to work with), this module fetches the friends of user
  51. (those are cached), their on-/offline status, and info about the
  52. region they are in from the MessageServer.
  53. - (*) It then informs the user about the on-/offline status of her friends.
  54. - It then informs all online friends currently on this region-server about
  55. user's new online status (this will save some network traffic, as local
  56. messages don't have to be transferred inter-region, and it will be all
  57. that has to be done in Standalone Mode).
  58. - For the rest of the online friends (those not on this region-server),
  59. this module uses the provided region-information to map users to
  60. regions, and sends one notification to every region containing the
  61. friends to inform on that server.
  62. - The region-server will handle that in the following way:
  63. - If it finds the friend, it informs her about the user being online.
  64. - If it doesn't find the friend (maybe she TPed away in the meantime),
  65. it stores that information.
  66. - After it processed all friends, it returns the list of friends it
  67. couldn't find.
  68. - If this list isn't empty, the FriendsModule re-requests information
  69. about those online friends that have been missed and starts at (*)
  70. again until all friends have been found, or until it tried 3 times
  71. (to prevent endless loops due to some uncaught error).
  72. NOTE: Online/Offline notifications don't need to be sent on region change.
  73. We implement two XMLRpc handlers here, handling all the inter-region things
  74. we have to handle:
  75. - On-/Offline-Notifications (bulk)
  76. - Terminate Friendship messages (single)
  77. */
  78. public class FriendsModule : IRegionModule, IFriendsModule
  79. {
  80. private class Transaction
  81. {
  82. public UUID agentID;
  83. public string agentName;
  84. public uint count;
  85. public Transaction(UUID agentID, string agentName)
  86. {
  87. this.agentID = agentID;
  88. this.agentName = agentName;
  89. this.count = 1;
  90. }
  91. }
  92. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  93. private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate);
  94. private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>();
  95. private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>();
  96. private Scene m_initialScene; // saves a lookup if we don't have a specific scene
  97. private Dictionary<ulong, Scene> m_scenes = new Dictionary<ulong,Scene>();
  98. private IMessageTransferModule m_TransferModule = null;
  99. private IGridService m_gridServices = null;
  100. #region IRegionModule Members
  101. public void Initialise(Scene scene, IConfigSource config)
  102. {
  103. lock (m_scenes)
  104. {
  105. if (m_scenes.Count == 0)
  106. {
  107. MainServer.Instance.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk);
  108. MainServer.Instance.AddXmlRPCHandler("terminate_friend", processTerminateFriend);
  109. m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max
  110. m_initialScene = scene;
  111. }
  112. if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle))
  113. m_scenes[scene.RegionInfo.RegionHandle] = scene;
  114. }
  115. scene.RegisterModuleInterface<IFriendsModule>(this);
  116. scene.EventManager.OnNewClient += OnNewClient;
  117. scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage;
  118. scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
  119. scene.EventManager.OnMakeChildAgent += MakeChildAgent;
  120. scene.EventManager.OnClientClosed += ClientClosed;
  121. }
  122. public void PostInitialise()
  123. {
  124. if (m_scenes.Count > 0)
  125. {
  126. m_TransferModule = m_initialScene.RequestModuleInterface<IMessageTransferModule>();
  127. m_gridServices = m_initialScene.GridService;
  128. }
  129. if (m_TransferModule == null)
  130. m_log.Error("[FRIENDS]: Unable to find a message transfer module, friendship offers will not work");
  131. }
  132. public void Close()
  133. {
  134. }
  135. public string Name
  136. {
  137. get { return "FriendsModule"; }
  138. }
  139. public bool IsSharedModule
  140. {
  141. get { return true; }
  142. }
  143. #endregion
  144. #region IInterregionFriendsComms
  145. public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online)
  146. {
  147. List<UUID> tpdAway = new List<UUID>();
  148. // destRegionHandle is a region on another server
  149. uint x = 0, y = 0;
  150. Utils.LongToUInts(destRegionHandle, out x, out y);
  151. GridRegion info = m_gridServices.GetRegionByPosition(m_initialScene.RegionInfo.ScopeID, (int)x, (int)y);
  152. if (info != null)
  153. {
  154. string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk";
  155. Hashtable reqParams = new Hashtable();
  156. reqParams["agentID"] = agentId.ToString();
  157. reqParams["agentOnline"] = online;
  158. int count = 0;
  159. foreach (UUID uuid in friends)
  160. {
  161. reqParams["friendID_" + count++] = uuid.ToString();
  162. }
  163. reqParams["friendCount"] = count;
  164. IList parameters = new ArrayList();
  165. parameters.Add(reqParams);
  166. try
  167. {
  168. XmlRpcRequest request = new XmlRpcRequest("presence_update_bulk", parameters);
  169. XmlRpcResponse response = request.Send(httpServer, 5000);
  170. Hashtable respData = (Hashtable)response.Value;
  171. count = (int)respData["friendCount"];
  172. for (int i = 0; i < count; ++i)
  173. {
  174. UUID uuid;
  175. if (UUID.TryParse((string)respData["friendID_" + i], out uuid)) tpdAway.Add(uuid);
  176. }
  177. }
  178. catch (WebException e)
  179. {
  180. // Ignore connect failures, simulators come and go
  181. //
  182. if (!e.Message.Contains("ConnectFailure"))
  183. {
  184. m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
  185. }
  186. }
  187. catch (Exception e)
  188. {
  189. m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
  190. }
  191. }
  192. else m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}???", destRegionHandle);
  193. return tpdAway;
  194. }
  195. public bool TriggerTerminateFriend(ulong destRegionHandle, UUID agentID, UUID exFriendID)
  196. {
  197. // destRegionHandle is a region on another server
  198. uint x = 0, y = 0;
  199. Utils.LongToUInts(destRegionHandle, out x, out y);
  200. GridRegion info = m_gridServices.GetRegionByPosition(m_initialScene.RegionInfo.ScopeID, (int)x, (int)y);
  201. if (info == null)
  202. {
  203. m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}", destRegionHandle);
  204. return false; // region not found???
  205. }
  206. string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk";
  207. Hashtable reqParams = new Hashtable();
  208. reqParams["agentID"] = agentID.ToString();
  209. reqParams["friendID"] = exFriendID.ToString();
  210. IList parameters = new ArrayList();
  211. parameters.Add(reqParams);
  212. try
  213. {
  214. XmlRpcRequest request = new XmlRpcRequest("terminate_friend", parameters);
  215. XmlRpcResponse response = request.Send(httpServer, 5000);
  216. Hashtable respData = (Hashtable)response.Value;
  217. return (bool)respData["success"];
  218. }
  219. catch (Exception e)
  220. {
  221. m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e);
  222. return false;
  223. }
  224. }
  225. #endregion
  226. #region Incoming XMLRPC messages
  227. /// <summary>
  228. /// Receive presence information changes about clients in other regions.
  229. /// </summary>
  230. /// <param name="req"></param>
  231. /// <returns></returns>
  232. public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req, IPEndPoint remoteClient)
  233. {
  234. Hashtable requestData = (Hashtable)req.Params[0];
  235. List<UUID> friendsNotHere = new List<UUID>();
  236. // this is called with the expectation that all the friends in the request are on this region-server.
  237. // But as some time passed since we checked (on the other region-server, via the MessagingServer),
  238. // some of the friends might have teleported away.
  239. // Actually, even now, between this line and the sending below, some people could TP away. So,
  240. // we'll have to lock the m_rootAgents list for the duration to prevent/delay that.
  241. lock (m_rootAgents)
  242. {
  243. List<ScenePresence> friendsHere = new List<ScenePresence>();
  244. try
  245. {
  246. UUID agentID = new UUID((string)requestData["agentID"]);
  247. bool agentOnline = (bool)requestData["agentOnline"];
  248. int count = (int)requestData["friendCount"];
  249. for (int i = 0; i < count; ++i)
  250. {
  251. UUID uuid;
  252. if (UUID.TryParse((string)requestData["friendID_" + i], out uuid))
  253. {
  254. if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid));
  255. else friendsNotHere.Add(uuid);
  256. }
  257. }
  258. // now send, as long as they are still here...
  259. UUID[] agentUUID = new UUID[] { agentID };
  260. if (agentOnline)
  261. {
  262. foreach (ScenePresence agent in friendsHere)
  263. {
  264. agent.ControllingClient.SendAgentOnline(agentUUID);
  265. }
  266. }
  267. else
  268. {
  269. foreach (ScenePresence agent in friendsHere)
  270. {
  271. agent.ControllingClient.SendAgentOffline(agentUUID);
  272. }
  273. }
  274. }
  275. catch(Exception e)
  276. {
  277. m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e);
  278. }
  279. }
  280. // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region,
  281. // which should be caught on the next iteration...
  282. Hashtable result = new Hashtable();
  283. int idx = 0;
  284. foreach (UUID uuid in friendsNotHere)
  285. {
  286. result["friendID_" + idx++] = uuid.ToString();
  287. }
  288. result["friendCount"] = idx;
  289. XmlRpcResponse response = new XmlRpcResponse();
  290. response.Value = result;
  291. return response;
  292. }
  293. public XmlRpcResponse processTerminateFriend(XmlRpcRequest req, IPEndPoint remoteClient)
  294. {
  295. Hashtable requestData = (Hashtable)req.Params[0];
  296. bool success = false;
  297. UUID agentID;
  298. UUID friendID;
  299. if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) &&
  300. requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID))
  301. {
  302. // try to find it and if it is there, prevent it to vanish before we sent the message
  303. lock (m_rootAgents)
  304. {
  305. if (m_rootAgents.ContainsKey(agentID))
  306. {
  307. m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID);
  308. GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID);
  309. success = true;
  310. }
  311. }
  312. }
  313. // return whether we were successful
  314. Hashtable result = new Hashtable();
  315. result["success"] = success;
  316. XmlRpcResponse response = new XmlRpcResponse();
  317. response.Value = result;
  318. return response;
  319. }
  320. #endregion
  321. #region Scene events
  322. private void OnNewClient(IClientAPI client)
  323. {
  324. // All friends establishment protocol goes over instant message
  325. // There's no way to send a message from the sim
  326. // to a user to 'add a friend' without causing dialog box spam
  327. // Subscribe to instant messages
  328. client.OnInstantMessage += OnInstantMessage;
  329. // Friend list management
  330. client.OnApproveFriendRequest += OnApproveFriendRequest;
  331. client.OnDenyFriendRequest += OnDenyFriendRequest;
  332. client.OnTerminateFriendship += OnTerminateFriendship;
  333. // ... calling card handling...
  334. client.OnOfferCallingCard += OnOfferCallingCard;
  335. client.OnAcceptCallingCard += OnAcceptCallingCard;
  336. client.OnDeclineCallingCard += OnDeclineCallingCard;
  337. // we need this one exactly once per agent session (see comments in the handler below)
  338. client.OnEconomyDataRequest += OnEconomyDataRequest;
  339. // if it leaves, we want to know, too
  340. client.OnLogout += OnLogout;
  341. client.OnGrantUserRights += GrantUserFriendRights;
  342. client.OnTrackAgent += FindAgent;
  343. client.OnFindAgent += FindAgent;
  344. }
  345. private void ClientClosed(UUID AgentId, Scene scene)
  346. {
  347. // agent's client was closed. As we handle logout in OnLogout, this here has only to handle
  348. // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client
  349. // agent is closed).
  350. // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around
  351. // in one of the regions here anymore.
  352. lock (m_rootAgents)
  353. {
  354. if (m_rootAgents.ContainsKey(AgentId))
  355. {
  356. m_rootAgents.Remove(AgentId);
  357. }
  358. }
  359. }
  360. private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID)
  361. {
  362. lock (m_rootAgents)
  363. {
  364. m_rootAgents[avatar.UUID] = avatar.RegionHandle;
  365. // Claim User! my user! Mine mine mine!
  366. }
  367. }
  368. private void MakeChildAgent(ScenePresence avatar)
  369. {
  370. lock (m_rootAgents)
  371. {
  372. if (m_rootAgents.ContainsKey(avatar.UUID))
  373. {
  374. // only delete if the region matches. As this is a shared module, the avatar could be
  375. // root agent in another region on this server.
  376. if (m_rootAgents[avatar.UUID] == avatar.RegionHandle)
  377. {
  378. m_rootAgents.Remove(avatar.UUID);
  379. // m_log.Debug("[FRIEND]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent");
  380. }
  381. }
  382. }
  383. }
  384. #endregion
  385. private ScenePresence GetRootPresenceFromAgentID(UUID AgentID)
  386. {
  387. ScenePresence returnAgent = null;
  388. lock (m_scenes)
  389. {
  390. ScenePresence queryagent = null;
  391. foreach (Scene scene in m_scenes.Values)
  392. {
  393. queryagent = scene.GetScenePresence(AgentID);
  394. if (queryagent != null)
  395. {
  396. if (!queryagent.IsChildAgent)
  397. {
  398. returnAgent = queryagent;
  399. break;
  400. }
  401. }
  402. }
  403. }
  404. return returnAgent;
  405. }
  406. private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID)
  407. {
  408. ScenePresence returnAgent = null;
  409. lock (m_scenes)
  410. {
  411. ScenePresence queryagent = null;
  412. foreach (Scene scene in m_scenes.Values)
  413. {
  414. queryagent = scene.GetScenePresence(AgentID);
  415. if (queryagent != null)
  416. {
  417. returnAgent = queryagent;
  418. break;
  419. }
  420. }
  421. }
  422. return returnAgent;
  423. }
  424. public void OfferFriendship(UUID fromUserId, IClientAPI toUserClient, string offerMessage)
  425. {
  426. CachedUserInfo userInfo = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(fromUserId);
  427. if (userInfo != null)
  428. {
  429. GridInstantMessage msg = new GridInstantMessage(
  430. toUserClient.Scene, fromUserId, userInfo.UserProfile.Name, toUserClient.AgentId,
  431. (byte)InstantMessageDialog.FriendshipOffered, offerMessage, false, Vector3.Zero);
  432. FriendshipOffered(msg);
  433. }
  434. else
  435. {
  436. m_log.ErrorFormat("[FRIENDS]: No user found for id {0} in OfferFriendship()", fromUserId);
  437. }
  438. }
  439. #region FriendRequestHandling
  440. private void OnInstantMessage(IClientAPI client, GridInstantMessage im)
  441. {
  442. // Friend Requests go by Instant Message.. using the dialog param
  443. // https://wiki.secondlife.com/wiki/ImprovedInstantMessage
  444. if (im.dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38
  445. {
  446. // fromAgentName is the *destination* name (the friend we offer friendship to)
  447. ScenePresence initiator = GetAnyPresenceFromAgentID(new UUID(im.fromAgentID));
  448. im.fromAgentName = initiator != null ? initiator.Name : "(hippo)";
  449. FriendshipOffered(im);
  450. }
  451. else if (im.dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39
  452. {
  453. FriendshipAccepted(client, im);
  454. }
  455. else if (im.dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40
  456. {
  457. FriendshipDeclined(client, im);
  458. }
  459. }
  460. /// <summary>
  461. /// Invoked when a user offers a friendship.
  462. /// </summary>
  463. ///
  464. /// <param name="im"></param>
  465. /// <param name="client"></param>
  466. private void FriendshipOffered(GridInstantMessage im)
  467. {
  468. // this is triggered by the initiating agent:
  469. // A local agent offers friendship to some possibly remote friend.
  470. // A IM is triggered, processed here and sent to the friend (possibly in a remote region).
  471. m_log.DebugFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}",
  472. im.fromAgentID, im.fromAgentName, im.toAgentID, im.imSessionID, im.message, im.offline);
  473. // 1.20 protocol sends an UUID in the message field, instead of the friendship offer text.
  474. // For interoperability, we have to clear that
  475. if (Util.isUUID(im.message)) im.message = "";
  476. // be sneeky and use the initiator-UUID as transactionID. This means we can be stateless.
  477. // we have to look up the agent name on friendship-approval, though.
  478. im.imSessionID = im.fromAgentID;
  479. if (m_TransferModule != null)
  480. {
  481. // Send it to whoever is the destination.
  482. // If new friend is local, it will send an IM to the viewer.
  483. // If new friend is remote, it will cause a OnGridInstantMessage on the remote server
  484. m_TransferModule.SendInstantMessage(
  485. im,
  486. delegate(bool success)
  487. {
  488. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  489. }
  490. );
  491. }
  492. }
  493. /// <summary>
  494. /// Invoked when a user accepts a friendship offer.
  495. /// </summary>
  496. /// <param name="im"></param>
  497. /// <param name="client"></param>
  498. private void FriendshipAccepted(IClientAPI client, GridInstantMessage im)
  499. {
  500. m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})",
  501. client.AgentId, im.fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog);
  502. }
  503. /// <summary>
  504. /// Invoked when a user declines a friendship offer.
  505. /// </summary>
  506. /// May not currently be used - see OnDenyFriendRequest() instead
  507. /// <param name="im"></param>
  508. /// <param name="client"></param>
  509. private void FriendshipDeclined(IClientAPI client, GridInstantMessage im)
  510. {
  511. UUID fromAgentID = new UUID(im.fromAgentID);
  512. UUID toAgentID = new UUID(im.toAgentID);
  513. // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator
  514. // toAgentID is initiator, fromAgentID declined friendship
  515. m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agent {1} {2}, imsession {3} to {4}: {5} (dialog {6})",
  516. client != null ? client.AgentId.ToString() : "<null>",
  517. fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog);
  518. // Send the decline to whoever is the destination.
  519. GridInstantMessage msg
  520. = new GridInstantMessage(
  521. client.Scene, fromAgentID, client.Name, toAgentID,
  522. im.dialog, im.message, im.offline != 0, im.Position);
  523. // If new friend is local, it will send an IM to the viewer.
  524. // If new friend is remote, it will cause a OnGridInstantMessage on the remote server
  525. m_TransferModule.SendInstantMessage(msg,
  526. delegate(bool success) {
  527. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  528. }
  529. );
  530. }
  531. private void OnGridInstantMessage(GridInstantMessage msg)
  532. {
  533. // This event won't be raised unless we have that agent,
  534. // so we can depend on the above not trying to send
  535. // via grid again
  536. //m_log.DebugFormat("[FRIEND]: Got GridIM from {0}, to {1}, imSession {2}, message {3}, dialog {4}",
  537. // msg.fromAgentID, msg.toAgentID, msg.imSessionID, msg.message, msg.dialog);
  538. if (msg.dialog == (byte)InstantMessageDialog.FriendshipOffered ||
  539. msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted ||
  540. msg.dialog == (byte)InstantMessageDialog.FriendshipDeclined)
  541. {
  542. // this should succeed as we *know* the root agent is here.
  543. m_TransferModule.SendInstantMessage(msg,
  544. delegate(bool success) {
  545. //m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  546. }
  547. );
  548. }
  549. if (msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted)
  550. {
  551. // for accept friendship, we have to do a bit more
  552. ApproveFriendship(new UUID(msg.fromAgentID), new UUID(msg.toAgentID), msg.fromAgentName);
  553. }
  554. }
  555. private void ApproveFriendship(UUID fromAgentID, UUID toAgentID, string fromName)
  556. {
  557. m_log.DebugFormat("[FRIEND]: Approve friendship from {0} (ID: {1}) to {2}",
  558. fromAgentID, fromName, toAgentID);
  559. // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now.
  560. lock (m_friendLists)
  561. {
  562. m_friendLists.Invalidate(fromAgentID.ToString());
  563. m_friendLists.Invalidate(toAgentID.ToString());
  564. }
  565. // now send presence update and add a calling card for the new friend
  566. ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID);
  567. if (initiator == null)
  568. {
  569. // quite wrong. Shouldn't happen.
  570. m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID);
  571. return;
  572. }
  573. m_log.DebugFormat("[FRIEND]: Tell {0} that {1} is online",
  574. initiator.Name, fromName);
  575. // tell initiator that friend is online
  576. initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID });
  577. // find the folder for the friend...
  578. //InventoryFolderImpl folder =
  579. // initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard);
  580. IInventoryService invService = initiator.Scene.InventoryService;
  581. InventoryFolderBase folder = invService.GetFolderForType(toAgentID, AssetType.CallingCard);
  582. if (folder != null)
  583. {
  584. // ... and add the calling card
  585. CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromName);
  586. }
  587. }
  588. private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders)
  589. {
  590. m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}",
  591. client.Name, client.AgentId, agentID, friendID);
  592. // store the new friend persistently for both avatars
  593. m_initialScene.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline);
  594. // The cache entries aren't valid anymore either, as we just added a friend to both sides.
  595. lock (m_friendLists)
  596. {
  597. m_friendLists.Invalidate(agentID.ToString());
  598. m_friendLists.Invalidate(friendID.ToString());
  599. }
  600. // if it's a local friend, we don't have to do the lookup
  601. ScenePresence friendPresence = GetAnyPresenceFromAgentID(friendID);
  602. if (friendPresence != null)
  603. {
  604. m_log.Debug("[FRIEND]: Local agent detected.");
  605. // create calling card
  606. CreateCallingCard(client, friendID, callingCardFolders[0], friendPresence.Name);
  607. // local message means OnGridInstantMessage won't be triggered, so do the work here.
  608. friendPresence.ControllingClient.SendInstantMessage(
  609. new GridInstantMessage(client.Scene, agentID,
  610. client.Name, friendID,
  611. (byte)InstantMessageDialog.FriendshipAccepted,
  612. agentID.ToString(), false, Vector3.Zero));
  613. ApproveFriendship(agentID, friendID, client.Name);
  614. }
  615. else
  616. {
  617. m_log.Debug("[FRIEND]: Remote agent detected.");
  618. // fetch the friend's name for the calling card.
  619. CachedUserInfo info = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(friendID);
  620. // create calling card
  621. CreateCallingCard(client, friendID, callingCardFolders[0],
  622. info.UserProfile.FirstName + " " + info.UserProfile.SurName);
  623. // Compose (remote) response to friend.
  624. GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID,
  625. (byte)InstantMessageDialog.FriendshipAccepted,
  626. agentID.ToString(), false, Vector3.Zero);
  627. if (m_TransferModule != null)
  628. {
  629. m_TransferModule.SendInstantMessage(msg,
  630. delegate(bool success) {
  631. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  632. }
  633. );
  634. }
  635. }
  636. // tell client that new friend is online
  637. client.SendAgentOnline(new UUID[] { friendID });
  638. }
  639. private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List<UUID> callingCardFolders)
  640. {
  641. m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}",
  642. client.Name, client.AgentId, agentID, friendID);
  643. // Compose response to other agent.
  644. GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID,
  645. (byte)InstantMessageDialog.FriendshipDeclined,
  646. agentID.ToString(), false, Vector3.Zero);
  647. // send decline to initiator
  648. if (m_TransferModule != null)
  649. {
  650. m_TransferModule.SendInstantMessage(msg,
  651. delegate(bool success) {
  652. m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success);
  653. }
  654. );
  655. }
  656. }
  657. private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID)
  658. {
  659. // client.AgentId == agentID!
  660. // this removes the friends from the stored friendlists. After the next login, they will be gone...
  661. m_initialScene.StoreRemoveFriendship(agentID, exfriendID);
  662. // ... now tell the two involved clients that they aren't friends anymore.
  663. // I don't know why we have to tell <agent>, as this was caused by her, but that's how it works in SL...
  664. client.SendTerminateFriend(exfriendID);
  665. // now send the friend, if online
  666. ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID);
  667. if (presence != null)
  668. {
  669. m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID);
  670. presence.ControllingClient.SendTerminateFriend(agentID);
  671. }
  672. else
  673. {
  674. // retry 3 times, in case the agent TPed from the last known region...
  675. for (int retry = 0; retry < 3; ++retry)
  676. {
  677. // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send
  678. UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID);
  679. if (null == data)
  680. break;
  681. if (!data.AgentOnline)
  682. {
  683. m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID);
  684. break; // if ex-friend isn't online, we don't need to send
  685. }
  686. m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}",
  687. agentID, exfriendID, data.Handle);
  688. // try to send to foreign region, retry if it fails (friend TPed away, for example)
  689. if (TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break;
  690. }
  691. }
  692. // clean up cache: FriendList is wrong now...
  693. lock (m_friendLists)
  694. {
  695. m_friendLists.Invalidate(agentID.ToString());
  696. m_friendLists.Invalidate(exfriendID.ToString());
  697. }
  698. }
  699. #endregion
  700. #region CallingCards
  701. private void OnOfferCallingCard(IClientAPI client, UUID destID, UUID transactionID)
  702. {
  703. m_log.DebugFormat("[CALLING CARD]: got offer from {0} for {1}, transaction {2}",
  704. client.AgentId, destID, transactionID);
  705. // This might be slightly wrong. On a multi-region server, we might get the child-agent instead of the root-agent
  706. // (or the root instead of the child)
  707. ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
  708. if (destAgent == null)
  709. {
  710. client.SendAlertMessage("The person you have offered a card to can't be found anymore.");
  711. return;
  712. }
  713. lock (m_pendingCallingcardRequests)
  714. {
  715. m_pendingCallingcardRequests[transactionID] = client.AgentId;
  716. }
  717. // inform the destination agent about the offer
  718. destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID);
  719. }
  720. private void CreateCallingCard(IClientAPI client, UUID creator, UUID folder, string name)
  721. {
  722. InventoryItemBase item = new InventoryItemBase();
  723. item.AssetID = UUID.Zero;
  724. item.AssetType = (int)AssetType.CallingCard;
  725. item.BasePermissions = (uint)PermissionMask.Copy;
  726. item.CreationDate = Util.UnixTimeSinceEpoch();
  727. item.CreatorId = creator.ToString();
  728. item.CurrentPermissions = item.BasePermissions;
  729. item.Description = "";
  730. item.EveryOnePermissions = (uint)PermissionMask.None;
  731. item.Flags = 0;
  732. item.Folder = folder;
  733. item.GroupID = UUID.Zero;
  734. item.GroupOwned = false;
  735. item.ID = UUID.Random();
  736. item.InvType = (int)InventoryType.CallingCard;
  737. item.Name = name;
  738. item.NextPermissions = item.EveryOnePermissions;
  739. item.Owner = client.AgentId;
  740. item.SalePrice = 10;
  741. item.SaleType = (byte)SaleType.Not;
  742. ((Scene)client.Scene).AddInventoryItem(client, item);
  743. }
  744. private void OnAcceptCallingCard(IClientAPI client, UUID transactionID, UUID folderID)
  745. {
  746. m_log.DebugFormat("[CALLING CARD]: User {0} ({1} {2}) accepted tid {3}, folder {4}",
  747. client.AgentId,
  748. client.FirstName, client.LastName,
  749. transactionID, folderID);
  750. UUID destID;
  751. lock (m_pendingCallingcardRequests)
  752. {
  753. if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
  754. {
  755. m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
  756. client.Name);
  757. return;
  758. }
  759. // else found pending calling card request with that transaction.
  760. m_pendingCallingcardRequests.Remove(transactionID);
  761. }
  762. ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
  763. // inform sender of the card that destination declined the offer
  764. if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID);
  765. // put a calling card into the inventory of receiver
  766. CreateCallingCard(client, destID, folderID, destAgent.Name);
  767. }
  768. private void OnDeclineCallingCard(IClientAPI client, UUID transactionID)
  769. {
  770. m_log.DebugFormat("[CALLING CARD]: User {0} (ID:{1}) declined card, tid {2}",
  771. client.Name, client.AgentId, transactionID);
  772. UUID destID;
  773. lock (m_pendingCallingcardRequests)
  774. {
  775. if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID))
  776. {
  777. m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.",
  778. client.Name);
  779. return;
  780. }
  781. // else found pending calling card request with that transaction.
  782. m_pendingCallingcardRequests.Remove(transactionID);
  783. }
  784. ScenePresence destAgent = GetAnyPresenceFromAgentID(destID);
  785. // inform sender of the card that destination declined the offer
  786. if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID);
  787. }
  788. /// <summary>
  789. /// Send presence information about a client to other clients in both this region and others.
  790. /// </summary>
  791. /// <param name="client"></param>
  792. /// <param name="friendList"></param>
  793. /// <param name="iAmOnline"></param>
  794. private void SendPresenceState(IClientAPI client, List<FriendListItem> friendList, bool iAmOnline)
  795. {
  796. //m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out");
  797. if (friendList == null || friendList.Count == 0)
  798. {
  799. //m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name);
  800. return; // nothing we can do if she doesn't have friends...
  801. }
  802. // collect sets of friendIDs; to send to (online and offline), and to receive from
  803. // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets.
  804. // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago...
  805. List<UUID> friendIDsToSendTo = new List<UUID>();
  806. List<UUID> candidateFriendIDsToReceive = new List<UUID>();
  807. foreach (FriendListItem item in friendList)
  808. {
  809. if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0)
  810. {
  811. // friend is allowed to see my presence => add
  812. if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0)
  813. friendIDsToSendTo.Add(item.Friend);
  814. if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0)
  815. candidateFriendIDsToReceive.Add(item.Friend);
  816. }
  817. }
  818. // we now have a list of "interesting" friends (which we have to find out on-/offline state for),
  819. // friends we want to send our online state to (if *they* are online, too), and
  820. // friends we want to receive online state for (currently unknown whether online or not)
  821. // as this processing might take some time and friends might TP away, we try up to three times to
  822. // reach them. Most of the time, we *will* reach them, and this loop won't loop
  823. int retry = 0;
  824. do
  825. {
  826. // build a list of friends to look up region-information and on-/offline-state for
  827. List<UUID> friendIDsToLookup = new List<UUID>(friendIDsToSendTo);
  828. foreach (UUID uuid in candidateFriendIDsToReceive)
  829. {
  830. if (!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid);
  831. }
  832. m_log.DebugFormat(
  833. "[FRIEND]: {0} to lookup, {1} to send to, {2} candidates to receive from for agent {3}",
  834. friendIDsToLookup.Count, friendIDsToSendTo.Count, candidateFriendIDsToReceive.Count, client.Name);
  835. // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't
  836. // necessarily contain the correct online state...
  837. Dictionary<UUID, FriendRegionInfo> friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup);
  838. m_log.DebugFormat(
  839. "[FRIEND]: Found {0} regionInfos for {1} friends of {2}",
  840. friendRegions.Count, friendIDsToLookup.Count, client.Name);
  841. // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops.
  842. UUID[] agentArr = new UUID[] { client.AgentId };
  843. // first, send to friend presence state to me, if I'm online...
  844. if (iAmOnline)
  845. {
  846. List<UUID> friendIDsToReceive = new List<UUID>();
  847. for (int i = candidateFriendIDsToReceive.Count - 1; i >= 0; --i)
  848. {
  849. UUID uuid = candidateFriendIDsToReceive[i];
  850. FriendRegionInfo info;
  851. if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline)
  852. {
  853. friendIDsToReceive.Add(uuid);
  854. }
  855. }
  856. m_log.DebugFormat(
  857. "[FRIEND]: Sending {0} online friends to {1}", friendIDsToReceive.Count, client.Name);
  858. if (friendIDsToReceive.Count > 0)
  859. client.SendAgentOnline(friendIDsToReceive.ToArray());
  860. // clear them for a possible second iteration; we don't have to repeat this
  861. candidateFriendIDsToReceive.Clear();
  862. }
  863. // now, send my presence state to my friends
  864. for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i)
  865. {
  866. UUID uuid = friendIDsToSendTo[i];
  867. FriendRegionInfo info;
  868. if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline)
  869. {
  870. // any client is good enough, root or child...
  871. ScenePresence agent = GetAnyPresenceFromAgentID(uuid);
  872. if (agent != null)
  873. {
  874. //m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name);
  875. // friend is online and on this server...
  876. if (iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr);
  877. else agent.ControllingClient.SendAgentOffline(agentArr);
  878. // done, remove it
  879. friendIDsToSendTo.RemoveAt(i);
  880. }
  881. }
  882. else
  883. {
  884. //m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i);
  885. // friend is offline => no need to try sending
  886. friendIDsToSendTo.RemoveAt(i);
  887. }
  888. }
  889. m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count);
  890. // we now have all the friends left that are online (we think), but not on this region-server
  891. if (friendIDsToSendTo.Count > 0)
  892. {
  893. // sort them into regions
  894. Dictionary<ulong, List<UUID>> friendsInRegion = new Dictionary<ulong,List<UUID>>();
  895. foreach (UUID uuid in friendIDsToSendTo)
  896. {
  897. ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already
  898. List<UUID> friends;
  899. if (!friendsInRegion.TryGetValue(handle, out friends))
  900. {
  901. friends = new List<UUID>();
  902. friendsInRegion[handle] = friends;
  903. }
  904. friends.Add(uuid);
  905. }
  906. m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count);
  907. // clear uuids list and collect missed friends in it for the next retry
  908. friendIDsToSendTo.Clear();
  909. // send bulk updates to the region
  910. foreach (KeyValuePair<ulong, List<UUID>> pair in friendsInRegion)
  911. {
  912. //m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line",
  913. // pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off");
  914. friendIDsToSendTo.AddRange(InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline));
  915. }
  916. }
  917. // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them.
  918. // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again...
  919. }
  920. while (++retry < 3 && friendIDsToSendTo.Count > 0);
  921. }
  922. private void OnEconomyDataRequest(UUID agentID)
  923. {
  924. // KLUDGE: This is the only way I found to get a message (only) after login was completed and the
  925. // client is connected enough to receive UDP packets).
  926. // This packet seems to be sent only once, just after connection was established to the first
  927. // region after login.
  928. // We use it here to trigger a presence update; the old update-on-login was never be heard by
  929. // the freshly logged in viewer, as it wasn't connected to the region at that time.
  930. // TODO: Feel free to replace this by a better solution if you find one.
  931. // get the agent. This should work every time, as we just got a packet from it
  932. //ScenePresence agent = GetRootPresenceFromAgentID(agentID);
  933. // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit
  934. ScenePresence agent = GetAnyPresenceFromAgentID(agentID);
  935. // just to be paranoid...
  936. if (agent == null)
  937. {
  938. m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID);
  939. return;
  940. }
  941. List<FriendListItem> fl;
  942. lock (m_friendLists)
  943. {
  944. fl = (List<FriendListItem>)m_friendLists.Get(agent.ControllingClient.AgentId.ToString(),
  945. m_initialScene.GetFriendList);
  946. }
  947. // tell everyone that we are online
  948. SendPresenceState(agent.ControllingClient, fl, true);
  949. }
  950. private void OnLogout(IClientAPI remoteClient)
  951. {
  952. List<FriendListItem> fl;
  953. lock (m_friendLists)
  954. {
  955. fl = (List<FriendListItem>)m_friendLists.Get(remoteClient.AgentId.ToString(),
  956. m_initialScene.GetFriendList);
  957. }
  958. // tell everyone that we are offline
  959. SendPresenceState(remoteClient, fl, false);
  960. }
  961. private void GrantUserFriendRights(IClientAPI remoteClient, UUID requester, UUID target, int rights)
  962. {
  963. ((Scene)remoteClient.Scene).CommsManager.UpdateUserFriendPerms(requester, target, (uint)rights);
  964. }
  965. public void FindAgent(IClientAPI remoteClient, UUID hunter, UUID target)
  966. {
  967. List<FriendListItem> friendList = GetUserFriends(hunter);
  968. foreach (FriendListItem item in friendList)
  969. {
  970. if (item.onlinestatus == true)
  971. {
  972. if (item.Friend == target && (item.FriendPerms & (uint)FriendRights.CanSeeOnMap) != 0)
  973. {
  974. ScenePresence SPTarget = ((Scene)remoteClient.Scene).GetScenePresence(target);
  975. string regionname = SPTarget.Scene.RegionInfo.RegionName;
  976. remoteClient.SendScriptTeleportRequest("FindAgent", regionname,new Vector3(SPTarget.AbsolutePosition),new Vector3(SPTarget.Lookat));
  977. }
  978. }
  979. else
  980. {
  981. remoteClient.SendAgentAlertMessage("The agent you are looking for is not online.", false);
  982. }
  983. }
  984. }
  985. public List<FriendListItem> GetUserFriends(UUID agentID)
  986. {
  987. List<FriendListItem> fl;
  988. lock (m_friendLists)
  989. {
  990. fl = (List<FriendListItem>)m_friendLists.Get(agentID.ToString(),
  991. m_initialScene.GetFriendList);
  992. }
  993. return fl;
  994. }
  995. }
  996. #endregion
  997. }