diff options
Diffstat (limited to '')
12 files changed, 935 insertions, 432 deletions
diff --git a/OpenSim/Region/Application/OpenSimBase.cs b/OpenSim/Region/Application/OpenSimBase.cs index 4f1eed8..2bb4b57 100644 --- a/OpenSim/Region/Application/OpenSimBase.cs +++ b/OpenSim/Region/Application/OpenSimBase.cs | |||
@@ -364,7 +364,8 @@ namespace OpenSim | |||
364 | m_commsManager | 364 | m_commsManager |
365 | = new CommunicationsLocal( | 365 | = new CommunicationsLocal( |
366 | m_networkServersInfo, m_httpServer, m_assetCache, userService, userService, | 366 | m_networkServersInfo, m_httpServer, m_assetCache, userService, userService, |
367 | inventoryService, backendService, backendService, libraryRootFolder, m_dumpAssetsToFile); | 367 | inventoryService, backendService, backendService, userService, |
368 | libraryRootFolder, m_dumpAssetsToFile); | ||
368 | 369 | ||
369 | // set up XMLRPC handler for client's initial login request message | 370 | // set up XMLRPC handler for client's initial login request message |
370 | m_httpServer.AddXmlRPCHandler("login_to_simulator", loginService.XmlRpcLoginMethod); | 371 | m_httpServer.AddXmlRPCHandler("login_to_simulator", loginService.XmlRpcLoginMethod); |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 5b3bc98..363b09c 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs | |||
@@ -4129,7 +4129,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
4129 | handlerApproveFriendRequest(this, agentID, transactionID, callingCardFolders); | 4129 | handlerApproveFriendRequest(this, agentID, transactionID, callingCardFolders); |
4130 | } | 4130 | } |
4131 | break; | 4131 | break; |
4132 | 4132 | ||
4133 | case PacketType.DeclineFriendship: | ||
4134 | DeclineFriendshipPacket dfriendpack = (DeclineFriendshipPacket)Pack; | ||
4135 | |||
4136 | if (OnDenyFriendRequest != null) | ||
4137 | { | ||
4138 | OnDenyFriendRequest(this, | ||
4139 | dfriendpack.AgentData.AgentID, | ||
4140 | dfriendpack.TransactionBlock.TransactionID, | ||
4141 | null); | ||
4142 | } | ||
4143 | break; | ||
4144 | |||
4133 | case PacketType.TerminateFriendship: | 4145 | case PacketType.TerminateFriendship: |
4134 | TerminateFriendshipPacket tfriendpack = (TerminateFriendshipPacket)Pack; | 4146 | TerminateFriendshipPacket tfriendpack = (TerminateFriendshipPacket)Pack; |
4135 | UUID listOwnerAgentID = tfriendpack.AgentData.AgentID; | 4147 | UUID listOwnerAgentID = tfriendpack.AgentData.AgentID; |
@@ -7648,6 +7660,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
7648 | OutPacket(p, ThrottleOutPacketType.Task); | 7660 | OutPacket(p, ThrottleOutPacketType.Task); |
7649 | } | 7661 | } |
7650 | 7662 | ||
7663 | public void SendTerminateFriend(UUID exFriendID) | ||
7664 | { | ||
7665 | TerminateFriendshipPacket p = (TerminateFriendshipPacket)PacketPool.Instance.GetPacket(PacketType.TerminateFriendship); | ||
7666 | p.AgentData.AgentID = AgentId; | ||
7667 | p.AgentData.SessionID = SessionId; | ||
7668 | p.ExBlock.OtherID = exFriendID; | ||
7669 | OutPacket(p, ThrottleOutPacketType.Task); | ||
7670 | } | ||
7671 | |||
7651 | public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) | 7672 | public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) |
7652 | { | 7673 | { |
7653 | int i; | 7674 | int i; |
diff --git a/OpenSim/Region/Communications/Local/CommunicationsLocal.cs b/OpenSim/Region/Communications/Local/CommunicationsLocal.cs index 71c44e5..48a635a 100644 --- a/OpenSim/Region/Communications/Local/CommunicationsLocal.cs +++ b/OpenSim/Region/Communications/Local/CommunicationsLocal.cs | |||
@@ -42,7 +42,8 @@ namespace OpenSim.Region.Communications.Local | |||
42 | IUserServiceAdmin userServiceAdmin, | 42 | IUserServiceAdmin userServiceAdmin, |
43 | LocalInventoryService inventoryService, | 43 | LocalInventoryService inventoryService, |
44 | IInterRegionCommunications interRegionService, | 44 | IInterRegionCommunications interRegionService, |
45 | IGridServices gridService, LibraryRootFolder libraryRootFolder, bool dumpAssetsToFile) | 45 | IGridServices gridService, IMessagingService messageService, |
46 | LibraryRootFolder libraryRootFolder, bool dumpAssetsToFile) | ||
46 | : base(serversInfo, httpServer, assetCache, dumpAssetsToFile, libraryRootFolder) | 47 | : base(serversInfo, httpServer, assetCache, dumpAssetsToFile, libraryRootFolder) |
47 | { | 48 | { |
48 | AddInventoryService(inventoryService); | 49 | AddInventoryService(inventoryService); |
@@ -53,6 +54,7 @@ namespace OpenSim.Region.Communications.Local | |||
53 | m_avatarService = (IAvatarService)userService; | 54 | m_avatarService = (IAvatarService)userService; |
54 | m_gridService = gridService; | 55 | m_gridService = gridService; |
55 | m_interRegion = interRegionService; | 56 | m_interRegion = interRegionService; |
57 | m_messageService = messageService; | ||
56 | } | 58 | } |
57 | } | 59 | } |
58 | } | 60 | } |
diff --git a/OpenSim/Region/Communications/Local/LocalBackEndServices.cs b/OpenSim/Region/Communications/Local/LocalBackEndServices.cs index 9034e49..4980378 100644 --- a/OpenSim/Region/Communications/Local/LocalBackEndServices.cs +++ b/OpenSim/Region/Communications/Local/LocalBackEndServices.cs | |||
@@ -39,7 +39,7 @@ namespace OpenSim.Region.Communications.Local | |||
39 | public class LocalBackEndServices : IGridServices, IInterRegionCommunications | 39 | public class LocalBackEndServices : IGridServices, IInterRegionCommunications |
40 | { | 40 | { |
41 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 41 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
42 | 42 | ||
43 | protected Dictionary<ulong, RegionInfo> m_regions = new Dictionary<ulong, RegionInfo>(); | 43 | protected Dictionary<ulong, RegionInfo> m_regions = new Dictionary<ulong, RegionInfo>(); |
44 | 44 | ||
45 | protected Dictionary<ulong, RegionCommsListener> m_regionListeners = | 45 | protected Dictionary<ulong, RegionCommsListener> m_regionListeners = |
@@ -50,7 +50,7 @@ namespace OpenSim.Region.Communications.Local | |||
50 | private Dictionary<string, string> m_queuedGridSettings = new Dictionary<string, string>(); | 50 | private Dictionary<string, string> m_queuedGridSettings = new Dictionary<string, string>(); |
51 | 51 | ||
52 | public string _gdebugRegionName = String.Empty; | 52 | public string _gdebugRegionName = String.Empty; |
53 | 53 | ||
54 | public bool RegionLoginsEnabled | 54 | public bool RegionLoginsEnabled |
55 | { | 55 | { |
56 | get { return m_regionLoginsEnabled; } | 56 | get { return m_regionLoginsEnabled; } |
@@ -523,11 +523,27 @@ namespace OpenSim.Region.Communications.Local | |||
523 | if (info.RegionName.StartsWith(name)) | 523 | if (info.RegionName.StartsWith(name)) |
524 | { | 524 | { |
525 | regions.Add(info); | 525 | regions.Add(info); |
526 | if (regions.Count >= maxNumber) break; | 526 | if (regions.Count >= maxNumber) break; |
527 | } | 527 | } |
528 | } | 528 | } |
529 | 529 | ||
530 | return regions; | 530 | return regions; |
531 | } | 531 | } |
532 | |||
533 | public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online) | ||
534 | { | ||
535 | // if we get to here, something is wrong: We are in standalone mode, but have users that are not on our server? | ||
536 | m_log.WarnFormat("[INTERREGION STANDALONE] Did find {0} users on a region not on our server: {1} ???", | ||
537 | friends.Count, destRegionHandle); | ||
538 | return new List<UUID>(); | ||
539 | } | ||
540 | |||
541 | public bool TriggerTerminateFriend (ulong regionHandle, UUID agentID, UUID exFriendID) | ||
542 | { | ||
543 | // if we get to here, something is wrong: We are in standalone mode, but have users that are not on our server? | ||
544 | m_log.WarnFormat("[INTERREGION STANDALONE] Did find user {0} on a region not on our server: {1} ???", | ||
545 | agentID, regionHandle); | ||
546 | return true; | ||
547 | } | ||
532 | } | 548 | } |
533 | } | 549 | } |
diff --git a/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs b/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs index d76f076..7f6fbc8 100644 --- a/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs +++ b/OpenSim/Region/Communications/OGS1/CommunicationsOGS1.cs | |||
@@ -56,7 +56,9 @@ namespace OpenSim.Region.Communications.OGS1 | |||
56 | m_defaultInventoryHost = invService.Host; | 56 | m_defaultInventoryHost = invService.Host; |
57 | } | 57 | } |
58 | 58 | ||
59 | m_userService = new OGS1UserServices(this); | 59 | OGS1UserServices userServices = new OGS1UserServices(this); |
60 | m_userService = userServices; | ||
61 | m_messageService = userServices; | ||
60 | m_avatarService = (IAvatarService)m_userService; | 62 | m_avatarService = (IAvatarService)m_userService; |
61 | } | 63 | } |
62 | 64 | ||
diff --git a/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs b/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs index 32628f8..d9e0370 100644 --- a/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs +++ b/OpenSim/Region/Communications/OGS1/OGS1GridServices.cs | |||
@@ -1785,5 +1785,83 @@ namespace OpenSim.Region.Communications.OGS1 | |||
1785 | return null; | 1785 | return null; |
1786 | } | 1786 | } |
1787 | } | 1787 | } |
1788 | |||
1789 | public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online) | ||
1790 | { | ||
1791 | List<UUID> tpdAway = new List<UUID>(); | ||
1792 | |||
1793 | // destRegionHandle is a region on another server | ||
1794 | RegionInfo info = RequestNeighbourInfo(destRegionHandle); | ||
1795 | if (info != null) | ||
1796 | { | ||
1797 | string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk"; | ||
1798 | |||
1799 | Hashtable reqParams = new Hashtable(); | ||
1800 | reqParams["agentID"] = agentId.ToString(); | ||
1801 | reqParams["agentOnline"] = online; | ||
1802 | int count = 0; | ||
1803 | foreach (UUID uuid in friends) | ||
1804 | { | ||
1805 | reqParams["friendID_" + count++] = uuid.ToString(); | ||
1806 | } | ||
1807 | reqParams["friendCount"] = count; | ||
1808 | |||
1809 | IList parameters = new ArrayList(); | ||
1810 | parameters.Add(reqParams); | ||
1811 | try | ||
1812 | { | ||
1813 | XmlRpcRequest request = new XmlRpcRequest("presence_update_bulk", parameters); | ||
1814 | XmlRpcResponse response = request.Send(httpServer, 5000); | ||
1815 | Hashtable respData = (Hashtable)response.Value; | ||
1816 | |||
1817 | count = (int)respData["friendCount"]; | ||
1818 | for (int i = 0; i < count; ++i) | ||
1819 | { | ||
1820 | UUID uuid; | ||
1821 | if(UUID.TryParse((string)respData["friendID_" + i], out uuid)) tpdAway.Add(uuid); | ||
1822 | } | ||
1823 | } | ||
1824 | catch(Exception e) | ||
1825 | { | ||
1826 | m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e); | ||
1827 | } | ||
1828 | } | ||
1829 | else m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}???", destRegionHandle); | ||
1830 | |||
1831 | return tpdAway; | ||
1832 | } | ||
1833 | |||
1834 | public bool TriggerTerminateFriend(ulong destRegionHandle, UUID agentID, UUID exFriendID) | ||
1835 | { | ||
1836 | // destRegionHandle is a region on another server | ||
1837 | RegionInfo info = RequestNeighbourInfo(destRegionHandle); | ||
1838 | if (info == null) | ||
1839 | { | ||
1840 | m_log.WarnFormat("[OGS1 GRID SERVICES]: Couldn't find region {0}", destRegionHandle); | ||
1841 | return false; // region not found??? | ||
1842 | } | ||
1843 | |||
1844 | string httpServer = "http://" + info.ExternalEndPoint.Address + ":" + info.HttpPort + "/presence_update_bulk"; | ||
1845 | |||
1846 | Hashtable reqParams = new Hashtable(); | ||
1847 | reqParams["agentID"] = agentID.ToString(); | ||
1848 | reqParams["friendID"] = exFriendID.ToString(); | ||
1849 | |||
1850 | IList parameters = new ArrayList(); | ||
1851 | parameters.Add(reqParams); | ||
1852 | try | ||
1853 | { | ||
1854 | XmlRpcRequest request = new XmlRpcRequest("terminate_friend", parameters); | ||
1855 | XmlRpcResponse response = request.Send(httpServer, 5000); | ||
1856 | Hashtable respData = (Hashtable)response.Value; | ||
1857 | |||
1858 | return (bool)respData["success"]; | ||
1859 | } | ||
1860 | catch(Exception e) | ||
1861 | { | ||
1862 | m_log.Error("[OGS1 GRID SERVICES]: InformFriendsInOtherRegion XMLRPC failure: ", e); | ||
1863 | return false; | ||
1864 | } | ||
1865 | } | ||
1788 | } | 1866 | } |
1789 | } \ No newline at end of file | 1867 | } |
diff --git a/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs b/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs index 28177d0..595c4a9 100644 --- a/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs +++ b/OpenSim/Region/Communications/OGS1/OGS1UserServices.cs | |||
@@ -39,7 +39,7 @@ using OpenSim.Framework.Communications; | |||
39 | 39 | ||
40 | namespace OpenSim.Region.Communications.OGS1 | 40 | namespace OpenSim.Region.Communications.OGS1 |
41 | { | 41 | { |
42 | public class OGS1UserServices : IUserService, IAvatarService | 42 | public class OGS1UserServices : IUserService, IAvatarService, IMessagingService |
43 | { | 43 | { |
44 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 44 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
45 | 45 | ||
@@ -722,6 +722,64 @@ namespace OpenSim.Region.Communications.OGS1 | |||
722 | return buddylist; | 722 | return buddylist; |
723 | } | 723 | } |
724 | 724 | ||
725 | public Dictionary<UUID, FriendRegionInfo> GetFriendRegionInfos (List<UUID> uuids) | ||
726 | { | ||
727 | Dictionary<UUID, FriendRegionInfo> result = new Dictionary<UUID, FriendRegionInfo>(); | ||
728 | |||
729 | // ask MessageServer about the current on-/offline status and regions the friends are in | ||
730 | ArrayList parameters = new ArrayList(); | ||
731 | Hashtable map = new Hashtable(); | ||
732 | |||
733 | ArrayList list = new ArrayList(); | ||
734 | foreach (UUID uuid in uuids) | ||
735 | { | ||
736 | list.Add(uuid.ToString()); | ||
737 | list.Add(uuid.ToString()); | ||
738 | } | ||
739 | map["uuids"] = list; | ||
740 | |||
741 | map["recv_key"] = m_parent.NetworkServersInfo.UserRecvKey; | ||
742 | map["send_key"] = m_parent.NetworkServersInfo.UserRecvKey; | ||
743 | |||
744 | parameters.Add(map); | ||
745 | |||
746 | try { | ||
747 | XmlRpcRequest req = new XmlRpcRequest("get_presence_info_bulk", parameters); | ||
748 | XmlRpcResponse resp = req.Send(m_parent.NetworkServersInfo.MessagingURL, 8000); | ||
749 | Hashtable respData = (Hashtable) resp.Value; | ||
750 | |||
751 | if (respData.ContainsKey("faultMessage")) | ||
752 | { | ||
753 | m_log.WarnFormat("[OGS1 USER SERVICES]: Contacting MessageServer about user-regions resulted in error: {0}", | ||
754 | respData["faultMessage"]); | ||
755 | } | ||
756 | else | ||
757 | { | ||
758 | int count = (int)respData["count"]; | ||
759 | m_log.DebugFormat("[OGS1 USER SERVICES]: Request returned {0} results.", count); | ||
760 | for (int i = 0; i < count; ++i) | ||
761 | { | ||
762 | UUID uuid; | ||
763 | if (UUID.TryParse((string)respData["uuid_" + i], out uuid)) | ||
764 | { | ||
765 | FriendRegionInfo info = new FriendRegionInfo(); | ||
766 | info.isOnline = (bool)respData["isOnline_" + i]; | ||
767 | if (info.isOnline) info.regionHandle = Convert.ToUInt64(respData["regionHandle_" + i]); | ||
768 | |||
769 | result.Add(uuid, info); | ||
770 | } | ||
771 | } | ||
772 | } | ||
773 | } | ||
774 | catch (WebException e) | ||
775 | { | ||
776 | m_log.ErrorFormat("[OGS1 USER SERVICES]: Network problems when trying to fetch friend infos: {0}", e.Message); | ||
777 | } | ||
778 | |||
779 | m_log.DebugFormat("[OGS1 USER SERVICES]: Returning {0} entries", result.Count); | ||
780 | return result; | ||
781 | } | ||
782 | |||
725 | #endregion | 783 | #endregion |
726 | 784 | ||
727 | /// Appearance | 785 | /// Appearance |
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs index a380700..e2cb2e0 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs | |||
@@ -34,43 +34,100 @@ using log4net; | |||
34 | using Nini.Config; | 34 | using Nini.Config; |
35 | using Nwc.XmlRpc; | 35 | using Nwc.XmlRpc; |
36 | using OpenSim.Framework; | 36 | using OpenSim.Framework; |
37 | using OpenSim.Framework.Communications.Cache; | ||
38 | using OpenSim.Framework.Servers; | ||
37 | using OpenSim.Region.Environment.Interfaces; | 39 | using OpenSim.Region.Environment.Interfaces; |
38 | using OpenSim.Region.Environment.Scenes; | 40 | using OpenSim.Region.Environment.Scenes; |
39 | 41 | ||
40 | namespace OpenSim.Region.Environment.Modules.Avatar.Friends | 42 | namespace OpenSim.Region.Environment.Modules.Avatar.Friends |
41 | { | 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 | |||
42 | public class FriendsModule : IRegionModule | 81 | public class FriendsModule : IRegionModule |
43 | { | 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 | |||
44 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 97 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
45 | 98 | ||
46 | private Dictionary<UUID, List<FriendListItem>> FriendLists = new Dictionary<UUID, List<FriendListItem>>(); | 99 | private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate); |
47 | private Dictionary<UUID, UUID> m_pendingFriendRequests = new Dictionary<UUID, UUID>(); | 100 | |
48 | private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>(); | 101 | private Dictionary<UUID, ulong> m_rootAgents = new Dictionary<UUID, ulong>(); |
49 | private Dictionary<UUID, List<StoredFriendListUpdate>> StoredFriendListUpdates = new Dictionary<UUID, List<StoredFriendListUpdate>>(); | ||
50 | 102 | ||
103 | private Dictionary<UUID, Transaction> m_pendingFriendRequests = new Dictionary<UUID, Transaction>(); | ||
51 | private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>(); | 104 | private Dictionary<UUID, UUID> m_pendingCallingcardRequests = new Dictionary<UUID,UUID>(); |
52 | 105 | ||
53 | private List<Scene> m_scene = new List<Scene>(); | 106 | private Scene m_initialScene; // saves a lookup if we don't have a specific scene |
107 | private Dictionary<ulong, Scene> m_scenes = new Dictionary<ulong,Scene>(); | ||
54 | 108 | ||
55 | #region IRegionModule Members | 109 | #region IRegionModule Members |
56 | 110 | ||
57 | public void Initialise(Scene scene, IConfigSource config) | 111 | public void Initialise(Scene scene, IConfigSource config) |
58 | { | 112 | { |
59 | lock (m_scene) | 113 | lock (m_scenes) |
60 | { | 114 | { |
61 | if (m_scene.Count == 0) | 115 | if (m_scenes.Count == 0) |
62 | { | 116 | { |
63 | scene.AddXmlRPCHandler("presence_update", processPresenceUpdate); | 117 | scene.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk); |
118 | scene.AddXmlRPCHandler("terminate_friend", processTerminateFriend); | ||
119 | m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max | ||
120 | m_initialScene = scene; | ||
64 | } | 121 | } |
65 | 122 | ||
66 | if (!m_scene.Contains(scene)) | 123 | if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle)) |
67 | m_scene.Add(scene); | 124 | m_scenes[scene.RegionInfo.RegionHandle] = scene; |
68 | } | 125 | } |
69 | scene.EventManager.OnNewClient += OnNewClient; | 126 | scene.EventManager.OnNewClient += OnNewClient; |
70 | scene.EventManager.OnGridInstantMessage += OnGridInstantMessage; | 127 | scene.EventManager.OnGridInstantMessage += OnGridInstantMessage; |
71 | scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; | 128 | scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; |
72 | scene.EventManager.OnMakeChildAgent += MakeChildAgent; | 129 | scene.EventManager.OnMakeChildAgent += MakeChildAgent; |
73 | scene.EventManager.OnClientClosed += ClientLoggedOut; | 130 | scene.EventManager.OnClientClosed += ClientClosed; |
74 | } | 131 | } |
75 | 132 | ||
76 | public void PostInitialise() | 133 | public void PostInitialise() |
@@ -93,82 +150,104 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
93 | 150 | ||
94 | #endregion | 151 | #endregion |
95 | 152 | ||
96 | public XmlRpcResponse processPresenceUpdate(XmlRpcRequest req) | 153 | public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req) |
97 | { | 154 | { |
98 | //m_log.Info("[FRIENDS]: Got Notification about a user! OMG"); | ||
99 | Hashtable requestData = (Hashtable)req.Params[0]; | 155 | Hashtable requestData = (Hashtable)req.Params[0]; |
100 | 156 | ||
101 | if (requestData.ContainsKey("agent_id") && requestData.ContainsKey("notify_id") && requestData.ContainsKey("status")) | 157 | List<UUID> friendsNotHere = new List<UUID>(); |
102 | { | ||
103 | UUID notifyAgentId = UUID.Zero; | ||
104 | UUID notifyAboutAgentId = UUID.Zero; | ||
105 | bool notifyOnlineStatus = false; | ||
106 | |||
107 | if ((string)requestData["status"] == "TRUE") | ||
108 | notifyOnlineStatus = true; | ||
109 | 158 | ||
110 | UUID.TryParse((string)requestData["notify_id"], out notifyAgentId); | 159 | // this is called with the expectation that all the friends in the request are on this region-server. |
111 | 160 | // But as some time passed since we checked (on the other region-server, via the MessagingServer), | |
112 | UUID.TryParse((string)requestData["agent_id"], out notifyAboutAgentId); | 161 | // some of the friends might have teleported away. |
113 | m_log.InfoFormat("[PRESENCE]: Got presence update for {0}, and we're telling {1}, with a status {2}", notifyAboutAgentId.ToString(), notifyAgentId.ToString(), notifyOnlineStatus.ToString()); | 162 | // Actually, even now, between this line and the sending below, some people could TP away. So, |
114 | ScenePresence avatar = GetRootPresenceFromAgentID(notifyAgentId); | 163 | // we'll have to lock the m_rootAgents list for the duration to prevent/delay that. |
115 | if (avatar != null) | 164 | lock(m_rootAgents) |
165 | { | ||
166 | List<ScenePresence> friendsHere = new List<ScenePresence>(); | ||
167 | try | ||
116 | { | 168 | { |
117 | if (avatar.IsChildAgent) | 169 | UUID agentID = new UUID((string)requestData["agentID"]); |
170 | bool agentOnline = (bool)requestData["agentOnline"]; | ||
171 | int count = (int)requestData["friendCount"]; | ||
172 | for (int i = 0; i < count; ++i) | ||
118 | { | 173 | { |
119 | StoredFriendListUpdate sob = new StoredFriendListUpdate(); | 174 | UUID uuid; |
120 | sob.OnlineYN = notifyOnlineStatus; | 175 | if (UUID.TryParse((string)requestData["friendID_" + i], out uuid)) |
121 | sob.storedAbout = notifyAboutAgentId; | ||
122 | sob.storedFor = notifyAgentId; | ||
123 | lock (StoredFriendListUpdates) | ||
124 | { | 176 | { |
125 | if (StoredFriendListUpdates.ContainsKey(notifyAgentId)) | 177 | if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid)); |
126 | { | 178 | else friendsNotHere.Add(uuid); |
127 | StoredFriendListUpdates[notifyAgentId].Add(sob); | ||
128 | } | ||
129 | else | ||
130 | { | ||
131 | List<StoredFriendListUpdate> newitem = new List<StoredFriendListUpdate>(); | ||
132 | newitem.Add(sob); | ||
133 | StoredFriendListUpdates.Add(notifyAgentId, newitem); | ||
134 | } | ||
135 | } | 179 | } |
136 | } | 180 | } |
137 | else | 181 | |
138 | { | 182 | // now send, as long as they are still here... |
139 | if (notifyOnlineStatus) | 183 | UUID[] agentUUID = new UUID[] { agentID }; |
140 | doFriendListUpdateOnline(notifyAboutAgentId); | 184 | if (agentOnline) |
141 | else | ||
142 | ClientLoggedOut(notifyAboutAgentId); | ||
143 | } | ||
144 | } | ||
145 | else | ||
146 | { | ||
147 | StoredFriendListUpdate sob = new StoredFriendListUpdate(); | ||
148 | sob.OnlineYN = notifyOnlineStatus; | ||
149 | sob.storedAbout = notifyAboutAgentId; | ||
150 | sob.storedFor = notifyAgentId; | ||
151 | lock (StoredFriendListUpdates) | ||
152 | { | 185 | { |
153 | if (StoredFriendListUpdates.ContainsKey(notifyAgentId)) | 186 | foreach (ScenePresence agent in friendsHere) |
154 | { | 187 | { |
155 | StoredFriendListUpdates[notifyAgentId].Add(sob); | 188 | agent.ControllingClient.SendAgentOnline(agentUUID); |
156 | } | 189 | } |
157 | else | 190 | } |
191 | else | ||
192 | { | ||
193 | foreach (ScenePresence agent in friendsHere) | ||
158 | { | 194 | { |
159 | List<StoredFriendListUpdate> newitem = new List<StoredFriendListUpdate>(); | 195 | agent.ControllingClient.SendAgentOffline(agentUUID); |
160 | newitem.Add(sob); | ||
161 | StoredFriendListUpdates.Add(notifyAgentId, newitem); | ||
162 | } | 196 | } |
163 | } | 197 | } |
164 | } | 198 | } |
199 | catch(Exception e) | ||
200 | { | ||
201 | m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e); | ||
202 | } | ||
203 | } | ||
165 | 204 | ||
205 | // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region, | ||
206 | // which should be caught on the next iteration... | ||
207 | Hashtable result = new Hashtable(); | ||
208 | int idx = 0; | ||
209 | foreach (UUID uuid in friendsNotHere) | ||
210 | { | ||
211 | result["friendID_" + idx++] = uuid.ToString(); | ||
166 | } | 212 | } |
167 | else | 213 | result["friendCount"] = idx; |
214 | |||
215 | XmlRpcResponse response = new XmlRpcResponse(); | ||
216 | response.Value = result; | ||
217 | |||
218 | return response; | ||
219 | } | ||
220 | |||
221 | public XmlRpcResponse processTerminateFriend(XmlRpcRequest req) | ||
222 | { | ||
223 | Hashtable requestData = (Hashtable)req.Params[0]; | ||
224 | |||
225 | bool success = false; | ||
226 | |||
227 | UUID agentID; | ||
228 | UUID friendID; | ||
229 | if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) && | ||
230 | requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID)) | ||
168 | { | 231 | { |
169 | m_log.Warn("[PRESENCE]: Malformed XMLRPC Presence request"); | 232 | // try to find it and if it is there, prevent it to vanish before we sent the message |
233 | lock(m_rootAgents) | ||
234 | { | ||
235 | if (m_rootAgents.ContainsKey(agentID)) | ||
236 | { | ||
237 | m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID); | ||
238 | GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID); | ||
239 | success = true; | ||
240 | } | ||
241 | } | ||
170 | } | 242 | } |
171 | return new XmlRpcResponse(); | 243 | |
244 | // return whether we were successful | ||
245 | Hashtable result = new Hashtable(); | ||
246 | result["success"] = success; | ||
247 | |||
248 | XmlRpcResponse response = new XmlRpcResponse(); | ||
249 | response.Value = result; | ||
250 | return response; | ||
172 | } | 251 | } |
173 | 252 | ||
174 | private void OnNewClient(IClientAPI client) | 253 | private void OnNewClient(IClientAPI client) |
@@ -176,245 +255,52 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
176 | // All friends establishment protocol goes over instant message | 255 | // All friends establishment protocol goes over instant message |
177 | // There's no way to send a message from the sim | 256 | // There's no way to send a message from the sim |
178 | // to a user to 'add a friend' without causing dialog box spam | 257 | // to a user to 'add a friend' without causing dialog box spam |
179 | // | ||
180 | // The base set of friends are added when the user signs on in their XMLRPC response | ||
181 | // Generated by LoginService. The friends are retreived from the database by the UserManager | ||
182 | 258 | ||
183 | // Subscribe to instant messages | 259 | // Subscribe to instant messages |
184 | |||
185 | client.OnInstantMessage += OnInstantMessage; | 260 | client.OnInstantMessage += OnInstantMessage; |
186 | client.OnApproveFriendRequest += OnApprovedFriendRequest; | 261 | |
262 | // Friend list management | ||
263 | client.OnApproveFriendRequest += OnApproveFriendRequest; | ||
187 | client.OnDenyFriendRequest += OnDenyFriendRequest; | 264 | client.OnDenyFriendRequest += OnDenyFriendRequest; |
188 | client.OnTerminateFriendship += OnTerminateFriendship; | 265 | client.OnTerminateFriendship += OnTerminateFriendship; |
266 | |||
267 | // ... calling card handling... | ||
189 | client.OnOfferCallingCard += OnOfferCallingCard; | 268 | client.OnOfferCallingCard += OnOfferCallingCard; |
190 | client.OnAcceptCallingCard += OnAcceptCallingCard; | 269 | client.OnAcceptCallingCard += OnAcceptCallingCard; |
191 | client.OnDeclineCallingCard += OnDeclineCallingCard; | 270 | client.OnDeclineCallingCard += OnDeclineCallingCard; |
192 | 271 | ||
193 | doFriendListUpdateOnline(client.AgentId); | 272 | // we need this one exactly once per agent session (see comments in the handler below) |
194 | 273 | client.OnEconomyDataRequest += OnEconomyDataRequest; | |
195 | } | ||
196 | |||
197 | private void doFriendListUpdateOnline(UUID AgentId) | ||
198 | { | ||
199 | List<FriendListItem> fl = new List<FriendListItem>(); | ||
200 | |||
201 | //bool addFLback = false; | ||
202 | |||
203 | lock (FriendLists) | ||
204 | { | ||
205 | if (FriendLists.ContainsKey(AgentId)) | ||
206 | { | ||
207 | fl = FriendLists[AgentId]; | ||
208 | } | ||
209 | else | ||
210 | { | ||
211 | fl = m_scene[0].GetFriendList(AgentId); | ||
212 | |||
213 | //lock (FriendLists) | ||
214 | //{ | ||
215 | if (!FriendLists.ContainsKey(AgentId)) | ||
216 | FriendLists.Add(AgentId, fl); | ||
217 | //} | ||
218 | } | ||
219 | } | ||
220 | |||
221 | List<UUID> UpdateUsers = new List<UUID>(); | ||
222 | |||
223 | foreach (FriendListItem f in fl) | ||
224 | { | ||
225 | if (m_rootAgents.ContainsKey(f.Friend)) | ||
226 | { | ||
227 | if (f.onlinestatus == false) | ||
228 | { | ||
229 | UpdateUsers.Add(f.Friend); | ||
230 | f.onlinestatus = true; | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | foreach (UUID user in UpdateUsers) | ||
235 | { | ||
236 | ScenePresence av = GetRootPresenceFromAgentID(user); | ||
237 | if (av != null) | ||
238 | { | ||
239 | List<FriendListItem> usrfl = new List<FriendListItem>(); | ||
240 | |||
241 | lock (FriendLists) | ||
242 | { | ||
243 | usrfl = FriendLists[user]; | ||
244 | } | ||
245 | |||
246 | lock (usrfl) | ||
247 | { | ||
248 | foreach (FriendListItem fli in usrfl) | ||
249 | { | ||
250 | if (fli.Friend == AgentId) | ||
251 | { | ||
252 | fli.onlinestatus = true; | ||
253 | UUID[] Agents = new UUID[1]; | ||
254 | Agents[0] = AgentId; | ||
255 | av.ControllingClient.SendAgentOnline(Agents); | ||
256 | |||
257 | } | ||
258 | } | ||
259 | } | ||
260 | } | ||
261 | } | ||
262 | |||
263 | if (UpdateUsers.Count > 0) | ||
264 | { | ||
265 | ScenePresence avatar = GetRootPresenceFromAgentID(AgentId); | ||
266 | if (avatar != null) | ||
267 | { | ||
268 | avatar.ControllingClient.SendAgentOnline(UpdateUsers.ToArray()); | ||
269 | } | ||
270 | 274 | ||
271 | } | 275 | // if it leaves, we want to know, too |
276 | client.OnLogout += OnLogout; | ||
272 | } | 277 | } |
273 | 278 | ||
274 | private void ClientLoggedOut(UUID AgentId) | 279 | private void ClientClosed(UUID AgentId) |
275 | { | 280 | { |
281 | // agent's client was closed. As we handle logout in OnLogout, this here has only to handle | ||
282 | // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client | ||
283 | // agent is closed). | ||
284 | // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around | ||
285 | // in one of the regions here anymore. | ||
276 | lock (m_rootAgents) | 286 | lock (m_rootAgents) |
277 | { | 287 | { |
278 | if (m_rootAgents.ContainsKey(AgentId)) | 288 | if (m_rootAgents.ContainsKey(AgentId)) |
279 | { | 289 | { |
280 | m_rootAgents.Remove(AgentId); | 290 | m_rootAgents.Remove(AgentId); |
281 | m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent logged out."); | 291 | m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent was closed."); |
282 | } | ||
283 | } | ||
284 | List<FriendListItem> lfli = new List<FriendListItem>(); | ||
285 | lock (FriendLists) | ||
286 | { | ||
287 | if (FriendLists.ContainsKey(AgentId)) | ||
288 | { | ||
289 | lfli = FriendLists[AgentId]; | ||
290 | } | ||
291 | } | ||
292 | List<UUID> updateUsers = new List<UUID>(); | ||
293 | foreach (FriendListItem fli in lfli) | ||
294 | { | ||
295 | if (fli.onlinestatus == true) | ||
296 | { | ||
297 | updateUsers.Add(fli.Friend); | ||
298 | } | ||
299 | } | ||
300 | lock (updateUsers) | ||
301 | { | ||
302 | for (int i = 0; i < updateUsers.Count; i++) | ||
303 | { | ||
304 | List<FriendListItem> flfli = new List<FriendListItem>(); | ||
305 | try | ||
306 | { | ||
307 | lock (FriendLists) | ||
308 | { | ||
309 | if (FriendLists.ContainsKey(updateUsers[i])) | ||
310 | flfli = FriendLists[updateUsers[i]]; | ||
311 | } | ||
312 | } | ||
313 | catch (IndexOutOfRangeException) | ||
314 | { | ||
315 | // Ignore the index out of range exception. | ||
316 | // This causes friend lists to get out of sync slightly.. however | ||
317 | // prevents a sim crash. | ||
318 | m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); | ||
319 | } | ||
320 | catch (ArgumentOutOfRangeException) | ||
321 | { | ||
322 | // Ignore the index out of range exception. | ||
323 | // This causes friend lists to get out of sync slightly.. however | ||
324 | // prevents a sim crash. | ||
325 | m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); | ||
326 | } | ||
327 | |||
328 | for (int j = 0; j < flfli.Count; j++) | ||
329 | { | ||
330 | try | ||
331 | { | ||
332 | if (flfli[i].Friend == AgentId) | ||
333 | { | ||
334 | flfli[i].onlinestatus = false; | ||
335 | } | ||
336 | } | ||
337 | |||
338 | catch (IndexOutOfRangeException) | ||
339 | { | ||
340 | // Ignore the index out of range exception. | ||
341 | // This causes friend lists to get out of sync slightly.. however | ||
342 | // prevents a sim crash. | ||
343 | m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); | ||
344 | } | ||
345 | catch (ArgumentOutOfRangeException) | ||
346 | { | ||
347 | // Ignore the index out of range exception. | ||
348 | // This causes friend lists to get out of sync slightly.. however | ||
349 | // prevents a sim crash. | ||
350 | m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); | ||
351 | } | ||
352 | } | ||
353 | } | ||
354 | |||
355 | for (int i = 0; i < updateUsers.Count; i++) | ||
356 | { | ||
357 | ScenePresence av = GetRootPresenceFromAgentID(updateUsers[i]); | ||
358 | if (av != null) | ||
359 | { | ||
360 | UUID[] agents = new UUID[1]; | ||
361 | agents[0] = AgentId; | ||
362 | av.ControllingClient.SendAgentOffline(agents); | ||
363 | } | ||
364 | } | 292 | } |
365 | } | 293 | } |
366 | lock (FriendLists) | ||
367 | { | ||
368 | FriendLists.Remove(AgentId); | ||
369 | } | ||
370 | } | 294 | } |
371 | 295 | ||
372 | private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) | 296 | private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) |
373 | { | 297 | { |
374 | lock (m_rootAgents) | 298 | lock (m_rootAgents) |
375 | { | 299 | { |
376 | if (m_rootAgents.ContainsKey(avatar.UUID)) | 300 | m_rootAgents[avatar.UUID] = avatar.RegionHandle; |
377 | { | 301 | m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); |
378 | if (avatar.RegionHandle != m_rootAgents[avatar.UUID]) | 302 | // Claim User! my user! Mine mine mine! |
379 | { | ||
380 | m_rootAgents[avatar.UUID] = avatar.RegionHandle; | ||
381 | m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); | ||
382 | if (avatar.JID.Length > 0) | ||
383 | { | ||
384 | // JId avatarID = new JId(avatar.JID); | ||
385 | // REST Post XMPP Stanzas! | ||
386 | } | ||
387 | // Claim User! my user! Mine mine mine! | ||
388 | } | ||
389 | } | ||
390 | else | ||
391 | { | ||
392 | m_rootAgents.Add(avatar.UUID, avatar.RegionHandle); | ||
393 | m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); | ||
394 | |||
395 | List<StoredFriendListUpdate> updateme = new List<StoredFriendListUpdate>(); | ||
396 | lock (StoredFriendListUpdates) | ||
397 | { | ||
398 | if (StoredFriendListUpdates.ContainsKey(avatar.UUID)) | ||
399 | { | ||
400 | updateme = StoredFriendListUpdates[avatar.UUID]; | ||
401 | StoredFriendListUpdates.Remove(avatar.UUID); | ||
402 | } | ||
403 | } | ||
404 | |||
405 | if (updateme.Count > 0) | ||
406 | { | ||
407 | foreach (StoredFriendListUpdate u in updateme) | ||
408 | { | ||
409 | if (u.OnlineYN) | ||
410 | doFriendListUpdateOnline(u.storedAbout); | ||
411 | else | ||
412 | ClientLoggedOut(u.storedAbout); | ||
413 | } | ||
414 | } | ||
415 | } | ||
416 | } | 303 | } |
417 | //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); | ||
418 | } | 304 | } |
419 | 305 | ||
420 | private void MakeChildAgent(ScenePresence avatar) | 306 | private void MakeChildAgent(ScenePresence avatar) |
@@ -423,6 +309,8 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
423 | { | 309 | { |
424 | if (m_rootAgents.ContainsKey(avatar.UUID)) | 310 | if (m_rootAgents.ContainsKey(avatar.UUID)) |
425 | { | 311 | { |
312 | // only delete if the region matches. As this is a shared module, the avatar could be | ||
313 | // root agent in another region on this server. | ||
426 | if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) | 314 | if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) |
427 | { | 315 | { |
428 | m_rootAgents.Remove(avatar.UUID); | 316 | m_rootAgents.Remove(avatar.UUID); |
@@ -435,12 +323,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
435 | private ScenePresence GetRootPresenceFromAgentID(UUID AgentID) | 323 | private ScenePresence GetRootPresenceFromAgentID(UUID AgentID) |
436 | { | 324 | { |
437 | ScenePresence returnAgent = null; | 325 | ScenePresence returnAgent = null; |
438 | lock (m_scene) | 326 | lock (m_scenes) |
439 | { | 327 | { |
440 | ScenePresence queryagent = null; | 328 | ScenePresence queryagent = null; |
441 | for (int i = 0; i < m_scene.Count; i++) | 329 | foreach (Scene scene in m_scenes.Values) |
442 | { | 330 | { |
443 | queryagent = m_scene[i].GetScenePresence(AgentID); | 331 | queryagent = scene.GetScenePresence(AgentID); |
444 | if (queryagent != null) | 332 | if (queryagent != null) |
445 | { | 333 | { |
446 | if (!queryagent.IsChildAgent) | 334 | if (!queryagent.IsChildAgent) |
@@ -457,12 +345,12 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
457 | private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID) | 345 | private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID) |
458 | { | 346 | { |
459 | ScenePresence returnAgent = null; | 347 | ScenePresence returnAgent = null; |
460 | lock (m_scene) | 348 | lock (m_scenes) |
461 | { | 349 | { |
462 | ScenePresence queryagent = null; | 350 | ScenePresence queryagent = null; |
463 | for (int i = 0; i < m_scene.Count; i++) | 351 | foreach (Scene scene in m_scenes.Values) |
464 | { | 352 | { |
465 | queryagent = m_scene[i].GetScenePresence(AgentID); | 353 | queryagent = scene.GetScenePresence(AgentID); |
466 | if (queryagent != null) | 354 | if (queryagent != null) |
467 | { | 355 | { |
468 | returnAgent = queryagent; | 356 | returnAgent = queryagent; |
@@ -485,141 +373,160 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
485 | // Friend Requests go by Instant Message.. using the dialog param | 373 | // Friend Requests go by Instant Message.. using the dialog param |
486 | // https://wiki.secondlife.com/wiki/ImprovedInstantMessage | 374 | // https://wiki.secondlife.com/wiki/ImprovedInstantMessage |
487 | 375 | ||
488 | // 38 == Offer friendship | 376 | if (dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38 |
489 | if (dialog == (byte) 38) | ||
490 | { | 377 | { |
491 | UUID friendTransactionID = UUID.Random(); | 378 | // this is triggered by the initiating agent and has two parts: |
379 | // A local agent offers friendship to some possibly remote friend. | ||
380 | // A IM is triggered, processed here (1), sent to the destination region, | ||
381 | // and processed there in this part of the code again (2). | ||
382 | // (1) has fromAgentSession != UUID.Zero, | ||
383 | // (2) has fromAgentSession == UUID.Zero (don't leak agent sessions to other agents) | ||
384 | // For (1), build the IM to send to the other region (and trigger sending it) | ||
385 | // FOr (2), just store the transaction; we will wait for Approval or Decline | ||
386 | |||
387 | // some properties are misused here: | ||
388 | // fromAgentName is the *destination* name (the friend we offer friendship to) | ||
389 | |||
390 | if (fromAgentSession != UUID.Zero) | ||
391 | { | ||
392 | // (1) | ||
393 | // send the friendship-offer to the target | ||
394 | m_log.InfoFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}", | ||
395 | fromAgentID, fromAgentName, toAgentID, imSessionID, message, offline); | ||
396 | |||
397 | UUID transactionID = UUID.Random(); | ||
398 | |||
399 | GridInstantMessage msg = new GridInstantMessage(); | ||
400 | msg.fromAgentID = fromAgentID.Guid; | ||
401 | msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session | ||
402 | msg.toAgentID = toAgentID.Guid; | ||
403 | msg.imSessionID = transactionID.Guid; // Start new transaction | ||
404 | m_log.DebugFormat("[FRIEND]: new transactionID: {0}", msg.imSessionID); | ||
405 | msg.timestamp = timestamp; | ||
406 | if (client != null) | ||
407 | { | ||
408 | msg.fromAgentName = client.Name; // fromAgentName; | ||
409 | } | ||
410 | else | ||
411 | { | ||
412 | msg.fromAgentName = "(hippos)"; // Added for posterity. This means that we can't figure out who sent it | ||
413 | } | ||
414 | msg.message = message; | ||
415 | msg.dialog = dialog; | ||
416 | msg.fromGroup = fromGroup; | ||
417 | msg.offline = offline; | ||
418 | msg.ParentEstateID = ParentEstateID; | ||
419 | msg.Position = Position; | ||
420 | msg.RegionID = RegionID.Guid; | ||
421 | msg.binaryBucket = binaryBucket; | ||
422 | |||
423 | m_log.DebugFormat("[FRIEND]: storing transactionID {0} on sender side", transactionID); | ||
424 | lock (m_pendingFriendRequests) | ||
425 | { | ||
426 | m_pendingFriendRequests.Add(transactionID, new Transaction(fromAgentID, fromAgentName)); | ||
427 | outPending(); | ||
428 | } | ||
492 | 429 | ||
493 | m_pendingFriendRequests.Add(friendTransactionID, fromAgentID); | 430 | // we don't want to get that new IM into here if we aren't local, as only on the destination |
431 | // should receive it. If we *are* local, *we* are the destination, so we have to receive it. | ||
432 | // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here. | ||
433 | InstantMessageReceiver recv = InstantMessageReceiver.IMModule; | ||
434 | if(GetAnyPresenceFromAgentID(toAgentID) != null) recv |= InstantMessageReceiver.FriendsModule; | ||
494 | 435 | ||
495 | m_log.Info("[FRIEND]: 38 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + | 436 | // We don't really care which local scene we pipe it through. |
496 | message); | 437 | m_initialScene.TriggerGridInstantMessage(msg, recv); |
497 | GridInstantMessage msg = new GridInstantMessage(); | ||
498 | msg.fromAgentID = fromAgentID.Guid; | ||
499 | msg.fromAgentSession = fromAgentSession.Guid; | ||
500 | msg.toAgentID = toAgentID.Guid; | ||
501 | msg.imSessionID = friendTransactionID.Guid; // This is the item we're mucking with here | ||
502 | m_log.Info("[FRIEND]: Filling Session: " + msg.imSessionID.ToString()); | ||
503 | msg.timestamp = timestamp; | ||
504 | if (client != null) | ||
505 | { | ||
506 | msg.fromAgentName = client.Name; // fromAgentName; | ||
507 | } | 438 | } |
508 | else | 439 | else |
509 | { | 440 | { |
510 | msg.fromAgentName = "(hippos)"; // Added for posterity. This means that we can't figure out who sent it | 441 | // (2) |
442 | // we are on the receiving end here; just add the transactionID to the stored transactions for later lookup | ||
443 | m_log.DebugFormat("[FRIEND]: storing transactionID {0} on receiver side", imSessionID); | ||
444 | lock (m_pendingFriendRequests) | ||
445 | { | ||
446 | // if both are on the same region-server, the transaction is stored already, but we have to update the name | ||
447 | if (m_pendingFriendRequests.ContainsKey(imSessionID)) | ||
448 | { | ||
449 | m_pendingFriendRequests[imSessionID].agentName = fromAgentName; | ||
450 | m_pendingFriendRequests[imSessionID].count++; | ||
451 | } | ||
452 | else m_pendingFriendRequests.Add(imSessionID, new Transaction(fromAgentID, fromAgentName)); | ||
453 | outPending(); | ||
454 | } | ||
511 | } | 455 | } |
512 | msg.message = message; | ||
513 | msg.dialog = dialog; | ||
514 | msg.fromGroup = fromGroup; | ||
515 | msg.offline = offline; | ||
516 | msg.ParentEstateID = ParentEstateID; | ||
517 | msg.Position = Position; | ||
518 | msg.RegionID = RegionID.Guid; | ||
519 | msg.binaryBucket = binaryBucket; | ||
520 | // We don't really care which scene we pipe it through. | ||
521 | m_scene[0].TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); | ||
522 | } | 456 | } |
523 | 457 | else if (dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39 | |
524 | // 39 == Accept Friendship | ||
525 | if (dialog == (byte) 39) | ||
526 | { | 458 | { |
527 | m_log.Info("[FRIEND]: 39 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + | 459 | // accepting the friendship offer causes a type 39 IM being sent to the (possibly remote) initiator |
528 | message); | 460 | // toAgentID is initiator, fromAgentID is new friend (which just approved) |
529 | } | 461 | m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agentSession {1}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})", |
462 | client != null ? client.AgentId.ToString() : "<null>", fromAgentSession, | ||
463 | fromAgentID, fromAgentName, imSessionID, toAgentID, message, dialog); | ||
464 | lock (m_pendingFriendRequests) | ||
465 | { | ||
466 | if (!m_pendingFriendRequests.ContainsKey(imSessionID)) | ||
467 | { | ||
468 | m_log.DebugFormat("[FRIEND]: Got friendship approval from {0} to {1} without matching transaction {2}", | ||
469 | fromAgentID, toAgentID, imSessionID); | ||
470 | return; // unknown transaction | ||
471 | } | ||
472 | // else found pending friend request with that transaction => remove it if we handled all | ||
473 | if (--m_pendingFriendRequests[imSessionID].count <= 0) m_pendingFriendRequests.Remove(imSessionID); | ||
474 | outPending(); | ||
475 | } | ||
530 | 476 | ||
531 | // 40 == Decline Friendship | 477 | // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now. |
532 | if (dialog == (byte) 40) | 478 | lock (m_friendLists) |
533 | { | 479 | { |
534 | m_log.Info("[FRIEND]: 40 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + | 480 | m_friendLists.Invalidate(toAgentID); |
535 | message); | 481 | m_friendLists.Invalidate(fromAgentID); |
536 | } | 482 | } |
537 | } | ||
538 | 483 | ||
539 | private void OnApprovedFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders) | 484 | // now send presence update and add a calling card for the new friend |
540 | { | ||
541 | if (m_pendingFriendRequests.ContainsKey(transactionID)) | ||
542 | { | ||
543 | // Found Pending Friend Request with that Transaction.. | ||
544 | Scene SceneAgentIn = m_scene[0]; | ||
545 | 485 | ||
546 | // Found Pending Friend Request with that Transaction.. | 486 | ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID); |
547 | ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); | 487 | if (initiator == null) |
548 | if (agentpresence != null) | ||
549 | { | 488 | { |
550 | SceneAgentIn = agentpresence.Scene; | 489 | // quite wrong. Shouldn't happen. |
490 | m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID); | ||
491 | return; | ||
551 | } | 492 | } |
552 | 493 | ||
553 | // Compose response to other agent. | 494 | // tell initiator that friend is online |
554 | GridInstantMessage msg = new GridInstantMessage(); | 495 | initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID }); |
555 | msg.toAgentID = m_pendingFriendRequests[transactionID].Guid; | ||
556 | msg.fromAgentID = agentID.Guid; | ||
557 | msg.fromAgentName = client.Name; | ||
558 | msg.fromAgentSession = client.SessionId.Guid; | ||
559 | msg.fromGroup = false; | ||
560 | msg.imSessionID = transactionID.Guid; | ||
561 | msg.message = agentID.Guid.ToString(); | ||
562 | msg.ParentEstateID = 0; | ||
563 | msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); | ||
564 | msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; | ||
565 | msg.dialog = (byte) 39; // Approved friend request | ||
566 | msg.Position = Vector3.Zero; | ||
567 | msg.offline = (byte) 0; | ||
568 | msg.binaryBucket = new byte[0]; | ||
569 | // We don't really care which scene we pipe it through, it goes to the shared IM Module and/or the database | ||
570 | |||
571 | SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); | ||
572 | SceneAgentIn.StoreAddFriendship(m_pendingFriendRequests[transactionID], agentID, (uint) 1); | ||
573 | |||
574 | |||
575 | //UUID[] Agents = new UUID[1]; | ||
576 | //Agents[0] = msg.toAgentID; | ||
577 | //av.ControllingClient.SendAgentOnline(Agents); | ||
578 | |||
579 | m_pendingFriendRequests.Remove(transactionID); | ||
580 | // TODO: Inform agent that the friend is online | ||
581 | } | ||
582 | } | ||
583 | 496 | ||
584 | private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders) | 497 | // find the folder for the friend... |
585 | { | 498 | InventoryFolderImpl folder = |
586 | if (m_pendingFriendRequests.ContainsKey(transactionID)) | 499 | initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard); |
500 | if (folder != null) | ||
501 | { | ||
502 | // ... and add the calling card | ||
503 | CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromAgentName); | ||
504 | } | ||
505 | } | ||
506 | else if (dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40 | ||
587 | { | 507 | { |
588 | Scene SceneAgentIn = m_scene[0]; | 508 | // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator |
589 | 509 | // toAgentID is initiator, fromAgentID declined friendship | |
590 | // Found Pending Friend Request with that Transaction.. | 510 | m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agentSession {1}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})", |
591 | ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); | 511 | client != null ? client.AgentId.ToString() : "<null>", fromAgentSession, |
592 | if (agentpresence != null) | 512 | fromAgentID, fromAgentName, imSessionID, toAgentID, message, dialog); |
513 | |||
514 | // not much to do, just clean up the transaction... | ||
515 | lock (m_pendingFriendRequests) | ||
593 | { | 516 | { |
594 | SceneAgentIn = agentpresence.Scene; | 517 | if (!m_pendingFriendRequests.ContainsKey(imSessionID)) |
518 | { | ||
519 | m_log.DebugFormat("[FRIEND]: Got friendship denial from {0} to {1} without matching transaction {2}", | ||
520 | fromAgentID, toAgentID, imSessionID); | ||
521 | return; // unknown transaction | ||
522 | } | ||
523 | // else found pending friend request with that transaction => remove it if we handled all | ||
524 | if (--m_pendingFriendRequests[imSessionID].count <= 0) m_pendingFriendRequests.Remove(imSessionID); | ||
525 | outPending(); | ||
595 | } | 526 | } |
596 | // Compose response to other agent. | ||
597 | GridInstantMessage msg = new GridInstantMessage(); | ||
598 | msg.toAgentID = m_pendingFriendRequests[transactionID].Guid; | ||
599 | msg.fromAgentID = agentID.Guid; | ||
600 | msg.fromAgentName = client.Name; | ||
601 | msg.fromAgentSession = client.SessionId.Guid; | ||
602 | msg.fromGroup = false; | ||
603 | msg.imSessionID = transactionID.Guid; | ||
604 | msg.message = agentID.Guid.ToString(); | ||
605 | msg.ParentEstateID = 0; | ||
606 | msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); | ||
607 | msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; | ||
608 | msg.dialog = (byte) 40; // Deny friend request | ||
609 | msg.Position = Vector3.Zero; | ||
610 | msg.offline = (byte) 0; | ||
611 | msg.binaryBucket = new byte[0]; | ||
612 | SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); | ||
613 | m_pendingFriendRequests.Remove(transactionID); | ||
614 | } | 527 | } |
615 | } | 528 | } |
616 | 529 | ||
617 | private void OnTerminateFriendship(IClientAPI client, UUID agent, UUID exfriendID) | ||
618 | { | ||
619 | m_scene[0].StoreRemoveFriendship(agent, exfriendID); | ||
620 | // TODO: Inform the client that the ExFriend is offline | ||
621 | } | ||
622 | |||
623 | private void OnGridInstantMessage(GridInstantMessage msg, InstantMessageReceiver whichModule) | 530 | private void OnGridInstantMessage(GridInstantMessage msg, InstantMessageReceiver whichModule) |
624 | { | 531 | { |
625 | if ((whichModule & InstantMessageReceiver.FriendsModule) == 0) | 532 | if ((whichModule & InstantMessageReceiver.FriendsModule) == 0) |
@@ -633,6 +540,182 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
633 | msg.binaryBucket); | 540 | msg.binaryBucket); |
634 | } | 541 | } |
635 | 542 | ||
543 | private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders) | ||
544 | { | ||
545 | m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}", | ||
546 | client.Name, client.AgentId, agentID, transactionID); | ||
547 | Transaction transaction; | ||
548 | lock (m_pendingFriendRequests) | ||
549 | { | ||
550 | if (!m_pendingFriendRequests.TryGetValue(transactionID, out transaction)) | ||
551 | { | ||
552 | m_log.DebugFormat("[FRIEND]: Got friendship approval {0} from {1} ({2}) without matching transaction {3}", | ||
553 | agentID, client.AgentId, client.Name, transactionID); | ||
554 | return; // unknown transaction | ||
555 | } | ||
556 | // else found pending friend request with that transaction => remove if done with all | ||
557 | if(--m_pendingFriendRequests[transactionID].count <= 0) m_pendingFriendRequests.Remove(transactionID); | ||
558 | outPending(); | ||
559 | } | ||
560 | |||
561 | UUID friendID = transaction.agentID; | ||
562 | m_log.DebugFormat("[FRIEND]: {0} ({1}) approved friendship request from {2}", | ||
563 | client.Name, client.AgentId, friendID); | ||
564 | |||
565 | Scene SceneAgentIn = m_initialScene; | ||
566 | // we need any presence to send the packets to, not necessarily the root agent... | ||
567 | ScenePresence agentpresence = GetAnyPresenceFromAgentID(agentID); | ||
568 | if (agentpresence != null) | ||
569 | { | ||
570 | SceneAgentIn = agentpresence.Scene; | ||
571 | } | ||
572 | |||
573 | // store the new friend persistently for both avatars | ||
574 | SceneAgentIn.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline); | ||
575 | |||
576 | // The cache entries aren't valid anymore either, as we just added a friend to both sides. | ||
577 | lock (m_friendLists) | ||
578 | { | ||
579 | m_friendLists.Invalidate(agentID); | ||
580 | m_friendLists.Invalidate(friendID); | ||
581 | } | ||
582 | |||
583 | // create calling card | ||
584 | CreateCallingCard(client, friendID, callingCardFolders[0], transaction.agentName); | ||
585 | |||
586 | // Compose response to other agent. | ||
587 | GridInstantMessage msg = new GridInstantMessage(); | ||
588 | msg.toAgentID = friendID.Guid; | ||
589 | msg.fromAgentID = agentID.Guid; | ||
590 | msg.fromAgentName = client.Name; | ||
591 | msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session | ||
592 | msg.fromGroup = false; | ||
593 | msg.imSessionID = transactionID.Guid; | ||
594 | msg.message = agentID.Guid.ToString(); | ||
595 | msg.ParentEstateID = 0; | ||
596 | msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); | ||
597 | msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; | ||
598 | msg.dialog = (byte) InstantMessageDialog.FriendshipAccepted; | ||
599 | msg.Position = Vector3.Zero; | ||
600 | msg.offline = (byte) 0; | ||
601 | msg.binaryBucket = new byte[0]; | ||
602 | |||
603 | // we don't want to get that new IM into here if we aren't local, as only on the destination | ||
604 | // should receive it. If we *are* local, *we* are the destination, so we have to receive it. | ||
605 | // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here. | ||
606 | InstantMessageReceiver recv = InstantMessageReceiver.IMModule; | ||
607 | if(GetAnyPresenceFromAgentID(friendID) != null) recv |= InstantMessageReceiver.FriendsModule; | ||
608 | |||
609 | // now we have to inform the agent about the friend. For the opposite direction, this happens in the handler | ||
610 | // of the type 39 IM | ||
611 | SceneAgentIn.TriggerGridInstantMessage(msg, recv); | ||
612 | |||
613 | // tell client that new friend is online | ||
614 | client.SendAgentOnline(new UUID[] { friendID }); | ||
615 | } | ||
616 | |||
617 | private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID transactionID, List<UUID> callingCardFolders) | ||
618 | { | ||
619 | m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}", | ||
620 | client.Name, client.AgentId, agentID, transactionID); | ||
621 | Transaction transaction; | ||
622 | lock (m_pendingFriendRequests) | ||
623 | { | ||
624 | if(!m_pendingFriendRequests.TryGetValue(transactionID, out transaction)) | ||
625 | { | ||
626 | m_log.DebugFormat("[FRIEND]: Got friendship denial {0} from {1} ({2}) without matching transaction {3}", | ||
627 | agentID, client.AgentId, client.Name, transactionID); | ||
628 | return; | ||
629 | } | ||
630 | // else found pending friend request with that transaction. | ||
631 | if(--m_pendingFriendRequests[transactionID].count <= 0) m_pendingFriendRequests.Remove(transactionID); | ||
632 | outPending(); | ||
633 | } | ||
634 | UUID friendID = transaction.agentID; | ||
635 | |||
636 | Scene SceneAgentIn = m_initialScene; | ||
637 | ScenePresence agentpresence = GetRootPresenceFromAgentID(agentID); | ||
638 | if (agentpresence != null) | ||
639 | { | ||
640 | SceneAgentIn = agentpresence.Scene; | ||
641 | } | ||
642 | |||
643 | // Compose response to other agent. | ||
644 | GridInstantMessage msg = new GridInstantMessage(); | ||
645 | msg.toAgentID = friendID.Guid; | ||
646 | msg.fromAgentID = agentID.Guid; | ||
647 | msg.fromAgentName = client.Name; | ||
648 | msg.fromAgentSession = UUID.Zero.Guid; // server IMs don't have a session | ||
649 | msg.fromGroup = false; | ||
650 | msg.imSessionID = transactionID.Guid; | ||
651 | msg.message = agentID.Guid.ToString(); | ||
652 | msg.ParentEstateID = 0; | ||
653 | msg.timestamp = (uint) Util.UnixTimeSinceEpoch(); | ||
654 | msg.RegionID = SceneAgentIn.RegionInfo.RegionID.Guid; | ||
655 | msg.dialog = (byte) InstantMessageDialog.FriendshipDeclined; | ||
656 | msg.Position = Vector3.Zero; | ||
657 | msg.offline = (byte) 0; | ||
658 | msg.binaryBucket = new byte[0]; | ||
659 | |||
660 | // we don't want to get that new IM into here if we aren't local, as only on the destination | ||
661 | // should receive it. If we *are* local, *we* are the destination, so we have to receive it. | ||
662 | // As grid-IMs are routed to all modules (in contrast to local IMs), we have to decide here. | ||
663 | InstantMessageReceiver recv = InstantMessageReceiver.IMModule; | ||
664 | if(GetAnyPresenceFromAgentID(friendID) != null) recv |= InstantMessageReceiver.FriendsModule; | ||
665 | |||
666 | // now we have to inform the agent about the friend. For the opposite direction, this happens in the handler | ||
667 | // of the type 39 IM | ||
668 | SceneAgentIn.TriggerGridInstantMessage(msg, recv); | ||
669 | } | ||
670 | |||
671 | private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID) | ||
672 | { | ||
673 | // client.AgentId == agentID! | ||
674 | |||
675 | // this removes the friends from the stored friendlists. After the next login, they will be gone... | ||
676 | m_initialScene.StoreRemoveFriendship(agentID, exfriendID); | ||
677 | |||
678 | // ... now tell the two involved clients that they aren't friends anymore. | ||
679 | |||
680 | // I don't know why we have to tell <agent>, as this was caused by her, but that's how it works in SL... | ||
681 | client.SendTerminateFriend(exfriendID); | ||
682 | |||
683 | // now send the friend, if online | ||
684 | ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID); | ||
685 | if (presence != null) | ||
686 | { | ||
687 | m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID); | ||
688 | presence.ControllingClient.SendTerminateFriend(agentID); | ||
689 | } | ||
690 | else | ||
691 | { | ||
692 | // retry 3 times, in case the agent TPed from the last known region... | ||
693 | for(int retry = 0; retry < 3; ++retry) | ||
694 | { | ||
695 | // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send | ||
696 | UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID); | ||
697 | if (!data.AgentOnline) | ||
698 | { | ||
699 | m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID); | ||
700 | break; // if ex-friend isn't online, we don't need to send | ||
701 | } | ||
702 | |||
703 | m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}", | ||
704 | agentID, exfriendID, data.Handle); | ||
705 | |||
706 | // try to send to foreign region, retry if it fails (friend TPed away, for example) | ||
707 | if (m_initialScene.TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break; | ||
708 | } | ||
709 | } | ||
710 | |||
711 | // clean up cache: FriendList is wrong now... | ||
712 | lock (m_friendLists) | ||
713 | { | ||
714 | m_friendLists.Invalidate(agentID); | ||
715 | m_friendLists.Invalidate(exfriendID); | ||
716 | } | ||
717 | } | ||
718 | |||
636 | #endregion | 719 | #endregion |
637 | 720 | ||
638 | #region CallingCards | 721 | #region CallingCards |
@@ -650,7 +733,10 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
650 | return; | 733 | return; |
651 | } | 734 | } |
652 | 735 | ||
653 | m_pendingCallingcardRequests[transactionID] = client.AgentId; | 736 | lock (m_pendingCallingcardRequests) |
737 | { | ||
738 | m_pendingCallingcardRequests[transactionID] = client.AgentId; | ||
739 | } | ||
654 | // inform the destination agent about the offer | 740 | // inform the destination agent about the offer |
655 | destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID); | 741 | destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID); |
656 | } | 742 | } |
@@ -687,19 +773,25 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
687 | client.FirstName, client.LastName, | 773 | client.FirstName, client.LastName, |
688 | transactionID, folderID); | 774 | transactionID, folderID); |
689 | UUID destID; | 775 | UUID destID; |
690 | if (m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) | 776 | lock (m_pendingCallingcardRequests) |
691 | { | 777 | { |
778 | if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) | ||
779 | { | ||
780 | m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.", | ||
781 | client.Name); | ||
782 | return; | ||
783 | } | ||
784 | // else found pending calling card request with that transaction. | ||
692 | m_pendingCallingcardRequests.Remove(transactionID); | 785 | m_pendingCallingcardRequests.Remove(transactionID); |
786 | } | ||
693 | 787 | ||
694 | ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); | ||
695 | // inform sender of the card that destination declined the offer | ||
696 | if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID); | ||
697 | 788 | ||
698 | // put a calling card into the inventory of receiver | 789 | ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); |
699 | CreateCallingCard(client, destID, folderID, destAgent.Name); | 790 | // inform sender of the card that destination declined the offer |
700 | } | 791 | if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID); |
701 | else m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} {1} without an offer before.", | 792 | |
702 | client.FirstName, client.LastName); | 793 | // put a calling card into the inventory of receiver |
794 | CreateCallingCard(client, destID, folderID, destAgent.Name); | ||
703 | } | 795 | } |
704 | 796 | ||
705 | private void OnDeclineCallingCard(IClientAPI client, UUID transactionID) | 797 | private void OnDeclineCallingCard(IClientAPI client, UUID transactionID) |
@@ -707,25 +799,230 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Friends | |||
707 | m_log.DebugFormat("[CALLING CARD]: User {0} declined card, tid {2}", | 799 | m_log.DebugFormat("[CALLING CARD]: User {0} declined card, tid {2}", |
708 | client.AgentId, transactionID); | 800 | client.AgentId, transactionID); |
709 | UUID destID; | 801 | UUID destID; |
710 | if (m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) | 802 | lock (m_pendingCallingcardRequests) |
711 | { | 803 | { |
804 | if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) | ||
805 | { | ||
806 | m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.", | ||
807 | client.Name); | ||
808 | return; | ||
809 | } | ||
810 | // else found pending calling card request with that transaction. | ||
712 | m_pendingCallingcardRequests.Remove(transactionID); | 811 | m_pendingCallingcardRequests.Remove(transactionID); |
812 | } | ||
813 | |||
814 | ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); | ||
815 | // inform sender of the card that destination declined the offer | ||
816 | if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID); | ||
817 | } | ||
818 | |||
819 | private void SendPresenceState(IClientAPI client, List<FriendListItem> friendList, bool iAmOnline) | ||
820 | { | ||
821 | m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out"); | ||
822 | |||
823 | if (friendList == null || friendList.Count == 0) | ||
824 | { | ||
825 | m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name); | ||
826 | return; // nothing we can do if she doesn't have friends... | ||
827 | } | ||
828 | |||
829 | // collect sets of friendIDs; to send to (online and offline), and to receive from | ||
830 | // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets. | ||
831 | // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago... | ||
832 | List<UUID> friendIDsToSendTo = new List<UUID>(); | ||
833 | List<UUID> friendIDsToReceiveFromOffline = new List<UUID>(); | ||
834 | List<UUID> friendIDsToReceiveFromOnline = new List<UUID>(); | ||
835 | foreach (FriendListItem item in friendList) | ||
836 | { | ||
837 | if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0) | ||
838 | { | ||
839 | // friend is allowed to see my presence => add | ||
840 | if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0) friendIDsToSendTo.Add(item.Friend); | ||
841 | |||
842 | // I'm allowed to see friend's presence => add as offline, we might reconsider in a momnet... | ||
843 | if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0) friendIDsToReceiveFromOffline.Add(item.Friend); | ||
844 | } | ||
845 | } | ||
846 | |||
847 | |||
848 | |||
849 | // we now have a list of "interesting" friends (which we have to find out on-/offline state for), | ||
850 | // friends we want to send our online state to (if *they* are online, too), and | ||
851 | // friends we want to receive online state for (currently unknown whether online or not) | ||
852 | |||
853 | // as this processing might take some time and friends might TP away, we try up to three times to | ||
854 | // reach them. Most of the time, we *will* reach them, and this loop won't loop | ||
855 | int retry = 0; | ||
856 | do | ||
857 | { | ||
858 | // build a list of friends to look up region-information and on-/offline-state for | ||
859 | List<UUID> friendIDsToLookup = new List<UUID>(friendIDsToSendTo); | ||
860 | foreach (UUID uuid in friendIDsToReceiveFromOffline) | ||
861 | { | ||
862 | if(!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid); | ||
863 | } | ||
864 | |||
865 | m_log.DebugFormat("[FRIEND]: {0} to lookup, {1} to send to, {2} to receive from for agent {3}", | ||
866 | friendIDsToLookup.Count, friendIDsToSendTo.Count, friendIDsToReceiveFromOffline.Count, client.Name); | ||
867 | |||
868 | // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't | ||
869 | // necessarily contain the correct online state... | ||
870 | Dictionary<UUID, FriendRegionInfo> friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup); | ||
871 | m_log.DebugFormat("[FRIEND]: Found {0} regionInfos for {1} friends of {2}", | ||
872 | friendRegions.Count, friendIDsToLookup.Count, client.Name); | ||
873 | |||
874 | // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops. | ||
875 | UUID[] agentArr = new UUID[] { client.AgentId }; | ||
876 | |||
877 | // first, send to friend presence state to me, if I'm online... | ||
878 | if (iAmOnline) | ||
879 | { | ||
880 | for (int i = friendIDsToReceiveFromOffline.Count - 1; i >= 0; --i) | ||
881 | { | ||
882 | UUID uuid = friendIDsToReceiveFromOffline[i]; | ||
883 | FriendRegionInfo info; | ||
884 | if (friendRegions.TryGetValue(uuid, out info) && info.isOnline) | ||
885 | { | ||
886 | friendIDsToReceiveFromOffline.RemoveAt(i); | ||
887 | friendIDsToReceiveFromOnline.Add(uuid); | ||
888 | } | ||
889 | } | ||
890 | m_log.DebugFormat("[FRIEND]: Sending {0} offline and {1} online friends to {2}", | ||
891 | friendIDsToReceiveFromOffline.Count, friendIDsToReceiveFromOnline.Count, client.Name); | ||
892 | if (friendIDsToReceiveFromOffline.Count > 0) client.SendAgentOffline(friendIDsToReceiveFromOffline.ToArray()); | ||
893 | if (friendIDsToReceiveFromOnline.Count > 0) client.SendAgentOnline(friendIDsToReceiveFromOnline.ToArray()); | ||
894 | |||
895 | // clear them for a possible second iteration; we don't have to repeat this | ||
896 | friendIDsToReceiveFromOffline.Clear(); | ||
897 | friendIDsToReceiveFromOnline.Clear(); | ||
898 | } | ||
899 | |||
900 | // now, send my presence state to my friends | ||
901 | for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i) | ||
902 | { | ||
903 | UUID uuid = friendIDsToSendTo[i]; | ||
904 | FriendRegionInfo info; | ||
905 | if (friendRegions.TryGetValue(uuid, out info) && info.isOnline) | ||
906 | { | ||
907 | // any client is good enough, root or child... | ||
908 | ScenePresence agent = GetAnyPresenceFromAgentID(uuid); | ||
909 | if(agent != null) | ||
910 | { | ||
911 | m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name); | ||
912 | |||
913 | // friend is online and on this server... | ||
914 | if(iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr); | ||
915 | else agent.ControllingClient.SendAgentOffline(agentArr); | ||
916 | |||
917 | // done, remove it | ||
918 | friendIDsToSendTo.RemoveAt(i); | ||
919 | } | ||
920 | } | ||
921 | else | ||
922 | { | ||
923 | m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i); | ||
713 | 924 | ||
714 | ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); | 925 | // friend is offline => no need to try sending |
715 | // inform sender of the card that destination declined the offer | 926 | friendIDsToSendTo.RemoveAt(i); |
716 | if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID); | 927 | } |
928 | } | ||
929 | |||
930 | m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count); | ||
931 | |||
932 | // we now have all the friends left that are online (we think), but not on this region-server | ||
933 | if (friendIDsToSendTo.Count > 0) | ||
934 | { | ||
935 | // sort them into regions | ||
936 | Dictionary<ulong, List<UUID>> friendsInRegion = new Dictionary<ulong,List<UUID>>(); | ||
937 | foreach (UUID uuid in friendIDsToSendTo) | ||
938 | { | ||
939 | ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already | ||
940 | List<UUID> friends; | ||
941 | if (!friendsInRegion.TryGetValue(handle, out friends)) | ||
942 | { | ||
943 | friends = new List<UUID>(); | ||
944 | friendsInRegion[handle] = friends; | ||
945 | } | ||
946 | friends.Add(uuid); | ||
947 | } | ||
948 | m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count); | ||
949 | |||
950 | // clear uuids list and collect missed friends in it for the next retry | ||
951 | friendIDsToSendTo.Clear(); | ||
952 | |||
953 | // send bulk updates to the region | ||
954 | foreach (KeyValuePair<ulong, List<UUID>> pair in friendsInRegion) | ||
955 | { | ||
956 | m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line", | ||
957 | pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off"); | ||
958 | |||
959 | friendIDsToSendTo.AddRange(m_initialScene.InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline)); | ||
960 | } | ||
961 | } | ||
962 | // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them. | ||
963 | // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again... | ||
717 | } | 964 | } |
718 | else m_log.WarnFormat("[CALLING CARD]: Got a DeclineCallingCard from {0} {1} without an offer before.", | 965 | while(++retry < 3 && friendIDsToSendTo.Count > 0); |
719 | client.FirstName, client.LastName); | ||
720 | } | 966 | } |
721 | } | ||
722 | 967 | ||
723 | #endregion | 968 | private void OnEconomyDataRequest(UUID agentID) |
969 | { | ||
970 | // KLUDGE: This is the only way I found to get a message (only) after login was completed and the | ||
971 | // client is connected enough to receive UDP packets). | ||
972 | // This packet seems to be sent only once, just after connection was established to the first | ||
973 | // region after login. | ||
974 | // We use it here to trigger a presence update; the old update-on-login was never be heard by | ||
975 | // the freshly logged in viewer, as it wasn't connected to the region at that time. | ||
976 | // TODO: Feel free to replace this by a better solution if you find one. | ||
977 | |||
978 | // get the agent. This should work every time, as we just got a packet from it | ||
979 | //ScenePresence agent = GetRootPresenceFromAgentID(agentID); | ||
980 | // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit | ||
981 | ScenePresence agent = GetAnyPresenceFromAgentID(agentID); | ||
982 | |||
983 | // just to be paranoid... | ||
984 | if (agent == null) | ||
985 | { | ||
986 | m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID); | ||
987 | return; | ||
988 | } | ||
724 | 989 | ||
725 | public struct StoredFriendListUpdate | 990 | List<FriendListItem> fl; |
726 | { | 991 | lock (m_friendLists) |
727 | public UUID storedFor; | 992 | { |
728 | public UUID storedAbout; | 993 | fl = (List<FriendListItem>)m_friendLists.Get(agent.ControllingClient.AgentId, |
729 | public bool OnlineYN; | 994 | m_initialScene.GetFriendList); |
995 | } | ||
996 | |||
997 | // tell everyone that we are online | ||
998 | SendPresenceState(agent.ControllingClient, fl, true); | ||
999 | } | ||
1000 | |||
1001 | private void OnLogout(IClientAPI remoteClient) | ||
1002 | { | ||
1003 | m_log.ErrorFormat("[FRIEND]: Client {0} logged out", remoteClient.Name); | ||
1004 | |||
1005 | List<FriendListItem> fl; | ||
1006 | lock (m_friendLists) | ||
1007 | { | ||
1008 | fl = (List<FriendListItem>)m_friendLists.Get(remoteClient.AgentId, | ||
1009 | m_initialScene.GetFriendList); | ||
1010 | } | ||
1011 | |||
1012 | // tell everyone that we are offline | ||
1013 | SendPresenceState(remoteClient, fl, false); | ||
1014 | } | ||
1015 | |||
1016 | private void outPending() | ||
1017 | { | ||
1018 | m_log.DebugFormat("[FRIEND]: got {0} requests pending", m_pendingFriendRequests.Count); | ||
1019 | foreach (KeyValuePair<UUID, Transaction> pair in m_pendingFriendRequests) | ||
1020 | { | ||
1021 | m_log.DebugFormat("[FRIEND]: tid={0}, agent={1}, name={2}, count={3}", | ||
1022 | pair.Key, pair.Value.agentID, pair.Value.agentName, pair.Value.count); | ||
1023 | } | ||
1024 | } | ||
730 | } | 1025 | } |
1026 | |||
1027 | #endregion | ||
731 | } | 1028 | } |
diff --git a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs index adf1103..5597381 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs | |||
@@ -137,8 +137,14 @@ namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage | |||
137 | // IM dialogs need to be pre-processed and have their sessionID filled by the server | 137 | // IM dialogs need to be pre-processed and have their sessionID filled by the server |
138 | // so the sim can match the transaction on the return packet. | 138 | // so the sim can match the transaction on the return packet. |
139 | 139 | ||
140 | // Don't send a Friend Dialog IM with a UUID.Zero session. | 140 | // Don't process IMs that are handled elsewhere (e.g. friend dialog |
141 | if (!dialogHandledElsewhere) | 141 | // IMs) with a non-UUID.Zero agent session, as those have been send |
142 | // by a client (either directly or from another region via | ||
143 | // inter-region communication) and will be processed in another | ||
144 | // module (e.g. the friends-module). | ||
145 | // IMs with fromAgentSession == UUID.Zero come from the server, and | ||
146 | // have to be passed to the matching viewer | ||
147 | if (!dialogHandledElsewhere || fromAgentSession == UUID.Zero) | ||
142 | { | 148 | { |
143 | // Try root avatar only first | 149 | // Try root avatar only first |
144 | foreach (Scene scene in m_scenes) | 150 | foreach (Scene scene in m_scenes) |
diff --git a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs index da7a10c..369b56c 100644 --- a/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs +++ b/OpenSim/Region/Environment/Modules/World/NPC/NPCAvatar.cs | |||
@@ -963,5 +963,9 @@ namespace OpenSim.Region.Environment.Modules.World.NPC | |||
963 | public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) | 963 | public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) |
964 | { | 964 | { |
965 | } | 965 | } |
966 | |||
967 | public void SendTerminateFriend(UUID exFriendID) | ||
968 | { | ||
969 | } | ||
966 | } | 970 | } |
967 | } | 971 | } |
diff --git a/OpenSim/Region/Environment/Scenes/Scene.cs b/OpenSim/Region/Environment/Scenes/Scene.cs index f411a7f..647682e 100644 --- a/OpenSim/Region/Environment/Scenes/Scene.cs +++ b/OpenSim/Region/Environment/Scenes/Scene.cs | |||
@@ -3200,6 +3200,20 @@ namespace OpenSim.Region.Environment.Scenes | |||
3200 | return CommsManager.GetUserFriendList(avatarID); | 3200 | return CommsManager.GetUserFriendList(avatarID); |
3201 | } | 3201 | } |
3202 | 3202 | ||
3203 | public Dictionary<UUID, FriendRegionInfo> GetFriendRegionInfos(List<UUID> uuids) | ||
3204 | { | ||
3205 | return CommsManager.GetFriendRegionInfos(uuids); | ||
3206 | } | ||
3207 | |||
3208 | public List<UUID> InformFriendsInOtherRegion(UUID agentId, ulong destRegionHandle, List<UUID> friends, bool online) | ||
3209 | { | ||
3210 | return CommsManager.InformFriendsInOtherRegion(agentId, destRegionHandle, friends, online); | ||
3211 | } | ||
3212 | |||
3213 | public bool TriggerTerminateFriend(ulong regionHandle, UUID agentID, UUID exFriendID) | ||
3214 | { | ||
3215 | return CommsManager.TriggerTerminateFriend(regionHandle, agentID, exFriendID); | ||
3216 | } | ||
3203 | 3217 | ||
3204 | #endregion | 3218 | #endregion |
3205 | 3219 | ||
diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs index b55e5b6..0131109 100644 --- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs +++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs | |||
@@ -962,5 +962,9 @@ namespace OpenSim.Region.Examples.SimpleModule | |||
962 | public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) | 962 | public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) |
963 | { | 963 | { |
964 | } | 964 | } |
965 | |||
966 | public void SendTerminateFriend(UUID exFriendID) | ||
967 | { | ||
968 | } | ||
965 | } | 969 | } |
966 | } | 970 | } |