Renamed and rearranged AvatarFactoryModule to eliminate redundant lookups of scene presence by client ID.
parent
32c3faedd6
commit
da794f34a5
|
@ -42,7 +42,7 @@ using OpenSim.Services.Interfaces;
|
||||||
|
|
||||||
namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
{
|
{
|
||||||
public class AvatarFactoryModule : IAvatarFactory, IRegionModule
|
public class AvatarFactoryModule : IAvatarFactoryModule, IRegionModule
|
||||||
{
|
{
|
||||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
private Scene m_scene = null;
|
private Scene m_scene = null;
|
||||||
|
@ -57,12 +57,12 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
|
|
||||||
private object m_setAppearanceLock = new object();
|
private object m_setAppearanceLock = new object();
|
||||||
|
|
||||||
#region RegionModule Members
|
#region IRegionModule
|
||||||
|
|
||||||
public void Initialise(Scene scene, IConfigSource config)
|
public void Initialise(Scene scene, IConfigSource config)
|
||||||
{
|
{
|
||||||
scene.RegisterModuleInterface<IAvatarFactory>(this);
|
scene.RegisterModuleInterface<IAvatarFactoryModule>(this);
|
||||||
scene.EventManager.OnNewClient += NewClient;
|
scene.EventManager.OnNewClient += SubscribeToClientEvents;
|
||||||
|
|
||||||
IConfig sconfig = config.Configs["Startup"];
|
IConfig sconfig = config.Configs["Startup"];
|
||||||
if (sconfig != null)
|
if (sconfig != null)
|
||||||
|
@ -98,104 +98,26 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
get { return false; }
|
get { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NewClient(IClientAPI client)
|
private void SubscribeToClientEvents(IClientAPI client)
|
||||||
{
|
{
|
||||||
client.OnRequestWearables += SendWearables;
|
client.OnRequestWearables += Client_OnRequestWearables;
|
||||||
client.OnSetAppearance += SetAppearanceFromClient;
|
client.OnSetAppearance += Client_OnSetAppearance;
|
||||||
client.OnAvatarNowWearing += AvatarIsWearing;
|
client.OnAvatarNowWearing += Client_OnAvatarNowWearing;
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveClient(IClientAPI client)
|
|
||||||
{
|
|
||||||
// client.OnAvatarNowWearing -= AvatarIsWearing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
#region IAvatarFactoryModule
|
||||||
/// Check for the existence of the baked texture assets.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
public bool ValidateBakedTextureCache(IClientAPI client)
|
|
||||||
{
|
|
||||||
return ValidateBakedTextureCache(client, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check for the existence of the baked texture assets. Request a rebake
|
/// Set appearance data (texture asset IDs and slider settings)
|
||||||
/// unless checkonly is true.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="sp"></param>
|
||||||
/// <param name="checkonly"></param>
|
|
||||||
private bool ValidateBakedTextureCache(IClientAPI client, bool checkonly)
|
|
||||||
{
|
|
||||||
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
|
||||||
if (sp == null)
|
|
||||||
{
|
|
||||||
m_log.WarnFormat("[AVFACTORY]: SetAppearance unable to find presence for {0}", client.AgentId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool defonly = true; // are we only using default textures
|
|
||||||
|
|
||||||
// Process the texture entry
|
|
||||||
for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++)
|
|
||||||
{
|
|
||||||
int idx = AvatarAppearance.BAKE_INDICES[i];
|
|
||||||
Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx];
|
|
||||||
|
|
||||||
// if there is no texture entry, skip it
|
|
||||||
if (face == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// m_log.DebugFormat(
|
|
||||||
// "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}",
|
|
||||||
// face.TextureID, idx, client.Name, client.AgentId);
|
|
||||||
|
|
||||||
// if the texture is one of the "defaults" then skip it
|
|
||||||
// this should probably be more intelligent (skirt texture doesnt matter
|
|
||||||
// if the avatar isnt wearing a skirt) but if any of the main baked
|
|
||||||
// textures is default then the rest should be as well
|
|
||||||
if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
defonly = false; // found a non-default texture reference
|
|
||||||
|
|
||||||
if (!CheckBakedTextureAsset(client, face.TextureID, idx))
|
|
||||||
{
|
|
||||||
// the asset didn't exist if we are only checking, then we found a bad
|
|
||||||
// one and we're done otherwise, ask for a rebake
|
|
||||||
if (checkonly)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_log.InfoFormat("[AVFACTORY]: missing baked texture {0}, requesting rebake", face.TextureID);
|
|
||||||
|
|
||||||
client.SendRebakeAvatarTextures(face.TextureID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_log.DebugFormat("[AVFACTORY]: Completed texture check for {0}", client.AgentId);
|
|
||||||
|
|
||||||
// If we only found default textures, then the appearance is not cached
|
|
||||||
return (defonly ? false : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set appearance data (texture asset IDs and slider settings) received from the client
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="texture"></param>
|
/// <param name="texture"></param>
|
||||||
/// <param name="visualParam"></param>
|
/// <param name="visualParam"></param>
|
||||||
public void SetAppearanceFromClient(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams)
|
public void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams)
|
||||||
{
|
{
|
||||||
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
// m_log.InfoFormat("[AVFACTORY]: start SetAppearance for {0}", client.AgentId);
|
||||||
if (sp == null)
|
|
||||||
{
|
|
||||||
m_log.WarnFormat("[AVFACTORY]: SetAppearance unable to find presence for {0}", client.AgentId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// m_log.InfoFormat("[AVFACTORY]: start SetAppearance for {0}", client.AgentId);
|
|
||||||
|
|
||||||
// TODO: This is probably not necessary any longer, just assume the
|
// TODO: This is probably not necessary any longer, just assume the
|
||||||
// textureEntry set implies that the appearance transaction is complete
|
// textureEntry set implies that the appearance transaction is complete
|
||||||
|
@ -208,18 +130,18 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
// Process the visual params, this may change height as well
|
// Process the visual params, this may change height as well
|
||||||
if (visualParams != null)
|
if (visualParams != null)
|
||||||
{
|
{
|
||||||
// string[] visualParamsStrings = new string[visualParams.Length];
|
// string[] visualParamsStrings = new string[visualParams.Length];
|
||||||
// for (int i = 0; i < visualParams.Length; i++)
|
// for (int i = 0; i < visualParams.Length; i++)
|
||||||
// visualParamsStrings[i] = visualParams[i].ToString();
|
// visualParamsStrings[i] = visualParams[i].ToString();
|
||||||
// m_log.DebugFormat(
|
// m_log.DebugFormat(
|
||||||
// "[AVFACTORY]: Setting visual params for {0} to {1}",
|
// "[AVFACTORY]: Setting visual params for {0} to {1}",
|
||||||
// client.Name, string.Join(", ", visualParamsStrings));
|
// client.Name, string.Join(", ", visualParamsStrings));
|
||||||
|
|
||||||
float oldHeight = sp.Appearance.AvatarHeight;
|
float oldHeight = sp.Appearance.AvatarHeight;
|
||||||
changed = sp.Appearance.SetVisualParams(visualParams);
|
changed = sp.Appearance.SetVisualParams(visualParams);
|
||||||
|
|
||||||
if (sp.Appearance.AvatarHeight != oldHeight && sp.Appearance.AvatarHeight > 0)
|
if (sp.Appearance.AvatarHeight != oldHeight && sp.Appearance.AvatarHeight > 0)
|
||||||
sp.SetHeight(sp.Appearance.AvatarHeight);
|
((ScenePresence)sp).SetHeight(sp.Appearance.AvatarHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the baked texture array
|
// Process the baked texture array
|
||||||
|
@ -227,39 +149,39 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
{
|
{
|
||||||
changed = sp.Appearance.SetTextureEntries(textureEntry) || changed;
|
changed = sp.Appearance.SetTextureEntries(textureEntry) || changed;
|
||||||
|
|
||||||
m_log.InfoFormat("[AVFACTORY]: received texture update for {0}", client.AgentId);
|
m_log.InfoFormat("[AVFACTORY]: received texture update for {0}", sp.UUID);
|
||||||
Util.FireAndForget(delegate(object o) { ValidateBakedTextureCache(client, false); });
|
Util.FireAndForget(delegate(object o) { ValidateBakedTextureCache(sp, false); });
|
||||||
|
|
||||||
// This appears to be set only in the final stage of the appearance
|
// This appears to be set only in the final stage of the appearance
|
||||||
// update transaction. In theory, we should be able to do an immediate
|
// update transaction. In theory, we should be able to do an immediate
|
||||||
// appearance send and save here.
|
// appearance send and save here.
|
||||||
|
|
||||||
}
|
}
|
||||||
// save only if there were changes, send no matter what (doesn't hurt to send twice)
|
// save only if there were changes, send no matter what (doesn't hurt to send twice)
|
||||||
if (changed)
|
if (changed)
|
||||||
QueueAppearanceSave(client.AgentId);
|
QueueAppearanceSave(sp.ControllingClient.AgentId);
|
||||||
|
|
||||||
QueueAppearanceSend(client.AgentId);
|
QueueAppearanceSend(sp.ControllingClient.AgentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_log.WarnFormat("[AVFACTORY]: complete SetAppearance for {0}:\n{1}",client.AgentId,sp.Appearance.ToString());
|
// m_log.WarnFormat("[AVFACTORY]: complete SetAppearance for {0}:\n{1}",client.AgentId,sp.Appearance.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public bool SendAppearance(UUID agentId)
|
||||||
/// Checks for the existance of a baked texture asset and
|
|
||||||
/// requests the viewer rebake if the asset is not found
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="textureID"></param>
|
|
||||||
/// <param name="idx"></param>
|
|
||||||
private bool CheckBakedTextureAsset(IClientAPI client, UUID textureID, int idx)
|
|
||||||
{
|
{
|
||||||
if (m_scene.AssetService.Get(textureID.ToString()) == null)
|
ScenePresence sp = m_scene.GetScenePresence(agentId);
|
||||||
|
if (sp == null)
|
||||||
{
|
{
|
||||||
m_log.WarnFormat("[AVFACTORY]: Missing baked texture {0} ({1}) for avatar {2}",
|
m_log.WarnFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentId);
|
||||||
textureID, idx, client.Name);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the appearance to everyone in the scene
|
||||||
|
sp.SendAppearanceToAllOtherAgents();
|
||||||
|
|
||||||
|
// Send animations back to the avatar as well
|
||||||
|
sp.Animator.SendAnimPack();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,35 +195,6 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
return GetBakedTextureFaces(sp);
|
return GetBakedTextureFaces(sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<BakeType, Primitive.TextureEntryFace> GetBakedTextureFaces(ScenePresence sp)
|
|
||||||
{
|
|
||||||
if (sp.IsChildAgent)
|
|
||||||
return new Dictionary<BakeType, Primitive.TextureEntryFace>();
|
|
||||||
|
|
||||||
Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures
|
|
||||||
= new Dictionary<BakeType, Primitive.TextureEntryFace>();
|
|
||||||
|
|
||||||
AvatarAppearance appearance = sp.Appearance;
|
|
||||||
Primitive.TextureEntryFace[] faceTextures = appearance.Texture.FaceTextures;
|
|
||||||
|
|
||||||
foreach (int i in Enum.GetValues(typeof(BakeType)))
|
|
||||||
{
|
|
||||||
BakeType bakeType = (BakeType)i;
|
|
||||||
|
|
||||||
if (bakeType == BakeType.Unknown)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// m_log.DebugFormat(
|
|
||||||
// "[AVFACTORY]: NPC avatar {0} has texture id {1} : {2}",
|
|
||||||
// acd.AgentID, i, acd.Appearance.Texture.FaceTextures[i]);
|
|
||||||
|
|
||||||
int ftIndex = (int)AppearanceManager.BakeTypeToAgentTextureIndex(bakeType);
|
|
||||||
bakedTextures[bakeType] = faceTextures[ftIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
return bakedTextures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SaveBakedTextures(UUID agentId)
|
public bool SaveBakedTextures(UUID agentId)
|
||||||
{
|
{
|
||||||
ScenePresence sp = m_scene.GetScenePresence(agentId);
|
ScenePresence sp = m_scene.GetScenePresence(agentId);
|
||||||
|
@ -346,35 +239,17 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
bakedTextureFace.TextureID, bakeType, sp.Name, m_scene.RegionInfo.RegionName);
|
bakedTextureFace.TextureID, bakeType, sp.Name, m_scene.RegionInfo.RegionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for (int i = 0; i < faceTextures.Length; i++)
|
|
||||||
// {
|
|
||||||
//// m_log.DebugFormat(
|
|
||||||
//// "[AVFACTORY]: NPC avatar {0} has texture id {1} : {2}",
|
|
||||||
//// acd.AgentID, i, acd.Appearance.Texture.FaceTextures[i]);
|
|
||||||
//
|
|
||||||
// if (faceTextures[i] == null)
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// AssetBase asset = m_scene.AssetService.Get(faceTextures[i].TextureID.ToString());
|
|
||||||
//
|
|
||||||
// if (asset != null)
|
|
||||||
// {
|
|
||||||
// asset.Temporary = false;
|
|
||||||
// m_scene.AssetService.Store(asset);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// m_log.WarnFormat(
|
|
||||||
// "[AV FACTORY]: Baked texture {0} for {1} in {2} not found when trying to save permanently",
|
|
||||||
// faceTextures[i].TextureID, sp.Name, m_scene.RegionInfo.RegionName);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region UpdateAppearanceTimer
|
/// <summary>
|
||||||
|
/// Check for the existence of the baked texture assets.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sp"></param>
|
||||||
|
public bool ValidateBakedTextureCache(IScenePresence sp)
|
||||||
|
{
|
||||||
|
return ValidateBakedTextureCache(sp, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue up a request to send appearance, makes it possible to
|
/// Queue up a request to send appearance, makes it possible to
|
||||||
|
@ -406,30 +281,107 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveAppearance(UUID agentid)
|
#endregion
|
||||||
{
|
|
||||||
// We must set appearance parameters in the en_US culture in order to avoid issues where values are saved
|
|
||||||
// in a culture where decimal points are commas and then reloaded in a culture which just treats them as
|
|
||||||
// number seperators.
|
|
||||||
Culture.SetCurrentCulture();
|
|
||||||
|
|
||||||
ScenePresence sp = m_scene.GetScenePresence(agentid);
|
#region AvatarFactoryModule private methods
|
||||||
if (sp == null)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check for the existence of the baked texture assets. Request a rebake
|
||||||
|
/// unless checkonly is true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="checkonly"></param>
|
||||||
|
private bool ValidateBakedTextureCache(IScenePresence sp, bool checkonly)
|
||||||
|
{
|
||||||
|
bool defonly = true; // are we only using default textures
|
||||||
|
|
||||||
|
// Process the texture entry
|
||||||
|
for (int i = 0; i < AvatarAppearance.BAKE_INDICES.Length; i++)
|
||||||
{
|
{
|
||||||
m_log.WarnFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentid);
|
int idx = AvatarAppearance.BAKE_INDICES[i];
|
||||||
return;
|
Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx];
|
||||||
|
|
||||||
|
// if there is no texture entry, skip it
|
||||||
|
if (face == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// m_log.DebugFormat(
|
||||||
|
// "[AVFACTORY]: Looking for texture {0}, id {1} for {2} {3}",
|
||||||
|
// face.TextureID, idx, client.Name, client.AgentId);
|
||||||
|
|
||||||
|
// if the texture is one of the "defaults" then skip it
|
||||||
|
// this should probably be more intelligent (skirt texture doesnt matter
|
||||||
|
// if the avatar isnt wearing a skirt) but if any of the main baked
|
||||||
|
// textures is default then the rest should be as well
|
||||||
|
if (face.TextureID == UUID.Zero || face.TextureID == AppearanceManager.DEFAULT_AVATAR_TEXTURE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
defonly = false; // found a non-default texture reference
|
||||||
|
|
||||||
|
if (!CheckBakedTextureAsset(sp, face.TextureID, idx))
|
||||||
|
{
|
||||||
|
// the asset didn't exist if we are only checking, then we found a bad
|
||||||
|
// one and we're done otherwise, ask for a rebake
|
||||||
|
if (checkonly)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_log.InfoFormat("[AVFACTORY]: missing baked texture {0}, requesting rebake", face.TextureID);
|
||||||
|
|
||||||
|
sp.ControllingClient.SendRebakeAvatarTextures(face.TextureID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_log.WarnFormat("[AVFACTORY] avatar {0} save appearance",agentid);
|
m_log.DebugFormat("[AVFACTORY]: Completed texture check for {0}", sp.UUID);
|
||||||
|
|
||||||
// This could take awhile since it needs to pull inventory
|
// If we only found default textures, then the appearance is not cached
|
||||||
// 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
|
return (defonly ? false : true);
|
||||||
// assets and item asset id changes to complete.
|
}
|
||||||
// I don't think we need to worry about doing this within m_setAppearanceLock since the queueing avoids
|
|
||||||
// multiple save requests.
|
|
||||||
SetAppearanceAssets(sp.UUID, sp.Appearance);
|
|
||||||
|
|
||||||
m_scene.AvatarService.SetAppearance(agentid, sp.Appearance);
|
/// <summary>
|
||||||
|
/// Checks for the existance of a baked texture asset and
|
||||||
|
/// requests the viewer rebake if the asset is not found
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sp"></param>
|
||||||
|
/// <param name="textureID"></param>
|
||||||
|
/// <param name="idx"></param>
|
||||||
|
private bool CheckBakedTextureAsset(IScenePresence sp, UUID textureID, int idx)
|
||||||
|
{
|
||||||
|
if (m_scene.AssetService.Get(textureID.ToString()) == null)
|
||||||
|
{
|
||||||
|
m_log.WarnFormat("[AVFACTORY]: Missing baked texture {0} ({1}) for avatar {2}",
|
||||||
|
textureID, idx, sp.Name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<BakeType, Primitive.TextureEntryFace> GetBakedTextureFaces(ScenePresence sp)
|
||||||
|
{
|
||||||
|
if (sp.IsChildAgent)
|
||||||
|
return new Dictionary<BakeType, Primitive.TextureEntryFace>();
|
||||||
|
|
||||||
|
Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures
|
||||||
|
= new Dictionary<BakeType, Primitive.TextureEntryFace>();
|
||||||
|
|
||||||
|
AvatarAppearance appearance = sp.Appearance;
|
||||||
|
Primitive.TextureEntryFace[] faceTextures = appearance.Texture.FaceTextures;
|
||||||
|
|
||||||
|
foreach (int i in Enum.GetValues(typeof(BakeType)))
|
||||||
|
{
|
||||||
|
BakeType bakeType = (BakeType)i;
|
||||||
|
|
||||||
|
if (bakeType == BakeType.Unknown)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// m_log.DebugFormat(
|
||||||
|
// "[AVFACTORY]: NPC avatar {0} has texture id {1} : {2}",
|
||||||
|
// acd.AgentID, i, acd.Appearance.Texture.FaceTextures[i]);
|
||||||
|
|
||||||
|
int ftIndex = (int)AppearanceManager.BakeTypeToAgentTextureIndex(bakeType);
|
||||||
|
bakedTextures[bakeType] = faceTextures[ftIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return bakedTextures;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleAppearanceUpdateTimer(object sender, EventArgs ea)
|
private void HandleAppearanceUpdateTimer(object sender, EventArgs ea)
|
||||||
|
@ -466,86 +418,30 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
m_updateTimer.Stop();
|
m_updateTimer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private void SaveAppearance(UUID agentid)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tell the client for this scene presence what items it should be wearing now
|
|
||||||
/// </summary>
|
|
||||||
public void SendWearables(IClientAPI client)
|
|
||||||
{
|
{
|
||||||
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
// We must set appearance parameters in the en_US culture in order to avoid issues where values are saved
|
||||||
|
// in a culture where decimal points are commas and then reloaded in a culture which just treats them as
|
||||||
|
// number seperators.
|
||||||
|
Culture.SetCurrentCulture();
|
||||||
|
|
||||||
|
ScenePresence sp = m_scene.GetScenePresence(agentid);
|
||||||
if (sp == null)
|
if (sp == null)
|
||||||
{
|
{
|
||||||
m_log.WarnFormat("[AVFACTORY]: SendWearables unable to find presence for {0}", client.AgentId);
|
m_log.WarnFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// m_log.DebugFormat("[AVFACTORY]: Received request for wearables of {0}", client.Name);
|
// m_log.WarnFormat("[AVFACTORY] avatar {0} save appearance",agentid);
|
||||||
|
|
||||||
client.SendWearables(sp.Appearance.Wearables, sp.Appearance.Serial++);
|
// This could take awhile since it needs to pull inventory
|
||||||
}
|
// 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
|
||||||
|
// assets and item asset id changes to complete.
|
||||||
|
// I don't think we need to worry about doing this within m_setAppearanceLock since the queueing avoids
|
||||||
|
// multiple save requests.
|
||||||
|
SetAppearanceAssets(sp.UUID, sp.Appearance);
|
||||||
|
|
||||||
/// <summary>
|
m_scene.AvatarService.SetAppearance(agentid, sp.Appearance);
|
||||||
/// Update what the avatar is wearing using an item from their inventory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="e"></param>
|
|
||||||
public void AvatarIsWearing(IClientAPI client, AvatarWearingArgs e)
|
|
||||||
{
|
|
||||||
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
|
||||||
if (sp == null)
|
|
||||||
{
|
|
||||||
m_log.WarnFormat("[AVFACTORY]: AvatarIsWearing unable to find presence for {0}", client.AgentId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// m_log.WarnFormat("[AVFACTORY]: AvatarIsWearing called for {0}", client.AgentId);
|
|
||||||
|
|
||||||
// we need to clean out the existing textures
|
|
||||||
sp.Appearance.ResetAppearance();
|
|
||||||
|
|
||||||
// operate on a copy of the appearance so we don't have to lock anything yet
|
|
||||||
AvatarAppearance avatAppearance = new AvatarAppearance(sp.Appearance, false);
|
|
||||||
|
|
||||||
foreach (AvatarWearingArgs.Wearable wear in e.NowWearing)
|
|
||||||
{
|
|
||||||
if (wear.Type < AvatarWearable.MAX_WEARABLES)
|
|
||||||
avatAppearance.Wearables[wear.Type].Add(wear.ItemID, UUID.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
avatAppearance.GetAssetsFrom(sp.Appearance);
|
|
||||||
|
|
||||||
lock (m_setAppearanceLock)
|
|
||||||
{
|
|
||||||
// Update only those fields that we have changed. This is important because the viewer
|
|
||||||
// often sends AvatarIsWearing and SetAppearance packets at once, and AvatarIsWearing
|
|
||||||
// shouldn't overwrite the changes made in SetAppearance.
|
|
||||||
sp.Appearance.Wearables = avatAppearance.Wearables;
|
|
||||||
sp.Appearance.Texture = avatAppearance.Texture;
|
|
||||||
|
|
||||||
// We don't need to send the appearance here since the "iswearing" will trigger a new set
|
|
||||||
// of visual param and baked texture changes. When those complete, the new appearance will be sent
|
|
||||||
|
|
||||||
QueueAppearanceSave(client.AgentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SendAppearance(UUID agentId)
|
|
||||||
{
|
|
||||||
ScenePresence sp = m_scene.GetScenePresence(agentId);
|
|
||||||
if (sp == null)
|
|
||||||
{
|
|
||||||
m_log.WarnFormat("[AVFACTORY]: Agent {0} no longer in the scene", agentId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the appearance to everyone in the scene
|
|
||||||
sp.SendAppearanceToAllOtherAgents();
|
|
||||||
|
|
||||||
// Send animations back to the avatar as well
|
|
||||||
sp.Animator.SendAnimPack();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetAppearanceAssets(UUID userID, AvatarAppearance appearance)
|
private void SetAppearanceAssets(UUID userID, AvatarAppearance appearance)
|
||||||
|
@ -587,5 +483,83 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
m_log.WarnFormat("[AVFACTORY]: user {0} has no inventory, appearance isn't going to work", userID);
|
m_log.WarnFormat("[AVFACTORY]: user {0} has no inventory, appearance isn't going to work", userID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Client Event Handlers
|
||||||
|
/// <summary>
|
||||||
|
/// Tell the client for this scene presence what items it should be wearing now
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
private void Client_OnRequestWearables(IClientAPI client)
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat("[AVFACTORY]: Client_OnRequestWearables called for {0} ({1})", client.Name, client.AgentId);
|
||||||
|
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
||||||
|
if (sp != null)
|
||||||
|
client.SendWearables(sp.Appearance.Wearables, sp.Appearance.Serial++);
|
||||||
|
else
|
||||||
|
m_log.WarnFormat("[AVFACTORY]: Client_OnRequestWearables unable to find presence for {0}", client.AgentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set appearance data (texture asset IDs and slider settings) received from a client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="texture"></param>
|
||||||
|
/// <param name="visualParam"></param>
|
||||||
|
private void Client_OnSetAppearance(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams)
|
||||||
|
{
|
||||||
|
// m_log.WarnFormat("[AVFACTORY]: Client_OnSetAppearance called for {0} ({1})", client.Name, client.AgentId);
|
||||||
|
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
||||||
|
if (sp != null)
|
||||||
|
SetAppearance(sp, textureEntry, visualParams);
|
||||||
|
else
|
||||||
|
m_log.WarnFormat("[AVFACTORY]: Client_OnSetAppearance unable to find presence for {0}", client.AgentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update what the avatar is wearing using an item from their inventory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void Client_OnAvatarNowWearing(IClientAPI client, AvatarWearingArgs e)
|
||||||
|
{
|
||||||
|
// m_log.WarnFormat("[AVFACTORY]: Client_OnAvatarNowWearing called for {0} ({1})", client.Name, client.AgentId);
|
||||||
|
ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
|
||||||
|
if (sp == null)
|
||||||
|
{
|
||||||
|
m_log.WarnFormat("[AVFACTORY]: Client_OnAvatarNowWearing unable to find presence for {0}", client.AgentId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to clean out the existing textures
|
||||||
|
sp.Appearance.ResetAppearance();
|
||||||
|
|
||||||
|
// operate on a copy of the appearance so we don't have to lock anything yet
|
||||||
|
AvatarAppearance avatAppearance = new AvatarAppearance(sp.Appearance, false);
|
||||||
|
|
||||||
|
foreach (AvatarWearingArgs.Wearable wear in e.NowWearing)
|
||||||
|
{
|
||||||
|
if (wear.Type < AvatarWearable.MAX_WEARABLES)
|
||||||
|
avatAppearance.Wearables[wear.Type].Add(wear.ItemID, UUID.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
avatAppearance.GetAssetsFrom(sp.Appearance);
|
||||||
|
|
||||||
|
lock (m_setAppearanceLock)
|
||||||
|
{
|
||||||
|
// Update only those fields that we have changed. This is important because the viewer
|
||||||
|
// often sends AvatarIsWearing and SetAppearance packets at once, and AvatarIsWearing
|
||||||
|
// shouldn't overwrite the changes made in SetAppearance.
|
||||||
|
sp.Appearance.Wearables = avatAppearance.Wearables;
|
||||||
|
sp.Appearance.Texture = avatAppearance.Texture;
|
||||||
|
|
||||||
|
// We don't need to send the appearance here since the "iswearing" will trigger a new set
|
||||||
|
// of visual param and baked texture changes. When those complete, the new appearance will be sent
|
||||||
|
|
||||||
|
QueueAppearanceSave(client.AgentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,15 +55,13 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
AvatarFactoryModule afm = new AvatarFactoryModule();
|
AvatarFactoryModule afm = new AvatarFactoryModule();
|
||||||
TestScene scene = SceneHelpers.SetupScene();
|
TestScene scene = SceneHelpers.SetupScene();
|
||||||
SceneHelpers.SetupSceneModules(scene, afm);
|
SceneHelpers.SetupSceneModules(scene, afm);
|
||||||
IClientAPI tc = SceneHelpers.AddScenePresence(scene, userId).ControllingClient;
|
ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId);
|
||||||
|
|
||||||
byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT];
|
byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT];
|
||||||
for (byte i = 0; i < visualParams.Length; i++)
|
for (byte i = 0; i < visualParams.Length; i++)
|
||||||
visualParams[i] = i;
|
visualParams[i] = i;
|
||||||
|
|
||||||
afm.SetAppearanceFromClient(tc, new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)), visualParams);
|
afm.SetAppearance(sp, new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)), visualParams);
|
||||||
|
|
||||||
ScenePresence sp = scene.GetScenePresence(userId);
|
|
||||||
|
|
||||||
// TODO: Check baked texture
|
// TODO: Check baked texture
|
||||||
Assert.AreEqual(visualParams, sp.Appearance.VisualParams);
|
Assert.AreEqual(visualParams, sp.Appearance.VisualParams);
|
||||||
|
@ -85,7 +83,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
AvatarFactoryModule afm = new AvatarFactoryModule();
|
AvatarFactoryModule afm = new AvatarFactoryModule();
|
||||||
TestScene scene = SceneHelpers.SetupScene(assetCache);
|
TestScene scene = SceneHelpers.SetupScene(assetCache);
|
||||||
SceneHelpers.SetupSceneModules(scene, afm);
|
SceneHelpers.SetupSceneModules(scene, afm);
|
||||||
IClientAPI tc = SceneHelpers.AddScenePresence(scene, userId).ControllingClient;
|
ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId);
|
||||||
|
|
||||||
// TODO: Use the actual BunchOfCaps functionality once we slot in the CapabilitiesModules
|
// TODO: Use the actual BunchOfCaps functionality once we slot in the CapabilitiesModules
|
||||||
AssetBase uploadedAsset;
|
AssetBase uploadedAsset;
|
||||||
|
@ -104,7 +102,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
|
||||||
Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex);
|
Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex);
|
||||||
eyesFace.TextureID = eyesTextureId;
|
eyesFace.TextureID = eyesTextureId;
|
||||||
|
|
||||||
afm.SetAppearanceFromClient(tc, bakedTextureEntry, visualParams);
|
afm.SetAppearance(sp, bakedTextureEntry, visualParams);
|
||||||
afm.SaveBakedTextures(userId);
|
afm.SaveBakedTextures(userId);
|
||||||
// Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures = afm.GetBakedTextureFaces(userId);
|
// Dictionary<BakeType, Primitive.TextureEntryFace> bakedTextures = afm.GetBakedTextureFaces(userId);
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,11 @@ using OpenSim.Framework;
|
||||||
|
|
||||||
namespace OpenSim.Region.Framework.Interfaces
|
namespace OpenSim.Region.Framework.Interfaces
|
||||||
{
|
{
|
||||||
public interface IAvatarFactory
|
public interface IAvatarFactoryModule
|
||||||
{
|
{
|
||||||
|
|
||||||
|
void SetAppearance(IScenePresence sp, Primitive.TextureEntry textureEntry, byte[] visualParams);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send the appearance of an avatar to others in the scene.
|
/// Send the appearance of an avatar to others in the scene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -57,7 +60,7 @@ namespace OpenSim.Region.Framework.Interfaces
|
||||||
/// <returns>true if a valid agent was found, false otherwise</returns>
|
/// <returns>true if a valid agent was found, false otherwise</returns>
|
||||||
bool SaveBakedTextures(UUID agentId);
|
bool SaveBakedTextures(UUID agentId);
|
||||||
|
|
||||||
bool ValidateBakedTextureCache(IClientAPI client);
|
bool ValidateBakedTextureCache(IScenePresence sp);
|
||||||
void QueueAppearanceSend(UUID agentid);
|
void QueueAppearanceSend(UUID agentid);
|
||||||
void QueueAppearanceSave(UUID agentid);
|
void QueueAppearanceSave(UUID agentid);
|
||||||
}
|
}
|
|
@ -135,7 +135,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
protected IXMLRPC m_xmlrpcModule;
|
protected IXMLRPC m_xmlrpcModule;
|
||||||
protected IWorldComm m_worldCommModule;
|
protected IWorldComm m_worldCommModule;
|
||||||
protected IAvatarFactory m_AvatarFactory;
|
protected IAvatarFactoryModule m_AvatarFactory;
|
||||||
protected IConfigSource m_config;
|
protected IConfigSource m_config;
|
||||||
protected IRegionSerialiserModule m_serialiser;
|
protected IRegionSerialiserModule m_serialiser;
|
||||||
protected IDialogModule m_dialogModule;
|
protected IDialogModule m_dialogModule;
|
||||||
|
@ -444,7 +444,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
public IAttachmentsModule AttachmentsModule { get; set; }
|
public IAttachmentsModule AttachmentsModule { get; set; }
|
||||||
|
|
||||||
public IAvatarFactory AvatarFactory
|
public IAvatarFactoryModule AvatarFactory
|
||||||
{
|
{
|
||||||
get { return m_AvatarFactory; }
|
get { return m_AvatarFactory; }
|
||||||
}
|
}
|
||||||
|
@ -1154,7 +1154,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
m_xmlrpcModule = RequestModuleInterface<IXMLRPC>();
|
m_xmlrpcModule = RequestModuleInterface<IXMLRPC>();
|
||||||
m_worldCommModule = RequestModuleInterface<IWorldComm>();
|
m_worldCommModule = RequestModuleInterface<IWorldComm>();
|
||||||
XferManager = RequestModuleInterface<IXfer>();
|
XferManager = RequestModuleInterface<IXfer>();
|
||||||
m_AvatarFactory = RequestModuleInterface<IAvatarFactory>();
|
m_AvatarFactory = RequestModuleInterface<IAvatarFactoryModule>();
|
||||||
AttachmentsModule = RequestModuleInterface<IAttachmentsModule>();
|
AttachmentsModule = RequestModuleInterface<IAttachmentsModule>();
|
||||||
m_serialiser = RequestModuleInterface<IRegionSerialiserModule>();
|
m_serialiser = RequestModuleInterface<IRegionSerialiserModule>();
|
||||||
m_dialogModule = RequestModuleInterface<IDialogModule>();
|
m_dialogModule = RequestModuleInterface<IDialogModule>();
|
||||||
|
|
|
@ -2538,7 +2538,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
// We have an appearance but we may not have the baked textures. Check the asset cache
|
// We have an appearance but we may not have the baked textures. Check the asset cache
|
||||||
// to see if all the baked textures are already here.
|
// to see if all the baked textures are already here.
|
||||||
if (m_scene.AvatarFactory != null)
|
if (m_scene.AvatarFactory != null)
|
||||||
cachedappearance = m_scene.AvatarFactory.ValidateBakedTextureCache(ControllingClient);
|
cachedappearance = m_scene.AvatarFactory.ValidateBakedTextureCache(this);
|
||||||
|
|
||||||
// If we aren't using a cached appearance, then clear out the baked textures
|
// If we aren't using a cached appearance, then clear out the baked textures
|
||||||
if (!cachedappearance)
|
if (!cachedappearance)
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance
|
||||||
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
|
|
||||||
protected Dictionary<UUID, Scene> m_scenes = new Dictionary<UUID, Scene>();
|
protected Dictionary<UUID, Scene> m_scenes = new Dictionary<UUID, Scene>();
|
||||||
protected IAvatarFactory m_avatarFactory;
|
protected IAvatarFactoryModule m_avatarFactory;
|
||||||
|
|
||||||
public string Name { get { return "Appearance Information Module"; } }
|
public string Name { get { return "Appearance Information Module"; } }
|
||||||
|
|
||||||
|
@ -106,14 +106,14 @@ namespace OpenSim.Region.OptionalModules.Avatar.Appearance
|
||||||
{
|
{
|
||||||
foreach (Scene scene in m_scenes.Values)
|
foreach (Scene scene in m_scenes.Values)
|
||||||
{
|
{
|
||||||
scene.ForEachClient(
|
scene.ForEachScenePresence(
|
||||||
delegate(IClientAPI client)
|
delegate(ScenePresence sp)
|
||||||
{
|
{
|
||||||
if (client is LLClientView && !((LLClientView)client).ChildAgentStatus())
|
if (sp.ControllingClient is LLClientView && !((LLClientView)sp.ControllingClient).ChildAgentStatus())
|
||||||
{
|
{
|
||||||
bool bakedTextureValid = scene.AvatarFactory.ValidateBakedTextureCache(client);
|
bool bakedTextureValid = scene.AvatarFactory.ValidateBakedTextureCache(sp);
|
||||||
MainConsole.Instance.OutputFormat(
|
MainConsole.Instance.OutputFormat(
|
||||||
"{0} baked appearance texture is {1}", client.Name, bakedTextureValid ? "OK" : "corrupt");
|
"{0} baked appearance texture is {1}", sp.Name, bakedTextureValid ? "OK" : "corrupt");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC
|
||||||
sp.Appearance = npcAppearance;
|
sp.Appearance = npcAppearance;
|
||||||
scene.AttachmentsModule.RezAttachments(sp);
|
scene.AttachmentsModule.RezAttachments(sp);
|
||||||
|
|
||||||
IAvatarFactory module = scene.RequestModuleInterface<IAvatarFactory>();
|
IAvatarFactoryModule module = scene.RequestModuleInterface<IAvatarFactoryModule>();
|
||||||
module.SendAppearance(sp.UUID);
|
module.SendAppearance(sp.UUID);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -106,7 +106,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests
|
||||||
// ScenePresence.SendInitialData() to reset our entire appearance.
|
// ScenePresence.SendInitialData() to reset our entire appearance.
|
||||||
scene.AssetService.Store(AssetHelpers.CreateAsset(originalFace8TextureId));
|
scene.AssetService.Store(AssetHelpers.CreateAsset(originalFace8TextureId));
|
||||||
|
|
||||||
afm.SetAppearanceFromClient(sp.ControllingClient, originalTe, null);
|
afm.SetAppearance(sp, originalTe, null);
|
||||||
|
|
||||||
INPCModule npcModule = scene.RequestModuleInterface<INPCModule>();
|
INPCModule npcModule = scene.RequestModuleInterface<INPCModule>();
|
||||||
UUID npcId = npcModule.CreateNPC("John", "Smith", new Vector3(128, 128, 30), scene, sp.Appearance);
|
UUID npcId = npcModule.CreateNPC("John", "Smith", new Vector3(128, 128, 30), scene, sp.Appearance);
|
||||||
|
|
|
@ -2405,7 +2405,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||||
|
|
||||||
protected LSL_Key SaveAppearanceToNotecard(ScenePresence sp, string notecard)
|
protected LSL_Key SaveAppearanceToNotecard(ScenePresence sp, string notecard)
|
||||||
{
|
{
|
||||||
IAvatarFactory appearanceModule = World.RequestModuleInterface<IAvatarFactory>();
|
IAvatarFactoryModule appearanceModule = World.RequestModuleInterface<IAvatarFactoryModule>();
|
||||||
|
|
||||||
if (appearanceModule != null)
|
if (appearanceModule != null)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue