Files
libremetaverse/libsecondlife-cs/AssetSystem/AppearanceManager.cs
John Hurliman 3a94792761 * Reworked the Login functions, added documentation and (untested) support for login redirects
* Converted _VisualParam_ back to a static array after further analysis
* Rewrote AppearanceManager to work with the VisualParam rewrite
* Cleaned up ImageTool
* Minor cleanup and FIXME note in name2key.exe
* Cleanup and FIXME note in Teleport.exe
* Reorganized the function naming in GridManager to make more sense (not completely done here)
* Added an enum for requesting the object or terrain layer for the map blocks, and more sun properties
* Made some of the MainAvatar properties read-only (eventually they will all be read-only)
* Added try/catch safeties in openjpegnet
* Finally killed the evil SecondLife.Tick()

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@905 52acb1d6-8a22-11de-b505-999d5b087335
2007-01-26 22:01:56 +00:00

498 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using libsecondlife;
using libsecondlife.AssetSystem;
using libsecondlife.InventorySystem;
using libsecondlife.Packets;
namespace libsecondlife.AssetSystem
{
public class AppearanceManager
{
public enum WearableType
{
Shape = 0,
Skin = 1,
Hair = 2,
Eyes = 3,
Shirt = 4,
Pants = 5,
Shoes = 6,
Socks = 7,
Jacket = 8,
Gloves = 9,
Undershirt = 10,
Underpants = 11,
Skirt = 12,
Count = 13,
Invalid = 255
};
protected SecondLife Client;
protected AssetManager AManager;
protected uint SerialNum = 1;
protected ManualResetEvent AgentWearablesSignal = null;
protected Dictionary<LLUUID, AssetWearable> WearableCache = new Dictionary<LLUUID, AssetWearable>();
protected List<LLUUID> WearableAssetQueue = new List<LLUUID>();
// This data defines all appearance info for an avatar
public AgentWearablesUpdatePacket.WearableDataBlock[] AgentWearablesData;
public SerializableDictionary<int, float> AgentAppearanceParams = new SerializableDictionary<int, float>();
public TextureEntry AgentTextureEntry = new TextureEntry("C228D1CF4B5D4BA884F4899A0796AA97"); // if this isn't valid, blame JH ;-)
/// <summary>
///
/// </summary>
/// <param name="client"></param>
public AppearanceManager(SecondLife client)
{
Client = client;
Client.Network.RegisterCallback(libsecondlife.Packets.PacketType.AgentWearablesUpdate, new NetworkManager.PacketCallback(AgentWearablesUpdateCallbackHandler));
AManager = client.Assets;
AManager.TransferRequestCompletedEvent += new AssetManager.On_TransferRequestCompleted(AManager_TransferRequestCompletedEvent);
}
#region Wear Stuff
/// <summary>
/// Add a single wearable to your outfit, replacing if nessesary.
/// </summary>
/// <param name="wearable"></param>
public void Wear(InventoryWearable wearable)
{
List<InventoryWearable> x = new List<InventoryWearable>();
x.Add(wearable);
Wear(x);
}
/// <summary>
/// Add the specified wearables to your outfit, replace existing ones if nessesary.
/// </summary>
/// <param name="wearables"></param>
public void Wear(List<InventoryWearable> wearables)
{
// Make sure we have some Wearable Data to start with.
if ((AgentWearablesData == null) || (AgentWearablesData.Length == 0))
{
GetWearables();
}
// Update with specified wearables
foreach (InventoryWearable iw in wearables)
{
byte type = ((AssetWearable)iw.Asset).TypeFromAsset;
AgentWearablesData[type].ItemID = iw.ItemID;
AgentWearablesData[type].AssetID = iw.AssetID;
}
// Create AgentIsNowWearing Packet, and send it
SendAgentIsNowWearing();
// Update local Appearance Info
GetAvatarAppearanceInfoFromWearableAssets();
// Send updated AgentSetAppearance to the grid
SendAgentSetAppearance();
}
/// <summary>
/// Equivalent to the SL "Replace Outfit" command. All clothing is removed, and replaced with wearables in given folder. Body wearables will be replaced if provided.
/// </summary>
/// <param name="outfitFolder">Contains the wearable items to put on.</param>
public void WearOutfit(InventoryFolder outfitFolder)
{
WearOutfit(outfitFolder, 10000);
}
/// <summary>
/// Equivalent to the SL "Replace Outfit" command. All clothing is removed, and replaced with wearables in given folder. Body wearables will be replaced if provided.
/// </summary>
/// <param name="outfitFolder">Contains the wearable items to put on.</param>
/// <param name="TimeOut">How long to wait for outfit directory information to download</param>
public void WearOutfit(InventoryFolder outfitFolder, int TimeOut)
{
// Refresh download of outfit folder
if (!outfitFolder.RequestDownloadContents(false, false, true, true).RequestComplete.WaitOne(TimeOut, false))
{
Console.WriteLine("An error occured while downloads the folder contents of : " + outfitFolder.Name);
}
// Make sure we have some Wearable Data to start with.
if ((AgentWearablesData == null) || (AgentWearablesData.Length == 0))
{
GetWearables();
}
// Flush the cached clothing wearables so we can redefine them
for (byte i = 4; i <= 12; i++)
{
AgentWearablesData[i].ItemID = LLUUID.Zero;
AgentWearablesData[i].AssetID = LLUUID.Zero;
}
// Replace with wearables from Outfit folder
foreach (InventoryBase ib in outfitFolder.GetContents())
{
if (ib is InventoryWearable)
{
InventoryWearable iw = (InventoryWearable)ib;
byte type = ((AssetWearable)iw.Asset).TypeFromAsset;
AgentWearablesData[type].ItemID = iw.ItemID;
AgentWearablesData[type].AssetID = iw.AssetID;
}
}
// Create AgentIsNowWearing Packet, and send it
SendAgentIsNowWearing();
// Update local Appearance Info
GetAvatarAppearanceInfoFromWearableAssets();
// Send updated AgentSetAppearance to the grid
SendAgentSetAppearance();
}
#endregion
/// <summary>
/// Creates and sends an AgentIsNowWearing packet based on the local cached AgentWearablesData array.
/// </summary>
protected void SendAgentIsNowWearing()
{
AgentIsNowWearingPacket nowWearing = new AgentIsNowWearingPacket();
nowWearing.AgentData.AgentID = Client.Network.AgentID;
nowWearing.AgentData.SessionID = Client.Network.SessionID;
nowWearing.WearableData = new AgentIsNowWearingPacket.WearableDataBlock[13];
for (byte i = 0; i <= 12; i++)
{
nowWearing.WearableData[i] = new AgentIsNowWearingPacket.WearableDataBlock();
nowWearing.WearableData[i].WearableType = i;
nowWearing.WearableData[i].ItemID = AgentWearablesData[i].ItemID;
}
Client.Network.SendPacket(nowWearing);
}
/// <summary>
/// Request from the server what wearables we're currently wearing. Update cached info.
/// </summary>
/// <returns>The wearable info for what we're currently wearing</returns>
protected AgentWearablesUpdatePacket.WearableDataBlock[] GetWearables()
{
AgentWearablesSignal = new ManualResetEvent(false);
AgentWearablesRequestPacket p = new AgentWearablesRequestPacket();
p.AgentData.AgentID = Client.Network.AgentID;
p.AgentData.SessionID = Client.Network.SessionID;
Client.Network.SendPacket(p);
AgentWearablesSignal.WaitOne();
return AgentWearablesData;
}
/// <summary>
/// Update the local Avatar Appearance information based on the contents of the assets as defined in the cached wearable data info.
/// </summary>
protected void GetAvatarAppearanceInfoFromWearableAssets()
{
// Only request wearable data, if we have to.
if ((AgentWearablesData == null) || (AgentWearablesData.Length == 0))
{
GetWearables();
}
foreach (AgentWearablesUpdatePacket.WearableDataBlock wdb in AgentWearablesData)
{
if (wdb.ItemID == LLUUID.Zero)
{
continue;
}
AssetWearable wearableAsset;
switch (wdb.WearableType)
{
case 0:
case 1:
case 2:
case 3:
wearableAsset = new AssetWearable_Body(wdb.AssetID, null);
break;
default:
wearableAsset = new AssetWearable_Clothing(wdb.AssetID, null);
break;
}
AssetRequestDownload request = Client.Assets.RequestInventoryAsset(wearableAsset.AssetID, wearableAsset.Type);
if (request.Wait(AssetManager.DefaultTimeout) != AssetRequestDownload.RequestStatus.Success)
{
throw new Exception("Asset (" + wearableAsset.AssetID.ToStringHyphenated() + ") unavailable (" + request.StatusMsg + ")");
}
wearableAsset.SetAssetData(request.GetAssetData());
if ((wearableAsset.AssetData == null) || (wearableAsset.AssetData.Length == 0))
{
Client.Log("Asset retrieval failed for AssetID: " + wearableAsset.AssetID, Helpers.LogLevel.Warning);
}
UpdateAgentTextureEntryAndAppearanceParams(wearableAsset);
}
UpdateAgentTextureEntryOrder();
}
protected void UpdateAgentTextureEntryOrder()
{
// Correct the order of the textures
foreach (uint faceid in AgentTextureEntry.FaceTextures.Keys)
{
if (faceid > 18)
{
Client.Log("Unknown order for FaceID: " + faceid + Environment.NewLine +
"Your wearables define a face that we don't know the order of. Please " +
"capture a AgentSetAppearance packet for your current outfit and submit to " +
"static.sprocket@gmail.com, thanks!", Helpers.LogLevel.Info);
break;
}
}
//Re-order texture faces to match Linden Labs internal data structure.
TextureEntry te2 = new TextureEntry(AgentTextureEntry.DefaultTexture.TextureID);
te2.CreateFace(18).TextureID = AgentTextureEntry.GetFace(18).TextureID;
te2.CreateFace(17).TextureID = AgentTextureEntry.GetFace(17).TextureID;
te2.CreateFace(16).TextureID = AgentTextureEntry.GetFace(16).TextureID;
te2.CreateFace(15).TextureID = AgentTextureEntry.GetFace(15).TextureID;
te2.CreateFace(14).TextureID = AgentTextureEntry.GetFace(14).TextureID;
te2.CreateFace(13).TextureID = AgentTextureEntry.GetFace(13).TextureID;
te2.CreateFace(12).TextureID = AgentTextureEntry.GetFace(12).TextureID;
// I wonder if shoes are somewhere in here?
te2.CreateFace(7).TextureID = AgentTextureEntry.GetFace(7).TextureID;
te2.CreateFace(6).TextureID = AgentTextureEntry.GetFace(6).TextureID;
te2.CreateFace(5).TextureID = AgentTextureEntry.GetFace(5).TextureID;
te2.CreateFace(4).TextureID = AgentTextureEntry.GetFace(4).TextureID;
te2.CreateFace(3).TextureID = AgentTextureEntry.GetFace(3).TextureID;
te2.CreateFace(2).TextureID = AgentTextureEntry.GetFace(2).TextureID;
te2.CreateFace(1).TextureID = AgentTextureEntry.GetFace(1).TextureID;
te2.CreateFace(0).TextureID = AgentTextureEntry.GetFace(0).TextureID;
AgentTextureEntry = te2;
}
protected void UpdateAgentTextureEntryAndAppearanceParams(AssetWearable wearableAsset)
{
try
{
foreach (KeyValuePair<uint, LLUUID> texture in wearableAsset.Textures)
{
AgentTextureEntry.CreateFace(texture.Key).TextureID = texture.Value;
}
lock (AgentAppearanceParams)
{
foreach (KeyValuePair<int, float> kvp in wearableAsset.Parameters)
{
AgentAppearanceParams[kvp.Key] = kvp.Value;
}
}
}
catch (Exception e)
{
Client.Log(e.ToString() + Environment.NewLine + wearableAsset.AssetDataToString(), Helpers.LogLevel.Error);
}
}
/// <summary>
/// Non-blocking async request of wearables, construction and sending of AgentSetAppearance
/// </summary>
public void BeginAgentSendAppearance()
{
AgentWearablesSignal = new ManualResetEvent(false);
AgentWearablesRequestPacket p = new AgentWearablesRequestPacket();
p.AgentData.AgentID = Client.Network.AgentID;
p.AgentData.SessionID = Client.Network.SessionID;
Client.Network.SendPacket(p);
}
/// <summary>
/// Send an AgentSetAppearance packet to the server to update your appearance.
/// </summary>
public void SendAgentSetAppearance()
{
// Get latest appearance info
if (AgentAppearanceParams.Count == 0)
{
GetAvatarAppearanceInfoFromWearableAssets();
}
AgentSetAppearancePacket p = new AgentSetAppearancePacket();
p.AgentData.AgentID = Client.Network.AgentID;
p.AgentData.SessionID = Client.Network.SessionID;
p.AgentData.SerialNum = ++SerialNum;
// Add Texture Data
p.ObjectData.TextureEntry = AgentTextureEntry.ToBytes();
p.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[218];
// Add Visual Params
lock (AgentAppearanceParams)
{
for (int i = 0; i < 218; i++)
{
VisualParam param = VisualParams.Params[i];
p.VisualParam[i] = new AgentSetAppearancePacket.VisualParamBlock();
if (AgentAppearanceParams.ContainsKey(param.ParamID))
{
p.VisualParam[i].ParamValue = Helpers.FloatToByte(AgentAppearanceParams[param.ParamID],
param.MinValue, param.MaxValue);
}
else
{
// Use the default value for this parameter
p.VisualParam[i].ParamValue = Helpers.FloatToByte(param.DefaultValue, param.MinValue,
param.MaxValue);
}
}
}
// Add Size Data
p.AgentData.Size = GetAgentSizeFromVisualParam(Helpers.ByteToFloat(p.VisualParam[25].ParamValue,
VisualParams.Params[25].MinValue, VisualParams.Params[25].MaxValue));
Client.Network.SendPacket(p);
}
/// <summary>
/// Determine agent size for AgentSetAppearance based on Visual Param data.
/// </summary>
/// <param name="heightParam"></param>
/// <returns></returns>
protected LLVector3 GetAgentSizeFromVisualParam(float heightParam)
{
float AV_Height_Range = 2.025506f - 1.50856f;
float AV_Height = 1.50856f + ((heightParam / 255.0f) * AV_Height_Range);
return new LLVector3(0.45f, 0.6f, AV_Height);
//return new LLVector3(0.45f, 0.6f, 1.0f);
}
#region Callback Handlers
private void AgentWearablesUpdateCallbackHandler(Packet packet, Simulator simulator)
{
AgentWearablesUpdatePacket wearablesPacket = (AgentWearablesUpdatePacket)packet;
AgentWearablesData = wearablesPacket.WearableData;
AgentWearablesSignal.Set();
// Queue download of wearables
foreach (AgentWearablesUpdatePacket.WearableDataBlock wdb in AgentWearablesData)
{
// Don't try to download if AssetID is zero
if (wdb.AssetID == LLUUID.Zero)
{
continue;
}
// Don't try to download, if it's already cached.
if (WearableCache.ContainsKey(wdb.AssetID))
{
AssetWearable aw = WearableCache[wdb.AssetID];
if (aw._AssetData != null)
{
continue;
}
}
// Don't try to download, if it's already in the download queue
if (WearableAssetQueue.Contains(wdb.AssetID))
{
continue;
}
AssetWearable wearableAsset;
switch (wdb.WearableType)
{
case 0:
case 1:
case 2:
case 3:
wearableAsset = new AssetWearable_Body(wdb.AssetID, null);
break;
default:
wearableAsset = new AssetWearable_Clothing(wdb.AssetID, null);
break;
}
WearableCache[wdb.AssetID] = wearableAsset;
WearableAssetQueue.Add(wdb.AssetID);
AssetRequestDownload request = Client.Assets.RequestInventoryAsset(wearableAsset.AssetID, wearableAsset.Type);
}
}
void AManager_TransferRequestCompletedEvent(AssetRequest request)
{
if( !(request is AssetRequestDownload) )
{
return;
}
AssetRequestDownload dlrequest = (AssetRequestDownload)request;
// Remove from the download queue
if (WearableAssetQueue.Contains(dlrequest.AssetID))
{
WearableAssetQueue.Remove(dlrequest.AssetID);
if (request.Status != AssetRequest.RequestStatus.Success)
{
return;
}
AssetWearable wearableAsset = WearableCache[dlrequest.AssetID];
wearableAsset.SetAssetData(dlrequest.GetAssetData());
if ((wearableAsset.AssetData == null) || (wearableAsset.AssetData.Length == 0))
{
Client.Log("Asset retrieval failed for AssetID: " + wearableAsset.AssetID, Helpers.LogLevel.Warning);
}
else
{
UpdateAgentTextureEntryAndAppearanceParams(wearableAsset);
UpdateAgentTextureEntryOrder();
if (WearableAssetQueue.Count == 0)
{
SendAgentSetAppearance();
}
}
}
}
#endregion
}
}