diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 5687e01178..17d951043e 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -576,6 +576,9 @@ namespace OpenSim.Framework void SendInstantMessage(LLUUID fromAgent, LLUUID fromAgentSession, string message, LLUUID toAgent, LLUUID imSessionID, string fromName, byte dialog, uint timeStamp); + void SendInstantMessage(LLUUID fromAgent, LLUUID fromAgentSession, string message, LLUUID toAgent, + LLUUID imSessionID, string fromName, byte dialog, uint timeStamp, + byte[] binaryBucket); void SendLayerData(float[] map); void SendLayerData(int px, int py, float[] map); @@ -620,7 +623,7 @@ namespace OpenSim.Framework void SendPrimTerseUpdate(ulong regionHandle, ushort timeDilation, uint localID, LLVector3 position, LLQuaternion rotation, LLVector3 velocity, LLVector3 rotationalvelocity); - + void SendInventoryFolderDetails(LLUUID ownerID, LLUUID folderID, List items, List folders, bool fetchFolders, bool fetchItems); @@ -635,6 +638,14 @@ namespace OpenSim.Framework void SendRemoveInventoryItem(LLUUID itemID); void SendTaskInventory(LLUUID taskID, short serial, byte[] fileName); + + /// + /// Used by the server to inform the client of a new inventory item. Used when transferring items + /// between avatars, possibly among other things. + /// + /// + void SendBulkUpdateInventory(InventoryItemBase item); + void SendXferPacket(ulong xferID, uint packet, byte[] data); void SendAvatarPickerReply(AvatarPickerReplyPacket Pack); diff --git a/OpenSim/Region/ClientStack/ClientView.cs b/OpenSim/Region/ClientStack/ClientView.cs index a67c2472b5..c98eb84cb8 100644 --- a/OpenSim/Region/ClientStack/ClientView.cs +++ b/OpenSim/Region/ClientStack/ClientView.cs @@ -482,6 +482,11 @@ namespace OpenSim.Region.ClientStack return result; } + /// + /// Try to process a packet using registered packet handlers + /// + /// + /// True if a handler was found which successfully processed the packet. protected virtual bool ProcessPacketMethod(Packet packet) { bool result = false; @@ -865,14 +870,30 @@ namespace OpenSim.Region.ClientStack } /// - /// + /// Send an instant message to this client /// /// /// public void SendInstantMessage(LLUUID fromAgent, LLUUID fromAgentSession, string message, LLUUID toAgent, LLUUID imSessionID, string fromName, byte dialog, uint timeStamp) { - ImprovedInstantMessagePacket msg = (ImprovedInstantMessagePacket)PacketPool.Instance.GetPacket(PacketType.ImprovedInstantMessage); + SendInstantMessage( + fromAgent, fromAgentSession, message, toAgent, + imSessionID, fromName, dialog, timeStamp, new byte[0]); + } + + /// + /// Send an instant message to this client + /// + /// + /// + public void SendInstantMessage(LLUUID fromAgent, LLUUID fromAgentSession, string message, LLUUID toAgent, + LLUUID imSessionID, string fromName, byte dialog, uint timeStamp, + byte[] binaryBucket) + { + ImprovedInstantMessagePacket msg + = (ImprovedInstantMessagePacket)PacketPool.Instance.GetPacket(PacketType.ImprovedInstantMessage); + msg.AgentData.AgentID = fromAgent; msg.AgentData.SessionID = fromAgentSession; msg.MessageBlock.FromAgentName = Helpers.StringToField(fromName); @@ -886,7 +907,7 @@ namespace OpenSim.Region.ClientStack msg.MessageBlock.Timestamp = timeStamp; msg.MessageBlock.ToAgentID = toAgent; msg.MessageBlock.Message = Helpers.StringToField(message); - msg.MessageBlock.BinaryBucket = new byte[0]; + msg.MessageBlock.BinaryBucket = binaryBucket; OutPacket(msg, ThrottleOutPacketType.Task); } @@ -1374,12 +1395,67 @@ namespace OpenSim.Region.ClientStack OutPacket(inventoryReply, ThrottleOutPacketType.Asset); } + + /// IClientAPI.SendBulkUpdateInventory(InventoryItemBase) + public void SendBulkUpdateInventory(InventoryItemBase item) + { + uint FULL_MASK_PERMISSIONS = (uint)PermissionMask.All; + + BulkUpdateInventoryPacket bulkUpdate + = (BulkUpdateInventoryPacket)PacketPool.Instance.GetPacket(PacketType.BulkUpdateInventory); + + bulkUpdate.AgentData.AgentID = AgentId; + bulkUpdate.AgentData.TransactionID = LLUUID.Random(); + + bulkUpdate.FolderData = new BulkUpdateInventoryPacket.FolderDataBlock[1]; + bulkUpdate.FolderData[0] = new BulkUpdateInventoryPacket.FolderDataBlock(); + bulkUpdate.FolderData[0].FolderID = LLUUID.Zero; + bulkUpdate.FolderData[0].ParentID = LLUUID.Zero; + bulkUpdate.FolderData[0].Type = -1; + bulkUpdate.FolderData[0].Name = new byte[0]; + + bulkUpdate.ItemData = new BulkUpdateInventoryPacket.ItemDataBlock[1]; + bulkUpdate.ItemData[0] = new BulkUpdateInventoryPacket.ItemDataBlock(); + bulkUpdate.ItemData[0].ItemID = item.inventoryID; + bulkUpdate.ItemData[0].AssetID = item.assetID; + bulkUpdate.ItemData[0].CreatorID = item.creatorsID; + bulkUpdate.ItemData[0].BaseMask = item.inventoryBasePermissions; + bulkUpdate.ItemData[0].CreationDate = 1000; + bulkUpdate.ItemData[0].Description = Helpers.StringToField(item.inventoryDescription); + bulkUpdate.ItemData[0].EveryoneMask = item.inventoryEveryOnePermissions; + bulkUpdate.ItemData[0].Flags = 0; + bulkUpdate.ItemData[0].FolderID = item.parentFolderID; + bulkUpdate.ItemData[0].GroupID = new LLUUID("00000000-0000-0000-0000-000000000000"); + bulkUpdate.ItemData[0].GroupMask = 0; + bulkUpdate.ItemData[0].InvType = (sbyte)item.invType; + bulkUpdate.ItemData[0].Name = Helpers.StringToField(item.inventoryName); + bulkUpdate.ItemData[0].NextOwnerMask = item.inventoryNextPermissions; + bulkUpdate.ItemData[0].OwnerID = item.avatarID; + bulkUpdate.ItemData[0].OwnerMask = item.inventoryCurrentPermissions; + bulkUpdate.ItemData[0].SalePrice = 100; + bulkUpdate.ItemData[0].SaleType = 0; + bulkUpdate.ItemData[0].Type = (sbyte)item.assetType; + bulkUpdate.ItemData[0].CRC = + Helpers.InventoryCRC(1000, 0, bulkUpdate.ItemData[0].InvType, + bulkUpdate.ItemData[0].Type, bulkUpdate.ItemData[0].AssetID, + bulkUpdate.ItemData[0].GroupID, 100, + bulkUpdate.ItemData[0].OwnerID, bulkUpdate.ItemData[0].CreatorID, + bulkUpdate.ItemData[0].ItemID, bulkUpdate.ItemData[0].FolderID, + FULL_MASK_PERMISSIONS, 1, FULL_MASK_PERMISSIONS, FULL_MASK_PERMISSIONS, + FULL_MASK_PERMISSIONS); + + OutPacket(bulkUpdate, ThrottleOutPacketType.Asset); + } /// IClientAPI.SendInventoryItemCreateUpdate(InventoryItemBase) public void SendInventoryItemCreateUpdate(InventoryItemBase Item) { uint FULL_MASK_PERMISSIONS = (uint)PermissionMask.All; - UpdateCreateInventoryItemPacket InventoryReply = (UpdateCreateInventoryItemPacket)PacketPool.Instance.GetPacket(PacketType.UpdateCreateInventoryItem); + + UpdateCreateInventoryItemPacket InventoryReply + = (UpdateCreateInventoryItemPacket)PacketPool.Instance.GetPacket( + PacketType.UpdateCreateInventoryItem); + // TODO: don't create new blocks if recycling an old packet InventoryReply.AgentData.AgentID = AgentId; InventoryReply.AgentData.SimApproved = true; diff --git a/OpenSim/Region/Environment/Modules/InstantMessageModule.cs b/OpenSim/Region/Environment/Modules/InstantMessageModule.cs index 53d9fd9496..6f7235e865 100644 --- a/OpenSim/Region/Environment/Modules/InstantMessageModule.cs +++ b/OpenSim/Region/Environment/Modules/InstantMessageModule.cs @@ -70,13 +70,17 @@ namespace OpenSim.Region.Environment.Modules uint ParentEstateID, LLVector3 Position, LLUUID RegionID, byte[] binaryBucket) { - bool FriendDialog = ((dialog == (byte)38) || (dialog == (byte)39) || (dialog == (byte)40)); + bool dialogHandledElsewhere + = ((dialog == (byte)38) || (dialog == (byte)39) || (dialog == (byte)40) + || dialog == (byte)InstantMessageDialog.InventoryOffered + || dialog == (byte)InstantMessageDialog.InventoryAccepted + || dialog == (byte)InstantMessageDialog.InventoryDeclined); // IM dialogs need to be pre-processed and have their sessionID filled by the server // so the sim can match the transaction on the return packet. // Don't send a Friend Dialog IM with a LLUUID.Zero session. - if (!(FriendDialog && imSessionID == LLUUID.Zero)) + if (!(dialogHandledElsewhere && imSessionID == LLUUID.Zero)) { foreach (Scene scene in m_scenes) { diff --git a/OpenSim/Region/Environment/Modules/InventoryModule.cs b/OpenSim/Region/Environment/Modules/InventoryModule.cs index a0f3832ed3..eadbb4f437 100644 --- a/OpenSim/Region/Environment/Modules/InventoryModule.cs +++ b/OpenSim/Region/Environment/Modules/InventoryModule.cs @@ -25,7 +25,13 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System; +using System.Collections.Generic; + +using libsecondlife; using Nini.Config; + +using OpenSim.Framework; using OpenSim.Region.Environment.Interfaces; using OpenSim.Region.Environment.Scenes; @@ -33,11 +39,22 @@ namespace OpenSim.Region.Environment.Modules { public class InventoryModule : IRegionModule { + private static readonly log4net.ILog m_log + = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene; + + /// + /// We need to keep track of the pending item offers between clients since the itemId offered only + /// occurs in the initial offer message, not the accept message. So this dictionary links + /// IM Session Ids to ItemIds + /// + private IDictionary m_pendingOffers = new Dictionary(); public void Initialise(Scene scene, IConfigSource config) - { + { m_scene = scene; + scene.EventManager.OnNewClient += OnNewClient; } public void PostInitialise() @@ -57,5 +74,144 @@ namespace OpenSim.Region.Environment.Modules { get { return false; } } + + private void OnNewClient(IClientAPI client) + { + // Inventory giving is conducted via instant message + client.OnInstantMessage += OnInstantMessage; + } + + private void OnInstantMessage(IClientAPI client, LLUUID fromAgentID, + LLUUID fromAgentSession, LLUUID toAgentID, + LLUUID imSessionID, uint timestamp, string fromAgentName, + string message, byte dialog, bool fromGroup, byte offline, + uint ParentEstateID, LLVector3 Position, LLUUID RegionID, + byte[] binaryBucket) + { + if (dialog == (byte)InstantMessageDialog.InventoryOffered) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Routing inventory offering message from {0}, {1} to {2}", + client.AgentId, client.Name, toAgentID); + + if (m_scene.Entities.ContainsKey(toAgentID) && m_scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = (ScenePresence)m_scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + //byte[] rawId = new byte[16]; + + // First byte of the array is probably the item type + // Next 16 bytes are the UUID + //Array.Copy(binaryBucket, 1, rawId, 0, 16); + + //LLUUID itemId = new LLUUID(new Guid(rawId)); + LLUUID itemId = new LLUUID(binaryBucket, 1); + + m_log.DebugFormat( + "[AGENT INVENTORY]: ItemId for giving is {0}", itemId); + + m_pendingOffers[imSessionID] = itemId; + + user.ControllingClient.SendInstantMessage( + fromAgentID, fromAgentSession, message, toAgentID, imSessionID, fromAgentName, + dialog, timestamp, binaryBucket); + + return; + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Agent {0} targeted for inventory give by {1}, {2} of {3} was a child agent!", + toAgentID, client.AgentId, client.Name, message); + } + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Could not find agent {0} for user {1}, {2} to give {3}", + toAgentID, client.AgentId, client.Name, message); + } + } + else if (dialog == (byte)InstantMessageDialog.InventoryAccepted) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Routing inventory accepted message from {0}, {1} to {2}", + client.AgentId, client.Name, toAgentID); + + if (m_scene.Entities.ContainsKey(toAgentID) && m_scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = (ScenePresence)m_scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + user.ControllingClient.SendInstantMessage( + fromAgentID, fromAgentSession, message, toAgentID, imSessionID, fromAgentName, + dialog, timestamp, binaryBucket); + + if (m_pendingOffers.ContainsKey(imSessionID)) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Accepted item id {0}", m_pendingOffers[imSessionID]); + + // Since the message originates from the accepting client, the toAgentID is + // the agent giving the item. + m_scene.GiveInventoryItem(client, toAgentID, m_pendingOffers[imSessionID]); + + m_pendingOffers.Remove(imSessionID); + } + else + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Could not find an item associated with session id {0} to accept", + imSessionID); + } + + return; + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Agent {0} targeted for inventory give by {1}, {2} of {3} was a child agent!", + toAgentID, client.AgentId, client.Name, message); + } + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Could not find agent {0} for user {1}, {2} to give {3}", + toAgentID, client.AgentId, client.Name, message); + } + } + else if (dialog == (byte)InstantMessageDialog.InventoryDeclined) + { + if (m_scene.Entities.ContainsKey(toAgentID) && m_scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = (ScenePresence)m_scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + user.ControllingClient.SendInstantMessage( + fromAgentID, fromAgentSession, message, toAgentID, imSessionID, fromAgentName, + dialog, timestamp, binaryBucket); + + if (m_pendingOffers.ContainsKey(imSessionID)) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Declined item id {0}", m_pendingOffers[imSessionID]); + + m_pendingOffers.Remove(imSessionID); + } + else + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Could not find an item associated with session id {0} to decline", + imSessionID); + } + } + } + } + } } } diff --git a/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs b/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs index 2b8f34454a..f6f136713f 100644 --- a/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Environment/Scenes/Scene.Inventory.cs @@ -286,10 +286,89 @@ namespace OpenSim.Region.Environment.Scenes "[AGENT INVENTORY]: Agent ID " + remoteClient.AgentId + " not found for an inventory item update."); } } + + /// + /// Give an inventory item from one avatar to another + /// + /// + /// + /// + public void GiveInventoryItem(IClientAPI recipientClient, LLUUID senderId, LLUUID itemId) + { + // Retrieve the item from the sender + CachedUserInfo senderUserInfo = CommsManager.UserProfileCacheService.GetUserDetails(senderId); + + if (senderUserInfo == null) + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Failed to find sending user {0} for item {1}", senderId, itemId); + + return; + } + + if (senderUserInfo.RootFolder != null) + { + InventoryItemBase item = senderUserInfo.RootFolder.HasItem(itemId); + if (item != null) + { + // TODO get recipient's root folder + CachedUserInfo recipientUserInfo + = CommsManager.UserProfileCacheService.GetUserDetails(recipientClient.AgentId); + + if (recipientUserInfo != null) + { + // Insert a copy of the item into the recipient + InventoryItemBase itemCopy = new InventoryItemBase(); + itemCopy.avatarID = recipientClient.AgentId; + itemCopy.creatorsID = recipientClient.AgentId; + itemCopy.inventoryID = LLUUID.Random(); + itemCopy.assetID = item.assetID; + itemCopy.inventoryDescription = item.inventoryDescription; + itemCopy.inventoryName = item.inventoryName; + itemCopy.assetType = item.assetType; + itemCopy.invType = item.invType; + itemCopy.parentFolderID = recipientUserInfo.RootFolder.folderID; + itemCopy.inventoryCurrentPermissions = 2147483647; + itemCopy.inventoryNextPermissions = 2147483647; + itemCopy.inventoryEveryOnePermissions = item.inventoryEveryOnePermissions; + itemCopy.inventoryBasePermissions = item.inventoryBasePermissions; + itemCopy.inventoryCurrentPermissions = item.inventoryCurrentPermissions; + + recipientUserInfo.AddItem(recipientClient.AgentId, itemCopy); + + // Let the recipient client know about this new item + recipientClient.SendBulkUpdateInventory(itemCopy); + } + else + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Could not find userinfo for recipient user {0}, {1} of item {2}, {3} from {4}", + recipientClient.Name, recipientClient.AgentId, item.inventoryName, + item.inventoryID, senderId); + } + } + else + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Failed to find item {0} to give to {1}", itemId, senderId); + + return; + } + } + else + { + m_log.Error("[AGENT INVENTORY]: Failed to find item " + itemId.ToString() + ", no root folder"); + return; + } + } public void CopyInventoryItem(IClientAPI remoteClient, uint callbackID, LLUUID oldAgentID, LLUUID oldItemID, LLUUID newFolderID, string newName) { + m_log.DebugFormat( + "[AGENT INVENTORY]: CopyInventoryItem received by {0} with oldAgentID {1}, oldItemID {2}, new FolderID {3}, newName {4}", + remoteClient.AgentId, oldAgentID, oldItemID, newFolderID, newName); + InventoryItemBase item = CommsManager.UserProfileCacheService.libraryRoot.HasItem(oldItemID); if (item == null) { @@ -349,9 +428,8 @@ namespace OpenSim.Region.Environment.Scenes public void MoveInventoryItem(IClientAPI remoteClient, LLUUID folderID, LLUUID itemID, int length, string newName) { - m_log.Info( - "[AGENT INVENTORY]: " + - "Moving item for " + remoteClient.AgentId.ToString()); + m_log.DebugFormat( + "[AGENT INVENTORY]: Moving item {0} to {1} for {2}", itemID, folderID, remoteClient.AgentId); CachedUserInfo userInfo = CommsManager.UserProfileCacheService.GetUserDetails(remoteClient.AgentId); if (userInfo == null) @@ -429,7 +507,8 @@ namespace OpenSim.Region.Environment.Scenes } /// - /// Create a new inventory item. + /// Create a new inventory item. Called when the client creates a new item directly within their + /// inventory (e.g. by selecting a context inventory menu option). /// /// /// diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs index 3f090df03d..9901a50884 100644 --- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs +++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs @@ -270,6 +270,12 @@ namespace OpenSim.Region.Examples.SimpleModule LLUUID imSessionID, string fromName, byte dialog, uint timeStamp) { } + + public virtual void SendInstantMessage(LLUUID fromAgent, LLUUID fromAgentSession, string message, LLUUID toAgent, + LLUUID imSessionID, string fromName, byte dialog, uint timeStamp, + byte[] binaryBucket) + { + } public virtual void SendLayerData(float[] map) { @@ -388,6 +394,11 @@ namespace OpenSim.Region.Examples.SimpleModule public virtual void SendRemoveInventoryItem(LLUUID itemID) { } + + /// IClientAPI.SendBulkUpdateInventory(InventoryItemBase) + public virtual void SendBulkUpdateInventory(InventoryItemBase item) + { + } public virtual void SendTaskInventory(LLUUID taskID, short serial, byte[] fileName) {