Files
libremetaverse/OpenMetaverse/AppearanceManager.cs
John Hurliman 8106fccdd2 * Changed Primitive.TextureEntry.ToBytes() to GetBytes() to follow naming conventions
* Added Primitive.TreeSpecies and Primitive.ScratchPad
* Converted Primitive.SoundFlags to the new SoundFlags enum
* Added a Utils.BytesToString() overload that accepts index and count parameters
* Added Utils.FloatToUInt16()
[Simian]
* Lots of changes in Simian to use the new unified ISceneProvider.ObjectAddOrUpdate() function
* Update flags are checked to determine the minimum sized packet that needs to be sent out for an update. ImprovedTerseObjectUpdate is working, and started work on ObjectUpdateCached (updates using this will currently not send)
* Adding three new variables to SimulationObject to store attachment-related state

git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@2478 52acb1d6-8a22-11de-b505-999d5b087335
2009-03-10 01:54:45 +00:00

1539 lines
69 KiB
C#

/*
* Copyright (c) 2006-2008, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using OpenMetaverse;
using OpenMetaverse.Packets;
using OpenMetaverse.Imaging;
namespace OpenMetaverse
{
public class InvalidOutfitException : Exception
{
public InvalidOutfitException(string message) : base(message) { }
}
/// <summary>
/// Manager class to for agents appearance, both body parts and clothing
/// </summary>
public class AppearanceManager
{
/// <summary>
///
/// </summary>
public enum TextureIndex
{
Unknown = -1,
HeadBodypaint = 0,
UpperShirt,
LowerPants,
EyesIris,
Hair,
UpperBodypaint,
LowerBodypaint,
LowerShoes,
HeadBaked,
UpperBaked,
LowerBaked,
EyesBaked,
LowerSocks,
UpperJacket,
LowerJacket,
UpperGloves,
UpperUndershirt,
LowerUnderpants,
Skirt,
SkirtBaked
}
/// <summary>
///
/// </summary>
public enum BakeType
{
Unknown = -1,
Head = 0,
UpperBody = 1,
LowerBody = 2,
Eyes = 3,
Skirt = 4
}
public class WearableData
{
public InventoryWearable Item;
public AssetWearable Asset;
}
/// <summary>
///
/// </summary>
public delegate void AgentWearablesCallback();
/// <summary>
///
/// </summary>
/// <param name="te"></param>
public delegate void AppearanceUpdatedCallback(Primitive.TextureEntry te);
/// <summary></summary>
public event AgentWearablesCallback OnAgentWearables;
/// <summary></summary>
public event AppearanceUpdatedCallback OnAppearanceUpdated;
/// <summary>Total number of wearables for each avatar</summary>
public const int WEARABLE_COUNT = 13;
/// <summary>Total number of baked textures on each avatar</summary>
public const int BAKED_TEXTURE_COUNT = 5;
/// <summary>Total number of wearables per bake layer</summary>
public const int WEARABLES_PER_LAYER = 7;
/// <summary>Total number of textures on an avatar, baked or not</summary>
public const int AVATAR_TEXTURE_COUNT = 20;
/// <summary>Map of what wearables are included in each bake</summary>
public static readonly WearableType[][] WEARABLE_BAKE_MAP = new WearableType[][]
{
new WearableType[] { WearableType.Shape, WearableType.Skin, WearableType.Hair, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid },
new WearableType[] { WearableType.Shape, WearableType.Skin, WearableType.Shirt, WearableType.Jacket, WearableType.Gloves, WearableType.Undershirt, WearableType.Invalid },
new WearableType[] { WearableType.Shape, WearableType.Skin, WearableType.Pants, WearableType.Shoes, WearableType.Socks, WearableType.Jacket, WearableType.Underpants },
new WearableType[] { WearableType.Eyes, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid },
new WearableType[] { WearableType.Skirt, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid }
};
/// <summary>Secret values to finalize the cache check hashes for each
/// bake</summary>
public static readonly UUID[] BAKED_TEXTURE_HASH = new UUID[]
{
new UUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"),
new UUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"),
new UUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"),
new UUID("b2cf28af-b840-1071-3c6a-78085d8128b5"),
new UUID("ea800387-ea1a-14e0-56cb-24f2022f969a")
};
/// <summary>Default avatar texture, used to detect when a custom
/// texture is not set for a face</summary>
public static readonly UUID DEFAULT_AVATAR_TEXTURE = new UUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97");
private GridClient Client;
private AssetManager Assets;
/// <summary>
/// An <seealso cref="T:InternalDictionary"/> which keeps track of wearables data
/// </summary>
public InternalDictionary<WearableType, WearableData> Wearables = new InternalDictionary<WearableType, WearableData>();
// As wearable assets are downloaded and decoded, the textures are added to this array
private UUID[] AgentTextures = new UUID[AVATAR_TEXTURE_COUNT];
// A cache of the textures worn, needed for rebaking
private AssetTexture[] AgentAssets = new AssetTexture[AVATAR_TEXTURE_COUNT];
protected struct PendingAssetDownload
{
public UUID Id;
public AssetType Type;
public PendingAssetDownload(UUID id, AssetType type)
{
Id = id;
Type = type;
}
}
// Wearable assets are downloaded one at a time, a new request is pulled off the queue
// and started when the previous one completes
private Queue<PendingAssetDownload> AssetDownloads = new Queue<PendingAssetDownload>();
// A list of all the images we are currently downloading, prior to baking
private Dictionary<UUID, TextureIndex> ImageDownloads = new Dictionary<UUID, TextureIndex>();
// A list of all the bakes we need to complete
private Dictionary<BakeType, Baker> PendingBakes = new Dictionary<BakeType, Baker>(BAKED_TEXTURE_COUNT);
// A list of all the uploads that are in progress
private Dictionary<UUID, TextureIndex> PendingUploads = new Dictionary<UUID, TextureIndex>(BAKED_TEXTURE_COUNT);
// Whether the handler for our current wearable list should automatically start downloading the assets
//private bool DownloadWearables = false;
private static int CacheCheckSerialNum = 1; //FIXME
private static uint SetAppearanceSerialNum = 1; //FIXME
private AutoResetEvent WearablesRequestEvent = new AutoResetEvent(false);
private AutoResetEvent WearablesDownloadedEvent = new AutoResetEvent(false);
private AutoResetEvent CachedResponseEvent = new AutoResetEvent(false);
private AutoResetEvent UpdateEvent = new AutoResetEvent(false);
// FIXME: Create a class-level appearance thread so multiple threads can't be launched
/// <summary>
/// Default constructor
/// </summary>
/// <param name="client">This agents <seealso cref="OpenMetaverse.GridClient"/> Object</param>
/// <param name="assets">Reference to an AssetManager object</param>
public AppearanceManager(GridClient client, AssetManager assets)
{
Client = client;
Assets = assets;
// Initialize AgentTextures to zero UUIDs
for (int i = 0; i < AgentTextures.Length; i++)
AgentTextures[i] = UUID.Zero;
Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesUpdateHandler));
Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, new NetworkManager.PacketCallback(AgentCachedTextureResponseHandler));
Client.Network.RegisterCallback(PacketType.RebakeAvatarTextures,new NetworkManager.PacketCallback(RebakeAvatarTexturesHandler));
Client.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected);
}
private static AssetType WearableTypeToAssetType(WearableType type)
{
switch (type)
{
case WearableType.Shape:
case WearableType.Skin:
case WearableType.Hair:
case WearableType.Eyes:
return AssetType.Bodypart;
case WearableType.Shirt:
case WearableType.Pants:
case WearableType.Shoes:
case WearableType.Socks:
case WearableType.Jacket:
case WearableType.Gloves:
case WearableType.Undershirt:
case WearableType.Underpants:
case WearableType.Skirt:
return AssetType.Clothing;
default:
throw new Exception("Unhandled wearable type " + type);
}
}
/// <summary>
/// Returns the assetID for a given WearableType
/// </summary>
/// <param name="type">the <seealso cref="OpenMetaverse.WearableType"/> of the asset</param>
/// <returns>The <seealso cref="OpenMetaverse.UUID"/> of the WearableType</returns>
public UUID GetWearableAsset(WearableType type)
{
WearableData wearable;
if (Wearables.TryGetValue(type, out wearable))
return wearable.Item.AssetUUID;
else
return UUID.Zero;
}
/// <summary>
/// Ask the server what we are wearing and set appearance based on that
/// </summary>
public void SetPreviousAppearance()
{
SetPreviousAppearance(true);
}
public void SetPreviousAppearance(bool bake)
{
Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartSetPreviousAppearance));
appearanceThread.Start(bake);
}
private void StartSetPreviousAppearance(object thread_params)
{
bool bake = (bool)thread_params;
SendAgentWearablesRequest();
WearablesRequestEvent.WaitOne();
UpdateAppearanceFromWearables(bake);
}
private class WearParams
{
public object Param;
public bool Bake;
public bool RemoveExistingAttachments;
public WearParams(object param, bool bake, bool removeExistingAttachments)
{
Param = param;
Bake = bake;
RemoveExistingAttachments = removeExistingAttachments;
}
}
/// <summary>
/// Replace the current outfit with a list of wearables and set appearance
/// </summary>
/// <param name="ibs">List of wearables that define the new outfit</param>
public void WearOutfit(List<InventoryBase> ibs)
{
WearOutfit(ibs, true);
}
/// <summary>
/// Replace the current outfit with a list of wearables and set appearance
/// </summary>
/// <param name="ibs">List of wearables that define the new outfit</param>
/// <param name="bake">Whether to bake textures for the avatar or not</param>
public void WearOutfit(List<InventoryBase> ibs, bool bake)
{
WearParams wearParams = new WearParams(ibs, bake,true);
Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfit));
appearanceThread.Start(wearParams);
}
/// <summary>
/// Add to the current outfit with the list supplied
/// </summary>
/// <param name="ibs">List of wearables that will be added to the outfit</param>
/// <param name="bake">Whether to bake textures for the avatar or not</param>
public void AddToOutfit(List<InventoryBase> ibs_new, bool bake)
{
List<InventoryBase> ibs_total = new List<InventoryBase>();
// Get what we are currently wearing
lock(Wearables.Dictionary)
{
foreach (KeyValuePair<WearableType, OpenMetaverse.AppearanceManager.WearableData> kvp in Wearables.Dictionary)
ibs_total.Add((InventoryBase)kvp.Value.Item);
}
// Add the new items at the end, ReplaceOutfitWearables() will do the right thing as it places each warable into a slot in order
// so the end of the list will overwrite earlier parts if they use the same slot.
foreach (InventoryBase item in ibs_new)
{
if (item is InventoryWearable)
ibs_total.Add(item);
}
WearParams wearParams = new WearParams(ibs_total, bake, false);
Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfit));
appearanceThread.Start(wearParams);
}
private void StartWearOutfit(object thread_params)
{
WearParams wearParams = (WearParams)thread_params;
List<InventoryBase> ibs = (List<InventoryBase>)wearParams.Param;
List<InventoryWearable> wearables = new List<InventoryWearable>();
List<InventoryBase> attachments = new List<InventoryBase>();
foreach (InventoryBase ib in ibs)
{
if (ib is InventoryWearable)
wearables.Add((InventoryWearable)ib);
else if (ib is InventoryAttachment || ib is InventoryObject)
attachments.Add(ib);
}
SendAgentWearablesRequest();
WearablesRequestEvent.WaitOne();
ReplaceOutfitWearables(wearables);
UpdateAppearanceFromWearables(wearParams.Bake);
AddAttachments(attachments, wearParams.RemoveExistingAttachments);
}
/// <summary>
/// Replace the current outfit with a folder and set appearance
/// </summary>
/// <param name="folder">UUID of the inventory folder to wear</param>
public void WearOutfit(UUID folder)
{
WearOutfit(folder, true);
}
/// <summary>
/// Replace the current outfit with a folder and set appearance
/// </summary>
/// <param name="path">Inventory path of the folder to wear</param>
public void WearOutfit(string[] path)
{
WearOutfit(path, true);
}
/// <summary>
/// Replace the current outfit with a folder and set appearance
/// </summary>
/// <param name="folder">Folder containing the new outfit</param>
/// <param name="bake">Whether to bake the avatar textures or not</param>
public void WearOutfit(UUID folder, bool bake)
{
WearParams wearOutfitParams = new WearParams(folder, bake,true);
Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfitFolder));
appearanceThread.Start(wearOutfitParams);
}
/// <summary>
/// Replace the current outfit with a folder and set appearance
/// </summary>
/// <param name="path">Path of folder containing the new outfit</param>
/// <param name="bake">Whether to bake the avatar textures or not</param>
public void WearOutfit(string[] path, bool bake)
{
WearParams wearOutfitParams = new WearParams(path, bake,true);
Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfitFolder));
appearanceThread.Start(wearOutfitParams);
}
public void WearOutfit(InventoryFolder folder, bool bake)
{
WearParams wearOutfitParams = new WearParams(folder, bake,true);
Thread appearanceThread = new Thread(new ParameterizedThreadStart(StartWearOutfitFolder));
appearanceThread.Start(wearOutfitParams);
}
private void StartWearOutfitFolder(object thread_params)
{
WearParams wearOutfitParams = (WearParams)thread_params;
SendAgentWearablesRequest(); // request current wearables async
List<InventoryWearable> wearables;
List<InventoryBase> attachments;
if (!GetFolderWearables(wearOutfitParams.Param, out wearables, out attachments)) // get wearables in outfit folder
return; // TODO: this error condition should be passed back to the client somehow
WearablesRequestEvent.WaitOne(); // wait for current wearables
ReplaceOutfitWearables(wearables); // replace current wearables with outfit folder
UpdateAppearanceFromWearables(wearOutfitParams.Bake);
AddAttachments(attachments, wearOutfitParams.RemoveExistingAttachments);
}
private bool GetFolderWearables(object _folder, out List<InventoryWearable> wearables, out List<InventoryBase> attachments)
{
UUID folder;
wearables = null;
attachments = null;
if (_folder.GetType() == typeof(string[]))
{
string[] path = (string[])_folder;
folder = Client.Inventory.FindObjectByPath(
Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, String.Join("/", path), 1000 * 20);
if (folder == UUID.Zero)
{
Logger.Log("Outfit path " + path + " not found", Helpers.LogLevel.Error, Client);
return false;
}
}
else
folder = (UUID)_folder;
wearables = new List<InventoryWearable>();
attachments = new List<InventoryBase>();
List<InventoryBase> objects = Client.Inventory.FolderContents(folder, Client.Self.AgentID,
false, true, InventorySortOrder.ByName, 1000 * 20);
if (objects != null)
{
foreach (InventoryBase ib in objects)
{
if (ib is InventoryWearable)
{
Logger.DebugLog("Adding wearable " + ib.Name, Client);
wearables.Add((InventoryWearable)ib);
}
else if (ib is InventoryAttachment)
{
Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client);
attachments.Add(ib);
}
else if (ib is InventoryObject)
{
Logger.DebugLog("Adding attachment (object) " + ib.Name, Client);
attachments.Add(ib);
}
else
{
Logger.DebugLog("Ignoring inventory item " + ib.Name, Client);
}
}
}
else
{
Logger.Log("Failed to download folder contents of + " + folder.ToString(),
Helpers.LogLevel.Error, Client);
return false;
}
return true;
}
// this method will download the assets for all inventory items in iws
private void ReplaceOutfitWearables(List<InventoryWearable> iws)
{
lock (Wearables.Dictionary)
{
Dictionary<WearableType, WearableData> preserve = new Dictionary<WearableType,WearableData>();
foreach (KeyValuePair<WearableType,WearableData> kvp in Wearables.Dictionary)
{
if (kvp.Value.Item.AssetType == AssetType.Bodypart)
preserve.Add(kvp.Key, kvp.Value);
}
Wearables.Dictionary = preserve;
foreach (InventoryWearable iw in iws)
{
WearableData wd = new WearableData();
wd.Item = iw;
Wearables.Dictionary[wd.Item.WearableType] = wd;
}
}
}
/// <summary>
/// Adds a list of attachments to avatar
/// </summary>
/// <param name="attachments">A List containing the attachments to add</param>
/// <param name="removeExistingFirst">If true, tells simulator to remove existing attachment
/// first</param>
public void AddAttachments(List<InventoryBase> attachments, bool removeExistingFirst)
{
// FIXME: Obey this
//const int OBJECTS_PER_PACKET = 4;
// Use RezMultipleAttachmentsFromInv to clear out current attachments, and attach new ones
RezMultipleAttachmentsFromInvPacket attachmentsPacket = new RezMultipleAttachmentsFromInvPacket();
attachmentsPacket.AgentData.AgentID = Client.Self.AgentID;
attachmentsPacket.AgentData.SessionID = Client.Self.SessionID;
attachmentsPacket.HeaderData.CompoundMsgID = UUID.Random();
attachmentsPacket.HeaderData.FirstDetachAll = removeExistingFirst;
attachmentsPacket.HeaderData.TotalObjects = (byte)attachments.Count;
attachmentsPacket.ObjectData = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock[attachments.Count];
for (int i = 0; i < attachments.Count; i++)
{
if (attachments[i] is InventoryAttachment)
{
InventoryAttachment attachment = (InventoryAttachment)attachments[i];
attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock();
attachmentsPacket.ObjectData[i].AttachmentPt = (byte)attachment.AttachmentPoint;
attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask;
attachmentsPacket.ObjectData[i].GroupMask = (uint)attachment.Permissions.GroupMask;
attachmentsPacket.ObjectData[i].ItemFlags = (uint)attachment.Flags;
attachmentsPacket.ObjectData[i].ItemID = attachment.UUID;
attachmentsPacket.ObjectData[i].Name = Utils.StringToBytes(attachment.Name);
attachmentsPacket.ObjectData[i].Description = Utils.StringToBytes(attachment.Description);
attachmentsPacket.ObjectData[i].NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask;
attachmentsPacket.ObjectData[i].OwnerID = attachment.OwnerID;
}
else if (attachments[i] is InventoryObject)
{
InventoryObject attachment = (InventoryObject)attachments[i];
attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock();
attachmentsPacket.ObjectData[i].AttachmentPt = 0;
attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask;
attachmentsPacket.ObjectData[i].GroupMask = (uint)attachment.Permissions.GroupMask;
attachmentsPacket.ObjectData[i].ItemFlags = (uint)attachment.Flags;
attachmentsPacket.ObjectData[i].ItemID = attachment.UUID;
attachmentsPacket.ObjectData[i].Name = Utils.StringToBytes(attachment.Name);
attachmentsPacket.ObjectData[i].Description = Utils.StringToBytes(attachment.Description);
attachmentsPacket.ObjectData[i].NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask;
attachmentsPacket.ObjectData[i].OwnerID = attachment.OwnerID;
}
else
{
Logger.Log("Cannot attach inventory item of type " + attachments[i].GetType().ToString(),
Helpers.LogLevel.Warning, Client);
}
}
Client.Network.SendPacket(attachmentsPacket);
}
/// <summary>
/// Attach an item to an avatar at a specific attach point
/// </summary>
/// <param name="item">A <seealso cref="OpenMetaverse.InventoryItem"/> to attach</param>
/// <param name="attachPoint">the <seealso cref="OpenMetaverse.AttachmentPoint"/> on the avatar
/// to attach the item to</param>
public void Attach(InventoryItem item, AttachmentPoint attachPoint)
{
Attach(item.UUID, item.OwnerID, item.Name, item.Description, item.Permissions, item.Flags,
attachPoint);
}
/// <summary>
/// Attach an item to an avatar specifying attachment details
/// </summary>
/// <param name="itemID">The <seealso cref="OpenMetaverse.UUID"/> of the item to attach</param>
/// <param name="ownerID">The <seealso cref="OpenMetaverse.UUID"/> attachments owner</param>
/// <param name="name">The name of the attachment</param>
/// <param name="description">The description of the attahment</param>
/// <param name="perms">The <seealso cref="OpenMetaverse.Permissions"/> to apply when attached</param>
/// <param name="itemFlags">The <seealso cref="OpenMetaverse.InventoryItemFlags"/> of the attachment</param>
/// <param name="attachPoint">the <seealso cref="OpenMetaverse.AttachmentPoint"/> on the avatar
/// to attach the item to</param>
public void Attach(UUID itemID, UUID ownerID, string name, string description,
Permissions perms, uint itemFlags, AttachmentPoint attachPoint)
{
// TODO: At some point it might be beneficial to have AppearanceManager track what we
// are currently wearing for attachments to make enumeration and detachment easier
RezSingleAttachmentFromInvPacket attach = new RezSingleAttachmentFromInvPacket();
attach.AgentData.AgentID = Client.Self.AgentID;
attach.AgentData.SessionID = Client.Self.SessionID;
attach.ObjectData.AttachmentPt = (byte)attachPoint;
attach.ObjectData.Description = Utils.StringToBytes(description);
attach.ObjectData.EveryoneMask = (uint)perms.EveryoneMask;
attach.ObjectData.GroupMask = (uint)perms.GroupMask;
attach.ObjectData.ItemFlags = itemFlags;
attach.ObjectData.ItemID = itemID;
attach.ObjectData.Name = Utils.StringToBytes(name);
attach.ObjectData.NextOwnerMask = (uint)perms.NextOwnerMask;
attach.ObjectData.OwnerID = ownerID;
Client.Network.SendPacket(attach);
}
/// <summary>
/// Detach an item from avatar using an <seealso cref="OpenMetaverse.InventoryItem"/> object
/// </summary>
/// <param name="item">An <seealso cref="OpenMetaverse.InventoryItem"/> object</param>
public void Detach(InventoryItem item)
{
Detach(item.UUID);
}
/// <summary>
/// Detach an Item from avatar by items <seealso cref="OpenMetaverse.UUID"/>
/// </summary>
/// <param name="itemID">The items ID to detach</param>
public void Detach(UUID itemID)
{
DetachAttachmentIntoInvPacket detach = new DetachAttachmentIntoInvPacket();
detach.ObjectData.AgentID = Client.Self.AgentID;
detach.ObjectData.ItemID = itemID;
Client.Network.SendPacket(detach);
}
private void UpdateAppearanceFromWearables(bool bake)
{
lock (AgentTextures)
{
for (int i = 0; i < AgentTextures.Length; i++)
AgentTextures[i] = UUID.Zero;
}
// Register an asset download callback to get wearable data
AssetManager.AssetReceivedCallback assetCallback = new AssetManager.AssetReceivedCallback(Assets_OnAssetReceived);
AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded);
Assets.OnAssetReceived += assetCallback;
Assets.OnImageReceived += imageCallback;
Assets.OnAssetUploaded += uploadCallback;
// Download assets for what we are wearing and fill in AgentTextures
DownloadWearableAssets();
WearablesDownloadedEvent.WaitOne();
// Unregister the asset download callback
Assets.OnAssetReceived -= assetCallback;
// Check if anything needs to be rebaked
if (bake) RequestCachedBakes();
// Tell the sim what we are wearing
SendAgentIsNowWearing();
// Wait for cached layer check to finish
if (bake) CachedResponseEvent.WaitOne();
// Unregister the image download and asset upload callbacks
Assets.OnImageReceived -= imageCallback;
Assets.OnAssetUploaded -= uploadCallback;
Logger.DebugLog("CachedResponseEvent completed", Client);
#region Send Appearance
Primitive.TextureEntry te = null;
ObjectManager.NewAvatarCallback updateCallback =
delegate(Simulator simulator, Avatar avatar, ulong regionHandle, ushort timeDilation)
{
if (avatar.LocalID == Client.Self.LocalID)
{
if (avatar.Textures.FaceTextures != null)
{
bool match = true;
for (uint i = 0; i < AgentTextures.Length; i++)
{
Primitive.TextureEntryFace face = avatar.Textures.FaceTextures[i];
if (face == null)
{
// If the texture is UUID.Zero the face should be null
if (AgentTextures[i] != UUID.Zero)
{
match = false;
break;
}
}
else if (face.TextureID != AgentTextures[i])
{
Logger.DebugLog("*** FACE is "+face.TextureID.ToString()+" Agent Texture is "+AgentTextures[i].ToString());
match = false;
//break;
}
}
if (!match)
Logger.Log("TextureEntry mismatch after updating our appearance", Helpers.LogLevel.Warning, Client);
te = avatar.Textures;
UpdateEvent.Set();
}
else
{
Logger.Log("Received an update for our avatar with a null FaceTextures array",
Helpers.LogLevel.Warning, Client);
}
}
};
Client.Objects.OnNewAvatar += updateCallback;
// Send all of the visual params and textures for our agent
SendAgentSetAppearance();
// Wait for the ObjectUpdate to come in for our avatar after changing appearance
if (UpdateEvent.WaitOne(1000 * 60, false))
{
if (OnAppearanceUpdated != null)
{
try { OnAppearanceUpdated(te); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
else
{
Logger.Log("Timed out waiting for our appearance to update on the simulator", Helpers.LogLevel.Warning, Client);
}
Client.Objects.OnNewAvatar -= updateCallback;
Logger.Log("Appearance update completed",Helpers.LogLevel.Info);
#endregion Send Appearance
}
/// <summary>
/// Build hashes out of the texture assetIDs for each baking layer to
/// ask the simulator whether it has cached copies of each baked texture
/// </summary>
public void RequestCachedBakes()
{
Logger.DebugLog("RequestCachedBakes()", Client);
List<KeyValuePair<int, UUID>> hashes = new List<KeyValuePair<int,UUID>>();
AgentCachedTexturePacket cache = new AgentCachedTexturePacket();
cache.AgentData.AgentID = Client.Self.AgentID;
cache.AgentData.SessionID = Client.Self.SessionID;
cache.AgentData.SerialNum = CacheCheckSerialNum;
// Build hashes for each of the bake layers from the individual components
for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
{
// Don't do a cache request for a skirt bake if we're not wearing a skirt
if (bakedIndex == (int)BakeType.Skirt &&
(!Wearables.ContainsKey(WearableType.Skirt) || Wearables.Dictionary[WearableType.Skirt].Asset.AssetID == UUID.Zero))
continue;
UUID hash = new UUID();
for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
{
WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
UUID assetID = GetWearableAsset(type);
// Build a hash of all the texture asset IDs in this baking layer
if (assetID != UUID.Zero) hash ^= assetID;
}
if (hash != UUID.Zero)
{
// Hash with our secret value for this baked layer
hash ^= BAKED_TEXTURE_HASH[bakedIndex];
// Add this to the list of hashes to send out
hashes.Add(new KeyValuePair<int, UUID>(bakedIndex, hash));
}
}
// Only send the packet out if there's something to check
if (hashes.Count > 0)
{
cache.WearableData = new AgentCachedTexturePacket.WearableDataBlock[hashes.Count];
for (int i = 0; i < hashes.Count; i++)
{
cache.WearableData[i] = new AgentCachedTexturePacket.WearableDataBlock();
cache.WearableData[i].TextureIndex = (byte)hashes[i].Key;
cache.WearableData[i].ID = hashes[i].Value;
Logger.DebugLog("Checking cache for index " + cache.WearableData[i].TextureIndex +
", ID: " + cache.WearableData[i].ID, Client);
}
// Increment our serial number for this packet
CacheCheckSerialNum++;
// Send it out
Client.Network.SendPacket(cache);
}
}
/// <summary>
/// Force a rebake of the currently worn textures
/// </summary>
public void ForceRebakeAvatarTextures()
{
//Kick the appearance setting as well, this sim probably wants this too a it asked for bakes
RebakeLayer(TextureIndex.HeadBaked);
RebakeLayer(TextureIndex.EyesBaked);
RebakeLayer(TextureIndex.LowerBaked);
RebakeLayer(TextureIndex.UpperBaked);
RebakeLayer(TextureIndex.SkirtBaked);
SendAgentSetAppearance();
}
/// <summary>
/// Ask the server what textures our avatar is currently wearing
/// </summary>
public void SendAgentWearablesRequest()
{
AgentWearablesRequestPacket request = new AgentWearablesRequestPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
Client.Network.SendPacket(request);
}
private void AgentWearablesUpdateHandler(Packet packet, Simulator simulator)
{
// Lock to prevent a race condition with multiple AgentWearables packets
lock (WearablesRequestEvent)
{
AgentWearablesUpdatePacket update = (AgentWearablesUpdatePacket)packet;
// Reset the Wearables collection
lock (Wearables.Dictionary) Wearables.Dictionary.Clear();
for (int i = 0; i < update.WearableData.Length; i++)
{
if (update.WearableData[i].AssetID != UUID.Zero)
{
WearableType type = (WearableType)update.WearableData[i].WearableType;
WearableData data = new WearableData();
data.Item = new InventoryWearable(update.WearableData[i].ItemID);
data.Item.WearableType = type;
data.Item.AssetType = WearableTypeToAssetType(type);
data.Item.AssetUUID = update.WearableData[i].AssetID;
// Add this wearable to our collection
lock (Wearables.Dictionary) Wearables.Dictionary[type] = data;
}
}
}
WearablesRequestEvent.Set();
}
private void SendAgentSetAppearance()
{
AgentSetAppearancePacket set = new AgentSetAppearancePacket();
set.AgentData.AgentID = Client.Self.AgentID;
set.AgentData.SessionID = Client.Self.SessionID;
set.AgentData.SerialNum = SetAppearanceSerialNum++;
set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[218];
float AgentSizeVPHeight = 0.0f;
float AgentSizeVPHeelHeight = 0.0f;
float AgentSizeVPPlatformHeight = 0.0f;
float AgentSizeVPHeadSize = 0.5f;
float AgentSizeVPLegLength = 0.0f;
float AgentSizeVPNeckLength = 0.0f;
float AgentSizeVPHipLength = 0.0f;
lock (Wearables.Dictionary)
{
// Only for debugging output
int count = 0, vpIndex = 0;
// Build the visual param array
foreach (KeyValuePair<int, VisualParam> kvp in VisualParams.Params)
{
VisualParam vp = kvp.Value;
// Only Group-0 parameters are sent in AgentSetAppearance packets
if (vp.Group == 0)
{
set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
// Try and find this value in our collection of downloaded wearables
foreach (WearableData data in Wearables.Dictionary.Values)
{
if (data.Asset != null && data.Asset.Params.ContainsKey(vp.ParamID))
{
set.VisualParam[vpIndex].ParamValue = Utils.FloatToByte(data.Asset.Params[vp.ParamID], vp.MinValue, vp.MaxValue);
count++;
switch (vp.ParamID)
{
case 33:
AgentSizeVPHeight = data.Asset.Params[vp.ParamID];
break;
case 198:
AgentSizeVPHeelHeight = data.Asset.Params[vp.ParamID];
break;
case 503:
AgentSizeVPPlatformHeight = data.Asset.Params[vp.ParamID];
break;
case 682:
AgentSizeVPHeadSize = data.Asset.Params[vp.ParamID];
break;
case 692:
AgentSizeVPLegLength = data.Asset.Params[vp.ParamID];
break;
case 756:
AgentSizeVPNeckLength = data.Asset.Params[vp.ParamID];
break;
case 842:
AgentSizeVPHipLength = data.Asset.Params[vp.ParamID];
break;
}
break;
}
}
++vpIndex;
}
}
// Build the texture entry for our agent
Primitive.TextureEntry te = new Primitive.TextureEntry(DEFAULT_AVATAR_TEXTURE);
// Put our AgentTextures array in to TextureEntry
lock (AgentTextures)
{
for (uint i = 0; i < AgentTextures.Length; i++)
{
if (AgentTextures[i] != UUID.Zero)
{
Primitive.TextureEntryFace face = te.CreateFace(i);
face.TextureID = AgentTextures[i];
}
}
}
foreach (WearableData data in Wearables.Dictionary.Values)
{
if (data.Asset != null)
{
foreach (KeyValuePair<TextureIndex, UUID> texture in data.Asset.Textures)
{
Primitive.TextureEntryFace face = te.CreateFace((uint)texture.Key);
face.TextureID = texture.Value;
Logger.DebugLog("Setting agent texture " + ((TextureIndex)texture.Key).ToString() + " to " +
texture.Value.ToString(), Client);
}
}
}
// Set the packet TextureEntry
set.ObjectData.TextureEntry = te.GetBytes();
}
// FIXME: Our hackish algorithm is making squished avatars. See
// http://www.OpenMetaverse.org/wiki/Agent_Size for discussion of the correct algorithm
//float height = Utils.ByteToFloat(set.VisualParam[33].ParamValue, VisualParams.Params[33].MinValue,
// VisualParams.Params[33].MaxValue);
// Takes into account the Shoe Heel/Platform offsets but not the Head Size Offset. But seems to work.
double AgentSizeBase = 1.706;
// The calculation for the Head Size scalar may be incorrect. But seems to work.
double AgentHeight = AgentSizeBase + (AgentSizeVPLegLength * .1918) + (AgentSizeVPHipLength * .0375) +
(AgentSizeVPHeight * .12022) + (AgentSizeVPHeadSize * .01117) + (AgentSizeVPNeckLength * .038) +
(AgentSizeVPHeelHeight * .08) + (AgentSizeVPPlatformHeight * .07);
set.AgentData.Size = new Vector3(0.45f, 0.6f, (float)AgentHeight);
// TODO: Account for not having all the textures baked yet
set.WearableData = new AgentSetAppearancePacket.WearableDataBlock[BAKED_TEXTURE_COUNT];
// Build hashes for each of the bake layers from the individual components
for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
{
UUID hash = new UUID();
for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
{
WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
UUID assetID = GetWearableAsset(type);
// Build a hash of all the texture asset IDs in this baking layer
if (assetID != UUID.Zero) hash ^= assetID;
}
if (hash != UUID.Zero)
{
// Hash with our secret value for this baked layer
hash ^= BAKED_TEXTURE_HASH[bakedIndex];
}
// Tell the server what cached texture assetID to use for each bake layer
set.WearableData[bakedIndex] = new AgentSetAppearancePacket.WearableDataBlock();
set.WearableData[bakedIndex].TextureIndex = (byte)bakedIndex;
set.WearableData[bakedIndex].CacheID = hash;
}
// Finally, send the packet
Client.Network.SendPacket(set);
}
private void SendAgentIsNowWearing()
{
Logger.DebugLog("SendAgentIsNowWearing()", Client);
AgentIsNowWearingPacket wearing = new AgentIsNowWearingPacket();
wearing.AgentData.AgentID = Client.Self.AgentID;
wearing.AgentData.SessionID = Client.Self.SessionID;
wearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[WEARABLE_COUNT];
for (int i = 0; i < WEARABLE_COUNT; i++)
{
WearableType type = (WearableType)i;
wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
wearing.WearableData[i].WearableType = (byte)i;
if (Wearables.ContainsKey(type))
wearing.WearableData[i].ItemID = Wearables.Dictionary[type].Item.UUID;
else
wearing.WearableData[i].ItemID = UUID.Zero;
}
Client.Network.SendPacket(wearing);
}
private TextureIndex BakeTypeToAgentTextureIndex(BakeType index)
{
switch (index)
{
case BakeType.Head:
return TextureIndex.HeadBaked;
case BakeType.UpperBody:
return TextureIndex.UpperBaked;
case BakeType.LowerBody:
return TextureIndex.LowerBaked;
case BakeType.Eyes:
return TextureIndex.EyesBaked;
case BakeType.Skirt:
return TextureIndex.SkirtBaked;
default:
return TextureIndex.Unknown;
}
}
private void DownloadWearableAssets()
{
lock(Wearables.Dictionary)
{
foreach (KeyValuePair<WearableType, WearableData> kvp in Wearables.Dictionary)
{
Logger.DebugLog("Requesting asset for wearable item " + kvp.Value.Item.WearableType + " (" + kvp.Value.Item.AssetUUID + ")", Client);
AssetDownloads.Enqueue(new PendingAssetDownload(kvp.Value.Item.AssetUUID, kvp.Value.Item.AssetType));
}
}
if (AssetDownloads.Count > 0)
{
PendingAssetDownload pad = AssetDownloads.Dequeue();
Assets.RequestAsset(pad.Id, pad.Type, true);
}
}
private void RebakeLayer(TextureIndex index)
{
BakeType bakeType = Baker.BakeTypeFor(index);
Dictionary<int, float> paramValues;
// Build a dictionary of appearance parameter indices and values from the wearables
paramValues=MakeParamValues();
Baker bake = new Baker(Client, bakeType, 0, paramValues);
for (int ii = 0; ii < AVATAR_TEXTURE_COUNT; ii++)
{
if(bakeType==Baker.BakeTypeFor((TextureIndex)ii) && AgentAssets[ii]!=null)
{
Logger.Log("Adding asset "+AgentAssets[ii].AssetID.ToString()+" to baker",Helpers.LogLevel.Debug);
bake.AddTexture((TextureIndex)ii,(AssetTexture)AgentAssets[ii],true);
}
}
UploadBake(bake);
}
private void UploadBake(Baker bake)
{
lock (PendingUploads)
{
if(PendingUploads.ContainsKey(bake.BakedTexture.AssetID))
{
Logger.Log("UploadBake(): Skipping Asset id "+bake.BakedTexture.AssetID.ToString()+" Already in progress",Helpers.LogLevel.Info, Client);
return;
}
// Upload the completed layer data and Add it to a pending uploads list
UUID id=Assets.RequestUpload(bake.BakedTexture, true);
PendingUploads.Add(UUID.Combine(id, Client.Self.SecureSessionID), BakeTypeToAgentTextureIndex(bake.BakeType));
}
Logger.DebugLog(String.Format("Bake {0} completed. Uploading asset {1}", bake.BakeType,
bake.BakedTexture.AssetID.ToString()), Client);
}
private int AddImageDownload(TextureIndex index)
{
UUID image = AgentTextures[(int)index];
if (image != UUID.Zero)
{
if (!ImageDownloads.ContainsKey(image))
{
Logger.DebugLog("Downloading layer " + index.ToString(), Client);
ImageDownloads.Add(image, index);
}
return 1;
}
return 0;
}
private Dictionary<int, float> MakeParamValues()
{
Dictionary<int, float> paramValues = new Dictionary<int, float>(VisualParams.Params.Count);
lock(Wearables.Dictionary)
{
foreach (KeyValuePair<int,VisualParam> kvp in VisualParams.Params)
{
// Only Group-0 parameters are sent in AgentSetAppearance packets
if (kvp.Value.Group == 0)
{
bool found = false;
VisualParam vp = kvp.Value;
// Try and find this value in our collection of downloaded wearables
foreach (WearableData data in Wearables.Dictionary.Values)
{
if (data.Asset.Params.ContainsKey(vp.ParamID))
{
paramValues.Add(vp.ParamID, data.Asset.Params[vp.ParamID]);
found = true;
break;
}
}
// Use a default value if we don't have one set for it
if (!found) paramValues.Add(vp.ParamID, vp.DefaultValue);
}
}
}
return paramValues;
}
private int AddImagesToDownload(BakeType bakeType)
{
int imageCount = 0;
// Download all of the images in this layer
switch (bakeType)
{
case BakeType.Head:
lock (ImageDownloads)
{
imageCount += AddImageDownload(TextureIndex.HeadBodypaint);
//imageCount += AddImageDownload(TextureIndex.Hair);
}
break;
case BakeType.UpperBody:
lock (ImageDownloads)
{
imageCount += AddImageDownload(TextureIndex.UpperBodypaint);
imageCount += AddImageDownload(TextureIndex.UpperGloves);
imageCount += AddImageDownload(TextureIndex.UpperUndershirt);
imageCount += AddImageDownload(TextureIndex.UpperShirt);
imageCount += AddImageDownload(TextureIndex.UpperJacket);
}
break;
case BakeType.LowerBody:
lock (ImageDownloads)
{
imageCount += AddImageDownload(TextureIndex.LowerBodypaint);
imageCount += AddImageDownload(TextureIndex.LowerUnderpants);
imageCount += AddImageDownload(TextureIndex.LowerSocks);
imageCount += AddImageDownload(TextureIndex.LowerShoes);
imageCount += AddImageDownload(TextureIndex.LowerPants);
imageCount += AddImageDownload(TextureIndex.LowerJacket);
}
break;
case BakeType.Eyes:
lock (ImageDownloads)
{
imageCount += AddImageDownload(TextureIndex.EyesIris);
}
break;
case BakeType.Skirt:
if (Wearables.ContainsKey(WearableType.Skirt))
{
lock (ImageDownloads)
{
imageCount += AddImageDownload(TextureIndex.Skirt);
}
}
break;
default:
Logger.Log("Unknown BakeType :" + bakeType.ToString(), Helpers.LogLevel.Warning, Client);
break;
}
return imageCount;
}
#region Callbacks
private void RebakeAvatarTexturesHandler(Packet packet, Simulator simulator)
{
RebakeAvatarTexturesPacket data=(RebakeAvatarTexturesPacket)packet;
Logger.Log("Request rebake for :"+data.TextureData.TextureID.ToString(),Helpers.LogLevel.Info);
lock (AgentTextures)
{
for (int i = 0; i < AgentTextures.Length; i++)
{
if(AgentTextures[i] == data.TextureData.TextureID)
{
// Its one of our baked layers, rebake this one
RebakeLayer((TextureIndex)i);
//Kick the appearance setting as well, this sim probably wants this too a it asked for bakes
SendAgentSetAppearance();
}
}
}
}
private void AgentCachedTextureResponseHandler(Packet packet, Simulator simulator)
{
Logger.DebugLog("AgentCachedTextureResponseHandler()", Client);
AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet;
lock (AgentTextures)
{
//If we are here then the user has tried to wear stuff or we are at login
// In either case the existing uploads of this class are very shortly going to be no good
PendingUploads.Clear();
foreach (AgentCachedTextureResponsePacket.WearableDataBlock block in response.WearableData)
{
//UUID hash=new UUID();
// For each missing element we need to bake our own texture
Logger.DebugLog("Cache response, index: " + block.TextureIndex + ", ID: " +
block.TextureID.ToString(), Client);
// FIXME: Use this. Right now we treat baked images on other sims as if they were missing
string host = Utils.BytesToString(block.HostName);
if (host.Length > 0) Logger.DebugLog("Cached bake exists on foreign host " + host, Client);
BakeType bakeType = (BakeType)block.TextureIndex;
// Note, still should handle block.TextureID != UUID.Zero && host.Length == 0
// Not sure what we should do as yet with that.
// Convert the baked index to an AgentTexture index
if (block.TextureID != UUID.Zero && host.Length != 0)
{
TextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
AgentTextures[(int)index] = block.TextureID;
AddImagesToDownload(bakeType); // We need to do this bit regardless for rebaking purposes later
}
else
{
int imageCount=AddImagesToDownload(bakeType);
if (!PendingBakes.ContainsKey(bakeType))
{
Logger.DebugLog("Initializing " + bakeType.ToString() + " bake with " + imageCount + " textures", Client);
Dictionary<int, float> paramValues=MakeParamValues();
// Build a dictionary of appearance parameter indices and values from the wearables
if (imageCount == 0)
{
// if there are no textures to download, we can bake right away and start the upload
Baker bake = new Baker(Client, bakeType, 0, paramValues);
UploadBake(bake);
}
else
{
lock (PendingBakes)
{
Baker bake=new Baker(Client, bakeType, imageCount,paramValues);
PendingBakes.Add(bakeType,bake);
}
}
}
else if (!PendingBakes.ContainsKey(bakeType))
{
Logger.Log("No cached bake for " + bakeType.ToString() + " and no textures for that " +
"layer, this is an unhandled case", Helpers.LogLevel.Error, Client);
}
}
}
}
if (ImageDownloads.Count == 0)
{
// No pending downloads for baking, we're done
CachedResponseEvent.Set();
}
else
{
lock (ImageDownloads)
{
List<UUID> imgKeys = new List<UUID>(ImageDownloads.Keys);
foreach (UUID image in imgKeys)
{
// Download all the images we need for baking
Assets.RequestImage(image, ImageType.Normal, 1013000.0f, 0, 0);
}
}
}
}
private void Assets_OnAssetReceived(AssetDownload download, Asset asset)
{
lock (Wearables.Dictionary)
{
// Check if this is a wearable we were waiting on
foreach (KeyValuePair<WearableType,WearableData> kvp in Wearables.Dictionary)
{
if (kvp.Value.Item.AssetUUID == download.AssetID)
{
// Make sure the download succeeded
if (download.Success)
{
kvp.Value.Asset = (AssetWearable)asset;
Logger.DebugLog("Downloaded wearable asset " + kvp.Value.Asset.Name, Client);
if (!kvp.Value.Asset.Decode())
{
Logger.Log("Failed to decode asset:" + Environment.NewLine +
Utils.BytesToString(asset.AssetData), Helpers.LogLevel.Error, Client);
}
lock (AgentTextures)
{
foreach (KeyValuePair<AppearanceManager.TextureIndex, UUID> texture in kvp.Value.Asset.Textures)
{
if (texture.Value != DEFAULT_AVATAR_TEXTURE) // this texture is not meant to be displayed
{
Logger.DebugLog("Setting " + texture.Key + " to " + texture.Value, Client);
AgentTextures[(int)texture.Key] = texture.Value;
}
}
}
}
else
{
Logger.Log("Wearable " + kvp.Key + "(" + download.AssetID.ToString() + ") failed to download, " +
download.Status.ToString(), Helpers.LogLevel.Warning, Client);
}
break;
}
}
}
if (AssetDownloads.Count > 0)
{
// Dowload the next wearable in line
PendingAssetDownload pad = AssetDownloads.Dequeue();
Assets.RequestAsset(pad.Id, pad.Type, true);
}
else
{
// Everything is downloaded
if (OnAgentWearables != null)
{
try { OnAgentWearables(); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
WearablesDownloadedEvent.Set();
}
}
private void Assets_OnImageReceived(ImageDownload image, AssetTexture assetTexture)
{
lock (ImageDownloads)
{
if (ImageDownloads.ContainsKey(image.ID))
{
ImageDownloads.Remove(image.ID);
// NOTE: This image may occupy more than one TextureIndex! We must finish this loop
for (int at = 0; at < AgentTextures.Length; at++)
{
if (AgentTextures[at] == image.ID)
{
TextureIndex index = (TextureIndex)at;
BakeType type = Baker.BakeTypeFor(index);
//BinaryWriter writer = new BinaryWriter(File.Create("wearable_" + index.ToString() + "_" + image.ID.ToString() + ".jp2"));
//writer.Write(image.AssetData);
//writer.Close();
bool baked = false;
AgentAssets[at]=assetTexture; //Cache this asset for rebaking, todo this could be better rather than dropping in this list.
if (PendingBakes.ContainsKey(type))
{
if (image.Success)
{
Logger.DebugLog("Finished downloading texture for " + index.ToString(), Client);
OpenJPEG.DecodeToImage(image.AssetData, out assetTexture.Image);
baked = PendingBakes[type].AddTexture(index, assetTexture, false);
}
else
{
Logger.Log("Texture for " + index.ToString() + " failed to download, " +
"bake will be incomplete", Helpers.LogLevel.Warning, Client);
baked = PendingBakes[type].MissingTexture(index);
}
}
if (baked)
{
UploadBake(PendingBakes[type]);
PendingBakes.Remove(type);
}
if (ImageDownloads.Count == 0 && PendingUploads.Count == 0)
{
// This is a failsafe catch, as the upload completed callback should normally
// be triggering the event
Logger.DebugLog("No pending downloads or uploads detected in OnImageReceived", Client);
CachedResponseEvent.Set();
}
else
{
Logger.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
ImageDownloads.Count, Client);
}
}
}
}
else
{
Logger.Log("Received an image download callback for an image we did not request " + image.ID.ToString(),
Helpers.LogLevel.Warning, Client);
}
}
}
private void Assets_OnAssetUploaded(AssetUpload upload)
{
lock (PendingUploads)
{
if (PendingUploads.ContainsKey(upload.AssetID))
{
if (upload.Success)
{
// Setup the TextureEntry with the new baked upload
TextureIndex index = PendingUploads[upload.AssetID];
AgentTextures[(int)index] = upload.AssetID;
Logger.DebugLog("Upload complete, AgentTextures " + index.ToString() + " set to " +
upload.AssetID.ToString(), Client);
}
else
{
Logger.Log("Asset upload " + upload.AssetID.ToString() + " failed",
Helpers.LogLevel.Warning, Client);
}
PendingUploads.Remove(upload.AssetID);
Logger.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
ImageDownloads.Count, Client);
if (PendingUploads.Count == 0 && ImageDownloads.Count == 0)
{
Logger.DebugLog("All pending image downloads and uploads complete", Client);
CachedResponseEvent.Set();
}
}
else
{
// TEMP
Logger.DebugLog("Upload " + upload.AssetID.ToString() + " was not found in PendingUploads", Client);
}
}
}
/// <summary>
/// Terminate any wait handles when the network layer disconnects
/// </summary>
private void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message)
{
WearablesRequestEvent.Set();
WearablesDownloadedEvent.Set();
CachedResponseEvent.Set();
UpdateEvent.Set();
}
#endregion Callbacks
}
}