Files
libremetaverse/libsecondlife/AppearanceManager.cs
phaik 6af90c73b4 fix for issue 566
git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1480 52acb1d6-8a22-11de-b505-999d5b087335
2007-11-08 21:17:02 +00:00

1320 lines
57 KiB
C#

/*
* Copyright (c) 2007, Second Life Reverse Engineering Team
* 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 Second Life Reverse Engineering Team 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 libsecondlife;
using libsecondlife.Packets;
using libsecondlife.Baking;
namespace libsecondlife
{
public class InvalidOutfitException : Exception
{
public InvalidOutfitException(string message) : base(message) { }
}
/// <summary>
///
/// </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>
/// <param name="wearables">A mapping of WearableTypes to KeyValuePairs
/// with Asset ID of the wearable as key and Item ID as value</param>
public delegate void AgentWearablesCallback(Dictionary<WearableType, KeyValuePair<LLUUID, LLUUID>> wearables);
/// <summary>
///
/// </summary>
/// <param name="textures">List of the textures our avatar is currently wearing</param>
public delegate void AppearanceUpdatedCallback(LLObject.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></summary>
public const int BAKED_TEXTURE_COUNT = 5;
/// <summary></summary>
public const int WEARABLES_PER_LAYER = 7;
/// <summary></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.Skin, 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 LLUUID[] BAKED_TEXTURE_HASH = new LLUUID[]
{
new LLUUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"),
new LLUUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"),
new LLUUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"),
new LLUUID("b2cf28af-b840-1071-3c6a-78085d8128b5"),
new LLUUID("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 LLUUID DEFAULT_AVATAR_TEXTURE = new LLUUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97");
private SecondLife Client;
private AssetManager Assets;
private Dictionary<WearableType, WearableData> Wearables = new Dictionary<WearableType, WearableData>();
// As wearable assets are downloaded and decoded, the textures are added to this array
private LLUUID[] AgentTextures = new LLUUID[AVATAR_TEXTURE_COUNT];
protected struct PendingAssetDownload
{
public LLUUID Id;
public AssetType Type;
public PendingAssetDownload(LLUUID 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<LLUUID, TextureIndex> ImageDownloads = new Dictionary<LLUUID, 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<LLUUID, TextureIndex> PendingUploads = new Dictionary<LLUUID, 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"></param>
/// <param name="assets"></param>
public AppearanceManager(SecondLife client, AssetManager assets)
{
Client = client;
Assets = assets;
// Initialize AgentTextures to zero UUIDs
for (int i = 0; i < AgentTextures.Length; i++)
AgentTextures[i] = LLUUID.Zero;
Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesUpdateHandler));
Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, new NetworkManager.PacketCallback(AgentCachedTextureResponseHandler));
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"></param>
/// <returns></returns>
public LLUUID GetWearableAsset(WearableType type)
{
WearableData wearable;
if (Wearables.TryGetValue(type, out wearable))
return wearable.Item.AssetUUID;
else
return LLUUID.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)
{
_bake = bake;
Thread appearanceThread = new Thread(new ThreadStart(StartSetPreviousAppearance));
appearanceThread.Start();
}
private bool _bake;
private void StartSetPreviousAppearance()
{
SendAgentWearablesRequest();
WearablesRequestEvent.WaitOne();
UpdateAppearanceFromWearables(_bake);
}
private class WearParams
{
public object Param;
public bool Bake;
public WearParams(object param, bool bake)
{
Param = param;
Bake = bake;
}
}
/// <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 = new WearParams(ibs, bake);
Thread appearanceThread = new Thread(new ThreadStart(StartWearOutfit));
appearanceThread.Start();
}
private WearParams _wearParams;
private void StartWearOutfit()
{
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, true);
}
/// <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(LLUUID 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(LLUUID folder, bool bake)
{
_wearOutfitParams = new WearParams(folder, bake);
Thread appearanceThread = new Thread(new ThreadStart(StartWearOutfitFolder));
appearanceThread.Start();
}
/// <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)
{
_wearOutfitParams = new WearParams(path, bake);
Thread appearanceThread = new Thread(new ThreadStart(StartWearOutfitFolder));
appearanceThread.Start();
}
private WearParams _wearOutfitParams;
private void StartWearOutfitFolder()
{
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, true);
}
private bool GetFolderWearables(object _folder, out List<InventoryWearable> wearables, out List<InventoryBase> attachments)
{
LLUUID 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 == LLUUID.Zero)
{
Client.Log("Outfit path " + path + " not found", Helpers.LogLevel.Error);
return false;
}
}
else
folder = (LLUUID)_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)
{
Client.DebugLog("Adding wearable " + ib.Name);
wearables.Add((InventoryWearable)ib);
}
else if (ib is InventoryAttachment)
{
Client.DebugLog("Adding attachment (attachment) " + ib.Name);
attachments.Add(ib);
}
else if (ib is InventoryObject)
{
Client.DebugLog("Adding attachment (object) " + ib.Name);
attachments.Add(ib);
}
else
{
Client.DebugLog("Ignoring inventory item " + ib.Name);
}
}
}
else
{
Client.Log("Failed to download folder contents of + " + folder.ToStringHyphenated(),
Helpers.LogLevel.Error);
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<WearableType, WearableData> preserve = new Dictionary<WearableType,WearableData>();
foreach (KeyValuePair<WearableType,WearableData> kvp in Wearables)
{
if (kvp.Value.Item.AssetType == AssetType.Bodypart)
preserve.Add(kvp.Key,kvp.Value);
}
Wearables = preserve;
foreach (InventoryWearable iw in iws)
{
WearableData wd = new WearableData();
wd.Item = iw;
Wearables[wd.Item.WearableType] = wd;
}
}
}
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 = LLUUID.Random();
attachmentsPacket.HeaderData.FirstDetachAll = true;
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 = 0;
attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask;
attachmentsPacket.ObjectData[i].GroupMask = (uint)attachment.Permissions.GroupMask;
attachmentsPacket.ObjectData[i].ItemFlags = attachment.Flags;
attachmentsPacket.ObjectData[i].ItemID = attachment.UUID;
attachmentsPacket.ObjectData[i].Name = Helpers.StringToField(attachment.Name);
attachmentsPacket.ObjectData[i].Description = Helpers.StringToField(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 = attachment.Flags;
attachmentsPacket.ObjectData[i].ItemID = attachment.UUID;
attachmentsPacket.ObjectData[i].Name = Helpers.StringToField(attachment.Name);
attachmentsPacket.ObjectData[i].Description = Helpers.StringToField(attachment.Description);
attachmentsPacket.ObjectData[i].NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask;
attachmentsPacket.ObjectData[i].OwnerID = attachment.OwnerID;
}
else
{
Client.Log("Cannot attach inventory item of type " + attachments[i].GetType().ToString(),
Helpers.LogLevel.Warning);
}
}
Client.Network.SendPacket(attachmentsPacket);
}
public void Attach(InventoryItem item, ObjectManager.AttachmentPoint attachPoint)
{
Attach(item.UUID, item.OwnerID, item.Name, item.Description, item.Permissions, item.Flags,
attachPoint);
}
public void Attach(LLUUID itemID, LLUUID ownerID, string name, string description,
Permissions perms, uint itemFlags, ObjectManager.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 = Helpers.StringToField(description);
attach.ObjectData.EveryoneMask = (uint)perms.EveryoneMask;
attach.ObjectData.GroupMask = (uint)perms.GroupMask;
attach.ObjectData.ItemFlags = itemFlags;
attach.ObjectData.ItemID = itemID;
attach.ObjectData.Name = Helpers.StringToField(name);
attach.ObjectData.NextOwnerMask = (uint)perms.NextOwnerMask;
attach.ObjectData.OwnerID = ownerID;
Client.Network.SendPacket(attach);
}
public void Detach(InventoryItem item)
{
Detach(item.UUID);
}
public void Detach(LLUUID 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] = LLUUID.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;
Client.DebugLog("CachedResponseEvent completed");
#region Send Appearance
LLObject.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++)
{
LLObject.TextureEntryFace face = avatar.Textures.FaceTextures[i];
if (face == null)
{
// If the texture is LLUUID.Zero the face should be null
if (AgentTextures[i] != LLUUID.Zero)
{
match = false;
break;
}
}
else if (face.TextureID != AgentTextures[i])
{
match = false;
break;
}
}
if (!match)
Client.Log("TextureEntry mismatch after updating our appearance", Helpers.LogLevel.Warning);
te = avatar.Textures;
UpdateEvent.Set();
}
else
{
Client.Log("Received an update for our avatar with a null FaceTextures array",
Helpers.LogLevel.Warning);
}
}
};
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) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
else
{
Client.Log("Timed out waiting for our appearance to update on the simulator", Helpers.LogLevel.Warning);
}
Client.Objects.OnNewAvatar -= updateCallback;
#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()
{
Client.DebugLog("RequestCachedBakes()");
List<KeyValuePair<int, LLUUID>> hashes = new List<KeyValuePair<int,LLUUID>>();
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[WearableType.Skirt].Asset.AssetID == LLUUID.Zero))
continue;
LLUUID hash = new LLUUID();
for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
{
WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
LLUUID assetID = GetWearableAsset(type);
// Build a hash of all the texture asset IDs in this baking layer
if (assetID != null) hash ^= assetID;
}
if (hash != LLUUID.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, LLUUID>(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;
Client.DebugLog("Checking cache for index " + cache.WearableData[i].TextureIndex +
", ID: " + cache.WearableData[i].ID);
}
// Increment our serial number for this packet
CacheCheckSerialNum++;
// Send it out
Client.Network.SendPacket(cache);
}
}
/// <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) Wearables.Clear();
for (int i = 0; i < update.WearableData.Length; i++)
{
if (update.WearableData[i].AssetID != LLUUID.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) Wearables[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[VisualParams.Params.Count];
lock (Wearables)
{
// Only for debugging output
int count = 0, vpIndex = 0;
// Build the visual param array
foreach (KeyValuePair<int,VisualParam> kvp in VisualParams.Params)
{
bool found = false;
set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
VisualParam vp = kvp.Value;
// Try and find this value in our collection of downloaded wearables
foreach (WearableData data in Wearables.Values)
{
if (data.Asset != null && data.Asset.Params.ContainsKey(vp.ParamID))
{
set.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(data.Asset.Params[vp.ParamID],
vp.MinValue, vp.MaxValue);
found = true;
count++;
break;
}
}
// Use a default value if we don't have one set for it
if (!found)
{
set.VisualParam[vpIndex].ParamValue = Helpers.FloatToByte(vp.DefaultValue,
vp.MinValue, vp.MaxValue);
}
vpIndex++;
}
Client.DebugLog("AgentSetAppearance contains " + count + " VisualParams");
// Build the texture entry for our agent
LLObject.TextureEntry te = new LLObject.TextureEntry(DEFAULT_AVATAR_TEXTURE);
// Put our AgentTextures array in to TextureEntry
lock (AgentTextures)
{
for (uint i = 0; i < AgentTextures.Length; i++)
{
if (AgentTextures[i] != LLUUID.Zero)
{
LLObject.TextureEntryFace face = te.CreateFace(i);
face.TextureID = AgentTextures[i];
}
}
}
foreach (WearableData data in Wearables.Values)
{
foreach (KeyValuePair<TextureIndex, LLUUID> texture in data.Asset.Textures)
{
LLObject.TextureEntryFace face = te.CreateFace((uint)texture.Key);
face.TextureID = texture.Value;
Client.DebugLog("Setting texture " + ((TextureIndex)texture.Key).ToString() + " to " +
texture.Value.ToStringHyphenated());
}
}
// Set the packet TextureEntry
set.ObjectData.TextureEntry = te.ToBytes();
}
// FIXME: Our hackish algorithm is making squished avatars. See
// http://www.libsecondlife.org/wiki/Agent_Size for discussion of the correct algorithm
float height = Helpers.ByteToFloat(set.VisualParam[33].ParamValue, VisualParams.Params[33].MinValue,
VisualParams.Params[33].MaxValue);
set.AgentData.Size = new LLVector3(0.45f, 0.6f, 1.50856f + ((height / 255.0f) * (2.025506f - 1.50856f)));
// 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++)
{
LLUUID hash = new LLUUID();
for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
{
WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
LLUUID assetID = GetWearableAsset(type);
// Build a hash of all the texture asset IDs in this baking layer
if (assetID != LLUUID.Zero) hash ^= assetID;
}
if (hash != LLUUID.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()
{
Client.DebugLog("SendAgentIsNowWearing()");
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[type].Item.UUID;
else
wearing.WearableData[i].ItemID = LLUUID.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()
{
foreach (KeyValuePair<WearableType, WearableData> kvp in Wearables)
{
Client.DebugLog("Requesting asset for wearable item" + kvp.Value.Item.Name + " (" + kvp.Value.Item.AssetUUID + ")");
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 CallOnAgentWearables()
{
if (OnAgentWearables != null)
{
// Refactor our internal Wearables dictionary in to something for the callback
Dictionary<WearableType, KeyValuePair<LLUUID, LLUUID>> wearables =
new Dictionary<WearableType, KeyValuePair<LLUUID, LLUUID>>();
lock (Wearables)
{
foreach (KeyValuePair<WearableType, WearableData> data in Wearables)
wearables.Add(data.Key, new KeyValuePair<LLUUID, LLUUID>(data.Value.Item.AssetUUID, data.Value.Item.UUID));
}
try { OnAgentWearables(wearables); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
private void UploadBake(Baker bake)
{
// Upload the completed layer data
LLUUID transactionID = Assets.RequestUpload(bake.BakedTexture, true, true, false);
Client.DebugLog(String.Format("Bake {0} completed. Uploading asset {1}", bake.BakeType,
bake.BakedTexture.AssetID.ToStringHyphenated()));
// Add it to a pending uploads list
lock (PendingUploads) PendingUploads.Add(bake.BakedTexture.AssetID, BakeTypeToAgentTextureIndex(bake.BakeType));
}
private int AddImageDownload(TextureIndex index)
{
LLUUID image = AgentTextures[(int)index];
if (image != LLUUID.Zero)
{
if (!ImageDownloads.ContainsKey(image))
{
Client.DebugLog("Downloading layer " + index.ToString());
ImageDownloads.Add(image, index);
}
return 1;
}
return 0;
}
#region Callbacks
private void AgentCachedTextureResponseHandler(Packet packet, Simulator simulator)
{
Client.DebugLog("AgentCachedTextureResponseHandler()");
AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet;
Dictionary<int, float> paramValues = new Dictionary<int, float>(VisualParams.Params.Count);
// Build a dictionary of appearance parameter indices and values from the wearables
foreach (KeyValuePair<int,VisualParam> kvp in VisualParams.Params)
{
bool found = false;
VisualParam vp = kvp.Value;
// Try and find this value in our collection of downloaded wearables
foreach (WearableData data in Wearables.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);
}
lock (AgentTextures)
{
foreach (AgentCachedTextureResponsePacket.WearableDataBlock block in response.WearableData)
{
// For each missing element we need to bake our own texture
Client.DebugLog("Cache response, index: " + block.TextureIndex + ", ID: " +
block.TextureID.ToStringHyphenated());
// FIXME: Use this. Right now we treat baked images on other sims as if they were missing
string host = Helpers.FieldToUTF8String(block.HostName);
if (host.Length > 0) Client.DebugLog("Cached bake exists on foreign host " + host);
BakeType bakeType = (BakeType)block.TextureIndex;
// Convert the baked index to an AgentTexture index
if (block.TextureID != LLUUID.Zero && host.Length == 0)
{
TextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
AgentTextures[(int)index] = block.TextureID;
}
else
{
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:
Client.Log("Unknown BakeType " + block.TextureIndex, Helpers.LogLevel.Warning);
break;
}
if (!PendingBakes.ContainsKey(bakeType))
{
Client.DebugLog("Initializing " + bakeType.ToString() + " bake with " + imageCount + " textures");
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)
PendingBakes.Add(bakeType, new Baker(Client, bakeType, imageCount, paramValues));
}
}
else if (!PendingBakes.ContainsKey(bakeType))
{
Client.Log("No cached bake for " + bakeType.ToString() + " and no textures for that " +
"layer, this is an unhandled case", Helpers.LogLevel.Error);
}
}
}
}
if (ImageDownloads.Count == 0)
{
// No pending downloads for baking, we're done
CachedResponseEvent.Set();
}
else
{
lock (ImageDownloads)
{
foreach (LLUUID image in ImageDownloads.Keys)
{
// Download all the images we need for baking
Assets.RequestImage(image, ImageType.Normal, 1013000.0f, 0);
}
}
}
}
private void Assets_OnAssetReceived(AssetDownload download, Asset asset)
{
lock (Wearables)
{
// Check if this is a wearable we were waiting on
foreach (KeyValuePair<WearableType,WearableData> kvp in Wearables)
{
if (kvp.Value.Item.AssetUUID == download.AssetID)
{
// Make sure the download succeeded
if (download.Success)
{
kvp.Value.Asset = (AssetWearable)asset;
kvp.Value.Asset.Decode();
Client.DebugLog("Downloaded wearable asset " + kvp.Value.Asset.Name);
lock (AgentTextures)
{
foreach (KeyValuePair<AppearanceManager.TextureIndex, LLUUID> texture in kvp.Value.Asset.Textures)
{
if (texture.Value != DEFAULT_AVATAR_TEXTURE) // this texture is not meant to be displayed
{
Client.DebugLog("Setting " + texture.Key + " to " + texture.Value);
AgentTextures[(int)texture.Key] = texture.Value;
}
}
}
}
else
{
Client.Log("Wearable " + kvp.Key + "(" + download.AssetID.ToStringHyphenated() + ") failed to download, " + download.Status.ToString(),Helpers.LogLevel.Warning);
}
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
WearablesDownloadedEvent.Set();
}
}
private void Assets_OnImageReceived(ImageDownload image, AssetTexture assetTexture)
{
lock (ImageDownloads)
{
if (ImageDownloads.ContainsKey(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;
Client.DebugLog("Finished downloading texture for " + index.ToString());
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;
if (PendingBakes.ContainsKey(type))
{
if (image.Success)
baked = PendingBakes[type].AddTexture(index, assetTexture);
else
{
Client.Log("Texture for " + index.ToString() + " failed to download, " +
"bake will be incomplete", Helpers.LogLevel.Warning);
baked = PendingBakes[type].MissingTexture(index);
}
}
if (baked)
{
UploadBake(PendingBakes[type]);
PendingBakes.Remove(type);
}
ImageDownloads.Remove(image.ID);
if (ImageDownloads.Count == 0 && PendingUploads.Count == 0)
{
// This is a failsafe catch, as the upload completed callback should normally
// be triggering the event
Client.DebugLog("No pending downloads or uploads detected in OnImageReceived");
CachedResponseEvent.Set();
}
else
{
Client.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
ImageDownloads.Count);
}
}
}
}
else
Client.Log("Received an image download callback for an image we did not request " + image.ID.ToStringHyphenated(), Helpers.LogLevel.Warning);
}
}
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;
Client.DebugLog("Upload complete, AgentTextures " + index.ToString() + " set to " +
upload.AssetID.ToStringHyphenated());
}
else
{
Client.Log("Asset upload " + upload.AssetID.ToStringHyphenated() + " failed",
Helpers.LogLevel.Warning);
}
PendingUploads.Remove(upload.AssetID);
Client.DebugLog("Pending uploads: " + PendingUploads.Count + ", pending downloads: " +
ImageDownloads.Count);
if (PendingUploads.Count == 0 && ImageDownloads.Count == 0)
{
Client.DebugLog("All pending image downloads and uploads complete");
CachedResponseEvent.Set();
}
}
else
{
// TEMP
Client.DebugLog("Upload " + upload.AssetID.ToStringHyphenated() + " was not found in PendingUploads");
}
}
}
/// <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
}
}