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