corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
4 *
5 * - Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Collections.Generic;
29 using System.Threading;
30 using System.Drawing;
31 using OpenMetaverse;
32 using OpenMetaverse.Packets;
33 using OpenMetaverse.Imaging;
34 using OpenMetaverse.Assets;
35 using OpenMetaverse.Http;
36 using OpenMetaverse.StructuredData;
37  
38 namespace OpenMetaverse
39 {
40 #region Enums
41  
42 /// <summary>
43 /// Index of TextureEntry slots for avatar appearances
44 /// </summary>
45 public enum AvatarTextureIndex
46 {
47 Unknown = -1,
48 HeadBodypaint = 0,
49 UpperShirt,
50 LowerPants,
51 EyesIris,
52 Hair,
53 UpperBodypaint,
54 LowerBodypaint,
55 LowerShoes,
56 HeadBaked,
57 UpperBaked,
58 LowerBaked,
59 EyesBaked,
60 LowerSocks,
61 UpperJacket,
62 LowerJacket,
63 UpperGloves,
64 UpperUndershirt,
65 LowerUnderpants,
66 Skirt,
67 SkirtBaked,
68 HairBaked,
69 LowerAlpha,
70 UpperAlpha,
71 HeadAlpha,
72 EyesAlpha,
73 HairAlpha,
74 HeadTattoo,
75 UpperTattoo,
76 LowerTattoo,
77 NumberOfEntries
78 }
79  
80 /// <summary>
81 /// Bake layers for avatar appearance
82 /// </summary>
83 public enum BakeType
84 {
85 Unknown = -1,
86 Head = 0,
87 UpperBody = 1,
88 LowerBody = 2,
89 Eyes = 3,
90 Skirt = 4,
91 Hair = 5
92 }
93  
94 /// <summary>
95 /// Appearance Flags, introdued with server side baking, currently unused
96 /// </summary>
97 [Flags]
98 public enum AppearanceFlags : uint
99 {
100 None = 0
101 }
102  
103  
104 #endregion Enums
105  
106 public class AppearanceManager
107 {
108 #region Constants
109 /// <summary>Mask for multiple attachments</summary>
110 public static readonly byte ATTACHMENT_ADD = 0x80;
111 /// <summary>Mapping between BakeType and AvatarTextureIndex</summary>
112 public static readonly byte[] BakeIndexToTextureIndex = new byte[BAKED_TEXTURE_COUNT] { 8, 9, 10, 11, 19, 20 };
113 /// <summary>Maximum number of concurrent downloads for wearable assets and textures</summary>
114 const int MAX_CONCURRENT_DOWNLOADS = 5;
115 /// <summary>Maximum number of concurrent uploads for baked textures</summary>
116 const int MAX_CONCURRENT_UPLOADS = 6;
117 /// <summary>Timeout for fetching inventory listings</summary>
118 const int INVENTORY_TIMEOUT = 1000 * 30;
119 /// <summary>Timeout for fetching a single wearable, or receiving a single packet response</summary>
120 const int WEARABLE_TIMEOUT = 1000 * 30;
121 /// <summary>Timeout for fetching a single texture</summary>
122 const int TEXTURE_TIMEOUT = 1000 * 120;
123 /// <summary>Timeout for uploading a single baked texture</summary>
124 const int UPLOAD_TIMEOUT = 1000 * 90;
125 /// <summary>Number of times to retry bake upload</summary>
126 const int UPLOAD_RETRIES = 2;
127 /// <summary>When changing outfit, kick off rebake after
128 /// 20 seconds has passed since the last change</summary>
129 const int REBAKE_DELAY = 1000 * 20;
130  
131 /// <summary>Total number of wearables for each avatar</summary>
132 public const int WEARABLE_COUNT = 16;
133 /// <summary>Total number of baked textures on each avatar</summary>
134 public const int BAKED_TEXTURE_COUNT = 6;
135 /// <summary>Total number of wearables per bake layer</summary>
136 public const int WEARABLES_PER_LAYER = 9;
137 /// <summary>Map of what wearables are included in each bake</summary>
138 public static readonly WearableType[][] WEARABLE_BAKE_MAP = new WearableType[][]
139 {
140 new WearableType[] { WearableType.Shape, WearableType.Skin, WearableType.Tattoo, WearableType.Hair, WearableType.Alpha, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid },
141 new WearableType[] { WearableType.Shape, WearableType.Skin, WearableType.Tattoo, WearableType.Shirt, WearableType.Jacket, WearableType.Gloves, WearableType.Undershirt, WearableType.Alpha, WearableType.Invalid },
142 new WearableType[] { WearableType.Shape, WearableType.Skin, WearableType.Tattoo, WearableType.Pants, WearableType.Shoes, WearableType.Socks, WearableType.Jacket, WearableType.Underpants, WearableType.Alpha },
143 new WearableType[] { WearableType.Eyes, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid },
144 new WearableType[] { WearableType.Skirt, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid },
145 new WearableType[] { WearableType.Hair, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid, WearableType.Invalid }
146 };
147 /// <summary>Magic values to finalize the cache check hashes for each
148 /// bake</summary>
149 public static readonly UUID[] BAKED_TEXTURE_HASH = new UUID[]
150 {
151 new UUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"),
152 new UUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"),
153 new UUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"),
154 new UUID("b2cf28af-b840-1071-3c6a-78085d8128b5"),
155 new UUID("ea800387-ea1a-14e0-56cb-24f2022f969a"),
156 new UUID("0af1ef7c-ad24-11dd-8790-001f5bf833e8")
157 };
158 /// <summary>Default avatar texture, used to detect when a custom
159 /// texture is not set for a face</summary>
160 public static readonly UUID DEFAULT_AVATAR_TEXTURE = new UUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97");
161  
162 #endregion Constants
163  
164 #region Structs / Classes
165  
166 /// <summary>
167 /// Contains information about a wearable inventory item
168 /// </summary>
169 public class WearableData
170 {
171 /// <summary>Inventory ItemID of the wearable</summary>
172 public UUID ItemID;
173 /// <summary>AssetID of the wearable asset</summary>
174 public UUID AssetID;
175 /// <summary>WearableType of the wearable</summary>
176 public WearableType WearableType;
177 /// <summary>AssetType of the wearable</summary>
178 public AssetType AssetType;
179 /// <summary>Asset data for the wearable</summary>
180 public AssetWearable Asset;
181  
182 public override string ToString()
183 {
184 return String.Format("ItemID: {0}, AssetID: {1}, WearableType: {2}, AssetType: {3}, Asset: {4}",
185 ItemID, AssetID, WearableType, AssetType, Asset != null ? Asset.Name : "(null)");
186 }
187 }
188  
189 /// <summary>
190 /// Data collected from visual params for each wearable
191 /// needed for the calculation of the color
192 /// </summary>
193 public struct ColorParamInfo
194 {
195 public VisualParam VisualParam;
196 public VisualColorParam VisualColorParam;
197 public float Value;
198 public WearableType WearableType;
199 }
200  
201 /// <summary>
202 /// Holds a texture assetID and the data needed to bake this layer into
203 /// an outfit texture. Used to keep track of currently worn textures
204 /// and baking data
205 /// </summary>
206 public struct TextureData
207 {
208 /// <summary>A texture AssetID</summary>
209 public UUID TextureID;
210 /// <summary>Asset data for the texture</summary>
211 public AssetTexture Texture;
212 /// <summary>Collection of alpha masks that needs applying</summary>
213 public Dictionary<VisualAlphaParam, float> AlphaMasks;
214 /// <summary>Tint that should be applied to the texture</summary>
215 public Color4 Color;
216 /// <summary>Where on avatar does this texture belong</summary>
217 public AvatarTextureIndex TextureIndex;
218  
219 public override string ToString()
220 {
221 return String.Format("TextureID: {0}, Texture: {1}",
222 TextureID, Texture != null ? Texture.AssetData.Length + " bytes" : "(null)");
223 }
224 }
225  
226 #endregion Structs / Classes
227  
228 #region Event delegates, Raise Events
229  
230 /// <summary>The event subscribers. null if no subcribers</summary>
231 private EventHandler<AgentWearablesReplyEventArgs> m_AgentWearablesReply;
232  
233 /// <summary>Raises the AgentWearablesReply event</summary>
234 /// <param name="e">An AgentWearablesReplyEventArgs object containing the
235 /// data returned from the data server</param>
236 protected virtual void OnAgentWearables(AgentWearablesReplyEventArgs e)
237 {
238 EventHandler<AgentWearablesReplyEventArgs> handler = m_AgentWearablesReply;
239 if (handler != null)
240 handler(this, e);
241 }
242  
243 /// <summary>Thread sync lock object</summary>
244 private readonly object m_AgentWearablesLock = new object();
245  
246 /// <summary>Triggered when an AgentWearablesUpdate packet is received,
247 /// telling us what our avatar is currently wearing
248 /// <see cref="RequestAgentWearables"/> request.</summary>
249 public event EventHandler<AgentWearablesReplyEventArgs> AgentWearablesReply
250 {
251 add { lock (m_AgentWearablesLock) { m_AgentWearablesReply += value; } }
252 remove { lock (m_AgentWearablesLock) { m_AgentWearablesReply -= value; } }
253 }
254  
255  
256 /// <summary>The event subscribers. null if no subcribers</summary>
257 private EventHandler<AgentCachedBakesReplyEventArgs> m_AgentCachedBakesReply;
258  
259 /// <summary>Raises the CachedBakesReply event</summary>
260 /// <param name="e">An AgentCachedBakesReplyEventArgs object containing the
261 /// data returned from the data server AgentCachedTextureResponse</param>
262 protected virtual void OnAgentCachedBakes(AgentCachedBakesReplyEventArgs e)
263 {
264 EventHandler<AgentCachedBakesReplyEventArgs> handler = m_AgentCachedBakesReply;
265 if (handler != null)
266 handler(this, e);
267 }
268  
269  
270 /// <summary>Thread sync lock object</summary>
271 private readonly object m_AgentCachedBakesLock = new object();
272  
273 /// <summary>Raised when an AgentCachedTextureResponse packet is
274 /// received, giving a list of cached bakes that were found on the
275 /// simulator
276 /// <seealso cref="RequestCachedBakes"/> request.</summary>
277 public event EventHandler<AgentCachedBakesReplyEventArgs> CachedBakesReply
278 {
279 add { lock (m_AgentCachedBakesLock) { m_AgentCachedBakesReply += value; } }
280 remove { lock (m_AgentCachedBakesLock) { m_AgentCachedBakesReply -= value; } }
281 }
282  
283 /// <summary>The event subscribers. null if no subcribers</summary>
284 private EventHandler<AppearanceSetEventArgs> m_AppearanceSet;
285  
286 /// <summary>Raises the AppearanceSet event</summary>
287 /// <param name="e">An AppearanceSetEventArgs object indicating if the operatin was successfull</param>
288 protected virtual void OnAppearanceSet(AppearanceSetEventArgs e)
289 {
290 EventHandler<AppearanceSetEventArgs> handler = m_AppearanceSet;
291 if (handler != null)
292 handler(this, e);
293 }
294  
295 /// <summary>Thread sync lock object</summary>
296 private readonly object m_AppearanceSetLock = new object();
297  
298 /// <summary>
299 /// Raised when appearance data is sent to the simulator, also indicates
300 /// the main appearance thread is finished.
301 /// </summary>
302 /// <seealso cref="RequestAgentSetAppearance"/> request.
303 public event EventHandler<AppearanceSetEventArgs> AppearanceSet
304 {
305 add { lock (m_AppearanceSetLock) { m_AppearanceSet += value; } }
306 remove { lock (m_AppearanceSetLock) { m_AppearanceSet -= value; } }
307 }
308  
309  
310 /// <summary>The event subscribers. null if no subcribers</summary>
311 private EventHandler<RebakeAvatarTexturesEventArgs> m_RebakeAvatarReply;
312  
313 /// <summary>Raises the RebakeAvatarRequested event</summary>
314 /// <param name="e">An RebakeAvatarTexturesEventArgs object containing the
315 /// data returned from the data server</param>
316 protected virtual void OnRebakeAvatar(RebakeAvatarTexturesEventArgs e)
317 {
318 EventHandler<RebakeAvatarTexturesEventArgs> handler = m_RebakeAvatarReply;
319 if (handler != null)
320 handler(this, e);
321 }
322  
323 /// <summary>Thread sync lock object</summary>
324 private readonly object m_RebakeAvatarLock = new object();
325  
326 /// <summary>
327 /// Triggered when the simulator requests the agent rebake its appearance.
328 /// </summary>
329 /// <seealso cref="RebakeAvatarRequest"/>
330 public event EventHandler<RebakeAvatarTexturesEventArgs> RebakeAvatarRequested
331 {
332 add { lock (m_RebakeAvatarLock) { m_RebakeAvatarReply += value; } }
333 remove { lock (m_RebakeAvatarLock) { m_RebakeAvatarReply -= value; } }
334 }
335  
336 #endregion
337  
338 #region Properties and public fields
339  
340 /// <summary>
341 /// Returns true if AppearanceManager is busy and trying to set or change appearance will fail
342 /// </summary>
343 public bool ManagerBusy
344 {
345 get
346 {
347 return AppearanceThreadRunning != 0;
348 }
349 }
350  
351 /// <summary>Visual parameters last sent to the sim</summary>
352 public byte[] MyVisualParameters = null;
353  
354 /// <summary>Textures about this client sent to the sim</summary>
355 public Primitive.TextureEntry MyTextures = null;
356  
357 #endregion Properties
358  
359 #region Private Members
360  
361 /// <summary>A cache of wearables currently being worn</summary>
362 private Dictionary<WearableType, WearableData> Wearables = new Dictionary<WearableType, WearableData>();
363 /// <summary>A cache of textures currently being worn</summary>
364 private TextureData[] Textures = new TextureData[(int)AvatarTextureIndex.NumberOfEntries];
365 /// <summary>Incrementing serial number for AgentCachedTexture packets</summary>
366 private int CacheCheckSerialNum = -1;
367 /// <summary>Incrementing serial number for AgentSetAppearance packets</summary>
368 private int SetAppearanceSerialNum = 0;
369 /// <summary>Indicates if WearablesRequest succeeded</summary>
370 private bool GotWearables = false;
371 /// <summary>Indicates whether or not the appearance thread is currently
372 /// running, to prevent multiple appearance threads from running
373 /// simultaneously</summary>
374 private int AppearanceThreadRunning = 0;
375 /// <summary>Reference to our agent</summary>
376 private GridClient Client;
377 /// <summary>
378 /// Timer used for delaying rebake on changing outfit
379 /// </summary>
380 private Timer RebakeScheduleTimer;
381 /// <summary>
382 /// Main appearance thread
383 /// </summary>
384 private Thread AppearanceThread;
385 /// <summary>
386 /// Is server baking complete. It needs doing only once
387 /// </summary>
388 private bool ServerBakingDone = false;
389 #endregion Private Members
390  
391 /// <summary>
392 /// Default constructor
393 /// </summary>
394 /// <param name="client">A reference to our agent</param>
395 public AppearanceManager(GridClient client)
396 {
397 Client = client;
398  
399 Client.Network.RegisterCallback(PacketType.AgentWearablesUpdate, AgentWearablesUpdateHandler);
400 Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, AgentCachedTextureResponseHandler);
401 Client.Network.RegisterCallback(PacketType.RebakeAvatarTextures, RebakeAvatarTexturesHandler);
402  
403 Client.Network.EventQueueRunning += Network_OnEventQueueRunning;
404 Client.Network.Disconnected += Network_OnDisconnected;
405 }
406  
407 #region Publics Methods
408  
409 /// <summary>
410 /// Obsolete method for setting appearance. This function no longer does anything.
411 /// Use RequestSetAppearance() to manually start the appearance thread
412 /// </summary>
413 [Obsolete("Appearance is now handled automatically")]
414 public void SetPreviousAppearance()
415 {
416 }
417  
418 /// <summary>
419 /// Obsolete method for setting appearance. This function no longer does anything.
420 /// Use RequestSetAppearance() to manually start the appearance thread
421 /// </summary>
422 /// <param name="allowBake">Unused parameter</param>
423 [Obsolete("Appearance is now handled automatically")]
424 public void SetPreviousAppearance(bool allowBake)
425 {
426 }
427  
428 /// <summary>
429 /// Starts the appearance setting thread
430 /// </summary>
431 public void RequestSetAppearance()
432 {
433 RequestSetAppearance(false);
434 }
435  
436 /// <summary>
437 /// Starts the appearance setting thread
438 /// </summary>
439 /// <param name="forceRebake">True to force rebaking, otherwise false</param>
440 public void RequestSetAppearance(bool forceRebake)
441 {
442 if (Interlocked.CompareExchange(ref AppearanceThreadRunning, 1, 0) != 0)
443 {
444 Logger.Log("Appearance thread is already running, skipping", Helpers.LogLevel.Warning);
445 return;
446 }
447  
448 // If we have an active delayed scheduled appearance bake, we dispose of it
449 if (RebakeScheduleTimer != null)
450 {
451 RebakeScheduleTimer.Dispose();
452 RebakeScheduleTimer = null;
453 }
454  
455 // This is the first time setting appearance, run through the entire sequence
456 AppearanceThread = new Thread(
457 delegate()
458 {
459 bool success = true;
460 try
461 {
462 if (forceRebake)
463 {
464 // Set all of the baked textures to UUID.Zero to force rebaking
465 for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
466 Textures[(int)BakeTypeToAgentTextureIndex((BakeType)bakedIndex)].TextureID = UUID.Zero;
467 }
468  
469 // Is this server side baking enabled sim
470 if (ServerBakingRegion())
471 {
472 if (!GotWearables)
473 {
474 // Fetch a list of the current agent wearables
475 if (GetAgentWearables())
476 {
477 GotWearables = true;
478 }
479 }
480  
481 if (!ServerBakingDone || forceRebake)
482 {
483 if (UpdateAvatarAppearance())
484 {
485 ServerBakingDone = true;
486 }
487 else
488 {
489 success = false;
490 }
491 }
492 }
493 else // Classic client side baking
494 {
495 if (!GotWearables)
496 {
497 // Fetch a list of the current agent wearables
498 if (!GetAgentWearables())
499 {
500 Logger.Log("Failed to retrieve a list of current agent wearables, appearance cannot be set",
501 Helpers.LogLevel.Error, Client);
502 throw new Exception("Failed to retrieve a list of current agent wearables, appearance cannot be set");
503 }
504 GotWearables = true;
505 }
506  
507 // If we get back to server side backing region re-request server bake
508 ServerBakingDone = false;
509  
510 // Download and parse all of the agent wearables
511 if (!DownloadWearables())
512 {
513 success = false;
514 Logger.Log("One or more agent wearables failed to download, appearance will be incomplete",
515 Helpers.LogLevel.Warning, Client);
516 }
517  
518 // If this is the first time setting appearance and we're not forcing rebakes, check the server
519 // for cached bakes
520 if (SetAppearanceSerialNum == 0 && !forceRebake)
521 {
522 // Compute hashes for each bake layer and compare against what the simulator currently has
523 if (!GetCachedBakes())
524 {
525 Logger.Log("Failed to get a list of cached bakes from the simulator, appearance will be rebaked",
526 Helpers.LogLevel.Warning, Client);
527 }
528 }
529  
530 // Download textures, compute bakes, and upload for any cache misses
531 if (!CreateBakes())
532 {
533 success = false;
534 Logger.Log("Failed to create or upload one or more bakes, appearance will be incomplete",
535 Helpers.LogLevel.Warning, Client);
536 }
537  
538 // Send the appearance packet
539 RequestAgentSetAppearance();
540 }
541 }
542 catch (Exception e)
543 {
544 Logger.Log(
545 string.Format("Failed to set appearance with exception {0}", e), Helpers.LogLevel.Warning, Client);
546  
547 success = false;
548 }
549 finally
550 {
551 AppearanceThreadRunning = 0;
552  
553 OnAppearanceSet(new AppearanceSetEventArgs(success));
554 }
555 }
556 );
557  
558 AppearanceThread.Name = "Appearance";
559 AppearanceThread.IsBackground = true;
560 AppearanceThread.Start();
561 }
562  
563 /// <summary>
564 /// Check if current region supports server side baking
565 /// </summary>
566 /// <returns>True if server side baking support is detected</returns>
567 public bool ServerBakingRegion()
568 {
569 return Client.Network.CurrentSim != null &&
570 ((Client.Network.CurrentSim.Protocols & RegionProtocols.AgentAppearanceService) != 0);
571 }
572  
573 /// <summary>
574 /// Ask the server what textures our agent is currently wearing
575 /// </summary>
576 public void RequestAgentWearables()
577 {
578 AgentWearablesRequestPacket request = new AgentWearablesRequestPacket();
579 request.AgentData.AgentID = Client.Self.AgentID;
580 request.AgentData.SessionID = Client.Self.SessionID;
581  
582 Client.Network.SendPacket(request);
583 }
584  
585 /// <summary>
586 /// Build hashes out of the texture assetIDs for each baking layer to
587 /// ask the simulator whether it has cached copies of each baked texture
588 /// </summary>
589 public void RequestCachedBakes()
590 {
591 List<AgentCachedTexturePacket.WearableDataBlock> hashes = new List<AgentCachedTexturePacket.WearableDataBlock>();
592  
593 // Build hashes for each of the bake layers from the individual components
594 lock (Wearables)
595 {
596 for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
597 {
598 // Don't do a cache request for a skirt bake if we're not wearing a skirt
599 if (bakedIndex == (int)BakeType.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
600 continue;
601  
602 // Build a hash of all the texture asset IDs in this baking layer
603 UUID hash = UUID.Zero;
604 for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
605 {
606 WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
607  
608 WearableData wearable;
609 if (type != WearableType.Invalid && Wearables.TryGetValue(type, out wearable))
610 hash ^= wearable.AssetID;
611 }
612  
613 if (hash != UUID.Zero)
614 {
615 // Hash with our secret value for this baked layer
616 hash ^= BAKED_TEXTURE_HASH[bakedIndex];
617  
618 // Add this to the list of hashes to send out
619 AgentCachedTexturePacket.WearableDataBlock block = new AgentCachedTexturePacket.WearableDataBlock();
620 block.ID = hash;
621 block.TextureIndex = (byte)bakedIndex;
622 hashes.Add(block);
623  
624 Logger.DebugLog("Checking cache for " + (BakeType)block.TextureIndex + ", hash=" + block.ID, Client);
625 }
626 }
627 }
628  
629 // Only send the packet out if there's something to check
630 if (hashes.Count > 0)
631 {
632 AgentCachedTexturePacket cache = new AgentCachedTexturePacket();
633 cache.AgentData.AgentID = Client.Self.AgentID;
634 cache.AgentData.SessionID = Client.Self.SessionID;
635 cache.AgentData.SerialNum = Interlocked.Increment(ref CacheCheckSerialNum);
636  
637 cache.WearableData = hashes.ToArray();
638  
639 Client.Network.SendPacket(cache);
640 }
641 }
642  
643 /// <summary>
644 /// Returns the AssetID of the asset that is currently being worn in a
645 /// given WearableType slot
646 /// </summary>
647 /// <param name="type">WearableType slot to get the AssetID for</param>
648 /// <returns>The UUID of the asset being worn in the given slot, or
649 /// UUID.Zero if no wearable is attached to the given slot or wearables
650 /// have not been downloaded yet</returns>
651 public UUID GetWearableAsset(WearableType type)
652 {
653 WearableData wearable;
654  
655 if (Wearables.TryGetValue(type, out wearable))
656 return wearable.AssetID;
657 else
658 return UUID.Zero;
659 }
660  
661 /// <summary>
662 /// Add a wearable to the current outfit and set appearance
663 /// </summary>
664 /// <param name="wearableItem">Wearable to be added to the outfit</param>
665 public void AddToOutfit(InventoryItem wearableItem)
666 {
667 List<InventoryItem> wearableItems = new List<InventoryItem> { wearableItem };
668 AddToOutfit(wearableItems);
669 }
670  
671 /// <summary>
672 /// Add a wearable to the current outfit and set appearance
673 /// </summary>
674 /// <param name="wearableItem">Wearable to be added to the outfit</param>
675 /// <param name="replace">Should existing item on the same point or of the same type be replaced</param>
676 public void AddToOutfit(InventoryItem wearableItem, bool replace)
677 {
678 List<InventoryItem> wearableItems = new List<InventoryItem> { wearableItem };
679 AddToOutfit(wearableItems, true);
680 }
681  
682 /// <summary>
683 /// Add a list of wearables to the current outfit and set appearance
684 /// </summary>
685 /// <param name="wearableItems">List of wearable inventory items to
686 /// be added to the outfit</param>
687 /// <param name="replace">Should existing item on the same point or of the same type be replaced</param>
688 public void AddToOutfit(List<InventoryItem> wearableItems)
689 {
690 AddToOutfit(wearableItems, true);
691 }
692  
693 /// <summary>
694 /// Add a list of wearables to the current outfit and set appearance
695 /// </summary>
696 /// <param name="wearableItems">List of wearable inventory items to
697 /// be added to the outfit</param>
698 /// <param name="replace">Should existing item on the same point or of the same type be replaced</param>
699 public void AddToOutfit(List<InventoryItem> wearableItems, bool replace)
700 {
701 List<InventoryWearable> wearables = new List<InventoryWearable>();
702 List<InventoryItem> attachments = new List<InventoryItem>();
703  
704 for (int i = 0; i < wearableItems.Count; i++)
705 {
706 InventoryItem item = wearableItems[i];
707  
708 if (item is InventoryWearable)
709 wearables.Add((InventoryWearable)item);
710 else if (item is InventoryAttachment || item is InventoryObject)
711 attachments.Add(item);
712 }
713  
714 lock (Wearables)
715 {
716 // Add the given wearables to the wearables collection
717 for (int i = 0; i < wearables.Count; i++)
718 {
719 InventoryWearable wearableItem = wearables[i];
720  
721 WearableData wd = new WearableData();
722 wd.AssetID = wearableItem.AssetUUID;
723 wd.AssetType = wearableItem.AssetType;
724 wd.ItemID = wearableItem.UUID;
725 wd.WearableType = wearableItem.WearableType;
726  
727 Wearables[wearableItem.WearableType] = wd;
728 }
729 }
730  
731 if (attachments.Count > 0)
732 {
733 AddAttachments(attachments, false, replace);
734 }
735  
736 if (wearables.Count > 0)
737 {
738 SendAgentIsNowWearing();
739 DelayedRequestSetAppearance();
740 }
741 }
742  
743 /// <summary>
744 /// Remove a wearable from the current outfit and set appearance
745 /// </summary>
746 /// <param name="wearableItem">Wearable to be removed from the outfit</param>
747 public void RemoveFromOutfit(InventoryItem wearableItem)
748 {
749 List<InventoryItem> wearableItems = new List<InventoryItem>();
750 wearableItems.Add(wearableItem);
751 RemoveFromOutfit(wearableItems);
752 }
753  
754  
755 /// <summary>
756 /// Removes a list of wearables from the current outfit and set appearance
757 /// </summary>
758 /// <param name="wearableItems">List of wearable inventory items to
759 /// be removed from the outfit</param>
760 public void RemoveFromOutfit(List<InventoryItem> wearableItems)
761 {
762 List<InventoryWearable> wearables = new List<InventoryWearable>();
763 List<InventoryItem> attachments = new List<InventoryItem>();
764  
765 for (int i = 0; i < wearableItems.Count; i++)
766 {
767 InventoryItem item = wearableItems[i];
768  
769 if (item is InventoryWearable)
770 wearables.Add((InventoryWearable)item);
771 else if (item is InventoryAttachment || item is InventoryObject)
772 attachments.Add(item);
773 }
774  
775 bool needSetAppearance = false;
776 lock (Wearables)
777 {
778 // Remove the given wearables from the wearables collection
779 for (int i = 0; i < wearables.Count; i++)
780 {
781 InventoryWearable wearableItem = wearables[i];
782 if (wearables[i].AssetType != AssetType.Bodypart // Remove if it's not a body part
783 && Wearables.ContainsKey(wearableItem.WearableType) // And we have that wearabe type
784 && Wearables[wearableItem.WearableType].ItemID == wearableItem.UUID // And we are wearing it
785 )
786 {
787 Wearables.Remove(wearableItem.WearableType);
788 needSetAppearance = true;
789 }
790 }
791 }
792  
793 for (int i = 0; i < attachments.Count; i++)
794 {
795 Detach(attachments[i].UUID);
796 }
797  
798 if (needSetAppearance)
799 {
800 SendAgentIsNowWearing();
801 DelayedRequestSetAppearance();
802 }
803 }
804  
805 /// <summary>
806 /// Replace the current outfit with a list of wearables and set appearance
807 /// </summary>
808 /// <param name="wearableItems">List of wearable inventory items that
809 /// define a new outfit</param>
810 public void ReplaceOutfit(List<InventoryItem> wearableItems)
811 {
812 ReplaceOutfit(wearableItems, true);
813 }
814  
815 /// <summary>
816 /// Replace the current outfit with a list of wearables and set appearance
817 /// </summary>
818 /// <param name="wearableItems">List of wearable inventory items that
819 /// define a new outfit</param>
820 /// <param name="safe">Check if we have all body parts, set this to false only
821 /// if you know what you're doing</param>
822 public void ReplaceOutfit(List<InventoryItem> wearableItems, bool safe)
823 {
824 List<InventoryWearable> wearables = new List<InventoryWearable>();
825 List<InventoryItem> attachments = new List<InventoryItem>();
826  
827 for (int i = 0; i < wearableItems.Count; i++)
828 {
829 InventoryItem item = wearableItems[i];
830  
831 if (item is InventoryWearable)
832 wearables.Add((InventoryWearable)item);
833 else if (item is InventoryAttachment || item is InventoryObject)
834 attachments.Add(item);
835 }
836  
837 if (safe)
838 {
839 // If we don't already have a the current agent wearables downloaded, updating to a
840 // new set of wearables that doesn't have all of the bodyparts can leave the avatar
841 // in an inconsistent state. If any bodypart entries are empty, we need to fetch the
842 // current wearables first
843 bool needsCurrentWearables = false;
844 lock (Wearables)
845 {
846 for (int i = 0; i < WEARABLE_COUNT; i++)
847 {
848 WearableType wearableType = (WearableType)i;
849 if (WearableTypeToAssetType(wearableType) == AssetType.Bodypart && !Wearables.ContainsKey(wearableType))
850 {
851 needsCurrentWearables = true;
852 break;
853 }
854 }
855 }
856  
857 if (needsCurrentWearables && !GetAgentWearables())
858 {
859 Logger.Log("Failed to fetch the current agent wearables, cannot safely replace outfit",
860 Helpers.LogLevel.Error);
861 return;
862 }
863 }
864  
865 // Replace our local Wearables collection, send the packet(s) to update our
866 // attachments, tell sim what we are wearing now, and start the baking process
867 if (!safe)
868 {
869 SetAppearanceSerialNum++;
870 }
871 ReplaceOutfit(wearables);
872 AddAttachments(attachments, true, false);
873 SendAgentIsNowWearing();
874 DelayedRequestSetAppearance();
875 }
876  
877 /// <summary>
878 /// Checks if an inventory item is currently being worn
879 /// </summary>
880 /// <param name="item">The inventory item to check against the agent
881 /// wearables</param>
882 /// <returns>The WearableType slot that the item is being worn in,
883 /// or WearbleType.Invalid if it is not currently being worn</returns>
884 public WearableType IsItemWorn(InventoryItem item)
885 {
886 lock (Wearables)
887 {
888 foreach (KeyValuePair<WearableType, WearableData> entry in Wearables)
889 {
890 if (entry.Value.ItemID == item.UUID)
891 return entry.Key;
892 }
893 }
894  
895 return WearableType.Invalid;
896 }
897  
898 /// <summary>
899 /// Returns a copy of the agents currently worn wearables
900 /// </summary>
901 /// <returns>A copy of the agents currently worn wearables</returns>
902 /// <remarks>Avoid calling this function multiple times as it will make
903 /// a copy of all of the wearable data each time</remarks>
904 public Dictionary<WearableType, WearableData> GetWearables()
905 {
906 lock (Wearables)
907 return new Dictionary<WearableType, WearableData>(Wearables);
908 }
909  
910 /// <summary>
911 /// Calls either <seealso cref="ReplaceOutfit"/> or
912 /// <seealso cref="AddToOutfit"/> depending on the value of
913 /// replaceItems
914 /// </summary>
915 /// <param name="wearables">List of wearable inventory items to add
916 /// to the outfit or become a new outfit</param>
917 /// <param name="replaceItems">True to replace existing items with the
918 /// new list of items, false to add these items to the existing outfit</param>
919 public void WearOutfit(List<InventoryBase> wearables, bool replaceItems)
920 {
921 List<InventoryItem> wearableItems = new List<InventoryItem>(wearables.Count);
922 for (int i = 0; i < wearables.Count; i++)
923 {
924 if (wearables[i] is InventoryItem)
925 wearableItems.Add((InventoryItem)wearables[i]);
926 }
927  
928 if (replaceItems)
929 ReplaceOutfit(wearableItems);
930 else
931 AddToOutfit(wearableItems);
932 }
933  
934 #endregion Publics Methods
935  
936 #region Attachments
937  
938 /// <summary>
939 /// Adds a list of attachments to our agent
940 /// </summary>
941 /// <param name="attachments">A List containing the attachments to add</param>
942 /// <param name="removeExistingFirst">If true, tells simulator to remove existing attachment
943 /// first</param>
944 public void AddAttachments(List<InventoryItem> attachments, bool removeExistingFirst)
945 {
946 AddAttachments(attachments, removeExistingFirst, true);
947 }
948  
949 /// <summary>
950 /// Adds a list of attachments to our agent
951 /// </summary>
952 /// <param name="attachments">A List containing the attachments to add</param>
953 /// <param name="removeExistingFirst">If true, tells simulator to remove existing attachment
954 /// <param name="replace">If true replace existing attachment on this attachment point, otherwise add to it (multi-attachments)</param>
955 /// first</param>
956 public void AddAttachments(List<InventoryItem> attachments, bool removeExistingFirst, bool replace)
957 {
958 // Use RezMultipleAttachmentsFromInv to clear out current attachments, and attach new ones
959 RezMultipleAttachmentsFromInvPacket attachmentsPacket = new RezMultipleAttachmentsFromInvPacket();
960 attachmentsPacket.AgentData.AgentID = Client.Self.AgentID;
961 attachmentsPacket.AgentData.SessionID = Client.Self.SessionID;
962  
963 attachmentsPacket.HeaderData.CompoundMsgID = UUID.Random();
964 attachmentsPacket.HeaderData.FirstDetachAll = removeExistingFirst;
965 attachmentsPacket.HeaderData.TotalObjects = (byte)attachments.Count;
966  
967 attachmentsPacket.ObjectData = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock[attachments.Count];
968 for (int i = 0; i < attachments.Count; i++)
969 {
970 if (attachments[i] is InventoryAttachment)
971 {
972 InventoryAttachment attachment = (InventoryAttachment)attachments[i];
973 attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock();
974 attachmentsPacket.ObjectData[i].AttachmentPt = replace ? (byte)attachment.AttachmentPoint : (byte)(ATTACHMENT_ADD | (byte)attachment.AttachmentPoint);
975 attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask;
976 attachmentsPacket.ObjectData[i].GroupMask = (uint)attachment.Permissions.GroupMask;
977 attachmentsPacket.ObjectData[i].ItemFlags = (uint)attachment.Flags;
978 attachmentsPacket.ObjectData[i].ItemID = attachment.UUID;
979 attachmentsPacket.ObjectData[i].Name = Utils.StringToBytes(attachment.Name);
980 attachmentsPacket.ObjectData[i].Description = Utils.StringToBytes(attachment.Description);
981 attachmentsPacket.ObjectData[i].NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask;
982 attachmentsPacket.ObjectData[i].OwnerID = attachment.OwnerID;
983 }
984 else if (attachments[i] is InventoryObject)
985 {
986 InventoryObject attachment = (InventoryObject)attachments[i];
987 attachmentsPacket.ObjectData[i] = new RezMultipleAttachmentsFromInvPacket.ObjectDataBlock();
988 attachmentsPacket.ObjectData[i].AttachmentPt = replace ? (byte)0 : ATTACHMENT_ADD;
989 attachmentsPacket.ObjectData[i].EveryoneMask = (uint)attachment.Permissions.EveryoneMask;
990 attachmentsPacket.ObjectData[i].GroupMask = (uint)attachment.Permissions.GroupMask;
991 attachmentsPacket.ObjectData[i].ItemFlags = (uint)attachment.Flags;
992 attachmentsPacket.ObjectData[i].ItemID = attachment.UUID;
993 attachmentsPacket.ObjectData[i].Name = Utils.StringToBytes(attachment.Name);
994 attachmentsPacket.ObjectData[i].Description = Utils.StringToBytes(attachment.Description);
995 attachmentsPacket.ObjectData[i].NextOwnerMask = (uint)attachment.Permissions.NextOwnerMask;
996 attachmentsPacket.ObjectData[i].OwnerID = attachment.OwnerID;
997 }
998 else
999 {
1000 Logger.Log("Cannot attach inventory item " + attachments[i].Name, Helpers.LogLevel.Warning, Client);
1001 }
1002 }
1003  
1004 Client.Network.SendPacket(attachmentsPacket);
1005 }
1006  
1007 /// <summary>
1008 /// Attach an item to our agent at a specific attach point
1009 /// </summary>
1010 /// <param name="item">A <seealso cref="OpenMetaverse.InventoryItem"/> to attach</param>
1011 /// <param name="attachPoint">the <seealso cref="OpenMetaverse.AttachmentPoint"/> on the avatar
1012 /// to attach the item to</param>
1013 public void Attach(InventoryItem item, AttachmentPoint attachPoint)
1014 {
1015 Attach(item, attachPoint, true);
1016 }
1017  
1018 /// <summary>
1019 /// Attach an item to our agent at a specific attach point
1020 /// </summary>
1021 /// <param name="item">A <seealso cref="OpenMetaverse.InventoryItem"/> to attach</param>
1022 /// <param name="attachPoint">the <seealso cref="OpenMetaverse.AttachmentPoint"/> on the avatar
1023 /// <param name="replace">If true replace existing attachment on this attachment point, otherwise add to it (multi-attachments)</param>
1024 /// to attach the item to</param>
1025 public void Attach(InventoryItem item, AttachmentPoint attachPoint, bool replace)
1026 {
1027 Attach(item.UUID, item.OwnerID, item.Name, item.Description, item.Permissions, item.Flags,
1028 attachPoint, replace);
1029 }
1030  
1031 /// <summary>
1032 /// Attach an item to our agent specifying attachment details
1033 /// </summary>
1034 /// <param name="itemID">The <seealso cref="OpenMetaverse.UUID"/> of the item to attach</param>
1035 /// <param name="ownerID">The <seealso cref="OpenMetaverse.UUID"/> attachments owner</param>
1036 /// <param name="name">The name of the attachment</param>
1037 /// <param name="description">The description of the attahment</param>
1038 /// <param name="perms">The <seealso cref="OpenMetaverse.Permissions"/> to apply when attached</param>
1039 /// <param name="itemFlags">The <seealso cref="OpenMetaverse.InventoryItemFlags"/> of the attachment</param>
1040 /// <param name="attachPoint">The <seealso cref="OpenMetaverse.AttachmentPoint"/> on the agent
1041 /// to attach the item to</param>
1042 public void Attach(UUID itemID, UUID ownerID, string name, string description,
1043 Permissions perms, uint itemFlags, AttachmentPoint attachPoint)
1044 {
1045 Attach(itemID, ownerID, name, description, perms, itemFlags, attachPoint, true);
1046 }
1047  
1048 /// <summary>
1049 /// Attach an item to our agent specifying attachment details
1050 /// </summary>
1051 /// <param name="itemID">The <seealso cref="OpenMetaverse.UUID"/> of the item to attach</param>
1052 /// <param name="ownerID">The <seealso cref="OpenMetaverse.UUID"/> attachments owner</param>
1053 /// <param name="name">The name of the attachment</param>
1054 /// <param name="description">The description of the attahment</param>
1055 /// <param name="perms">The <seealso cref="OpenMetaverse.Permissions"/> to apply when attached</param>
1056 /// <param name="itemFlags">The <seealso cref="OpenMetaverse.InventoryItemFlags"/> of the attachment</param>
1057 /// <param name="attachPoint">The <seealso cref="OpenMetaverse.AttachmentPoint"/> on the agent
1058 /// <param name="replace">If true replace existing attachment on this attachment point, otherwise add to it (multi-attachments)</param>
1059 /// to attach the item to</param>
1060 public void Attach(UUID itemID, UUID ownerID, string name, string description,
1061 Permissions perms, uint itemFlags, AttachmentPoint attachPoint, bool replace)
1062 {
1063 // TODO: At some point it might be beneficial to have AppearanceManager track what we
1064 // are currently wearing for attachments to make enumeration and detachment easier
1065 RezSingleAttachmentFromInvPacket attach = new RezSingleAttachmentFromInvPacket();
1066  
1067 attach.AgentData.AgentID = Client.Self.AgentID;
1068 attach.AgentData.SessionID = Client.Self.SessionID;
1069  
1070 attach.ObjectData.AttachmentPt = replace ? (byte)attachPoint : (byte)(ATTACHMENT_ADD | (byte)attachPoint);
1071 attach.ObjectData.Description = Utils.StringToBytes(description);
1072 attach.ObjectData.EveryoneMask = (uint)perms.EveryoneMask;
1073 attach.ObjectData.GroupMask = (uint)perms.GroupMask;
1074 attach.ObjectData.ItemFlags = itemFlags;
1075 attach.ObjectData.ItemID = itemID;
1076 attach.ObjectData.Name = Utils.StringToBytes(name);
1077 attach.ObjectData.NextOwnerMask = (uint)perms.NextOwnerMask;
1078 attach.ObjectData.OwnerID = ownerID;
1079  
1080 Client.Network.SendPacket(attach);
1081 }
1082  
1083 /// <summary>
1084 /// Detach an item from our agent using an <seealso cref="OpenMetaverse.InventoryItem"/> object
1085 /// </summary>
1086 /// <param name="item">An <seealso cref="OpenMetaverse.InventoryItem"/> object</param>
1087 public void Detach(InventoryItem item)
1088 {
1089 Detach(item.UUID);
1090 }
1091  
1092 /// <summary>
1093 /// Detach an item from our agent
1094 /// </summary>
1095 /// <param name="itemID">The inventory itemID of the item to detach</param>
1096 public void Detach(UUID itemID)
1097 {
1098 DetachAttachmentIntoInvPacket detach = new DetachAttachmentIntoInvPacket();
1099 detach.ObjectData.AgentID = Client.Self.AgentID;
1100 detach.ObjectData.ItemID = itemID;
1101  
1102 Client.Network.SendPacket(detach);
1103 }
1104  
1105 #endregion Attachments
1106  
1107 #region Appearance Helpers
1108  
1109 /// <summary>
1110 /// Inform the sim which wearables are part of our current outfit
1111 /// </summary>
1112 private void SendAgentIsNowWearing()
1113 {
1114 AgentIsNowWearingPacket wearing = new AgentIsNowWearingPacket();
1115 wearing.AgentData.AgentID = Client.Self.AgentID;
1116 wearing.AgentData.SessionID = Client.Self.SessionID;
1117 wearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[WEARABLE_COUNT];
1118  
1119 lock (Wearables)
1120 {
1121 for (int i = 0; i < WEARABLE_COUNT; i++)
1122 {
1123 WearableType type = (WearableType)i;
1124 wearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
1125 wearing.WearableData[i].WearableType = (byte)i;
1126  
1127 if (Wearables.ContainsKey(type))
1128 wearing.WearableData[i].ItemID = Wearables[type].ItemID;
1129 else
1130 wearing.WearableData[i].ItemID = UUID.Zero;
1131 }
1132 }
1133  
1134 Client.Network.SendPacket(wearing);
1135 }
1136  
1137 /// <summary>
1138 /// Replaces the Wearables collection with a list of new wearable items
1139 /// </summary>
1140 /// <param name="wearableItems">Wearable items to replace the Wearables collection with</param>
1141 private void ReplaceOutfit(List<InventoryWearable> wearableItems)
1142 {
1143 Dictionary<WearableType, WearableData> newWearables = new Dictionary<WearableType, WearableData>();
1144  
1145 lock (Wearables)
1146 {
1147 // Preserve body parts from the previous set of wearables. They may be overwritten,
1148 // but cannot be missing in the new set
1149 foreach (KeyValuePair<WearableType, WearableData> entry in Wearables)
1150 {
1151 if (entry.Value.AssetType == AssetType.Bodypart)
1152 newWearables[entry.Key] = entry.Value;
1153 }
1154  
1155 // Add the given wearables to the new wearables collection
1156 for (int i = 0; i < wearableItems.Count; i++)
1157 {
1158 InventoryWearable wearableItem = wearableItems[i];
1159  
1160 WearableData wd = new WearableData();
1161 wd.AssetID = wearableItem.AssetUUID;
1162 wd.AssetType = wearableItem.AssetType;
1163 wd.ItemID = wearableItem.UUID;
1164 wd.WearableType = wearableItem.WearableType;
1165  
1166 newWearables[wearableItem.WearableType] = wd;
1167 }
1168  
1169 // Replace the Wearables collection
1170 Wearables = newWearables;
1171 }
1172 }
1173  
1174 /// <summary>
1175 /// Calculates base color/tint for a specific wearable
1176 /// based on its params
1177 /// </summary>
1178 /// <param name="param">All the color info gathered from wearable's VisualParams
1179 /// passed as list of ColorParamInfo tuples</param>
1180 /// <returns>Base color/tint for the wearable</returns>
1181 public static Color4 GetColorFromParams(List<ColorParamInfo> param)
1182 {
1183 // Start off with a blank slate, black, fully transparent
1184 Color4 res = new Color4(0, 0, 0, 0);
1185  
1186 // Apply color modification from each color parameter
1187 foreach (ColorParamInfo p in param)
1188 {
1189 int n = p.VisualColorParam.Colors.Length;
1190  
1191 Color4 paramColor = new Color4(0, 0, 0, 0);
1192  
1193 if (n == 1)
1194 {
1195 // We got only one color in this param, use it for application
1196 // to the final color
1197 paramColor = p.VisualColorParam.Colors[0];
1198 }
1199 else if (n > 1)
1200 {
1201 // We have an array of colors in this parameter
1202 // First, we need to find out, based on param value
1203 // between which two elements of the array our value lands
1204  
1205 // Size of the step using which we iterate from Min to Max
1206 float step = (p.VisualParam.MaxValue - p.VisualParam.MinValue) / ((float)n - 1);
1207  
1208 // Our color should land inbetween colors in the array with index a and b
1209 int indexa = 0;
1210 int indexb = 0;
1211  
1212 int i = 0;
1213  
1214 for (float a = p.VisualParam.MinValue; a <= p.VisualParam.MaxValue; a += step)
1215 {
1216 if (a <= p.Value)
1217 {
1218 indexa = i;
1219 }
1220 else
1221 {
1222 break;
1223 }
1224  
1225 i++;
1226 }
1227  
1228 // Sanity check that we don't go outside bounds of the array
1229 if (indexa > n - 1)
1230 indexa = n - 1;
1231  
1232 indexb = (indexa == n - 1) ? indexa : indexa + 1;
1233  
1234 // How far is our value from Index A on the
1235 // line from Index A to Index B
1236 float distance = p.Value - (float)indexa * step;
1237  
1238 // We are at Index A (allowing for some floating point math fuzz),
1239 // use the color on that index
1240 if (distance < 0.00001f || indexa == indexb)
1241 {
1242 paramColor = p.VisualColorParam.Colors[indexa];
1243 }
1244 else
1245 {
1246 // Not so simple as being precisely on the index eh? No problem.
1247 // We take the two colors that our param value places us between
1248 // and then find the value for each ARGB element that is
1249 // somewhere on the line between color1 and color2 at some
1250 // distance from the first color
1251 Color4 c1 = paramColor = p.VisualColorParam.Colors[indexa];
1252 Color4 c2 = paramColor = p.VisualColorParam.Colors[indexb];
1253  
1254 // Distance is some fraction of the step, use that fraction
1255 // to find the value in the range from color1 to color2
1256 paramColor = Color4.Lerp(c1, c2, distance / step);
1257 }
1258  
1259 // Please leave this fragment even if its commented out
1260 // might prove useful should ($deity forbid) there be bugs in this code
1261 //string carray = "";
1262 //foreach (Color c in p.VisualColorParam.Colors)
1263 //{
1264 // carray += c.ToString() + " - ";
1265 //}
1266 //Logger.DebugLog("Calculating color for " + p.WearableType + " from " + p.VisualParam.Name + ", value is " + p.Value + " in range " + p.VisualParam.MinValue + " - " + p.VisualParam.MaxValue + " step " + step + " with " + n + " elements " + carray + " A: " + indexa + " B: " + indexb + " at distance " + distance);
1267 }
1268  
1269 // Now that we have calculated color from the scale of colors
1270 // that visual params provided, lets apply it to the result
1271 switch (p.VisualColorParam.Operation)
1272 {
1273 case VisualColorOperation.Add:
1274 res += paramColor;
1275 break;
1276 case VisualColorOperation.Multiply:
1277 res *= paramColor;
1278 break;
1279 case VisualColorOperation.Blend:
1280 res = Color4.Lerp(res, paramColor, p.Value);
1281 break;
1282 }
1283 }
1284  
1285 return res;
1286 }
1287  
1288 /// <summary>
1289 /// Blocking method to populate the Wearables dictionary
1290 /// </summary>
1291 /// <returns>True on success, otherwise false</returns>
1292 bool GetAgentWearables()
1293 {
1294 AutoResetEvent wearablesEvent = new AutoResetEvent(false);
1295 EventHandler<AgentWearablesReplyEventArgs> wearablesCallback = ((s, e) => wearablesEvent.Set());
1296  
1297 AgentWearablesReply += wearablesCallback;
1298  
1299 RequestAgentWearables();
1300  
1301 bool success = wearablesEvent.WaitOne(WEARABLE_TIMEOUT, false);
1302  
1303 AgentWearablesReply -= wearablesCallback;
1304  
1305 return success;
1306 }
1307  
1308 /// <summary>
1309 /// Blocking method to populate the Textures array with cached bakes
1310 /// </summary>
1311 /// <returns>True on success, otherwise false</returns>
1312 bool GetCachedBakes()
1313 {
1314 AutoResetEvent cacheCheckEvent = new AutoResetEvent(false);
1315 EventHandler<AgentCachedBakesReplyEventArgs> cacheCallback = (sender, e) => cacheCheckEvent.Set();
1316  
1317 CachedBakesReply += cacheCallback;
1318  
1319 RequestCachedBakes();
1320  
1321 bool success = cacheCheckEvent.WaitOne(WEARABLE_TIMEOUT, false);
1322  
1323 CachedBakesReply -= cacheCallback;
1324  
1325 return success;
1326 }
1327  
1328 /// <summary>
1329 /// Populates textures and visual params from a decoded asset
1330 /// </summary>
1331 /// <param name="wearable">Wearable to decode</param>
1332 /// <summary>
1333 /// Populates textures and visual params from a decoded asset
1334 /// </summary>
1335 /// <param name="wearable">Wearable to decode</param>
1336 public static void DecodeWearableParams(WearableData wearable, ref TextureData[] textures)
1337 {
1338 Dictionary<VisualAlphaParam, float> alphaMasks = new Dictionary<VisualAlphaParam, float>();
1339 List<ColorParamInfo> colorParams = new List<ColorParamInfo>();
1340  
1341 // Populate collection of alpha masks from visual params
1342 // also add color tinting information
1343 foreach (KeyValuePair<int, float> kvp in wearable.Asset.Params)
1344 {
1345 if (!VisualParams.Params.ContainsKey(kvp.Key)) continue;
1346  
1347 VisualParam p = VisualParams.Params[kvp.Key];
1348  
1349 ColorParamInfo colorInfo = new ColorParamInfo();
1350 colorInfo.WearableType = wearable.WearableType;
1351 colorInfo.VisualParam = p;
1352 colorInfo.Value = kvp.Value;
1353  
1354 // Color params
1355 if (p.ColorParams.HasValue)
1356 {
1357 colorInfo.VisualColorParam = p.ColorParams.Value;
1358  
1359 if (wearable.WearableType == WearableType.Tattoo)
1360 {
1361 if (kvp.Key == 1062 || kvp.Key == 1063 || kvp.Key == 1064)
1362 {
1363 colorParams.Add(colorInfo);
1364 }
1365 }
1366 else if (wearable.WearableType == WearableType.Jacket)
1367 {
1368 if (kvp.Key == 809 || kvp.Key == 810 || kvp.Key == 811)
1369 {
1370 colorParams.Add(colorInfo);
1371 }
1372 }
1373 else if (wearable.WearableType == WearableType.Hair)
1374 {
1375 // Param 112 - Rainbow
1376 // Param 113 - Red
1377 // Param 114 - Blonde
1378 // Param 115 - White
1379 if (kvp.Key == 112 || kvp.Key == 113 || kvp.Key == 114 || kvp.Key == 115)
1380 {
1381 colorParams.Add(colorInfo);
1382 }
1383 }
1384 else if (wearable.WearableType == WearableType.Skin)
1385 {
1386 // For skin we skip makeup params for now and use only the 3
1387 // that are used to determine base skin tone
1388 // Param 108 - Rainbow Color
1389 // Param 110 - Red Skin (Ruddiness)
1390 // Param 111 - Pigment
1391 if (kvp.Key == 108 || kvp.Key == 110 || kvp.Key == 111)
1392 {
1393 colorParams.Add(colorInfo);
1394 }
1395 }
1396 else
1397 {
1398 colorParams.Add(colorInfo);
1399 }
1400 }
1401  
1402 // Add alpha mask
1403 if (p.AlphaParams.HasValue && p.AlphaParams.Value.TGAFile != string.Empty && !p.IsBumpAttribute && !alphaMasks.ContainsKey(p.AlphaParams.Value))
1404 {
1405 alphaMasks.Add(p.AlphaParams.Value, kvp.Value == 0 ? 0.01f : kvp.Value);
1406 }
1407  
1408 // Alhpa masks can also be specified in sub "driver" params
1409 if (p.Drivers != null)
1410 {
1411 for (int i = 0; i < p.Drivers.Length; i++)
1412 {
1413 if (VisualParams.Params.ContainsKey(p.Drivers[i]))
1414 {
1415 VisualParam driver = VisualParams.Params[p.Drivers[i]];
1416 if (driver.AlphaParams.HasValue && driver.AlphaParams.Value.TGAFile != string.Empty && !driver.IsBumpAttribute && !alphaMasks.ContainsKey(driver.AlphaParams.Value))
1417 {
1418 alphaMasks.Add(driver.AlphaParams.Value, kvp.Value == 0 ? 0.01f : kvp.Value);
1419 }
1420 }
1421 }
1422 }
1423 }
1424  
1425 Color4 wearableColor = Color4.White; // Never actually used
1426 if (colorParams.Count > 0)
1427 {
1428 wearableColor = GetColorFromParams(colorParams);
1429 Logger.DebugLog("Setting tint " + wearableColor + " for " + wearable.WearableType);
1430 }
1431  
1432 // Loop through all of the texture IDs in this decoded asset and put them in our cache of worn textures
1433 foreach (KeyValuePair<AvatarTextureIndex, UUID> entry in wearable.Asset.Textures)
1434 {
1435 int i = (int)entry.Key;
1436  
1437 // Update information about color and alpha masks for this texture
1438 textures[i].AlphaMasks = alphaMasks;
1439 textures[i].Color = wearableColor;
1440  
1441 // If this texture changed, update the TextureID and clear out the old cached texture asset
1442 if (textures[i].TextureID != entry.Value)
1443 {
1444 // Treat DEFAULT_AVATAR_TEXTURE as null
1445 if (entry.Value != AppearanceManager.DEFAULT_AVATAR_TEXTURE)
1446 textures[i].TextureID = entry.Value;
1447 else
1448 textures[i].TextureID = UUID.Zero;
1449  
1450 textures[i].Texture = null;
1451 }
1452 }
1453 }
1454  
1455 /// <summary>
1456 /// Blocking method to download and parse currently worn wearable assets
1457 /// </summary>
1458 /// <returns>True on success, otherwise false</returns>
1459 private bool DownloadWearables()
1460 {
1461 bool success = true;
1462  
1463 // Make a copy of the wearables dictionary to enumerate over
1464 Dictionary<WearableType, WearableData> wearables;
1465 lock (Wearables)
1466 wearables = new Dictionary<WearableType, WearableData>(Wearables);
1467  
1468 // We will refresh the textures (zero out all non bake textures)
1469 for (int i = 0; i < Textures.Length; i++)
1470 {
1471 bool isBake = false;
1472 for (int j = 0; j < BakeIndexToTextureIndex.Length; j++)
1473 {
1474 if (BakeIndexToTextureIndex[j] == i)
1475 {
1476 isBake = true;
1477 break;
1478 }
1479 }
1480 if (!isBake)
1481 Textures[i] = new TextureData();
1482 }
1483  
1484 int pendingWearables = wearables.Count;
1485 foreach (WearableData wearable in wearables.Values)
1486 {
1487 if (wearable.Asset != null)
1488 {
1489 DecodeWearableParams(wearable, ref Textures);
1490 --pendingWearables;
1491 }
1492 }
1493  
1494 if (pendingWearables == 0)
1495 return true;
1496  
1497 Logger.DebugLog("Downloading " + pendingWearables + " wearable assets");
1498  
1499 Parallel.ForEach<WearableData>(Math.Min(pendingWearables, MAX_CONCURRENT_DOWNLOADS), wearables.Values,
1500 delegate(WearableData wearable)
1501 {
1502 if (wearable.Asset == null)
1503 {
1504 AutoResetEvent downloadEvent = new AutoResetEvent(false);
1505  
1506 // Fetch this wearable asset
1507 Client.Assets.RequestAsset(wearable.AssetID, wearable.AssetType, true,
1508 delegate(AssetDownload transfer, Asset asset)
1509 {
1510 if (transfer.Success && asset is AssetWearable)
1511 {
1512 // Update this wearable with the freshly downloaded asset
1513 wearable.Asset = (AssetWearable)asset;
1514  
1515 if (wearable.Asset.Decode())
1516 {
1517 DecodeWearableParams(wearable, ref Textures);
1518 Logger.DebugLog("Downloaded wearable asset " + wearable.WearableType + " with " + wearable.Asset.Params.Count +
1519 " visual params and " + wearable.Asset.Textures.Count + " textures", Client);
1520  
1521 }
1522 else
1523 {
1524 wearable.Asset = null;
1525 Logger.Log("Failed to decode asset:" + Environment.NewLine +
1526 Utils.BytesToString(asset.AssetData), Helpers.LogLevel.Error, Client);
1527 }
1528 }
1529 else
1530 {
1531 Logger.Log("Wearable " + wearable.AssetID + "(" + wearable.WearableType + ") failed to download, " +
1532 transfer.Status, Helpers.LogLevel.Warning, Client);
1533 }
1534  
1535 downloadEvent.Set();
1536 }
1537 );
1538  
1539 if (!downloadEvent.WaitOne(WEARABLE_TIMEOUT, false))
1540 {
1541 Logger.Log("Timed out downloading wearable asset " + wearable.AssetID + " (" + wearable.WearableType + ")",
1542 Helpers.LogLevel.Error, Client);
1543 success = false;
1544 }
1545  
1546 --pendingWearables;
1547 }
1548 }
1549 );
1550  
1551 return success;
1552 }
1553  
1554 /// <summary>
1555 /// Get a list of all of the textures that need to be downloaded for a
1556 /// single bake layer
1557 /// </summary>
1558 /// <param name="bakeType">Bake layer to get texture AssetIDs for</param>
1559 /// <returns>A list of texture AssetIDs to download</returns>
1560 private List<UUID> GetTextureDownloadList(BakeType bakeType)
1561 {
1562 List<AvatarTextureIndex> indices = BakeTypeToTextures(bakeType);
1563 List<UUID> textures = new List<UUID>();
1564  
1565 for (int i = 0; i < indices.Count; i++)
1566 {
1567 AvatarTextureIndex index = indices[i];
1568  
1569 if (index == AvatarTextureIndex.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
1570 continue;
1571  
1572 AddTextureDownload(index, textures);
1573 }
1574  
1575 return textures;
1576 }
1577  
1578 /// <summary>
1579 /// Helper method to lookup the TextureID for a single layer and add it
1580 /// to a list if it is not already present
1581 /// </summary>
1582 /// <param name="index"></param>
1583 /// <param name="textures"></param>
1584 private void AddTextureDownload(AvatarTextureIndex index, List<UUID> textures)
1585 {
1586 TextureData textureData = Textures[(int)index];
1587 // Add the textureID to the list if this layer has a valid textureID set, it has not already
1588 // been downloaded, and it is not already in the download list
1589 if (textureData.TextureID != UUID.Zero && textureData.Texture == null && !textures.Contains(textureData.TextureID))
1590 textures.Add(textureData.TextureID);
1591 }
1592  
1593 /// <summary>
1594 /// Blocking method to download all of the textures needed for baking
1595 /// the given bake layers
1596 /// </summary>
1597 /// <param name="bakeLayers">A list of layers that need baking</param>
1598 /// <remarks>No return value is given because the baking will happen
1599 /// whether or not all textures are successfully downloaded</remarks>
1600 private void DownloadTextures(List<BakeType> bakeLayers)
1601 {
1602 List<UUID> textureIDs = new List<UUID>();
1603  
1604 for (int i = 0; i < bakeLayers.Count; i++)
1605 {
1606 List<UUID> layerTextureIDs = GetTextureDownloadList(bakeLayers[i]);
1607  
1608 for (int j = 0; j < layerTextureIDs.Count; j++)
1609 {
1610 UUID uuid = layerTextureIDs[j];
1611 if (!textureIDs.Contains(uuid))
1612 textureIDs.Add(uuid);
1613 }
1614 }
1615  
1616 Logger.DebugLog("Downloading " + textureIDs.Count + " textures for baking");
1617  
1618 Parallel.ForEach<UUID>(MAX_CONCURRENT_DOWNLOADS, textureIDs,
1619 delegate(UUID textureID)
1620 {
1621 try
1622 {
1623 AutoResetEvent downloadEvent = new AutoResetEvent(false);
1624  
1625 Client.Assets.RequestImage(textureID,
1626 delegate(TextureRequestState state, AssetTexture assetTexture)
1627 {
1628 if (state == TextureRequestState.Finished)
1629 {
1630 assetTexture.Decode();
1631  
1632 for (int i = 0; i < Textures.Length; i++)
1633 {
1634 if (Textures[i].TextureID == textureID)
1635 Textures[i].Texture = assetTexture;
1636 }
1637 }
1638 else
1639 {
1640 Logger.Log("Texture " + textureID + " failed to download, one or more bakes will be incomplete",
1641 Helpers.LogLevel.Warning);
1642 }
1643  
1644 downloadEvent.Set();
1645 }
1646 );
1647  
1648 downloadEvent.WaitOne(TEXTURE_TIMEOUT, false);
1649 }
1650 catch (Exception e)
1651 {
1652 Logger.Log(
1653 string.Format("Download of texture {0} failed with exception {1}", textureID, e),
1654 Helpers.LogLevel.Warning, Client);
1655 }
1656 }
1657 );
1658 }
1659  
1660 /// <summary>
1661 /// Blocking method to create and upload baked textures for all of the
1662 /// missing bakes
1663 /// </summary>
1664 /// <returns>True on success, otherwise false</returns>
1665 private bool CreateBakes()
1666 {
1667 bool success = true;
1668 List<BakeType> pendingBakes = new List<BakeType>();
1669  
1670 // Check each bake layer in the Textures array for missing bakes
1671 for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
1672 {
1673 AvatarTextureIndex textureIndex = BakeTypeToAgentTextureIndex((BakeType)bakedIndex);
1674  
1675 if (Textures[(int)textureIndex].TextureID == UUID.Zero)
1676 {
1677 // If this is the skirt layer and we're not wearing a skirt then skip it
1678 if (bakedIndex == (int)BakeType.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
1679 continue;
1680  
1681 pendingBakes.Add((BakeType)bakedIndex);
1682 }
1683 }
1684  
1685 if (pendingBakes.Count > 0)
1686 {
1687 DownloadTextures(pendingBakes);
1688  
1689 Parallel.ForEach<BakeType>(Math.Min(MAX_CONCURRENT_UPLOADS, pendingBakes.Count), pendingBakes,
1690 delegate(BakeType bakeType)
1691 {
1692 if (!CreateBake(bakeType))
1693 success = false;
1694 }
1695 );
1696 }
1697  
1698 // Free up all the textures we're holding on to
1699 for (int i = 0; i < Textures.Length; i++)
1700 {
1701 Textures[i].Texture = null;
1702 }
1703  
1704 // We just allocated and freed a ridiculous amount of memory while
1705 // baking. Signal to the GC to clean up
1706 GC.Collect();
1707  
1708 return success;
1709 }
1710  
1711 /// <summary>
1712 /// Blocking method to create and upload a baked texture for a single
1713 /// bake layer
1714 /// </summary>
1715 /// <param name="bakeType">Layer to bake</param>
1716 /// <returns>True on success, otherwise false</returns>
1717 private bool CreateBake(BakeType bakeType)
1718 {
1719 List<AvatarTextureIndex> textureIndices = BakeTypeToTextures(bakeType);
1720 Baker oven = new Baker(bakeType);
1721  
1722 for (int i = 0; i < textureIndices.Count; i++)
1723 {
1724 AvatarTextureIndex textureIndex = textureIndices[i];
1725 TextureData texture = Textures[(int)textureIndex];
1726 texture.TextureIndex = textureIndex;
1727  
1728 oven.AddTexture(texture);
1729 }
1730  
1731 int start = Environment.TickCount;
1732 oven.Bake();
1733 Logger.DebugLog("Baking " + bakeType + " took " + (Environment.TickCount - start) + "ms");
1734  
1735 UUID newAssetID = UUID.Zero;
1736 int retries = UPLOAD_RETRIES;
1737  
1738 while (newAssetID == UUID.Zero && retries > 0)
1739 {
1740 newAssetID = UploadBake(oven.BakedTexture.AssetData);
1741 --retries;
1742 }
1743  
1744 Textures[(int)BakeTypeToAgentTextureIndex(bakeType)].TextureID = newAssetID;
1745  
1746 if (newAssetID == UUID.Zero)
1747 {
1748 Logger.Log("Failed uploading bake " + bakeType, Helpers.LogLevel.Warning);
1749 return false;
1750 }
1751  
1752 return true;
1753 }
1754  
1755 /// <summary>
1756 /// Blocking method to upload a baked texture
1757 /// </summary>
1758 /// <param name="textureData">Five channel JPEG2000 texture data to upload</param>
1759 /// <returns>UUID of the newly created asset on success, otherwise UUID.Zero</returns>
1760 private UUID UploadBake(byte[] textureData)
1761 {
1762 UUID bakeID = UUID.Zero;
1763 AutoResetEvent uploadEvent = new AutoResetEvent(false);
1764  
1765 Client.Assets.RequestUploadBakedTexture(textureData,
1766 delegate(UUID newAssetID)
1767 {
1768 bakeID = newAssetID;
1769 uploadEvent.Set();
1770 }
1771 );
1772  
1773 // FIXME: evalute the need for timeout here, RequestUploadBakedTexture() will
1774 // timout either on Client.Settings.TRANSFER_TIMEOUT or Client.Settings.CAPS_TIMEOUT
1775 // depending on which upload method is used.
1776 uploadEvent.WaitOne(UPLOAD_TIMEOUT, false);
1777  
1778 return bakeID;
1779 }
1780  
1781 /// <summary>
1782 /// Creates a dictionary of visual param values from the downloaded wearables
1783 /// </summary>
1784 /// <returns>A dictionary of visual param indices mapping to visual param
1785 /// values for our agent that can be fed to the Baker class</returns>
1786 private Dictionary<int, float> MakeParamValues()
1787 {
1788 Dictionary<int, float> paramValues = new Dictionary<int, float>(VisualParams.Params.Count);
1789  
1790 lock (Wearables)
1791 {
1792 foreach (KeyValuePair<int, VisualParam> kvp in VisualParams.Params)
1793 {
1794 // Only Group-0 parameters are sent in AgentSetAppearance packets
1795 if (kvp.Value.Group == 0)
1796 {
1797 bool found = false;
1798 VisualParam vp = kvp.Value;
1799  
1800 // Try and find this value in our collection of downloaded wearables
1801 foreach (WearableData data in Wearables.Values)
1802 {
1803 float paramValue;
1804 if (data.Asset != null && data.Asset.Params.TryGetValue(vp.ParamID, out paramValue))
1805 {
1806 paramValues.Add(vp.ParamID, paramValue);
1807 found = true;
1808 break;
1809 }
1810 }
1811  
1812 // Use a default value if we don't have one set for it
1813 if (!found) paramValues.Add(vp.ParamID, vp.DefaultValue);
1814 }
1815 }
1816 }
1817  
1818 return paramValues;
1819 }
1820  
1821 /// <summary>
1822 /// Initate server baking process
1823 /// </summary>
1824 /// <returns>True if the server baking was successful</returns>
1825 private bool UpdateAvatarAppearance()
1826 {
1827 Caps caps = Client.Network.CurrentSim.Caps;
1828 if (caps == null)
1829 {
1830 return false;
1831 }
1832  
1833 Uri url = caps.CapabilityURI("UpdateAvatarAppearance");
1834 if (url == null)
1835 {
1836 return false;
1837 }
1838  
1839 InventoryFolder COF = GetCOF();
1840 if (COF == null)
1841 {
1842 return false;
1843 }
1844 else
1845 {
1846 // TODO: create Current Outfit Folder
1847 }
1848  
1849 CapsClient capsRequest = new CapsClient(url);
1850 OSDMap request = new OSDMap(1);
1851 request["cof_version"] = COF.Version;
1852  
1853 string msg = "Setting server side baking failed";
1854  
1855 OSD res = capsRequest.GetResponse(request, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT * 2);
1856  
1857 if (res != null && res is OSDMap)
1858 {
1859 OSDMap result = (OSDMap)res;
1860 if (result["success"])
1861 {
1862 Logger.Log("Successfully set appearance", Helpers.LogLevel.Info, Client);
1863 // TODO: Set local visual params and baked textures based on the result here
1864 return true;
1865 }
1866 else
1867 {
1868 if (result.ContainsKey("error"))
1869 {
1870 msg += ": " + result["error"].AsString();
1871 }
1872 }
1873 }
1874  
1875 Logger.Log(msg, Helpers.LogLevel.Error, Client);
1876  
1877 return false;
1878 }
1879  
1880 /// <summary>
1881 /// Get the latest version of COF
1882 /// </summary>
1883 /// <returns>Current Outfit Folder (or null if getting the data failed)</returns>
1884 private InventoryFolder GetCOF()
1885 {
1886 List<InventoryBase> root = null;
1887 AutoResetEvent folderReceived = new AutoResetEvent(false);
1888  
1889 EventHandler<FolderUpdatedEventArgs> callback = (sender, e) =>
1890 {
1891 if (e.FolderID == Client.Inventory.Store.RootFolder.UUID)
1892 {
1893 if (e.Success)
1894 {
1895 root = Client.Inventory.Store.GetContents(Client.Inventory.Store.RootFolder.UUID);
1896 }
1897 folderReceived.Set();
1898 }
1899 };
1900  
1901 Client.Inventory.FolderUpdated += callback;
1902 Client.Inventory.RequestFolderContentsCap(Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, true, true, InventorySortOrder.ByDate);
1903 folderReceived.WaitOne(Client.Settings.CAPS_TIMEOUT);
1904 Client.Inventory.FolderUpdated -= callback;
1905  
1906 InventoryFolder COF = null;
1907  
1908 // COF should be in the root folder. Request update to get the latest versio number
1909 if (root != null)
1910 {
1911 foreach (InventoryBase baseItem in root)
1912 {
1913 if (baseItem is InventoryFolder && ((InventoryFolder)baseItem).PreferredType == AssetType.CurrentOutfitFolder)
1914 {
1915 COF = (InventoryFolder)baseItem;
1916 break;
1917 }
1918 }
1919 }
1920  
1921 return COF;
1922 }
1923  
1924 /// <summary>
1925 /// Create an AgentSetAppearance packet from Wearables data and the
1926 /// Textures array and send it
1927 /// </summary>
1928 private void RequestAgentSetAppearance()
1929 {
1930 AgentSetAppearancePacket set = MakeAppearancePacket();
1931 Client.Network.SendPacket(set);
1932 Logger.DebugLog("Send AgentSetAppearance packet");
1933 }
1934  
1935 public AgentSetAppearancePacket MakeAppearancePacket()
1936 {
1937 AgentSetAppearancePacket set = new AgentSetAppearancePacket();
1938 set.AgentData.AgentID = Client.Self.AgentID;
1939 set.AgentData.SessionID = Client.Self.SessionID;
1940 set.AgentData.SerialNum = (uint)Interlocked.Increment(ref SetAppearanceSerialNum);
1941  
1942 // Visual params used in the agent height calculation
1943 float agentSizeVPHeight = 0.0f;
1944 float agentSizeVPHeelHeight = 0.0f;
1945 float agentSizeVPPlatformHeight = 0.0f;
1946 float agentSizeVPHeadSize = 0.5f;
1947 float agentSizeVPLegLength = 0.0f;
1948 float agentSizeVPNeckLength = 0.0f;
1949 float agentSizeVPHipLength = 0.0f;
1950  
1951 lock (Wearables)
1952 {
1953 #region VisualParam
1954  
1955 int vpIndex = 0;
1956 int nrParams;
1957 bool wearingPhysics = false;
1958  
1959 foreach (WearableData wearable in Wearables.Values)
1960 {
1961 if (wearable.WearableType == WearableType.Physics)
1962 {
1963 wearingPhysics = true;
1964 break;
1965 }
1966 }
1967  
1968 if (wearingPhysics)
1969 {
1970 nrParams = 251;
1971 }
1972 else
1973 {
1974 nrParams = 218;
1975 }
1976  
1977 set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[nrParams];
1978  
1979 foreach (KeyValuePair<int, VisualParam> kvp in VisualParams.Params)
1980 {
1981 VisualParam vp = kvp.Value;
1982 float paramValue = 0f;
1983 bool found = false;
1984  
1985 // Try and find this value in our collection of downloaded wearables
1986 foreach (WearableData data in Wearables.Values)
1987 {
1988 if (data.Asset != null && data.Asset.Params.TryGetValue(vp.ParamID, out paramValue))
1989 {
1990 found = true;
1991 break;
1992 }
1993 }
1994  
1995 // Use a default value if we don't have one set for it
1996 if (!found)
1997 paramValue = vp.DefaultValue;
1998  
1999 // Only Group-0 parameters are sent in AgentSetAppearance packets
2000 if (kvp.Value.Group == 0)
2001 {
2002 set.VisualParam[vpIndex] = new AgentSetAppearancePacket.VisualParamBlock();
2003 set.VisualParam[vpIndex].ParamValue = Utils.FloatToByte(paramValue, vp.MinValue, vp.MaxValue);
2004 ++vpIndex;
2005 }
2006  
2007 // Check if this is one of the visual params used in the agent height calculation
2008 switch (vp.ParamID)
2009 {
2010 case 33:
2011 agentSizeVPHeight = paramValue;
2012 break;
2013 case 198:
2014 agentSizeVPHeelHeight = paramValue;
2015 break;
2016 case 503:
2017 agentSizeVPPlatformHeight = paramValue;
2018 break;
2019 case 682:
2020 agentSizeVPHeadSize = paramValue;
2021 break;
2022 case 692:
2023 agentSizeVPLegLength = paramValue;
2024 break;
2025 case 756:
2026 agentSizeVPNeckLength = paramValue;
2027 break;
2028 case 842:
2029 agentSizeVPHipLength = paramValue;
2030 break;
2031 }
2032  
2033 if (vpIndex == nrParams) break;
2034 }
2035  
2036 MyVisualParameters = new byte[set.VisualParam.Length];
2037 for (int i = 0; i < set.VisualParam.Length; i++)
2038 {
2039 MyVisualParameters[i] = set.VisualParam[i].ParamValue;
2040 }
2041  
2042 #endregion VisualParam
2043  
2044 #region TextureEntry
2045  
2046 Primitive.TextureEntry te = new Primitive.TextureEntry(DEFAULT_AVATAR_TEXTURE);
2047  
2048 for (uint i = 0; i < Textures.Length; i++)
2049 {
2050 if ((i == 0 || i == 5 || i == 6) && Client.Settings.CLIENT_IDENTIFICATION_TAG != UUID.Zero)
2051 {
2052 Primitive.TextureEntryFace face = te.CreateFace(i);
2053 face.TextureID = Client.Settings.CLIENT_IDENTIFICATION_TAG;
2054 Logger.DebugLog("Sending client identification tag: " + Client.Settings.CLIENT_IDENTIFICATION_TAG, Client);
2055 }
2056 else if (Textures[i].TextureID != UUID.Zero)
2057 {
2058 Primitive.TextureEntryFace face = te.CreateFace(i);
2059 face.TextureID = Textures[i].TextureID;
2060 Logger.DebugLog("Sending texture entry for " + (AvatarTextureIndex)i + " to " + Textures[i].TextureID, Client);
2061 }
2062 }
2063  
2064 set.ObjectData.TextureEntry = te.GetBytes();
2065 MyTextures = te;
2066  
2067 #endregion TextureEntry
2068  
2069 #region WearableData
2070  
2071 set.WearableData = new AgentSetAppearancePacket.WearableDataBlock[BAKED_TEXTURE_COUNT];
2072  
2073 // Build hashes for each of the bake layers from the individual components
2074 for (int bakedIndex = 0; bakedIndex < BAKED_TEXTURE_COUNT; bakedIndex++)
2075 {
2076 UUID hash = UUID.Zero;
2077  
2078 for (int wearableIndex = 0; wearableIndex < WEARABLES_PER_LAYER; wearableIndex++)
2079 {
2080 WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
2081  
2082 WearableData wearable;
2083 if (type != WearableType.Invalid && Wearables.TryGetValue(type, out wearable))
2084 hash ^= wearable.AssetID;
2085 }
2086  
2087 if (hash != UUID.Zero)
2088 {
2089 // Hash with our magic value for this baked layer
2090 hash ^= BAKED_TEXTURE_HASH[bakedIndex];
2091 }
2092  
2093 // Tell the server what cached texture assetID to use for each bake layer
2094 set.WearableData[bakedIndex] = new AgentSetAppearancePacket.WearableDataBlock();
2095 set.WearableData[bakedIndex].TextureIndex = BakeIndexToTextureIndex[bakedIndex];
2096 set.WearableData[bakedIndex].CacheID = hash;
2097 Logger.DebugLog("Sending TextureIndex " + (BakeType)bakedIndex + " with CacheID " + hash, Client);
2098 }
2099  
2100 #endregion WearableData
2101  
2102 #region Agent Size
2103  
2104 // Takes into account the Shoe Heel/Platform offsets but not the HeadSize offset. Seems to work.
2105 double agentSizeBase = 1.706;
2106  
2107 // The calculation for the HeadSize scalar may be incorrect, but it seems to work
2108 double agentHeight = agentSizeBase + (agentSizeVPLegLength * .1918) + (agentSizeVPHipLength * .0375) +
2109 (agentSizeVPHeight * .12022) + (agentSizeVPHeadSize * .01117) + (agentSizeVPNeckLength * .038) +
2110 (agentSizeVPHeelHeight * .08) + (agentSizeVPPlatformHeight * .07);
2111  
2112 set.AgentData.Size = new Vector3(0.45f, 0.6f, (float)agentHeight);
2113  
2114 #endregion Agent Size
2115  
2116 if (Client.Settings.AVATAR_TRACKING)
2117 {
2118 Avatar me;
2119 if (Client.Network.CurrentSim.ObjectsAvatars.TryGetValue(Client.Self.LocalID, out me))
2120 {
2121 me.Textures = MyTextures;
2122 me.VisualParameters = MyVisualParameters;
2123 }
2124 }
2125 }
2126 return set;
2127 }
2128  
2129 private void DelayedRequestSetAppearance()
2130 {
2131 if (RebakeScheduleTimer == null)
2132 {
2133 RebakeScheduleTimer = new Timer(RebakeScheduleTimerTick);
2134 }
2135 try { RebakeScheduleTimer.Change(REBAKE_DELAY, Timeout.Infinite); }
2136 catch { }
2137 }
2138  
2139 private void RebakeScheduleTimerTick(Object state)
2140 {
2141 RequestSetAppearance(true);
2142 }
2143 #endregion Appearance Helpers
2144  
2145 #region Inventory Helpers
2146  
2147 private bool GetFolderWearables(string[] folderPath, out List<InventoryWearable> wearables, out List<InventoryItem> attachments)
2148 {
2149 UUID folder = Client.Inventory.FindObjectByPath(
2150 Client.Inventory.Store.RootFolder.UUID, Client.Self.AgentID, String.Join("/", folderPath), INVENTORY_TIMEOUT);
2151  
2152 if (folder != UUID.Zero)
2153 {
2154 return GetFolderWearables(folder, out wearables, out attachments);
2155 }
2156 else
2157 {
2158 Logger.Log("Failed to resolve outfit folder path " + folderPath, Helpers.LogLevel.Error, Client);
2159 wearables = null;
2160 attachments = null;
2161 return false;
2162 }
2163 }
2164  
2165 private bool GetFolderWearables(UUID folder, out List<InventoryWearable> wearables, out List<InventoryItem> attachments)
2166 {
2167 wearables = new List<InventoryWearable>();
2168 attachments = new List<InventoryItem>();
2169 List<InventoryBase> objects = Client.Inventory.FolderContents(folder, Client.Self.AgentID, false, true,
2170 InventorySortOrder.ByName, INVENTORY_TIMEOUT);
2171  
2172 if (objects != null)
2173 {
2174 foreach (InventoryBase ib in objects)
2175 {
2176 if (ib is InventoryWearable)
2177 {
2178 Logger.DebugLog("Adding wearable " + ib.Name, Client);
2179 wearables.Add((InventoryWearable)ib);
2180 }
2181 else if (ib is InventoryAttachment)
2182 {
2183 Logger.DebugLog("Adding attachment (attachment) " + ib.Name, Client);
2184 attachments.Add((InventoryItem)ib);
2185 }
2186 else if (ib is InventoryObject)
2187 {
2188 Logger.DebugLog("Adding attachment (object) " + ib.Name, Client);
2189 attachments.Add((InventoryItem)ib);
2190 }
2191 else
2192 {
2193 Logger.DebugLog("Ignoring inventory item " + ib.Name, Client);
2194 }
2195 }
2196 }
2197 else
2198 {
2199 Logger.Log("Failed to download folder contents of + " + folder, Helpers.LogLevel.Error, Client);
2200 return false;
2201 }
2202  
2203 return true;
2204 }
2205  
2206 #endregion Inventory Helpers
2207  
2208 #region Callbacks
2209  
2210 protected void AgentWearablesUpdateHandler(object sender, PacketReceivedEventArgs e)
2211 {
2212 bool changed = false;
2213 AgentWearablesUpdatePacket update = (AgentWearablesUpdatePacket)e.Packet;
2214  
2215 lock (Wearables)
2216 {
2217 #region Test if anything changed in this update
2218  
2219 for (int i = 0; i < update.WearableData.Length; i++)
2220 {
2221 AgentWearablesUpdatePacket.WearableDataBlock block = update.WearableData[i];
2222  
2223 if (block.AssetID != UUID.Zero)
2224 {
2225 WearableData wearable;
2226 if (Wearables.TryGetValue((WearableType)block.WearableType, out wearable))
2227 {
2228 if (wearable.AssetID != block.AssetID || wearable.ItemID != block.ItemID)
2229 {
2230 // A different wearable is now set for this index
2231 changed = true;
2232 break;
2233 }
2234 }
2235 else
2236 {
2237 // A wearable is now set for this index
2238 changed = true;
2239 break;
2240 }
2241 }
2242 else if (Wearables.ContainsKey((WearableType)block.WearableType))
2243 {
2244 // This index is now empty
2245 changed = true;
2246 break;
2247 }
2248 }
2249  
2250 #endregion Test if anything changed in this update
2251  
2252 if (changed)
2253 {
2254 Logger.DebugLog("New wearables received in AgentWearablesUpdate");
2255 Wearables.Clear();
2256  
2257 for (int i = 0; i < update.WearableData.Length; i++)
2258 {
2259 AgentWearablesUpdatePacket.WearableDataBlock block = update.WearableData[i];
2260  
2261 if (block.AssetID != UUID.Zero)
2262 {
2263 WearableType type = (WearableType)block.WearableType;
2264  
2265 WearableData data = new WearableData();
2266 data.Asset = null;
2267 data.AssetID = block.AssetID;
2268 data.AssetType = WearableTypeToAssetType(type);
2269 data.ItemID = block.ItemID;
2270 data.WearableType = type;
2271  
2272 // Add this wearable to our collection
2273 Wearables[type] = data;
2274 }
2275 }
2276 }
2277 else
2278 {
2279 Logger.DebugLog("Duplicate AgentWearablesUpdate received, discarding");
2280 }
2281 }
2282  
2283 if (changed)
2284 {
2285 // Fire the callback
2286 OnAgentWearables(new AgentWearablesReplyEventArgs());
2287 }
2288 }
2289  
2290 protected void RebakeAvatarTexturesHandler(object sender, PacketReceivedEventArgs e)
2291 {
2292 RebakeAvatarTexturesPacket rebake = (RebakeAvatarTexturesPacket)e.Packet;
2293  
2294 // allow the library to do the rebake
2295 if (Client.Settings.SEND_AGENT_APPEARANCE)
2296 {
2297 RequestSetAppearance(true);
2298 }
2299  
2300 OnRebakeAvatar(new RebakeAvatarTexturesEventArgs(rebake.TextureData.TextureID));
2301 }
2302  
2303 protected void AgentCachedTextureResponseHandler(object sender, PacketReceivedEventArgs e)
2304 {
2305 AgentCachedTextureResponsePacket response = (AgentCachedTextureResponsePacket)e.Packet;
2306  
2307 for (int i = 0; i < response.WearableData.Length; i++)
2308 {
2309 AgentCachedTextureResponsePacket.WearableDataBlock block = response.WearableData[i];
2310 BakeType bakeType = (BakeType)block.TextureIndex;
2311 AvatarTextureIndex index = BakeTypeToAgentTextureIndex(bakeType);
2312  
2313 Logger.DebugLog("Cache response for " + bakeType + ", TextureID=" + block.TextureID, Client);
2314  
2315 if (block.TextureID != UUID.Zero)
2316 {
2317 // A simulator has a cache of this bake layer
2318  
2319 // FIXME: Use this. Right now we don't bother to check if this is a foreign host
2320 string host = Utils.BytesToString(block.HostName);
2321  
2322 Textures[(int)index].TextureID = block.TextureID;
2323 }
2324 else
2325 {
2326 // The server does not have a cache of this bake layer
2327 // FIXME:
2328 }
2329 }
2330  
2331 OnAgentCachedBakes(new AgentCachedBakesReplyEventArgs());
2332 }
2333  
2334 private void Network_OnEventQueueRunning(object sender, EventQueueRunningEventArgs e)
2335 {
2336 if (e.Simulator == Client.Network.CurrentSim && Client.Settings.SEND_AGENT_APPEARANCE)
2337 {
2338 // Update appearance each time we enter a new sim and capabilities have been retrieved
2339 Client.Appearance.RequestSetAppearance();
2340 }
2341 }
2342  
2343 private void Network_OnDisconnected(object sender, DisconnectedEventArgs e)
2344 {
2345 if (RebakeScheduleTimer != null)
2346 {
2347 RebakeScheduleTimer.Dispose();
2348 RebakeScheduleTimer = null;
2349 }
2350  
2351 if (AppearanceThread != null)
2352 {
2353 if (AppearanceThread.IsAlive)
2354 {
2355 AppearanceThread.Abort();
2356 }
2357 AppearanceThread = null;
2358 AppearanceThreadRunning = 0;
2359 }
2360 }
2361  
2362 #endregion Callbacks
2363  
2364 #region Static Helpers
2365  
2366 /// <summary>
2367 /// Converts a WearableType to a bodypart or clothing WearableType
2368 /// </summary>
2369 /// <param name="type">A WearableType</param>
2370 /// <returns>AssetType.Bodypart or AssetType.Clothing or AssetType.Unknown</returns>
2371 public static AssetType WearableTypeToAssetType(WearableType type)
2372 {
2373 switch (type)
2374 {
2375 case WearableType.Shape:
2376 case WearableType.Skin:
2377 case WearableType.Hair:
2378 case WearableType.Eyes:
2379 return AssetType.Bodypart;
2380 case WearableType.Shirt:
2381 case WearableType.Pants:
2382 case WearableType.Shoes:
2383 case WearableType.Socks:
2384 case WearableType.Jacket:
2385 case WearableType.Gloves:
2386 case WearableType.Undershirt:
2387 case WearableType.Underpants:
2388 case WearableType.Skirt:
2389 case WearableType.Tattoo:
2390 case WearableType.Alpha:
2391 case WearableType.Physics:
2392 return AssetType.Clothing;
2393 default:
2394 return AssetType.Unknown;
2395 }
2396 }
2397  
2398 /// <summary>
2399 /// Converts a BakeType to the corresponding baked texture slot in AvatarTextureIndex
2400 /// </summary>
2401 /// <param name="index">A BakeType</param>
2402 /// <returns>The AvatarTextureIndex slot that holds the given BakeType</returns>
2403 public static AvatarTextureIndex BakeTypeToAgentTextureIndex(BakeType index)
2404 {
2405 switch (index)
2406 {
2407 case BakeType.Head:
2408 return AvatarTextureIndex.HeadBaked;
2409 case BakeType.UpperBody:
2410 return AvatarTextureIndex.UpperBaked;
2411 case BakeType.LowerBody:
2412 return AvatarTextureIndex.LowerBaked;
2413 case BakeType.Eyes:
2414 return AvatarTextureIndex.EyesBaked;
2415 case BakeType.Skirt:
2416 return AvatarTextureIndex.SkirtBaked;
2417 case BakeType.Hair:
2418 return AvatarTextureIndex.HairBaked;
2419 default:
2420 return AvatarTextureIndex.Unknown;
2421 }
2422 }
2423  
2424 /// <summary>
2425 /// Gives the layer number that is used for morph mask
2426 /// </summary>
2427 /// <param name="bakeType">>A BakeType</param>
2428 /// <returns>Which layer number as defined in BakeTypeToTextures is used for morph mask</returns>
2429 public static AvatarTextureIndex MorphLayerForBakeType(BakeType bakeType)
2430 {
2431 // Indexes return here correspond to those returned
2432 // in BakeTypeToTextures(), those two need to be in sync.
2433 // Which wearable layer is used for morph is defined in avatar_lad.xml
2434 // by looking for <layer> that has <morph_mask> defined in it, and
2435 // looking up which wearable is defined in that layer. Morph mask
2436 // is never combined, it's always a straight copy of one single clothing
2437 // item's alpha channel per bake.
2438 switch (bakeType)
2439 {
2440 case BakeType.Head:
2441 return AvatarTextureIndex.Hair; // hair
2442 case BakeType.UpperBody:
2443 return AvatarTextureIndex.UpperShirt; // shirt
2444 case BakeType.LowerBody:
2445 return AvatarTextureIndex.LowerPants; // lower pants
2446 case BakeType.Skirt:
2447 return AvatarTextureIndex.Skirt; // skirt
2448 case BakeType.Hair:
2449 return AvatarTextureIndex.Hair; // hair
2450 default:
2451 return AvatarTextureIndex.Unknown;
2452 }
2453 }
2454  
2455 /// <summary>
2456 /// Converts a BakeType to a list of the texture slots that make up that bake
2457 /// </summary>
2458 /// <param name="bakeType">A BakeType</param>
2459 /// <returns>A list of texture slots that are inputs for the given bake</returns>
2460 public static List<AvatarTextureIndex> BakeTypeToTextures(BakeType bakeType)
2461 {
2462 List<AvatarTextureIndex> textures = new List<AvatarTextureIndex>();
2463  
2464 switch (bakeType)
2465 {
2466 case BakeType.Head:
2467 textures.Add(AvatarTextureIndex.HeadBodypaint);
2468 textures.Add(AvatarTextureIndex.HeadTattoo);
2469 textures.Add(AvatarTextureIndex.Hair);
2470 textures.Add(AvatarTextureIndex.HeadAlpha);
2471 break;
2472 case BakeType.UpperBody:
2473 textures.Add(AvatarTextureIndex.UpperBodypaint);
2474 textures.Add(AvatarTextureIndex.UpperTattoo);
2475 textures.Add(AvatarTextureIndex.UpperGloves);
2476 textures.Add(AvatarTextureIndex.UpperUndershirt);
2477 textures.Add(AvatarTextureIndex.UpperShirt);
2478 textures.Add(AvatarTextureIndex.UpperJacket);
2479 textures.Add(AvatarTextureIndex.UpperAlpha);
2480 break;
2481 case BakeType.LowerBody:
2482 textures.Add(AvatarTextureIndex.LowerBodypaint);
2483 textures.Add(AvatarTextureIndex.LowerTattoo);
2484 textures.Add(AvatarTextureIndex.LowerUnderpants);
2485 textures.Add(AvatarTextureIndex.LowerSocks);
2486 textures.Add(AvatarTextureIndex.LowerShoes);
2487 textures.Add(AvatarTextureIndex.LowerPants);
2488 textures.Add(AvatarTextureIndex.LowerJacket);
2489 textures.Add(AvatarTextureIndex.LowerAlpha);
2490 break;
2491 case BakeType.Eyes:
2492 textures.Add(AvatarTextureIndex.EyesIris);
2493 textures.Add(AvatarTextureIndex.EyesAlpha);
2494 break;
2495 case BakeType.Skirt:
2496 textures.Add(AvatarTextureIndex.Skirt);
2497 break;
2498 case BakeType.Hair:
2499 textures.Add(AvatarTextureIndex.Hair);
2500 textures.Add(AvatarTextureIndex.HairAlpha);
2501 break;
2502 }
2503  
2504 return textures;
2505 }
2506  
2507 #endregion Static Helpers
2508 }
2509  
2510 #region AppearanceManager EventArgs Classes
2511  
2512 /// <summary>Contains the Event data returned from the data server from an AgentWearablesRequest</summary>
2513 public class AgentWearablesReplyEventArgs : EventArgs
2514 {
2515 /// <summary>Construct a new instance of the AgentWearablesReplyEventArgs class</summary>
2516 public AgentWearablesReplyEventArgs()
2517 {
2518 }
2519 }
2520  
2521 /// <summary>Contains the Event data returned from the data server from an AgentCachedTextureResponse</summary>
2522 public class AgentCachedBakesReplyEventArgs : EventArgs
2523 {
2524 /// <summary>Construct a new instance of the AgentCachedBakesReplyEventArgs class</summary>
2525 public AgentCachedBakesReplyEventArgs()
2526 {
2527 }
2528 }
2529  
2530 /// <summary>Contains the Event data returned from an AppearanceSetRequest</summary>
2531 public class AppearanceSetEventArgs : EventArgs
2532 {
2533 private readonly bool m_success;
2534  
2535 /// <summary>Indicates whether appearance setting was successful</summary>
2536 public bool Success { get { return m_success; } }
2537 /// <summary>
2538 /// Triggered when appearance data is sent to the sim and
2539 /// the main appearance thread is done.</summary>
2540 /// <param name="success">Indicates whether appearance setting was successful</param>
2541 public AppearanceSetEventArgs(bool success)
2542 {
2543 this.m_success = success;
2544 }
2545 }
2546  
2547 /// <summary>Contains the Event data returned from the data server from an RebakeAvatarTextures</summary>
2548 public class RebakeAvatarTexturesEventArgs : EventArgs
2549 {
2550 private readonly UUID m_textureID;
2551  
2552 /// <summary>The ID of the Texture Layer to bake</summary>
2553 public UUID TextureID { get { return m_textureID; } }
2554  
2555 /// <summary>
2556 /// Triggered when the simulator sends a request for this agent to rebake
2557 /// its appearance
2558 /// </summary>
2559 /// <param name="textureID">The ID of the Texture Layer to bake</param>
2560 public RebakeAvatarTexturesEventArgs(UUID textureID)
2561 {
2562 this.m_textureID = textureID;
2563 }
2564  
2565 }
2566 #endregion
2567 }