corrade-vassal – Blame information for rev 1
?pathlinks?
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 | } |