opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
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 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the OpenSimulator Project nor the |
||
13 | * names of its contributors may be used to endorse or promote products |
||
14 | * derived from this software without specific prior written permission. |
||
15 | * |
||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
26 | */ |
||
27 | |||
28 | using System; |
||
29 | using System.Collections.Generic; |
||
30 | using System.Reflection; |
||
31 | using System.Threading; |
||
32 | using System.Text; |
||
33 | using System.Timers; |
||
34 | using log4net; |
||
35 | using Nini.Config; |
||
36 | using OpenMetaverse; |
||
37 | using OpenSim.Framework; |
||
38 | using OpenSim.Region.Framework.Interfaces; |
||
39 | using OpenSim.Region.Framework.Scenes; |
||
40 | using OpenSim.Services.Interfaces; |
||
41 | |||
42 | using Mono.Addins; |
||
43 | using PermissionMask = OpenSim.Framework.PermissionMask; |
||
44 | |||
45 | namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory |
||
46 | { |
||
47 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AvatarFactoryModule")] |
||
48 | public class AvatarFactoryModule : IAvatarFactoryModule, INonSharedRegionModule |
||
49 | { |
||
50 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
51 | |||
52 | public const string BAKED_TEXTURES_REPORT_FORMAT = "{0,-9} {1}"; |
||
53 | |||
54 | private Scene m_scene = null; |
||
55 | |||
56 | private int m_savetime = 5; // seconds to wait before saving changed appearance |
||
57 | private int m_sendtime = 2; // seconds to wait before sending changed appearance |
||
58 | private bool m_reusetextures = false; |
||
59 | |||
60 | private int m_checkTime = 500; // milliseconds to wait between checks for appearance updates |
||
61 | private System.Timers.Timer m_updateTimer = new System.Timers.Timer(); |
||
62 | private Dictionary<UUID,long> m_savequeue = new Dictionary<UUID,long>(); |
||
63 | private Dictionary<UUID,long> m_sendqueue = new Dictionary<UUID,long>(); |
||
64 | |||
65 | private object m_setAppearanceLock = new object(); |
||
66 | |||
67 | #region Region Module interface |
||
68 | |||
69 | public void Initialise(IConfigSource config) |
||
70 | { |
||
71 | |||
72 | IConfig appearanceConfig = config.Configs["Appearance"]; |
||
73 | if (appearanceConfig != null) |
||
74 | { |
||
75 | m_savetime = Convert.ToInt32(appearanceConfig.GetString("DelayBeforeAppearanceSave",Convert.ToString(m_savetime))); |
||
76 | m_sendtime = Convert.ToInt32(appearanceConfig.GetString("DelayBeforeAppearanceSend",Convert.ToString(m_sendtime))); |
||
77 | m_reusetextures = appearanceConfig.GetBoolean("ReuseTextures",m_reusetextures); |
||
78 | |||
79 | // m_log.InfoFormat("[AVFACTORY] configured for {0} save and {1} send",m_savetime,m_sendtime); |
||
80 | } |
||
81 | |||
82 | } |
||
83 | |||
84 | public void AddRegion(Scene scene) |
||
85 | { |
||
86 | if (m_scene == null) |
||
87 | m_scene = scene; |
||
88 | |||
89 | scene.RegisterModuleInterface<IAvatarFactoryModule>(this); |
||
90 | scene.EventManager.OnNewClient += SubscribeToClientEvents; |
||
91 | } |
||
92 | |||
93 | public void RemoveRegion(Scene scene) |
||
94 | { |
||
95 | if (scene == m_scene) |
||
96 | { |
||
97 | scene.UnregisterModuleInterface<IAvatarFactoryModule>(this); |
||
98 | scene.EventManager.OnNewClient -= SubscribeToClientEvents; |
||
99 | } |
||
100 | |||
101 | m_scene = null; |
||
102 | } |
||
103 | |||
104 | public void RegionLoaded(Scene scene) |
||
105 | { |
||
106 | m_updateTimer.Enabled = false; |
||
107 | m_updateTimer.AutoReset = true; |
||
108 | m_updateTimer.Interval = m_checkTime; // 500 milliseconds wait to start async ops |
||
109 | m_updateTimer.Elapsed += new ElapsedEventHandler(HandleAppearanceUpdateTimer); |
||
110 | } |
||
111 | |||
112 | public void Close() |
||
113 | { |
||
114 | } |
||
115 | |||
116 | public string Name |
||
117 | { |
||
118 | get { return "Default Avatar Factory"; } |
||
119 | } |
||
120 | |||
121 | public bool IsSharedModule |
||
122 | { |
||
123 | get { return false; } |
||
124 | } |
||
125 | |||
126 | public Type ReplaceableInterface |
||
127 | { |
||
128 | get { return null; } |
||
129 | } |
||
130 | |||
131 | |||
132 | private void SubscribeToClientEvents(IClientAPI client) |
||
133 | { |
||
134 | client.OnRequestWearables += Client_OnRequestWearables; |
||
135 | client.OnSetAppearance += Client_OnSetAppearance; |
||
136 | client.OnAvatarNowWearing += Client_OnAvatarNowWearing; |
||
137 | client.OnCachedTextureRequest += Client_OnCachedTextureRequest; |
||
138 | } |
||
139 | |||
140 | #endregion |
||
141 | |||
142 | #region IAvatarFactoryModule |
||
143 | |||
144 | /// </summary> |
||
145 | /// <param name="sp"></param> |
||
146 | /// <param name="texture"></param> |
||
147 | /// <param name="visualParam"></param> |
||
148 | public void SetAppearance(IScenePresence sp, AvatarAppearance appearance, WearableCacheItem[] cacheItems) |
||
149 | { |
||
150 | SetAppearance(sp, appearance.Texture, appearance.VisualParams, cacheItems); |
||
151 | } |
||
152 | |||
153 | |||
154 | public void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams, Vector3 avSize, WearableCacheItem[] cacheItems) |
||
155 | { |
||
156 | float oldoff = sp.Appearance.AvatarFeetOffset; |
||
157 | Vector3 oldbox = sp.Appearance.AvatarBoxSize; |
||
158 | |||
159 | SetAppearance(sp, textureEntry, visualParams, cacheItems); |
||
160 | sp.Appearance.SetSize(avSize); |
||
161 | |||
162 | float off = sp.Appearance.AvatarFeetOffset; |
||
163 | Vector3 box = sp.Appearance.AvatarBoxSize; |
||
164 | if (oldoff != off || oldbox != box) |
||
165 | ((ScenePresence)sp).SetSize(box, off); |
||
166 | } |
||
167 | |||
168 | /// <summary> |
||
169 | /// Set appearance data (texture asset IDs and slider settings) |
||
170 | /// </summary> |
||
171 | /// <param name="sp"></param> |
||
172 | /// <param name="texture"></param> |
||
173 | /// <param name="visualParam"></param> |
||
174 | public void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams, WearableCacheItem[] cacheItems) |
||
175 | { |
||
176 | // m_log.DebugFormat( |
||
177 | // "[AVFACTORY]: start SetAppearance for {0}, te {1}, visualParams {2}", |
||
178 | // sp.Name, textureEntry, visualParams); |
||
179 | |||
180 | // TODO: This is probably not necessary any longer, just assume the |
||
181 | // textureEntry set implies that the appearance transaction is complete |
||
182 | bool changed = false; |
||
183 | |||
184 | // Process the texture entry transactionally, this doesn't guarantee that Appearance is |
||
185 | // going to be handled correctly but it does serialize the updates to the appearance |
||
186 | lock (m_setAppearanceLock) |
||
187 | { |
||
188 | // Process the visual params, this may change height as well |
||
189 | if (visualParams != null) |
||
190 | { |
||
191 | // string[] visualParamsStrings = new string[visualParams.Length]; |
||
192 | // for (int i = 0; i < visualParams.Length; i++) |
||
193 | // visualParamsStrings[i] = visualParams[i].ToString(); |
||
194 | // m_log.DebugFormat( |
||
195 | // "[AVFACTORY]: Setting visual params for {0} to {1}", |
||
196 | // client.Name, string.Join(", ", visualParamsStrings)); |
||
197 | /* |
||
198 | float oldHeight = sp.Appearance.AvatarHeight; |
||
199 | changed = sp.Appearance.SetVisualParams(visualParams); |
||
200 | |||
201 | if (sp.Appearance.AvatarHeight != oldHeight && sp.Appearance.AvatarHeight > 0) |
||
202 | ((ScenePresence)sp).SetHeight(sp.Appearance.AvatarHeight); |
||
203 | */ |
||
204 | // float oldoff = sp.Appearance.AvatarFeetOffset; |
||
205 | // Vector3 oldbox = sp.Appearance.AvatarBoxSize; |
||
206 | changed = sp.Appearance.SetVisualParams(visualParams); |
||
207 | // float off = sp.Appearance.AvatarFeetOffset; |
||
208 | // Vector3 box = sp.Appearance.AvatarBoxSize; |
||
209 | // if(oldoff != off || oldbox != box) |
||
210 | // ((ScenePresence)sp).SetSize(box,off); |
||
211 | |||
212 | } |
||
213 | |||
214 | // Process the baked texture array |
||
215 | if (textureEntry != null) |
||
216 | { |
||
217 | m_log.DebugFormat("[AVFACTORY]: Received texture update for {0} {1}", sp.Name, sp.UUID); |
||
218 | |||
219 | // WriteBakedTexturesReport(sp, m_log.DebugFormat); |
||
220 | |||
221 | changed = sp.Appearance.SetTextureEntries(textureEntry) || changed; |
||
222 | |||
223 | // WriteBakedTexturesReport(sp, m_log.DebugFormat); |
||
224 | |||
225 | // If bake textures are missing and this is not an NPC, request a rebake from client |
||
226 | if (!ValidateBakedTextureCache(sp) && (((ScenePresence)sp).PresenceType != PresenceType.Npc)) |
||
227 | RequestRebake(sp, true); |
||
228 | |||
229 | // This appears to be set only in the final stage of the appearance |
||
230 | // update transaction. In theory, we should be able to do an immediate |
||
231 | // appearance send and save here. |
||
232 | } |
||
233 | |||
234 | // NPC should send to clients immediately and skip saving appearance |
||
235 | if (((ScenePresence)sp).PresenceType == PresenceType.Npc) |
||
236 | { |
||
237 | SendAppearance((ScenePresence)sp); |
||
238 | return; |
||
239 | } |
||
240 | |||
241 | // save only if there were changes, send no matter what (doesn't hurt to send twice) |
||
242 | if (changed) |
||
243 | QueueAppearanceSave(sp.ControllingClient.AgentId); |
||
244 | |||
245 | QueueAppearanceSend(sp.ControllingClient.AgentId); |
||
246 | } |
||
247 | |||
248 | // m_log.WarnFormat("[AVFACTORY]: complete SetAppearance for {0}:\n{1}",client.AgentId,sp.Appearance.ToString()); |
||
249 | } |
||
250 | |||
251 | private void SendAppearance(ScenePresence sp) |
||
252 | { |
||
253 | // Send the appearance to everyone in the scene |
||
254 | sp.SendAppearanceToAllOtherAgents(); |
||
255 | |||
256 | // Send animations back to the avatar as well |
||
257 | sp.Animator.SendAnimPack(); |
||
258 | } |
||
259 | |||
260 | public bool SendAppearance(UUID agentId) |
||
261 | { |
||
262 | // m_log.DebugFormat("[AVFACTORY]: Sending appearance for {0}", agentId); |
||
263 | |||
264 | ScenePresence sp = m_scene.GetScenePresence(agentId); |
||
265 | if (sp == null) |
||
266 | { |
||
267 | // This is expected if the user has gone away. |
||
268 | // m_log.DebugFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentId); |
||
269 | return false; |
||
270 | } |
||
271 | |||
272 | SendAppearance(sp); |
||
273 | return true; |
||
274 | } |
||
275 | |||
276 | public Dictionary<BakeType, Primitive.TextureEntryFace> GetBakedTextureFaces(UUID agentId) |
||
277 | { |
||
278 | ScenePresence sp = m_scene.GetScenePresence(agentId); |
||
279 | |||
280 | if (sp == null) |
||
281 | return new Dictionary<BakeType, Primitive.TextureEntryFace>(); |
||
282 | |||
283 | return GetBakedTextureFaces(sp); |
||
284 | } |
||
285 | |||
286 | public WearableCacheItem[] GetCachedItems(UUID agentId) |
||
287 | { |
||
288 | ScenePresence sp = m_scene.GetScenePresence(agentId); |
||
289 | WearableCacheItem[] items = sp.Appearance.WearableCacheItems; |
||
290 | //foreach (WearableCacheItem item in items) |
||
291 | //{ |
||
292 | |||
293 | //} |
||
294 | return items; |
||
295 | } |
||
296 | |||
297 | public bool SaveBakedTextures(UUID agentId) |
||
298 | { |
||
299 | ScenePresence sp = m_scene.GetScenePresence(agentId); |
||
300 | |||
301 | if (sp == null) |
||
302 | return false; |
||
303 | |||
304 | m_log.DebugFormat( |
||
305 | "[AV FACTORY]: Permanently saving baked textures for {0} in {1}", |
||
306 | sp.Name, m_scene.RegionInfo.RegionName); |
||
307 | |||
308 | Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures = GetBakedTextureFaces(sp); |
||
309 | |||
310 | if (bakedTextures.Count == 0) |
||
311 | return false; |
||
312 | |||
313 | foreach (BakeType bakeType in bakedTextures.Keys) |
||
314 | { |
||
315 | Primitive.TextureEntryFace bakedTextureFace = bakedTextures[bakeType]; |
||
316 | |||
317 | if (bakedTextureFace == null) |
||
318 | { |
||
319 | // This can happen legitimately, since some baked textures might not exist |
||
320 | //m_log.WarnFormat( |
||
321 | // "[AV FACTORY]: No texture ID set for {0} for {1} in {2} not found when trying to save permanently", |
||
322 | // bakeType, sp.Name, m_scene.RegionInfo.RegionName); |
||
323 | continue; |
||
324 | } |
||
325 | |||
326 | AssetBase asset = m_scene.AssetService.Get(bakedTextureFace.TextureID.ToString()); |
||
327 | |||
328 | if (asset != null) |
||
329 | { |
||
330 | // Replace an HG ID with the simple asset ID so that we can persist textures for foreign HG avatars |
||
331 | asset.ID = asset.FullID.ToString(); |
||
332 | |||
333 | asset.Temporary = false; |
||
334 | asset.Local = false; |
||
335 | m_scene.AssetService.Store(asset); |
||
336 | } |
||
337 | else |
||
338 | { |
||
339 | m_log.WarnFormat( |
||
340 | "[AV FACTORY]: Baked texture id {0} not found for bake {1} for avatar {2} in {3} when trying to save permanently", |
||
341 | bakedTextureFace.TextureID, bakeType, sp.Name, m_scene.RegionInfo.RegionName); |
||
342 | } |
||
343 | } |
||
344 | return true; |
||
345 | } |
||
346 | |||
347 | /// <summary> |
||
348 | /// Queue up a request to send appearance. |
||
349 | /// </summary> |
||
350 | /// <remarks> |
||
351 | /// Makes it possible to accumulate changes without sending out each one separately. |
||
352 | /// </remarks> |
||
353 | /// <param name="agentId"></param> |
||
354 | public void QueueAppearanceSend(UUID agentid) |
||
355 | { |
||
356 | // m_log.DebugFormat("[AVFACTORY]: Queue appearance send for {0}", agentid); |
||
357 | |||
358 | // 10000 ticks per millisecond, 1000 milliseconds per second |
||
359 | long timestamp = DateTime.Now.Ticks + Convert.ToInt64(m_sendtime * 1000 * 10000); |
||
360 | lock (m_sendqueue) |
||
361 | { |
||
362 | m_sendqueue[agentid] = timestamp; |
||
363 | m_updateTimer.Start(); |
||
364 | } |
||
365 | } |
||
366 | |||
367 | public void QueueAppearanceSave(UUID agentid) |
||
368 | { |
||
369 | // m_log.DebugFormat("[AVFACTORY]: Queueing appearance save for {0}", agentid); |
||
370 | |||
371 | // 10000 ticks per millisecond, 1000 milliseconds per second |
||
372 | long timestamp = DateTime.Now.Ticks + Convert.ToInt64(m_savetime * 1000 * 10000); |
||
373 | lock (m_savequeue) |
||
374 | { |
||
375 | m_savequeue[agentid] = timestamp; |
||
376 | m_updateTimer.Start(); |
||
377 | } |
||
378 | } |
||
379 | |||
380 | public bool ValidateBakedTextureCache(IScenePresence sp) |
||
381 | { |
||
382 | bool defonly = true; // are we only using default textures |
||
383 | IImprovedAssetCache cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); |
||
384 | IBakedTextureModule bakedModule = m_scene.RequestModuleInterface<IBakedTextureModule>(); |
||
385 | WearableCacheItem[] wearableCache = null; |
||
386 | |||
387 | // Cache wearable data for teleport. |
||
388 | // Only makes sense if there's a bake module and a cache module |
||
389 | if (bakedModule != null && cache != null) |
||
390 | { |
||
391 | try |
||
392 | { |
||
393 | wearableCache = bakedModule.Get(sp.UUID); |
||
394 | } |
||
395 | catch (Exception) |
||
396 | { |
||
397 | |||
398 | } |
||
399 | if (wearableCache != null) |
||
400 | { |
||
401 | for (int i = 0; i < wearableCache.Length; i++) |
||
402 | { |
||
403 | cache.Cache(wearableCache[i].TextureAsset); |
||
404 | } |
||
405 | } |
||
406 | } |
||
407 | /* |
||
408 | IBakedTextureModule bakedModule = m_scene.RequestModuleInterface<IBakedTextureModule>(); |
||
409 | if (invService.GetRootFolder(userID) != null) |
||
410 | { |
||
411 | WearableCacheItem[] wearableCache = null; |
||
412 | if (bakedModule != null) |
||
413 | { |
||
414 | try |
||
415 | { |
||
416 | wearableCache = bakedModule.Get(userID); |
||
417 | appearance.WearableCacheItems = wearableCache; |
||
418 | appearance.WearableCacheItemsDirty = false; |
||
419 | foreach (WearableCacheItem item in wearableCache) |
||
420 | { |
||
421 | appearance.Texture.FaceTextures[item.TextureIndex].TextureID = item.TextureID; |
||
422 | } |
||
423 | } |
||
424 | catch (Exception) |
||
425 | { |
||
426 | |||
427 | } |
||
428 | } |
||
429 | */ |
||
430 | |||
431 | // Process the texture entry |
||
432 | for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) |
||
433 | { |
||
434 | int idx = AvatarAppearance.BAKE_INDICES[i]; |
||
435 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; |
||
436 | |||
437 | // No face, so lets check our baked service cache, teleport or login. |
||
438 | if (face == null) |
||
439 | { |
||
440 | if (wearableCache != null) |
||
441 | { |
||
442 | // If we find the an appearance item, set it as the textureentry and the face |
||
443 | WearableCacheItem searchitem = WearableCacheItem.SearchTextureIndex((uint) idx, wearableCache); |
||
444 | if (searchitem != null) |
||
445 | { |
||
446 | sp.Appearance.Texture.FaceTextures[idx] = sp.Appearance.Texture.CreateFace((uint) idx); |
||
447 | sp.Appearance.Texture.FaceTextures[idx].TextureID = searchitem.TextureID; |
||
448 | face = sp.Appearance.Texture.FaceTextures[idx]; |
||
449 | } |
||
450 | else |
||
451 | { |
||
452 | // if there is no texture entry and no baked cache, skip it |
||
453 | continue; |
||
454 | } |
||
455 | } |
||
456 | else |
||
457 | { |
||
458 | //No texture entry face and no cache. Skip this face. |
||
459 | continue; |
||
460 | } |
||
461 | } |
||
462 | |||
463 | |||
464 | // m_log.DebugFormat( |
||
465 | // "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}", |
||
466 | // face.TextureID, idx, client.Name, client.AgentId); |
||
467 | |||
468 | // if the texture is one of the "defaults" then skip it |
||
469 | // this should probably be more intelligent (skirt texture doesnt matter |
||
470 | // if the avatar isnt wearing a skirt) but if any of the main baked |
||
471 | // textures is default then the rest should be as well |
||
472 | if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE) |
||
473 | continue; |
||
474 | |||
475 | defonly = false; // found a non-default texture reference |
||
476 | |||
477 | if (cache != null) |
||
478 | { |
||
479 | if (!cache.Check(face.TextureID.ToString())) |
||
480 | return false; |
||
481 | } |
||
482 | else |
||
483 | { |
||
484 | if (m_scene.AssetService.Get(face.TextureID.ToString()) == null) |
||
485 | return false; |
||
486 | } |
||
487 | } |
||
488 | |||
489 | // m_log.DebugFormat("[AVFACTORY]: Completed texture check for {0} {1}", sp.Name, sp.UUID); |
||
490 | |||
491 | // If we only found default textures, then the appearance is not cached |
||
492 | return (defonly ? false : true); |
||
493 | } |
||
494 | |||
495 | public int RequestRebake(IScenePresence sp, bool missingTexturesOnly) |
||
496 | { |
||
497 | int texturesRebaked = 0; |
||
498 | IImprovedAssetCache cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); |
||
499 | |||
500 | for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++) |
||
501 | { |
||
502 | int idx = AvatarAppearance.BAKE_INDICES[i]; |
||
503 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx]; |
||
504 | |||
505 | // if there is no texture entry, skip it |
||
506 | if (face == null) |
||
507 | continue; |
||
508 | |||
509 | // m_log.DebugFormat( |
||
510 | // "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}", |
||
511 | // face.TextureID, idx, client.Name, client.AgentId); |
||
512 | |||
513 | // if the texture is one of the "defaults" then skip it |
||
514 | // this should probably be more intelligent (skirt texture doesnt matter |
||
515 | // if the avatar isnt wearing a skirt) but if any of the main baked |
||
516 | // textures is default then the rest should be as well |
||
517 | if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE) |
||
518 | continue; |
||
519 | |||
520 | if (missingTexturesOnly) |
||
521 | { |
||
522 | if (cache != null) |
||
523 | { |
||
524 | if (cache.Check(face.TextureID.ToString())) |
||
525 | continue; |
||
526 | else |
||
527 | { |
||
528 | m_log.DebugFormat( |
||
529 | "[AVFACTORY]: Missing baked texture {0} ({1}) for {2}, requesting rebake.", |
||
530 | face.TextureID, idx, sp.Name); |
||
531 | } |
||
532 | } |
||
533 | else |
||
534 | { |
||
535 | if (m_scene.AssetService.Get(face.TextureID.ToString()) != null) |
||
536 | { |
||
537 | continue; |
||
538 | } |
||
539 | |||
540 | else |
||
541 | { |
||
542 | // On inter-simulator teleports, this occurs if baked textures are not being stored by the |
||
543 | // grid asset service (which means that they are not available to the new region and so have |
||
544 | // to be re-requested from the client). |
||
545 | // |
||
546 | // The only available core OpenSimulator behaviour right now |
||
547 | // is not to store these textures, temporarily or otherwise. |
||
548 | m_log.DebugFormat( |
||
549 | "[AVFACTORY]: Missing baked texture {0} ({1}) for {2}, requesting rebake.", |
||
550 | face.TextureID, idx, sp.Name); |
||
551 | } |
||
552 | } |
||
553 | } |
||
554 | else |
||
555 | { |
||
556 | m_log.DebugFormat( |
||
557 | "[AVFACTORY]: Requesting rebake of {0} ({1}) for {2}.", |
||
558 | face.TextureID, idx, sp.Name); |
||
559 | } |
||
560 | |||
561 | texturesRebaked++; |
||
562 | sp.ControllingClient.SendRebakeAvatarTextures(face.TextureID); |
||
563 | } |
||
564 | |||
565 | return texturesRebaked; |
||
566 | } |
||
567 | |||
568 | #endregion |
||
569 | |||
570 | #region AvatarFactoryModule private methods |
||
571 | |||
572 | private Dictionary<BakeType, Primitive.TextureEntryFace> GetBakedTextureFaces(ScenePresence sp) |
||
573 | { |
||
574 | if (sp.IsChildAgent) |
||
575 | return new Dictionary<BakeType, Primitive.TextureEntryFace>(); |
||
576 | |||
577 | Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures |
||
578 | = new Dictionary<BakeType, Primitive.TextureEntryFace>(); |
||
579 | |||
580 | AvatarAppearance appearance = sp.Appearance; |
||
581 | Primitive.TextureEntryFace[] faceTextures = appearance.Texture.FaceTextures; |
||
582 | |||
583 | foreach (int i in Enum.GetValues(typeof(BakeType))) |
||
584 | { |
||
585 | BakeType bakeType = (BakeType)i; |
||
586 | |||
587 | if (bakeType == BakeType.Unknown) |
||
588 | continue; |
||
589 | |||
590 | // m_log.DebugFormat( |
||
591 | // "[AVFACTORY]: NPC avatar {0} has texture id {1} : {2}", |
||
592 | // acd.AgentID, i, acd.Appearance.Texture.FaceTextures[i]); |
||
593 | |||
594 | int ftIndex = (int)AppearanceManager.BakeTypeToAgentTextureIndex(bakeType); |
||
595 | Primitive.TextureEntryFace texture = faceTextures[ftIndex]; // this will be null if there's no such baked texture |
||
596 | bakedTextures[bakeType] = texture; |
||
597 | } |
||
598 | |||
599 | return bakedTextures; |
||
600 | } |
||
601 | |||
602 | private void HandleAppearanceUpdateTimer(object sender, EventArgs ea) |
||
603 | { |
||
604 | long now = DateTime.Now.Ticks; |
||
605 | |||
606 | lock (m_sendqueue) |
||
607 | { |
||
608 | Dictionary<UUID, long> sends = new Dictionary<UUID, long>(m_sendqueue); |
||
609 | foreach (KeyValuePair<UUID, long> kvp in sends) |
||
610 | { |
||
611 | // We have to load the key and value into local parameters to avoid a race condition if we loop |
||
612 | // around and load kvp with a different value before FireAndForget has launched its thread. |
||
613 | UUID avatarID = kvp.Key; |
||
614 | long sendTime = kvp.Value; |
||
615 | |||
616 | // m_log.DebugFormat("[AVFACTORY]: Handling queued appearance updates for {0}, update delta to now is {1}", avatarID, sendTime - now); |
||
617 | |||
618 | if (sendTime < now) |
||
619 | { |
||
620 | Util.FireAndForget(o => SendAppearance(avatarID)); |
||
621 | m_sendqueue.Remove(avatarID); |
||
622 | } |
||
623 | } |
||
624 | } |
||
625 | |||
626 | lock (m_savequeue) |
||
627 | { |
||
628 | Dictionary<UUID, long> saves = new Dictionary<UUID, long>(m_savequeue); |
||
629 | foreach (KeyValuePair<UUID, long> kvp in saves) |
||
630 | { |
||
631 | // We have to load the key and value into local parameters to avoid a race condition if we loop |
||
632 | // around and load kvp with a different value before FireAndForget has launched its thread. |
||
633 | UUID avatarID = kvp.Key; |
||
634 | long sendTime = kvp.Value; |
||
635 | |||
636 | if (sendTime < now) |
||
637 | { |
||
638 | Util.FireAndForget(o => SaveAppearance(avatarID)); |
||
639 | m_savequeue.Remove(avatarID); |
||
640 | } |
||
641 | } |
||
642 | |||
643 | // We must lock both queues here so that QueueAppearanceSave() or *Send() don't m_updateTimer.Start() on |
||
644 | // another thread inbetween the first count calls and m_updateTimer.Stop() on this thread. |
||
645 | lock (m_sendqueue) |
||
646 | if (m_savequeue.Count == 0 && m_sendqueue.Count == 0) |
||
647 | m_updateTimer.Stop(); |
||
648 | } |
||
649 | } |
||
650 | |||
651 | private void SaveAppearance(UUID agentid) |
||
652 | { |
||
653 | // We must set appearance parameters in the en_US culture in order to avoid issues where values are saved |
||
654 | // in a culture where decimal points are commas and then reloaded in a culture which just treats them as |
||
655 | // number seperators. |
||
656 | Culture.SetCurrentCulture(); |
||
657 | |||
658 | ScenePresence sp = m_scene.GetScenePresence(agentid); |
||
659 | if (sp == null) |
||
660 | { |
||
661 | // This is expected if the user has gone away. |
||
662 | // m_log.DebugFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentid); |
||
663 | return; |
||
664 | } |
||
665 | |||
666 | // m_log.DebugFormat("[AVFACTORY]: Saving appearance for avatar {0}", agentid); |
||
667 | |||
668 | // This could take awhile since it needs to pull inventory |
||
669 | // We need to do it at the point of save so that there is a sufficient delay for any upload of new body part/shape |
||
670 | // assets and item asset id changes to complete. |
||
671 | // I don't think we need to worry about doing this within m_setAppearanceLock since the queueing avoids |
||
672 | // multiple save requests. |
||
673 | SetAppearanceAssets(sp.UUID, sp.Appearance); |
||
674 | |||
675 | // List<AvatarAttachment> attachments = sp.Appearance.GetAttachments(); |
||
676 | // foreach (AvatarAttachment att in attachments) |
||
677 | // { |
||
678 | // m_log.DebugFormat( |
||
679 | // "[AVFACTORY]: For {0} saving attachment {1} at point {2}", |
||
680 | // sp.Name, att.ItemID, att.AttachPoint); |
||
681 | // } |
||
682 | |||
683 | m_scene.AvatarService.SetAppearance(agentid, sp.Appearance); |
||
684 | |||
685 | // Trigger this here because it's the final step in the set/queue/save process for appearance setting. |
||
686 | // Everything has been updated and stored. Ensures bakes have been persisted (if option is set to persist bakes). |
||
687 | m_scene.EventManager.TriggerAvatarAppearanceChanged(sp); |
||
688 | } |
||
689 | |||
690 | private void SetAppearanceAssets(UUID userID, AvatarAppearance appearance) |
||
691 | { |
||
692 | IInventoryService invService = m_scene.InventoryService; |
||
693 | bool resetwearable = false; |
||
694 | if (invService.GetRootFolder(userID) != null) |
||
695 | { |
||
696 | for (int i = 0; i < AvatarWearable.MAX_WEARABLES; i++) |
||
697 | { |
||
698 | for (int j = 0; j < appearance.Wearables[i].Count; j++) |
||
699 | { |
||
700 | // Check if the default wearables are not set |
||
701 | if (appearance.Wearables[i][j].ItemID == UUID.Zero) |
||
702 | { |
||
703 | switch ((WearableType) i) |
||
704 | { |
||
705 | case WearableType.Eyes: |
||
706 | case WearableType.Hair: |
||
707 | case WearableType.Shape: |
||
708 | case WearableType.Skin: |
||
709 | //case WearableType.Underpants: |
||
710 | TryAndRepairBrokenWearable((WearableType)i, invService, userID, appearance); |
||
711 | resetwearable = true; |
||
712 | m_log.Warn("[AVFACTORY]: UUID.Zero Wearables, passing fake values."); |
||
713 | resetwearable = true; |
||
714 | break; |
||
715 | |||
716 | } |
||
717 | continue; |
||
718 | } |
||
719 | |||
720 | // Ignore ruth's assets except for the body parts! missing body parts fail avatar appearance on V1 |
||
721 | if (appearance.Wearables[i][j].ItemID == AvatarWearable.DefaultWearables[i][0].ItemID) |
||
722 | { |
||
723 | switch ((WearableType)i) |
||
724 | { |
||
725 | case WearableType.Eyes: |
||
726 | case WearableType.Hair: |
||
727 | case WearableType.Shape: |
||
728 | case WearableType.Skin: |
||
729 | //case WearableType.Underpants: |
||
730 | TryAndRepairBrokenWearable((WearableType)i, invService, userID, appearance); |
||
731 | |||
732 | m_log.WarnFormat("[AVFACTORY]: {0} Default Wearables, passing existing values.", (WearableType)i); |
||
733 | resetwearable = true; |
||
734 | break; |
||
735 | |||
736 | } |
||
737 | continue; |
||
738 | } |
||
739 | |||
740 | InventoryItemBase baseItem = new InventoryItemBase(appearance.Wearables[i][j].ItemID, userID); |
||
741 | baseItem = invService.GetItem(baseItem); |
||
742 | |||
743 | if (baseItem != null) |
||
744 | { |
||
745 | appearance.Wearables[i].Add(appearance.Wearables[i][j].ItemID, baseItem.AssetID); |
||
746 | int unmodifiedWearableIndexForClosure = i; |
||
747 | m_scene.AssetService.Get(baseItem.AssetID.ToString(), this, |
||
748 | delegate(string x, object y, AssetBase z) |
||
749 | { |
||
750 | if (z == null) |
||
751 | { |
||
752 | TryAndRepairBrokenWearable( |
||
753 | (WearableType)unmodifiedWearableIndexForClosure, invService, |
||
754 | userID, appearance); |
||
755 | } |
||
756 | }); |
||
757 | } |
||
758 | else |
||
759 | { |
||
760 | m_log.ErrorFormat( |
||
761 | "[AVFACTORY]: Can't find inventory item {0} for {1}, setting to default", |
||
762 | appearance.Wearables[i][j].ItemID, (WearableType)i); |
||
763 | |||
764 | TryAndRepairBrokenWearable((WearableType)i, invService, userID, appearance); |
||
765 | resetwearable = true; |
||
766 | |||
767 | } |
||
768 | } |
||
769 | } |
||
770 | |||
771 | // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... |
||
772 | if (appearance.Wearables[(int) WearableType.Eyes] == null) |
||
773 | { |
||
774 | m_log.WarnFormat("[AVFACTORY]: {0} Eyes are Null, passing existing values.", (WearableType.Eyes)); |
||
775 | |||
776 | TryAndRepairBrokenWearable(WearableType.Eyes, invService, userID, appearance); |
||
777 | resetwearable = true; |
||
778 | } |
||
779 | else |
||
780 | { |
||
781 | if (appearance.Wearables[(int) WearableType.Eyes][0].ItemID == UUID.Zero) |
||
782 | { |
||
783 | m_log.WarnFormat("[AVFACTORY]: Eyes are UUID.Zero are broken, {0} {1}", |
||
784 | appearance.Wearables[(int) WearableType.Eyes][0].ItemID, |
||
785 | appearance.Wearables[(int) WearableType.Eyes][0].AssetID); |
||
786 | TryAndRepairBrokenWearable(WearableType.Eyes, invService, userID, appearance); |
||
787 | resetwearable = true; |
||
788 | |||
789 | } |
||
790 | |||
791 | } |
||
792 | // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... |
||
793 | if (appearance.Wearables[(int)WearableType.Shape] == null) |
||
794 | { |
||
795 | m_log.WarnFormat("[AVFACTORY]: {0} shape is Null, passing existing values.", (WearableType.Shape)); |
||
796 | |||
797 | TryAndRepairBrokenWearable(WearableType.Shape, invService, userID, appearance); |
||
798 | resetwearable = true; |
||
799 | } |
||
800 | else |
||
801 | { |
||
802 | if (appearance.Wearables[(int)WearableType.Shape][0].ItemID == UUID.Zero) |
||
803 | { |
||
804 | m_log.WarnFormat("[AVFACTORY]: Shape is UUID.Zero and broken, {0} {1}", |
||
805 | appearance.Wearables[(int)WearableType.Shape][0].ItemID, |
||
806 | appearance.Wearables[(int)WearableType.Shape][0].AssetID); |
||
807 | TryAndRepairBrokenWearable(WearableType.Shape, invService, userID, appearance); |
||
808 | resetwearable = true; |
||
809 | |||
810 | } |
||
811 | |||
812 | } |
||
813 | // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... |
||
814 | if (appearance.Wearables[(int)WearableType.Hair] == null) |
||
815 | { |
||
816 | m_log.WarnFormat("[AVFACTORY]: {0} Hair is Null, passing existing values.", (WearableType.Hair)); |
||
817 | |||
818 | TryAndRepairBrokenWearable(WearableType.Hair, invService, userID, appearance); |
||
819 | resetwearable = true; |
||
820 | } |
||
821 | else |
||
822 | { |
||
823 | if (appearance.Wearables[(int)WearableType.Hair][0].ItemID == UUID.Zero) |
||
824 | { |
||
825 | m_log.WarnFormat("[AVFACTORY]: Hair is UUID.Zero and broken, {0} {1}", |
||
826 | appearance.Wearables[(int)WearableType.Hair][0].ItemID, |
||
827 | appearance.Wearables[(int)WearableType.Hair][0].AssetID); |
||
828 | TryAndRepairBrokenWearable(WearableType.Hair, invService, userID, appearance); |
||
829 | resetwearable = true; |
||
830 | |||
831 | } |
||
832 | |||
833 | } |
||
834 | // I don't know why we have to test for this again... but the above switches do not capture these scenarios for some reason.... |
||
835 | if (appearance.Wearables[(int)WearableType.Skin] == null) |
||
836 | { |
||
837 | m_log.WarnFormat("[AVFACTORY]: {0} Skin is Null, passing existing values.", (WearableType.Skin)); |
||
838 | |||
839 | TryAndRepairBrokenWearable(WearableType.Skin, invService, userID, appearance); |
||
840 | resetwearable = true; |
||
841 | } |
||
842 | else |
||
843 | { |
||
844 | if (appearance.Wearables[(int)WearableType.Skin][0].ItemID == UUID.Zero) |
||
845 | { |
||
846 | m_log.WarnFormat("[AVFACTORY]: Skin is UUID.Zero and broken, {0} {1}", |
||
847 | appearance.Wearables[(int)WearableType.Skin][0].ItemID, |
||
848 | appearance.Wearables[(int)WearableType.Skin][0].AssetID); |
||
849 | TryAndRepairBrokenWearable(WearableType.Skin, invService, userID, appearance); |
||
850 | resetwearable = true; |
||
851 | |||
852 | } |
||
853 | |||
854 | } |
||
855 | if (resetwearable) |
||
856 | { |
||
857 | ScenePresence presence = null; |
||
858 | if (m_scene.TryGetScenePresence(userID, out presence)) |
||
859 | { |
||
860 | presence.ControllingClient.SendWearables(presence.Appearance.Wearables, |
||
861 | presence.Appearance.Serial++); |
||
862 | } |
||
863 | } |
||
864 | |||
865 | } |
||
866 | else |
||
867 | { |
||
868 | m_log.WarnFormat("[AVFACTORY]: user {0} has no inventory, appearance isn't going to work", userID); |
||
869 | } |
||
870 | } |
||
871 | private void TryAndRepairBrokenWearable(WearableType type, IInventoryService invService, UUID userID,AvatarAppearance appearance) |
||
872 | { |
||
873 | UUID defaultwearable = GetDefaultItem(type); |
||
874 | if (defaultwearable != UUID.Zero) |
||
875 | { |
||
876 | UUID newInvItem = UUID.Random(); |
||
877 | InventoryItemBase itembase = new InventoryItemBase(newInvItem, userID) |
||
878 | { |
||
879 | AssetID = |
||
880 | defaultwearable, |
||
881 | AssetType |
||
882 | = |
||
883 | (int) |
||
884 | AssetType |
||
885 | .Bodypart, |
||
886 | CreatorId |
||
887 | = |
||
888 | userID |
||
889 | .ToString |
||
890 | (), |
||
891 | //InvType = (int)InventoryType.Wearable, |
||
892 | |||
893 | Description |
||
894 | = |
||
895 | "Failed Wearable Replacement", |
||
896 | Folder = |
||
897 | invService |
||
898 | .GetFolderForType |
||
899 | (userID, |
||
900 | AssetType |
||
901 | .Bodypart) |
||
902 | .ID, |
||
903 | Flags = (uint) type, |
||
904 | Name = Enum.GetName(typeof (WearableType), type), |
||
905 | BasePermissions = (uint) PermissionMask.Copy, |
||
906 | CurrentPermissions = (uint) PermissionMask.Copy, |
||
907 | EveryOnePermissions = (uint) PermissionMask.Copy, |
||
908 | GroupPermissions = (uint) PermissionMask.Copy, |
||
909 | NextPermissions = (uint) PermissionMask.Copy |
||
910 | }; |
||
911 | invService.AddItem(itembase); |
||
912 | UUID LinkInvItem = UUID.Random(); |
||
913 | itembase = new InventoryItemBase(LinkInvItem, userID) |
||
914 | { |
||
915 | AssetID = |
||
916 | newInvItem, |
||
917 | AssetType |
||
918 | = |
||
919 | (int) |
||
920 | AssetType |
||
921 | .Link, |
||
922 | CreatorId |
||
923 | = |
||
924 | userID |
||
925 | .ToString |
||
926 | (), |
||
927 | InvType = (int) InventoryType.Wearable, |
||
928 | |||
929 | Description |
||
930 | = |
||
931 | "Failed Wearable Replacement", |
||
932 | Folder = |
||
933 | invService |
||
934 | .GetFolderForType |
||
935 | (userID, |
||
936 | AssetType |
||
937 | .CurrentOutfitFolder) |
||
938 | .ID, |
||
939 | Flags = (uint) type, |
||
940 | Name = Enum.GetName(typeof (WearableType), type), |
||
941 | BasePermissions = (uint) PermissionMask.Copy, |
||
942 | CurrentPermissions = (uint) PermissionMask.Copy, |
||
943 | EveryOnePermissions = (uint) PermissionMask.Copy, |
||
944 | GroupPermissions = (uint) PermissionMask.Copy, |
||
945 | NextPermissions = (uint) PermissionMask.Copy |
||
946 | }; |
||
947 | invService.AddItem(itembase); |
||
948 | appearance.Wearables[(int)type] = new AvatarWearable(newInvItem, GetDefaultItem(type)); |
||
949 | ScenePresence presence = null; |
||
950 | if (m_scene.TryGetScenePresence(userID, out presence)) |
||
951 | { |
||
952 | m_scene.SendInventoryUpdate(presence.ControllingClient, |
||
953 | invService.GetFolderForType(userID, |
||
954 | AssetType |
||
955 | .CurrentOutfitFolder), |
||
956 | false, true); |
||
957 | } |
||
958 | } |
||
959 | } |
||
960 | private UUID GetDefaultItem(WearableType wearable) |
||
961 | { |
||
962 | // These are ruth |
||
963 | UUID ret = UUID.Zero; |
||
964 | switch (wearable) |
||
965 | { |
||
966 | case WearableType.Eyes: |
||
967 | ret = new UUID("4bb6fa4d-1cd2-498a-a84c-95c1a0e745a7"); |
||
968 | break; |
||
969 | case WearableType.Hair: |
||
970 | ret = new UUID("d342e6c0-b9d2-11dc-95ff-0800200c9a66"); |
||
971 | break; |
||
972 | case WearableType.Pants: |
||
973 | ret = new UUID("00000000-38f9-1111-024e-222222111120"); |
||
974 | break; |
||
975 | case WearableType.Shape: |
||
976 | ret = new UUID("66c41e39-38f9-f75a-024e-585989bfab73"); |
||
977 | break; |
||
978 | case WearableType.Shirt: |
||
979 | ret = new UUID("00000000-38f9-1111-024e-222222111110"); |
||
980 | break; |
||
981 | case WearableType.Skin: |
||
982 | ret = new UUID("77c41e39-38f9-f75a-024e-585989bbabbb"); |
||
983 | break; |
||
984 | case WearableType.Undershirt: |
||
985 | ret = new UUID("16499ebb-3208-ec27-2def-481881728f47"); |
||
986 | break; |
||
987 | case WearableType.Underpants: |
||
988 | ret = new UUID("4ac2e9c7-3671-d229-316a-67717730841d"); |
||
989 | break; |
||
990 | } |
||
991 | |||
992 | return ret; |
||
993 | } |
||
994 | #endregion |
||
995 | |||
996 | #region Client Event Handlers |
||
997 | /// <summary> |
||
998 | /// Tell the client for this scene presence what items it should be wearing now |
||
999 | /// </summary> |
||
1000 | /// <param name="client"></param> |
||
1001 | private void Client_OnRequestWearables(IClientAPI client) |
||
1002 | { |
||
1003 | Util.FireAndForget(delegate(object x) |
||
1004 | { |
||
1005 | Thread.Sleep(4000); |
||
1006 | |||
1007 | // m_log.DebugFormat("[AVFACTORY]: Client_OnRequestWearables called for {0} ({1})", client.Name, client.AgentId); |
||
1008 | ScenePresence sp = m_scene.GetScenePresence(client.AgentId); |
||
1009 | if (sp != null) |
||
1010 | client.SendWearables(sp.Appearance.Wearables, sp.Appearance.Serial++); |
||
1011 | else |
||
1012 | m_log.WarnFormat("[AVFACTORY]: Client_OnRequestWearables unable to find presence for {0}", client.AgentId); |
||
1013 | }); |
||
1014 | } |
||
1015 | |||
1016 | /// <summary> |
||
1017 | /// Set appearance data (texture asset IDs and slider settings) received from a client |
||
1018 | /// </summary> |
||
1019 | /// <param name="client"></param> |
||
1020 | /// <param name="texture"></param> |
||
1021 | /// <param name="visualParam"></param> |
||
1022 | private void Client_OnSetAppearance(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams, Vector3 avSize, WearableCacheItem[] cacheItems) |
||
1023 | { |
||
1024 | // m_log.WarnFormat("[AVFACTORY]: Client_OnSetAppearance called for {0} ({1})", client.Name, client.AgentId); |
||
1025 | ScenePresence sp = m_scene.GetScenePresence(client.AgentId); |
||
1026 | if (sp != null) |
||
1027 | SetAppearance(sp, textureEntry, visualParams,avSize, cacheItems); |
||
1028 | else |
||
1029 | m_log.WarnFormat("[AVFACTORY]: Client_OnSetAppearance unable to find presence for {0}", client.AgentId); |
||
1030 | } |
||
1031 | |||
1032 | /// <summary> |
||
1033 | /// Update what the avatar is wearing using an item from their inventory. |
||
1034 | /// </summary> |
||
1035 | /// <param name="client"></param> |
||
1036 | /// <param name="e"></param> |
||
1037 | private void Client_OnAvatarNowWearing(IClientAPI client, AvatarWearingArgs e) |
||
1038 | { |
||
1039 | // m_log.WarnFormat("[AVFACTORY]: Client_OnAvatarNowWearing called for {0} ({1})", client.Name, client.AgentId); |
||
1040 | ScenePresence sp = m_scene.GetScenePresence(client.AgentId); |
||
1041 | if (sp == null) |
||
1042 | { |
||
1043 | m_log.WarnFormat("[AVFACTORY]: Client_OnAvatarNowWearing unable to find presence for {0}", client.AgentId); |
||
1044 | return; |
||
1045 | } |
||
1046 | |||
1047 | // we need to clean out the existing textures |
||
1048 | sp.Appearance.ResetAppearance(); |
||
1049 | |||
1050 | // operate on a copy of the appearance so we don't have to lock anything yet |
||
1051 | AvatarAppearance avatAppearance = new AvatarAppearance(sp.Appearance, false); |
||
1052 | |||
1053 | foreach (AvatarWearingArgs.Wearable wear in e.NowWearing) |
||
1054 | { |
||
1055 | if (wear.Type < AvatarWearable.MAX_WEARABLES) |
||
1056 | avatAppearance.Wearables[wear.Type].Add(wear.ItemID, UUID.Zero); |
||
1057 | } |
||
1058 | |||
1059 | avatAppearance.GetAssetsFrom(sp.Appearance); |
||
1060 | |||
1061 | lock (m_setAppearanceLock) |
||
1062 | { |
||
1063 | // Update only those fields that we have changed. This is important because the viewer |
||
1064 | // often sends AvatarIsWearing and SetAppearance packets at once, and AvatarIsWearing |
||
1065 | // shouldn't overwrite the changes made in SetAppearance. |
||
1066 | sp.Appearance.Wearables = avatAppearance.Wearables; |
||
1067 | sp.Appearance.Texture = avatAppearance.Texture; |
||
1068 | |||
1069 | // We don't need to send the appearance here since the "iswearing" will trigger a new set |
||
1070 | // of visual param and baked texture changes. When those complete, the new appearance will be sent |
||
1071 | |||
1072 | QueueAppearanceSave(client.AgentId); |
||
1073 | } |
||
1074 | } |
||
1075 | |||
1076 | /// <summary> |
||
1077 | /// Respond to the cached textures request from the client |
||
1078 | /// </summary> |
||
1079 | /// <param name="client"></param> |
||
1080 | /// <param name="serial"></param> |
||
1081 | /// <param name="cachedTextureRequest"></param> |
||
1082 | private void Client_OnCachedTextureRequest(IClientAPI client, int serial, List<CachedTextureRequestArg> cachedTextureRequest) |
||
1083 | { |
||
1084 | // m_log.WarnFormat("[AVFACTORY]: Client_OnCachedTextureRequest called for {0} ({1})", client.Name, client.AgentId); |
||
1085 | ScenePresence sp = m_scene.GetScenePresence(client.AgentId); |
||
1086 | |||
1087 | List<CachedTextureResponseArg> cachedTextureResponse = new List<CachedTextureResponseArg>(); |
||
1088 | foreach (CachedTextureRequestArg request in cachedTextureRequest) |
||
1089 | { |
||
1090 | UUID texture = UUID.Zero; |
||
1091 | int index = request.BakedTextureIndex; |
||
1092 | |||
1093 | if (m_reusetextures) |
||
1094 | { |
||
1095 | // this is the most insanely dumb way to do this... however it seems to |
||
1096 | // actually work. if the appearance has been reset because wearables have |
||
1097 | // changed then the texture entries are zero'd out until the bakes are |
||
1098 | // uploaded. on login, if the textures exist in the cache (eg if you logged |
||
1099 | // into the simulator recently, then the appearance will pull those and send |
||
1100 | // them back in the packet and you won't have to rebake. if the textures aren't |
||
1101 | // in the cache then the intial makeroot() call in scenepresence will zero |
||
1102 | // them out. |
||
1103 | // |
||
1104 | // a better solution (though how much better is an open question) is to |
||
1105 | // store the hashes in the appearance and compare them. Thats's coming. |
||
1106 | |||
1107 | Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[index]; |
||
1108 | if (face != null) |
||
1109 | texture = face.TextureID; |
||
1110 | |||
1111 | // m_log.WarnFormat("[AVFACTORY]: reuse texture {0} for index {1}",texture,index); |
||
1112 | } |
||
1113 | |||
1114 | CachedTextureResponseArg response = new CachedTextureResponseArg(); |
||
1115 | response.BakedTextureIndex = index; |
||
1116 | response.BakedTextureID = texture; |
||
1117 | response.HostName = null; |
||
1118 | |||
1119 | cachedTextureResponse.Add(response); |
||
1120 | } |
||
1121 | |||
1122 | // m_log.WarnFormat("[AVFACTORY]: serial is {0}",serial); |
||
1123 | // The serial number appears to be used to match requests and responses |
||
1124 | // in the texture transaction. We just send back the serial number |
||
1125 | // that was provided in the request. The viewer bumps this for us. |
||
1126 | client.SendCachedTextureResponse(sp, serial, cachedTextureResponse); |
||
1127 | } |
||
1128 | |||
1129 | |||
1130 | #endregion |
||
1131 | |||
1132 | public void WriteBakedTexturesReport(IScenePresence sp, ReportOutputAction outputAction) |
||
1133 | { |
||
1134 | outputAction("For {0} in {1}", sp.Name, m_scene.RegionInfo.RegionName); |
||
1135 | outputAction(BAKED_TEXTURES_REPORT_FORMAT, "Bake Type", "UUID"); |
||
1136 | |||
1137 | Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures = GetBakedTextureFaces(sp.UUID); |
||
1138 | |||
1139 | foreach (BakeType bt in bakedTextures.Keys) |
||
1140 | { |
||
1141 | string rawTextureID; |
||
1142 | |||
1143 | if (bakedTextures[bt] == null) |
||
1144 | { |
||
1145 | rawTextureID = "not set"; |
||
1146 | } |
||
1147 | else |
||
1148 | { |
||
1149 | rawTextureID = bakedTextures[bt].TextureID.ToString(); |
||
1150 | |||
1151 | if (m_scene.AssetService.Get(rawTextureID) == null) |
||
1152 | rawTextureID += " (not found)"; |
||
1153 | else |
||
1154 | rawTextureID += " (uploaded)"; |
||
1155 | } |
||
1156 | |||
1157 | outputAction(BAKED_TEXTURES_REPORT_FORMAT, bt, rawTextureID); |
||
1158 | } |
||
1159 | |||
1160 | bool bakedTextureValid = m_scene.AvatarFactory.ValidateBakedTextureCache(sp); |
||
1161 | outputAction("{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "incomplete"); |
||
1162 | } |
||
1163 | } |
||
1164 | } |