OpenSim: sending inventory client side...and have the avatar take notice!

recently i was in need of being able to send inventory items (well, a whole folder full of stuff) to the inventory of a logged-in avatar.

just copying the the inventory items to the avatar inventory (for example, using Scene.GiveInventoryItem() or Scene.GiveInventoryFolder()) will not cause your avatar to notice that there is additional junk in her inventory all of a sudden — once the avatar is logged out and logs in again, she will find the stuff in her inventory. kicking the avatar out every time you want to send some inventory her way is a tad bad on the user experience side1. clearly not something we want to do.

after trying a 101 ways of sending inventory to the avatar and none of them catching, i finally remembered that avatars can give inventory to one another, even whole folders!3 taking my favorite tool to the OpenSim source tree4 i found code in the InventoryTransferModule that seemed to take care of this process. the first attempt of just sending a GridInstantMessage via the usual means was not producing the right result: the avatar would get the pop-up that a folder was being given to her and would she like to keep it? looked good but checking the inventory revealed it to be a big blue lie.

here’s what i finally figured out. you need:

  • a ScenePresence object representing the avatar
  • the UUID of the sending “avatar”
  • the name of the sending “avatar”
  • the InventoryNodeBase object (actually you would pass in a subclass such as InventoryItemBase or InventoryFolderBase or similar)

here’s the “pseudo-code”:

[c-sharp]
// pseudo code: send inventory content to a logged in avatar's
// inventory 
//
// avatar: ScenePresence object
// src: struct containing "UUID" and "Name" of the source "avatar"

// get the CachedUserInfo object for the target avatar
CachedUserInfo cuiAvatar = 
    m_commsManager.UserProfileCacheService.GetUserDetails(avatar,UUID);
if (!cuiAvatar.HasReceivedInventory) cuiAvatar.FetchInventory();

// target folder is the Clothing folder (type 5)
InventoryFolderImpl dstFolder = cuiAvatar.FindFolderForType(5);

// copy source folder from src avatar to dstFolder
copyFolder = scene.GiveInventoryFolder(avatar.UUID, src.UUID, 
                                       dstFolder.ID);

// content is in the target folder, ping target avatar and let her
// know about it: we do this through a GridInstantMessage

// GridInstantMessage: prepare bucket byte array first byte is asset
// type, remaining bytes are the UUID of the InventoryItem
byte[] idBytes = item.ID.GetBytes();
byte[] bucket = new byte[1 + idBytes.Length];

if (item is InventoryFolderBase)
    bucket[0] = (byte)AssetType.Folder;
else if (item is InventoryItemBase)
    bucket[0] = (byte)(item as e).AssetType;

Array.Copy(idBytes, 0, bucket, 1, idBytes.Length);

GridInstantMessage im = 
    new GridInstantMessage(scene, src.UUID, src.Name, avatar.UUID,  
                           (byte)InstantMessageDialog.InventoryOffered,
                           false, item.Name, item.ID,
                           Vector3.Zero, bucket);

// send bulk update inventory message and GridInstantMessage
avatar.ControllingClient.SendBulkUpdateInventory(item);
avatar.ControllingClient.SendInstantMessage(im);
[/c-sharp]

  1. though, i was reminded of windows’s tendency of asking you to reboot ever time you installed some piece of software…2 

  2. on second thoughts, that just proves the earlier point about this not being very user friendly… 

  3. yes, i’m a bit dense sometimes…or…too focused! :-) 

  4. no, not that one. emacs’s M-x grep-find