/* * 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.Drawing; using System.Drawing.Imaging; using System.Collections.Generic; using System.Threading; using libsecondlife; using libsecondlife.Utilities.Assets; using libsecondlife.Packets; namespace libsecondlife.Utilities.Appearance { /// /// /// public class Wearable { /// /// /// public enum WearableType : byte { /// Shape = 0, /// Skin, /// Hair, /// Eyes, /// Shirt, /// Pants, /// Shoes, /// Socks, /// Jacket, /// Gloves, /// Undershirt, /// Underpants, /// Skirt, /// Invalid = 255 }; /// /// /// public enum ForSale { /// Not for sale Not = 0, /// The original is for sale Original = 1, /// Copies are for sale Copy = 2, /// The contents of the object are for sale Contents = 3 } public string Name = String.Empty; public string Description = String.Empty; public WearableType Type = WearableType.Shape; public ForSale Sale = ForSale.Not; public int SalePrice = 0; public LLUUID Creator = LLUUID.Zero; public LLUUID Owner = LLUUID.Zero; public LLUUID LastOwner = LLUUID.Zero; public LLUUID Group = LLUUID.Zero; public bool GroupOwned = false; public Helpers.PermissionType BasePermissions; public Helpers.PermissionType EveryonePermissions; public Helpers.PermissionType OwnerPermissions; public Helpers.PermissionType NextOwnerPermissions; public Helpers.PermissionType GroupPermissions; public Dictionary Params = new Dictionary(); public Dictionary Textures = new Dictionary(); private SecondLife Client; private string[] ForSaleNames = new string[] { "not", "orig", "copy", "cntn" }; /// /// Default constructor /// /// Reference to the SecondLife client public Wearable(SecondLife client) { Client = client; } public 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: return AssetType.Clothing; default: return AssetType.Unknown; } } public bool ImportAsset(string data) { int version = -1; int n = -1; try { n = data.IndexOf('\n'); version = Int32.Parse(data.Substring(19, n - 18)); data = data.Remove(0, n); if (version != 22) { Client.Log("Wearable asset has unrecognized version " + version, Helpers.LogLevel.Warning); return false; } n = data.IndexOf('\n'); Name = data.Substring(0, n); data = data.Remove(0, n); n = data.IndexOf('\n'); Description = data.Substring(0, n); data = data.Remove(0, n); // Split in to an upper and lower half string[] parts = data.Split(new string[] { "parameters" }, StringSplitOptions.None); parts[1] = "parameters" + parts[1]; // Parse the upper half string[] lines = parts[0].Split('\n'); foreach (string thisline in lines) { string line = thisline.Trim(); string[] fields = line.Split('\t'); if (fields.Length == 2) { if (fields[0] == "creator_mask") { // Deprecated, apply this as the base mask BasePermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); } else if (fields[0] == "base_mask") { BasePermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); } else if (fields[0] == "owner_mask") { OwnerPermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); } else if (fields[0] == "group_mask") { GroupPermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); } else if (fields[0] == "everyone_mask") { EveryonePermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); } else if (fields[0] == "next_owner_mask") { NextOwnerPermissions = (Helpers.PermissionType)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber); } else if (fields[0] == "creator_id") { Creator = new LLUUID(fields[1]); } else if (fields[0] == "owner_id") { Owner = new LLUUID(fields[1]); } else if (fields[0] == "last_owner_id") { LastOwner = new LLUUID(fields[1]); } else if (fields[0] == "group_id") { Group = new LLUUID(fields[1]); } else if (fields[0] == "group_owned") { GroupOwned = (Int32.Parse(fields[1]) != 0); } else if (fields[0] == "sale_type") { for (int i = 0; i < ForSaleNames.Length; i++) { if (fields[1] == ForSaleNames[i]) { Sale = (ForSale)i; break; } } } else if (fields[0] == "sale_price") { SalePrice = Int32.Parse(fields[1]); } else if (fields[0] == "perm_mask") { Client.Log("Wearable asset has deprecated perm_mask field, ignoring", Helpers.LogLevel.Warning); } } else if (line.StartsWith("type ")) { Type = (WearableType)Int32.Parse(line.Substring(5)); break; } } // Break up the lower half in to parameters and textures string[] lowerparts = parts[1].Split(new string[] { "textures" }, StringSplitOptions.None); lowerparts[1] = "textures" + lowerparts[1]; // Parse the parameters lines = lowerparts[0].Split('\n'); foreach (string line in lines) { string[] fields = line.Split(' '); // Use exception handling to deal with all the lines we aren't interested in try { int id = Int32.Parse(fields[0]); float weight = Single.Parse(fields[1]); Params[id] = weight; } catch (Exception) { } } // Parse the textures lines = lowerparts[1].Split('\n'); foreach (string line in lines) { string[] fields = line.Split(' '); // Use exception handling to deal with all the lines we aren't interested in try { int id = Int32.Parse(fields[0]); LLUUID texture = LLUUID.Parse(fields[1]); Textures[id] = texture; } catch (Exception) { } } return true; } catch (Exception e) { Client.Log("Failed to parse wearable asset: " + e.ToString(), Helpers.LogLevel.Warning); } return false; } public string ExportAsset() { StringBuilder data = new StringBuilder("LLWearable version 22\n"); data.Append(Name); data.Append("\n\n"); data.Append("\tpermissions 0\n\t{\n"); data.Append("\t\tbase_mask\t"); data.Append(Helpers.UIntToHexString((uint)BasePermissions)); data.Append("\n"); data.Append("\t\towner_mask\t"); data.Append(Helpers.UIntToHexString((uint)OwnerPermissions)); data.Append("\n"); data.Append("\t\tgroup_mask\t"); data.Append(Helpers.UIntToHexString((uint)GroupPermissions)); data.Append("\n"); data.Append("\t\teveryone_mask\t"); data.Append(Helpers.UIntToHexString((uint)EveryonePermissions)); data.Append("\n"); data.Append("\t\tnext_owner_mask\t"); data.Append(Helpers.UIntToHexString((uint)NextOwnerPermissions)); data.Append("\n"); data.Append("\t\tcreator_id\t"); data.Append(Creator.ToStringHyphenated()); data.Append("\n"); data.Append("\t\towner_id\t"); data.Append(Owner.ToStringHyphenated()); data.Append("\n"); data.Append("\t\tlast_owner_id\t"); data.Append(LastOwner.ToStringHyphenated()); data.Append("\n"); data.Append("\t\tgroup_id\t"); data.Append(Group.ToStringHyphenated()); data.Append("\n"); if (GroupOwned) data.Append("\t\tgroup_owned\t1\n"); data.Append("\t}\n"); data.Append("\tsale_info\t0\n"); data.Append("\t{\n"); data.Append("\t\tsale_type\t"); data.Append(ForSaleNames[(int)Sale]); data.Append("\n"); data.Append("\t\tsale_price\t"); data.Append(SalePrice); data.Append("\n"); data.Append("\t}\n"); data.Append("type "); data.Append((int)Type); data.Append("\n"); data.Append("parameters "); data.Append(Params.Count); data.Append("\n"); foreach (KeyValuePair param in Params) { data.Append(param.Key); data.Append(" "); data.Append(Helpers.FloatToTerseString(param.Value)); data.Append("\n"); } data.Append("textures "); data.Append(Textures.Count); data.Append("\n"); foreach (KeyValuePair texture in Textures) { data.Append(texture.Key); data.Append(" "); data.Append(texture.Value.ToStringHyphenated()); data.Append("\n"); } return data.ToString(); } } /// /// /// public struct WearableData { public Wearable Wearable; public LLUUID AssetID; public LLUUID ItemID; } /// /// /// public class AppearanceManager { /// /// /// public enum TextureIndex { Unknown = -1, HeadBodypaint = 0, UpperShirt, LowerPants, EyesIris, Hair, UpperBodypaint, LowerBodypaint, LowerShoes, HeadBaked, UpperBaked, LowerBaked, EyesBaked, LowerSocks, UpperJacket, LowerJacket, UpperUndershirt, LowerUnderpants, Skirt, SkirtBaked } /// /// /// public enum BakeType { Head = 0, UpperBody = 1, LowerBody = 2, Eyes = 3, Skirt = 4 } /// /// /// /// A mapping of WearableTypes to KeyValuePairs /// with Asset ID of the wearable as key and Item ID as value public delegate void AgentWearablesCallback(Dictionary> wearables); /// public event AgentWearablesCallback OnAgentWearables; /// Total number of wearables for each avatar public const int WEARABLE_COUNT = 13; /// public const int BAKED_TEXTURE_COUNT = 5; /// public const int WEARABLES_PER_LAYER = 7; /// public const int AVATAR_TEXTURE_COUNT = 20; /// Map of what wearables are included in each bake public static readonly Wearable.WearableType[][] WEARABLE_BAKE_MAP = new Wearable.WearableType[][] { new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Hair, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid }, new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Shirt, Wearable.WearableType.Jacket, Wearable.WearableType.Gloves, Wearable.WearableType.Undershirt, Wearable.WearableType.Invalid }, new Wearable.WearableType[] { Wearable.WearableType.Shape, Wearable.WearableType.Skin, Wearable.WearableType.Pants, Wearable.WearableType.Shoes, Wearable.WearableType.Socks, Wearable.WearableType.Jacket, Wearable.WearableType.Underpants }, new Wearable.WearableType[] { Wearable.WearableType.Eyes, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid }, new Wearable.WearableType[] { Wearable.WearableType.Skin, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid, Wearable.WearableType.Invalid } }; /// Secret values to finalize the cache check hashes for each /// bake 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") }; /// Default avatar texture, used to detect when a custom /// texture is not set for a face public static readonly LLUUID DEFAULT_AVATAR_TEXTURE = new LLUUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97"); private SecondLife Client; private AssetManager Assets; private Dictionary Wearables = new Dictionary(); // As wearable assets are downloaded and decoded, the textures are added to this list private Dictionary AgentTextures = new Dictionary(); // 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> DownloadQueue = new Queue>(); // A list of all the images we are currently downloading, prior to baking private Dictionary ImageDownloads = new Dictionary(); // A list of all the bakes we need to complete private Dictionary PendingBakes = new Dictionary(BAKED_TEXTURE_COUNT); // A list of all the uploads that are in progress private Dictionary PendingUploads = new Dictionary(BAKED_TEXTURE_COUNT); // Whether the handler for our current wearable list should automatically start downloading the assets private bool DownloadWearables = false; private int CacheCheckSerialNum = 0; private uint SetAppearanceSerialNum = 0; private ManualResetEvent WearablesDownloadedEvent = new ManualResetEvent(false); private ManualResetEvent CachedResponseEvent = new ManualResetEvent(false); // FIXME: Create a class-level appearance thread so multiple threads can't be launched /// /// Default constructor /// /// /// public AppearanceManager(SecondLife client, libsecondlife.Utilities.Assets.AssetManager assets) { Client = client; Assets = assets; Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesHandler)); Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, new NetworkManager.PacketCallback(AgentCachedTextureResponseHandler)); } /// /// If the appearance thread is running it is terminated here /// ~AppearanceManager() { WearablesDownloadedEvent.Set(); CachedResponseEvent.Set(); } /// /// Returns the assetID for a given WearableType /// /// /// public LLUUID GetWearableAsset(Wearable.WearableType type) { if (Wearables.ContainsKey(type)) return Wearables[type].AssetID; else return null; } /// /// /// /// public void SetPreviousAppearance() { // Clear out any previous data DownloadWearables = false; lock (Wearables) Wearables.Clear(); lock (AgentTextures) AgentTextures.Clear(); lock (DownloadQueue) DownloadQueue.Clear(); Thread appearanceThread = new Thread(new ThreadStart(StartSetPreviousAppearance)); appearanceThread.Start(); } /// /// Build hashes out of the texture assetIDs for each baking layer to /// ask the simulator whether it has cached copies of each baked layer /// public void RequestCachedBakes() { List> hashes = new List>(); AgentCachedTexturePacket cache = new AgentCachedTexturePacket(); cache.AgentData.AgentID = Client.Network.AgentID; cache.AgentData.SessionID = Client.Network.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(Wearable.WearableType.Skirt) || Wearables[Wearable.WearableType.Skirt].AssetID == LLUUID.Zero)) continue; LLUUID hash = new LLUUID(); for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++) { Wearable.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(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); } } /// /// Ask the server what textures our avatar is currently wearing /// public void RequestAgentWearables() { AgentWearablesRequestPacket request = new AgentWearablesRequestPacket(); request.AgentData.AgentID = Client.Network.AgentID; request.AgentData.SessionID = Client.Network.SessionID; Client.Network.SendPacket(request); } private void StartSetPreviousAppearance() { WearablesDownloadedEvent.Reset(); CachedResponseEvent.Reset(); DownloadWearables = true; // 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; // Ask the server what we are currently wearing RequestAgentWearables(); WearablesDownloadedEvent.WaitOne(); // Unregister the asset download callback Assets.OnAssetReceived -= assetCallback; Client.DebugLog("WearablesDownloadEvent completed"); // Now that we know what the avatar is wearing, we can check if anything needs to be rebaked RequestCachedBakes(); // Send a list of what we are currently wearing SendAgentWearables(); CachedResponseEvent.WaitOne(); // Unregister the image download and asset upload callbacks Assets.OnImageReceived -= imageCallback; Assets.OnAssetUploaded -= uploadCallback; Client.DebugLog("CachedResponseEvent completed"); // Send all of the visual params and textures for our agent SendAgentSetAppearance(); } private void SendAgentSetAppearance() { AgentSetAppearancePacket set = new AgentSetAppearancePacket(); set.AgentData.AgentID = Client.Network.AgentID; set.AgentData.SessionID = Client.Network.SessionID; set.AgentData.SerialNum = SetAppearanceSerialNum++; set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[VisualParams.Params.Length]; lock (Wearables) { // Only for debugging output int count = 0; // Build the visual param array for (int i = 0; i < VisualParams.Params.Length; i++) { bool found = false; set.VisualParam[i] = new AgentSetAppearancePacket.VisualParamBlock(); // Try and find this value in our collection of downloaded wearables foreach (WearableData data in Wearables.Values) { if (data.Wearable.Params.ContainsKey(i)) { set.VisualParam[i].ParamValue = Helpers.FloatToByte(data.Wearable.Params[i], VisualParams.Params[i].MinValue, VisualParams.Params[i].MaxValue); found = true; count++; break; } } // Use a default value if we don't have one set for it if (!found) { set.VisualParam[i].ParamValue = Helpers.FloatToByte(VisualParams.Params[i].DefaultValue, VisualParams.Params[i].MinValue, VisualParams.Params[i].MaxValue); } } Client.DebugLog("Sending " + count + " VisualParams"); // Build the texture entry for our agent LLObject.TextureEntry te = new LLObject.TextureEntry(DEFAULT_AVATAR_TEXTURE); // Put our AgentTextures dictionary in to TextureEntry lock (AgentTextures) { foreach (KeyValuePair texture in AgentTextures) { LLObject.TextureEntryFace face = te.CreateFace((uint)texture.Key); face.TextureID = texture.Value; } } foreach (WearableData data in Wearables.Values) { foreach (KeyValuePair texture in data.Wearable.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(); } // This is a bit hackish, but a whole lot better than implementing the actual algorithm float height = Helpers.ByteToFloat(set.VisualParam[25].ParamValue, VisualParams.Params[25].MinValue, VisualParams.Params[25].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++) { Wearable.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]; } // 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 SendAgentWearables() { Client.DebugLog("Wearables contains " + Wearables.Count + " entries"); AgentIsNowWearingPacket wearing = new AgentIsNowWearingPacket(); wearing.AgentData.AgentID = Client.Network.AgentID; wearing.AgentData.SessionID = Client.Network.SessionID; wearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[WEARABLE_COUNT]; for (int i = 0; i < WEARABLE_COUNT; i++) { Wearable.WearableType type = (Wearable.WearableType)i; wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock(); wearing.WearableData[i].WearableType = (byte)i; if (Wearables.ContainsKey(type)) wearing.WearableData[i].ItemID = Wearables[type].ItemID; else wearing.WearableData[i].ItemID = LLUUID.Zero; } Client.Network.SendPacket(wearing); } private TextureIndex BakedIndexToAgentTextureIndex(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 AgentWearablesHandler(Packet packet, Simulator simulator) { // Lock to prevent a race condition with multiple AgentWearables packets lock (WearablesDownloadedEvent) { 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) { Wearable.WearableType type = (Wearable.WearableType)update.WearableData[i].WearableType; WearableData data = new WearableData(); data.AssetID = update.WearableData[i].AssetID; data.ItemID = update.WearableData[i].ItemID; data.Wearable = new Wearable(Client); data.Wearable.Type = type; // Add this wearable to our collection lock (Wearables) Wearables[type] = data; // Convert WearableType to AssetType AssetType assetType = Wearable.WearableTypeToAssetType(type); Client.DebugLog("Downloading wearable " + type.ToString() + ": " + data.AssetID.ToStringHyphenated()); // Add this wearable asset to the download queue if (DownloadWearables) { KeyValuePair download = new KeyValuePair(data.AssetID, assetType); DownloadQueue.Enqueue(download); } } } if (DownloadQueue.Count > 0) { KeyValuePair download = DownloadQueue.Dequeue(); Assets.RequestAsset(download.Key, download.Value, true); } // Don't download wearables twice in a row DownloadWearables = false; } if (OnAgentWearables != null) { // Refactor our internal Wearables dictionary in to something for the callback Dictionary> wearables = new Dictionary>(); lock (Wearables) { foreach (KeyValuePair data in Wearables) wearables.Add(data.Key, new KeyValuePair(data.Value.AssetID, data.Value.ItemID)); } try { OnAgentWearables(wearables); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void AgentCachedTextureResponseHandler(Packet packet, Simulator simulator) { AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)packet; Dictionary paramValues = new Dictionary(VisualParams.Params.Length); // Build a dictionary of appearance parameter indices and values from the wearables for (int i = 0; i < VisualParams.Params.Length; i++) { bool found = false; // Try and find this value in our collection of downloaded wearables foreach (WearableData data in Wearables.Values) { if (data.Wearable.Params.ContainsKey(i)) { paramValues.Add(i,data.Wearable.Params[i]); found = true; break; } } // Use a default value if we don't have one set for it if (!found) paramValues.Add(i, VisualParams.Params[i].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); // Convert the baked index to an AgentTexture index if (block.TextureID != LLUUID.Zero && host.Length == 0) { TextureIndex index = BakedIndexToAgentTextureIndex((BakeType)block.TextureIndex); AgentTextures[index] = block.TextureID; } else { // Download all of the images in this layer. This is kind of hacky to be // hardcoded in but it is the most straightforward way to do what we need switch ((BakeType)block.TextureIndex) { case BakeType.Head: lock (ImageDownloads) { PendingBakes.Add(BakeType.Head, new BakeLayer(Client, 2, paramValues)); ImageDownloads.Add(AgentTextures[TextureIndex.HeadBodypaint], TextureIndex.HeadBodypaint); ImageDownloads.Add(AgentTextures[TextureIndex.Hair], TextureIndex.Hair); } break; case BakeType.UpperBody: lock (ImageDownloads) { PendingBakes.Add(BakeType.UpperBody, new BakeLayer(Client, 4, paramValues)); ImageDownloads.Add(AgentTextures[TextureIndex.UpperBodypaint], TextureIndex.UpperBodypaint); ImageDownloads.Add(AgentTextures[TextureIndex.UpperUndershirt], TextureIndex.UpperUndershirt); ImageDownloads.Add(AgentTextures[TextureIndex.UpperShirt], TextureIndex.UpperShirt); ImageDownloads.Add(AgentTextures[TextureIndex.UpperJacket], TextureIndex.UpperJacket); // TODO: Where are the gloves? } break; case BakeType.LowerBody: lock (ImageDownloads) { PendingBakes.Add(BakeType.LowerBody, new BakeLayer(Client, 6, paramValues)); ImageDownloads.Add(AgentTextures[TextureIndex.LowerBodypaint], TextureIndex.LowerBodypaint); ImageDownloads.Add(AgentTextures[TextureIndex.LowerUnderpants], TextureIndex.LowerUnderpants); ImageDownloads.Add(AgentTextures[TextureIndex.LowerSocks], TextureIndex.LowerSocks); ImageDownloads.Add(AgentTextures[TextureIndex.LowerShoes], TextureIndex.LowerShoes); ImageDownloads.Add(AgentTextures[TextureIndex.LowerPants], TextureIndex.LowerPants); ImageDownloads.Add(AgentTextures[TextureIndex.LowerJacket], TextureIndex.LowerJacket); } break; case BakeType.Eyes: lock (ImageDownloads) { PendingBakes.Add(BakeType.Eyes, new BakeLayer(Client, 1, paramValues)); ImageDownloads.Add(AgentTextures[TextureIndex.EyesIris], TextureIndex.EyesIris); } break; case BakeType.Skirt: if (Wearables.ContainsKey(Wearable.WearableType.Skirt)) { lock (ImageDownloads) { PendingBakes.Add(BakeType.Skirt, new BakeLayer(Client, 1, paramValues)); ImageDownloads.Add(AgentTextures[TextureIndex.Skirt], TextureIndex.Skirt); } } break; default: Client.Log("Unknown BakeType " + block.TextureIndex, Helpers.LogLevel.Warning); break; } } } } 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 asset) { lock (Wearables) { // Check if this is a wearable we were waiting on foreach (WearableData data in Wearables.Values) { if (data.AssetID == asset.AssetID) { // Make sure the download succeeded if (asset.Success) { // Convert the downloaded asset to a string string wearableData = Helpers.FieldToUTF8String(asset.AssetData); // Attempt to parse the wearable data if (data.Wearable.ImportAsset(wearableData)) { lock (AgentTextures) { foreach (KeyValuePair texture in data.Wearable.Textures) AgentTextures[(TextureIndex)texture.Key] = texture.Value; } Client.DebugLog("Imported wearable asset " + data.Wearable.Type.ToString()); } else { Client.Log("Failed to decode wearable asset " + asset.AssetID.ToStringHyphenated(), Helpers.LogLevel.Warning); } } else { Client.Log("Wearable " + data.Wearable.Type.ToString() + "(" + asset.AssetID.ToStringHyphenated() + ") failed to download, " + asset.Status.ToString(), Helpers.LogLevel.Warning); } break; } } } if (DownloadQueue.Count > 0) { // Dowload the next wearable in line KeyValuePair download = DownloadQueue.Dequeue(); Assets.RequestAsset(download.Key, download.Value, true); } else { // Everything is downloaded WearablesDownloadedEvent.Set(); } } private void Assets_OnImageReceived(ImageDownload image) { lock (ImageDownloads) { if (ImageDownloads.ContainsKey(image.ID)) { TextureIndex index = ImageDownloads[image.ID]; BakeType type = BakeType.Head; BakeLayer.BakeOrder order = BakeLayer.BakeOrder.HeadBodypaint; if (image.Success) { // Add this image to a baking layer switch (index) { case TextureIndex.HeadBodypaint: type = BakeType.Head; order = BakeLayer.BakeOrder.HeadBodypaint; break; case TextureIndex.Hair: type = BakeType.Head; order = BakeLayer.BakeOrder.Hair; break; case TextureIndex.UpperBodypaint: type = BakeType.UpperBody; order = BakeLayer.BakeOrder.UpperBodypaint; break; case TextureIndex.UpperUndershirt: type = BakeType.UpperBody; order = BakeLayer.BakeOrder.UpperUndershirt; break; case TextureIndex.UpperShirt: type = BakeType.UpperBody; order = BakeLayer.BakeOrder.UpperShirt; break; case TextureIndex.UpperJacket: type = BakeType.UpperBody; order = BakeLayer.BakeOrder.UpperJacket; break; case TextureIndex.LowerBodypaint: type = BakeType.LowerBody; order = BakeLayer.BakeOrder.LowerBodypaint; break; case TextureIndex.LowerUnderpants: type = BakeType.LowerBody; order = BakeLayer.BakeOrder.LowerUnderpants; break; case TextureIndex.LowerSocks: type = BakeType.LowerBody; order = BakeLayer.BakeOrder.LowerSocks; break; case TextureIndex.LowerShoes: type = BakeType.LowerBody; order = BakeLayer.BakeOrder.LowerShoes; break; case TextureIndex.LowerPants: type = BakeType.LowerBody; order = BakeLayer.BakeOrder.LowerPants; break; case TextureIndex.LowerJacket: type = BakeType.LowerBody; order = BakeLayer.BakeOrder.LowerJacket; break; case TextureIndex.EyesIris: type = BakeType.Eyes; order = BakeLayer.BakeOrder.EyesIris; break; case TextureIndex.Skirt: type = BakeType.Skirt; order = BakeLayer.BakeOrder.Skirt; break; default: Client.Log("Image downloaded for unknown TextureIndex " + index.ToString(), Helpers.LogLevel.Warning); break; } if (PendingBakes.ContainsKey(type) && PendingBakes[type].AddImage(order, image.AssetData)) { // Create a transactionID and assetID for this upload LLUUID transactionID = LLUUID.Random(); LLUUID assetID = transactionID.Combine(Client.Network.SecureSessionID); // Upload the completed bake data Assets.RequestUpload(transactionID, AssetType.Texture, PendingBakes[type].FinalData, true, true, false); // Add it to a pending uploads list lock (PendingUploads) PendingUploads.Add(assetID, index); // Remove this bake from the pending list PendingBakes.Remove(type); } } else { Client.Log("Texture " + image.ID.ToStringHyphenated() + " failed to download, " + "bake will be incomplete", Helpers.LogLevel.Warning); } 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 CachedResponseEvent.Set(); } } } } private void Assets_OnAssetUploaded(AssetUpload upload) { lock (PendingUploads) { if (PendingUploads.ContainsKey(upload.AssetID)) { if (upload.Success) { // FIXME: Setup the TextureEntry with the new baked upload } else { Client.Log("Asset upload " + upload.AssetID.ToStringHyphenated() + " failed", Helpers.LogLevel.Warning); } PendingUploads.Remove(upload.AssetID); if (PendingUploads.Count == 0 && ImageDownloads.Count == 0) { CachedResponseEvent.Set(); } } else { // TEMP Client.DebugLog("Upload " + upload.AssetID.ToStringHyphenated() + " was not found in PendingUploads"); } } } } }