Update LibreMetavese and add support for ResetParcelAcccessLists

This commit is contained in:
Madpeter 2025-09-28 00:11:54 +01:00
parent 8f79b49495
commit b890fdc54b
421 changed files with 51141 additions and 631 deletions

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2021-2022, Sjofn LLC. * Copyright (c) 2021-2025, Sjofn LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -418,8 +418,7 @@ namespace OpenMetaverse.StructuredData
{ {
if (!Attribute.IsDefined(field, typeof(NonSerializedAttribute))) if (!Attribute.IsDefined(field, typeof(NonSerializedAttribute)))
{ {
OSD serializedField; if (serialized.TryGetValue(field.Name, out var serializedField))
if (serialized.TryGetValue(field.Name, out serializedField))
field.SetValue(obj, ToObject(field.FieldType, serializedField)); field.SetValue(obj, ToObject(field.FieldType, serializedField));
} }
} }
@ -433,8 +432,8 @@ namespace OpenMetaverse.StructuredData
{ {
private readonly bool _mBool; private readonly bool _mBool;
private static readonly byte[] trueBinary = { 0x31 }; private static readonly byte[] TrueBinary = { 0x31 };
private static readonly byte[] falseBinary = { 0x30 }; private static readonly byte[] FalseBinary = { 0x30 };
public override OSDType Type => OSDType.Boolean; public override OSDType Type => OSDType.Boolean;
@ -447,7 +446,7 @@ namespace OpenMetaverse.StructuredData
public override int AsInteger() { return _mBool ? 1 : 0; } public override int AsInteger() { return _mBool ? 1 : 0; }
public override double AsReal() { return _mBool ? 1d : 0d; } public override double AsReal() { return _mBool ? 1d : 0d; }
public override string AsString() { return _mBool ? "1" : "0"; } public override string AsString() { return _mBool ? "1" : "0"; }
public override byte[] AsBinary() { return _mBool ? trueBinary : falseBinary; } public override byte[] AsBinary() { return _mBool ? TrueBinary : FalseBinary; }
public override OSD Copy() { return new OSDBoolean(_mBool); } public override OSD Copy() { return new OSDBoolean(_mBool); }
public override string ToString() { return AsString(); } public override string ToString() { return AsString(); }
@ -596,58 +595,50 @@ namespace OpenMetaverse.StructuredData
public override int AsInteger() public override int AsInteger()
{ {
double dbl; if (double.TryParse(_mString, out var dbl))
if (double.TryParse(_mString, out dbl))
return (int)Math.Floor(dbl); return (int)Math.Floor(dbl);
return 0; return 0;
} }
public override uint AsUInteger() public override uint AsUInteger()
{ {
double dbl; if (double.TryParse(_mString, out var dbl))
if (double.TryParse(_mString, out dbl))
return (uint)Math.Floor(dbl); return (uint)Math.Floor(dbl);
return 0; return 0;
} }
public override long AsLong() public override long AsLong()
{ {
double dbl; if (double.TryParse(_mString, out var dbl))
if (double.TryParse(_mString, out dbl))
return (long)Math.Floor(dbl); return (long)Math.Floor(dbl);
return 0; return 0;
} }
public override ulong AsULong() public override ulong AsULong()
{ {
double dbl; if (double.TryParse(_mString, out var dbl))
if (double.TryParse(_mString, out dbl))
return (ulong)Math.Floor(dbl); return (ulong)Math.Floor(dbl);
return 0; return 0;
} }
public override double AsReal() public override double AsReal()
{ {
double dbl; return double.TryParse(_mString, out var dbl) ? dbl : 0d;
return double.TryParse(_mString, out dbl) ? dbl : 0d;
} }
public override string AsString() { return _mString; } public override string AsString() { return _mString; }
public override byte[] AsBinary() { return Encoding.UTF8.GetBytes(_mString); } public override byte[] AsBinary() { return Encoding.UTF8.GetBytes(_mString); }
public override UUID AsUUID() public override UUID AsUUID()
{ {
UUID uuid; return UUID.TryParse(_mString, out var uuid) ? uuid : UUID.Zero;
return UUID.TryParse(_mString, out uuid) ? uuid : UUID.Zero;
} }
public override DateTime AsDate() public override DateTime AsDate()
{ {
DateTime dt; return DateTime.TryParse(_mString, out var dt) ? dt : Utils.Epoch;
return DateTime.TryParse(_mString, out dt) ? dt : Utils.Epoch;
} }
public override Uri AsUri() public override Uri AsUri()
{ {
Uri uri; return Uri.TryCreate(_mString, UriKind.RelativeOrAbsolute, out var uri) ? uri : null;
return Uri.TryCreate(_mString, UriKind.RelativeOrAbsolute, out uri) ? uri : null;
} }
public override string ToString() { return AsString(); } public override string ToString() { return AsString(); }
@ -900,12 +891,8 @@ namespace OpenMetaverse.StructuredData
public OSD this[string key] public OSD this[string key]
{ {
get get => _mMap.TryGetValue(key, out var llsd) ? llsd : new OSD();
{ set => _mMap[key] = value;
OSD llsd;
return _mMap.TryGetValue(key, out llsd) ? llsd : new OSD();
}
set { _mMap[key] = value; }
} }
public bool ContainsKey(string key) public bool ContainsKey(string key)
@ -940,9 +927,7 @@ namespace OpenMetaverse.StructuredData
public bool Contains(KeyValuePair<string, OSD> kvp) public bool Contains(KeyValuePair<string, OSD> kvp)
{ {
// This is a bizarre function... we don't really implement it return _mMap.Contains(kvp);
// properly, hopefully no one wants to use it
return _mMap.ContainsKey(kvp.Key);
} }
public void CopyTo(KeyValuePair<string, OSD>[] array, int index) public void CopyTo(KeyValuePair<string, OSD>[] array, int index)

View file

@ -1127,7 +1127,7 @@ namespace OpenMetaverse
} }
/// <summary> /// <summary>
/// Packs to 32-bit unsigned integers in to a 64-bit unsigned integer /// Packs two 32-bit unsigned integers in to a 64-bit unsigned integer
/// </summary> /// </summary>
/// <param name="a">The left-hand (or X) value</param> /// <param name="a">The left-hand (or X) value</param>
/// <param name="b">The right-hand (or Y) value</param> /// <param name="b">The right-hand (or Y) value</param>

View file

@ -1253,6 +1253,8 @@ namespace OpenMetaverse
} }
#endregion Callbacks #endregion Callbacks
private const string AGENT_PROFILE_CAP = "AgentProfile";
/// <summary>Reference to the GridClient instance</summary> /// <summary>Reference to the GridClient instance</summary>
private readonly GridClient Client; private readonly GridClient Client;
/// <summary>Used for movement and camera tracking</summary> /// <summary>Used for movement and camera tracking</summary>
@ -1269,7 +1271,7 @@ namespace OpenMetaverse
#region Properties #region Properties
/// <summary>Your (client) avatars <see cref="UUID"/></summary> /// <summary>Your (client) avatar's <see cref="UUID"/></summary>
/// <remarks>"client", "agent", and "avatar" all represent the same thing</remarks> /// <remarks>"client", "agent", and "avatar" all represent the same thing</remarks>
public UUID AgentID { get; private set; } public UUID AgentID { get; private set; }
@ -1491,6 +1493,7 @@ namespace OpenMetaverse
private readonly ManualResetEvent teleportEvent = new ManualResetEvent(false); private readonly ManualResetEvent teleportEvent = new ManualResetEvent(false);
private uint heightWidthGenCounter; private uint heightWidthGenCounter;
private readonly Dictionary<UUID, AssetGesture> gestureCache = new Dictionary<UUID, AssetGesture>(); private readonly Dictionary<UUID, AssetGesture> gestureCache = new Dictionary<UUID, AssetGesture>();
#endregion Private Members #endregion Private Members
/// <summary> /// <summary>
@ -1566,41 +1569,144 @@ namespace OpenMetaverse
} }
#region Chat and instant messages #region Chat and instant messages
/// <summary>
/// Maximum size in bytes of chat messages
/// </summary>
public const int MaxChatMessageSize = 1023;
protected int message_chunk_group_id = 0; // a int that starts at 1 goes upto 500 (then back to 1) /// <summary>
/// Maximum size in bytes of script dialog labels
/// </summary>
public const int MaxScriptDialogLabelSize = 254;
/// <summary>
/// Splits a multi-byte string into parts of at most <paramref name="splitSizeInBytes"/> bytes. Attempts to avoid
/// splitting multi-byte characters (like UTF-8) to preserve integrity. Useful for chunked string processing.
/// </summary>
/// <param name="message">The input string to split. May contain multi-byte characters (e.g., UTF-8 text).</param>
/// <param name="splitSizeInBytes">Maximum byte size per part of the split string. Must be greater than 0.</param>
/// <param name="maxParts">Maximum number of parts to create. Defaults to <see cref="int.MaxValue"/> if unspecified.</param>
/// <returns>
/// A <see cref="List{T}"/> of strings, each part limited to <paramref name="splitSizeInBytes"/> bytes or less.
/// Total parts do not exceed <paramref name="maxParts"/>. Excessive parts over <paramref name="maxParts"/> are discarded.
/// </returns>
/// <exception cref="Exception">
/// Thrown if <paramref name="splitSizeInBytes"/> is less than 1, as splitting below 1 byte is not possible.
/// </exception>
private static List<string> SplitMultibyteString(string message, int splitSizeInBytes, int maxParts = int.MaxValue)
{
if (splitSizeInBytes < 1)
{
throw new Exception("Split size must be at least 1 byte");
}
if (message.Length == 0)
{
return new List<string>()
{
string.Empty
};
}
var messageParts = new List<string>();
var messageBytes = System.Text.Encoding.UTF8.GetBytes(message);
var messageBytesOffset = 0;
while (messageParts.Count < maxParts && messageBytesOffset < messageBytes.Length)
{
var partEndOffset = Math.Min(messageBytesOffset + splitSizeInBytes, messageBytes.Length);
if (partEndOffset < messageBytes.Length)
{
// Starting at the desired split point (previous split point offset + splitSizeInBytes),
// iterate backwards, checking each byte until we find a byte that is not a continuation
// byte (0x80-0xBF).
// See: https://en.wikipedia.org/wiki/UTF-8#Byte_map
while (partEndOffset > messageBytesOffset)
{
var currentByte = messageBytes[partEndOffset];
if(currentByte < 0x80 || currentByte > 0xBF)
{
// The current byte of the message is not a continuation byte and we can split the message here.
break;
}
partEndOffset--;
}
}
if (partEndOffset == messageBytesOffset)
{
// Edge case where we're forced to split the multi-byte character, such as when splitSizeInBytes is 1
partEndOffset++;
}
var newMessagePart = System.Text.Encoding.UTF8.GetString(
messageBytes,
messageBytesOffset, partEndOffset - messageBytesOffset
);
messageParts.Add(newMessagePart);
messageBytesOffset = partEndOffset;
}
return messageParts;
}
/// <summary>
/// Send a text message from the Agent to the Simulator on a negative channel via ScriptDialogReplyPacket
/// </summary>
/// <param name="message">A <see cref="string"/> containing the message. Messages longer
/// than <see cref="MaxScriptDialogLabelSize"/> bytes will be truncated to <see cref="MaxScriptDialogLabelSize"/>
/// bytes.</param>
/// <param name="channel">The channel to send the message on</param>
private void ChatOnNegativeChannel(string message, int channel)
{
var messageChunks = SplitMultibyteString(message, MaxScriptDialogLabelSize, 1);
foreach (var messageChunk in messageChunks)
{
var chatPacket = new ScriptDialogReplyPacket
{
AgentData =
{
AgentID = AgentID,
SessionID = Client.Self.SessionID
},
Data =
{
ObjectID = AgentID,
ChatChannel = channel,
ButtonIndex = 0,
ButtonLabel = Utils.StringToBytes(messageChunk)
}
};
Client.Network.SendPacket(chatPacket);
}
}
/// <summary> /// <summary>
/// Send a text message from the Agent to the Simulator /// Send a text message from the Agent to the Simulator
/// </summary> /// </summary>
/// <param name="message">A <see cref="string"/> containing the message</param> /// <param name="message">A <see cref="string"/> containing the message</param>
/// <param name="channel">The channel to send the message on, 0 is the public channel. Channels above 0 /// <param name="channel">The channel to send the message on, 0 is the public channel. Messages
/// can be used however only scripts listening on the specified channel will see the message</param> /// sent on non-negative channels longer than <see cref="MaxChatMessageSize"/> bytes will be split
/// and sent in chunks of at most <see cref="MaxChatMessageSize"/> bytes. Messages on negative
/// channels will be truncated to at most <see cref="MaxScriptDialogLabelSize"/> bytes and chat
/// type will be ignored.</param>
/// <param name="type">Denotes the type of message being sent, shout, whisper, etc.</param> /// <param name="type">Denotes the type of message being sent, shout, whisper, etc.</param>
/// <param name="allow_split_message">Enables large messages to be split into chunks of 900 with [CHUNKGROUPID|CHUNKID|TOTALCHUNKS] at the start</param> /// <param name="splitLargeMessages">Allows splitting of long messages into multiple chat messages if true,
/// <param name="hide_chunk_grouping">Hides [CHUNKGROUPID|CHUNKID|TOTALCHUNKS] at the start of chunked messages</param> /// otherwise long messages will be truncated to their maximum size.</param>
public void Chat(string message, int channel, ChatType type,bool allow_split_message=true,bool hide_chunk_grouping=true) public void Chat(string message, int channel, ChatType type, bool splitLargeMessages = true)
{ {
if ((message.Length > 900) && (allow_split_message == true)) if (channel < 0)
{ {
int group_id = message_chunk_group_id; ChatOnNegativeChannel(message, channel);
message_chunk_group_id++; return;
if (message_chunk_group_id > 500) { message_chunk_group_id = 1; }
string[] chunks = message.SplitBy(900).ToArray();
int chunkid = 1;
foreach(string chunk in chunks)
{
string chunk_grouping = "";
if(hide_chunk_grouping == false)
{
chunk_grouping = $"[{group_id}|{chunkid}|{chunks.Length}]";
} }
Chat($"{chunk_grouping}{chunk}", channel, type, false);
chunkid++; var messageChunks = SplitMultibyteString(message, MaxChatMessageSize, splitLargeMessages ? int.MaxValue : 1);
} foreach (var messageChunk in messageChunks)
}
else
{ {
ChatFromViewerPacket chat = new ChatFromViewerPacket var chatPacket = new ChatFromViewerPacket
{ {
AgentData = AgentData =
{ {
@ -1610,11 +1716,11 @@ namespace OpenMetaverse
ChatData = ChatData =
{ {
Channel = channel, Channel = channel,
Message = Utils.StringToBytes(message), Message = Utils.StringToBytes(messageChunk),
Type = (byte) type Type = (byte) type
} }
}; };
Client.Network.SendPacket(chat); Client.Network.SendPacket(chatPacket);
} }
} }
@ -1722,12 +1828,21 @@ namespace OpenMetaverse
InstantMessageDialog dialog, InstantMessageOnline offline, Vector3 position, UUID regionID, InstantMessageDialog dialog, InstantMessageOnline offline, Vector3 position, UUID regionID,
byte[] binaryBucket) byte[] binaryBucket)
{ {
if (target != UUID.Zero) if (target == UUID.Zero)
{
Logger.Log($"Suppressing instant message \"{message}\" to UUID.Zero", Helpers.LogLevel.Error, Client);
return;
}
var messageParts = SplitMultibyteString(message, MaxChatMessageSize);
foreach (var messagePart in messageParts)
{ {
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();
if (imSessionID.Equals(UUID.Zero) || imSessionID.Equals(AgentID)) if (imSessionID.Equals(UUID.Zero) || imSessionID.Equals(AgentID))
{
imSessionID = AgentID.Equals(target) ? AgentID : target ^ AgentID; imSessionID = AgentID.Equals(target) ? AgentID : target ^ AgentID;
}
im.AgentData.AgentID = Client.Self.AgentID; im.AgentData.AgentID = Client.Self.AgentID;
im.AgentData.SessionID = Client.Self.SessionID; im.AgentData.SessionID = Client.Self.SessionID;
@ -1736,7 +1851,7 @@ namespace OpenMetaverse
im.MessageBlock.FromAgentName = Utils.StringToBytes(fromName); im.MessageBlock.FromAgentName = Utils.StringToBytes(fromName);
im.MessageBlock.FromGroup = false; im.MessageBlock.FromGroup = false;
im.MessageBlock.ID = imSessionID; im.MessageBlock.ID = imSessionID;
im.MessageBlock.Message = Utils.StringToBytes(message); im.MessageBlock.Message = Utils.StringToBytes(messagePart);
im.MessageBlock.Offline = (byte)offline; im.MessageBlock.Offline = (byte)offline;
im.MessageBlock.ToAgentID = target; im.MessageBlock.ToAgentID = target;
@ -1750,11 +1865,6 @@ namespace OpenMetaverse
// Send the message // Send the message
Client.Network.SendPacket(im); Client.Network.SendPacket(im);
} }
else
{
Logger.Log($"Suppressing instant message \"{message}\" to UUID.Zero",
Helpers.LogLevel.Error, Client);
}
} }
/// <summary> /// <summary>
@ -1777,7 +1887,15 @@ namespace OpenMetaverse
{ {
lock (GroupChatSessions.Dictionary) lock (GroupChatSessions.Dictionary)
{ {
if (GroupChatSessions.ContainsKey(groupID)) if (!GroupChatSessions.ContainsKey(groupID))
{
Logger.Log("No Active group chat session appears to exist, use RequestJoinGroupChat() to join one",
Helpers.LogLevel.Error, Client);
return;
}
var messageChunks = SplitMultibyteString(message, MaxChatMessageSize);
foreach (var messageChunk in messageChunks)
{ {
ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket
{ {
@ -1791,7 +1909,7 @@ namespace OpenMetaverse
Dialog = (byte) InstantMessageDialog.SessionSend, Dialog = (byte) InstantMessageDialog.SessionSend,
FromAgentName = Utils.StringToBytes(fromName), FromAgentName = Utils.StringToBytes(fromName),
FromGroup = false, FromGroup = false,
Message = Utils.StringToBytes(message), Message = Utils.StringToBytes(messageChunk),
Offline = 0, Offline = 0,
ID = groupID, ID = groupID,
ToAgentID = groupID, ToAgentID = groupID,
@ -1801,14 +1919,8 @@ namespace OpenMetaverse
} }
}; };
Client.Network.SendPacket(im); Client.Network.SendPacket(im);
} }
else
{
Logger.Log("No Active group chat session appears to exist, use RequestJoinGroupChat() to join one",
Helpers.LogLevel.Error, Client);
}
} }
} }
@ -3321,14 +3433,36 @@ namespace OpenMetaverse
#endregion Teleporting #endregion Teleporting
#region Misc #region Profile
/// <summary> /// <summary>
/// Update agent profile /// Update agent profile
/// </summary> /// </summary>
/// <param name="profile"><see cref="OpenMetaverse.Avatar.AvatarProperties"/> struct containing updated /// <param name="profile"><see cref="Avatar.AvatarProperties"/> struct containing updated
/// profile information</param> /// profile information</param>
/// <remarks>
/// The behavior between LLUDP and Http Capability differs. See each method's remarks
/// </remarks>
/// <seealso cref="UpdateProfileUdp"/>
/// <seealso cref="UpdateProfileHttp"/>
public void UpdateProfile(Avatar.AvatarProperties profile) public void UpdateProfile(Avatar.AvatarProperties profile)
{
if (Client.Network.CurrentSim.Caps.CapabilityURI(AGENT_PROFILE_CAP) != null)
{
UpdateProfileHttp(profile);
}
else
{
UpdateProfileUdp(profile);
}
}
/// <summary>
/// Update agent profile via simulator LLUDP
/// </summary>
/// <param name="profile"><see cref="Avatar.AvatarProperties"/> struct containing updated
/// profile information</param>
public void UpdateProfileUdp(Avatar.AvatarProperties profile)
{ {
AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket
{ {
@ -3352,10 +3486,50 @@ namespace OpenMetaverse
Client.Network.SendPacket(apup); Client.Network.SendPacket(apup);
} }
/// <summary>
/// Update agent profile
/// </summary>
/// <param name="profile"><see cref="Avatar.AvatarProperties"/> struct containing updated
/// profile information</param>
/// <param name="cancellationToken"></param>
/// <remarks>
/// Only updates about text fields, profile url, and allow_publish.
/// Does not update image UUID, etc. like the legacy LLUDP request.
/// </remarks>
public async void UpdateProfileHttp(Avatar.AvatarProperties profile, CancellationToken cancellationToken = default)
{
var payload = new OSDMap
{
["sl_about_text"] = profile.AboutText,
["fl_about_text"] = profile.FirstLifeText,
["profile_url"] = profile.ProfileURL,
["allow_publish"] = profile.AllowPublish,
["sl_image_id"] = profile.ProfileImage,
["fl_image_id"] = profile.FirstLifeImage
};
var capability = Client.Network.CurrentSim.Caps.CapabilityURI(AGENT_PROFILE_CAP);
var uri = new Uri($"{capability}/{AgentID}");
await Client.HttpCapsClient.PutRequestAsync(uri, OSDFormat.Xml, payload, cancellationToken,
(response, data, error) =>
{
if (error != null)
{
Logger.Log($"AgentProfile update failed: {error.Message}", Helpers.LogLevel.Warning);
return;
}
if (response.IsSuccessStatusCode)
{
Logger.Log("AgentProfile update succeeded.", Helpers.LogLevel.Debug);
}
});
}
/// <summary> /// <summary>
/// Update agent's profile interests /// Update agent's profile interests
/// </summary> /// </summary>
/// <param name="interests">selection of interests from <see cref="T:OpenMetaverse.Avatar.Interests"/> struct</param> /// <param name="interests">selection of interests from <see cref="Avatar.Interests"/> struct</param>
public void UpdateInterests(Avatar.Interests interests) public void UpdateInterests(Avatar.Interests interests)
{ {
AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket
@ -3383,7 +3557,21 @@ namespace OpenMetaverse
/// </summary> /// </summary>
/// <param name="target">target avatar for notes</param> /// <param name="target">target avatar for notes</param>
/// <param name="notes">notes to store</param> /// <param name="notes">notes to store</param>
/// <seealso cref="UpdateProfileHttp"/>
/// <seealso cref="UpdateProfileUdp"/>
public void UpdateProfileNotes(UUID target, string notes) public void UpdateProfileNotes(UUID target, string notes)
{
if (Client.Network.CurrentSim.Caps.CapabilityURI(AGENT_PROFILE_CAP) != null)
{
UpdateProfileNotesHttp(target, notes);
}
else
{
UpdateProfileNotesUdp(target, notes);
}
}
private void UpdateProfileNotesUdp(UUID target, string notes)
{ {
AvatarNotesUpdatePacket anup = new AvatarNotesUpdatePacket AvatarNotesUpdatePacket anup = new AvatarNotesUpdatePacket
{ {
@ -3402,32 +3590,36 @@ namespace OpenMetaverse
} }
/// <summary> /// <summary>
/// Set the height and the width of the client window. This is used /// Update agent's private notes for target avatar using HTTP capability system
/// by the server to build a virtual camera frustum for our avatar
/// </summary> /// </summary>
/// <param name="height">New height of the viewer window</param> /// <param name="target">target avatar for notes</param>
/// <param name="width">New width of the viewer window</param> /// <param name="notes">notes to store</param>
public void SetHeightWidth(ushort height, ushort width) /// <param name="cancellationToken"></param>
public async void UpdateProfileNotesHttp(UUID target, string notes, CancellationToken cancellationToken = default)
{ {
AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket var payload = new OSDMap { ["notes"] = notes };
{
AgentData =
{
AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID,
CircuitCode = Client.Network.CircuitCode
},
HeightWidthBlock =
{
Height = height,
Width = width,
GenCounter = heightWidthGenCounter++
}
};
Client.Network.SendPacket(heightwidth); var capability = Client.Network.CurrentSim.Caps.CapabilityURI(AGENT_PROFILE_CAP);
var uri = new Uri($"{capability}/{target}");
await Client.HttpCapsClient.PutRequestAsync(uri, OSDFormat.Xml, payload, cancellationToken,
(response, data, error) =>
{
if (error != null)
{
Logger.Log($"AgentProfile notes update failed: {error.Message}", Helpers.LogLevel.Warning);
} }
if (response.IsSuccessStatusCode)
{
Logger.Log("AgentProfile notes update succeeded.", Helpers.LogLevel.Debug);
}
});
}
#endregion Profile
#region Mute List
/// <summary> /// <summary>
/// Request the list of muted objects and avatars for this agent /// Request the list of muted objects and avatars for this agent
/// </summary> /// </summary>
@ -3535,10 +3727,41 @@ namespace OpenMetaverse
} }
} }
#endregion Mute List
#region Misc
/// <summary>
/// Set the height and the width of the client window. This is used
/// by the server to build a virtual camera frustum for our avatar
/// </summary>
/// <param name="height">New height of the viewer window</param>
/// <param name="width">New width of the viewer window</param>
public void SetHeightWidth(ushort height, ushort width)
{
AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket
{
AgentData =
{
AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID,
CircuitCode = Client.Network.CircuitCode
},
HeightWidthBlock =
{
Height = height,
Width = width,
GenCounter = heightWidthGenCounter++
}
};
Client.Network.SendPacket(heightwidth);
}
/// <summary> /// <summary>
/// Sets home location to agents current position /// Sets home location to agents current position
/// </summary> /// </summary>
/// <remarks>will fire an AlertMessage (<see cref="E:OpenMetaverse.AgentManager.OnAlertMessage"/>) with /// <remarks>will fire an AlertMessage (<see cref="OpenMetaverse.AgentManager.OnAlertMessage"/>) with
/// success or failure message</remarks> /// success or failure message</remarks>
public void SetHome() public void SetHome()
{ {
@ -3564,7 +3787,7 @@ namespace OpenMetaverse
/// Move an agent in to a simulator. This packet is the last packet /// Move an agent in to a simulator. This packet is the last packet
/// needed to complete the transition in to a new simulator /// needed to complete the transition in to a new simulator
/// </summary> /// </summary>
/// <param name="simulator"><see cref="T:OpenMetaverse.Simulator"/> Object</param> /// <param name="simulator"><see cref="OpenMetaverse.Simulator"/> Object</param>
public void CompleteAgentMovement(Simulator simulator) public void CompleteAgentMovement(Simulator simulator)
{ {
CompleteAgentMovementPacket move = new CompleteAgentMovementPacket CompleteAgentMovementPacket move = new CompleteAgentMovementPacket
@ -3584,10 +3807,10 @@ namespace OpenMetaverse
/// <summary> /// <summary>
/// Reply to script permissions request /// Reply to script permissions request
/// </summary> /// </summary>
/// <param name="simulator"><see cref="T:OpenMetaverse.Simulator"/> Object</param> /// <param name="simulator"><see cref="OpenMetaverse.Simulator"/> Object</param>
/// <param name="itemID"><see cref="UUID"/> of the itemID requesting permissions</param> /// <param name="itemID"><see cref="UUID"/> of the itemID requesting permissions</param>
/// <param name="taskID"><see cref="UUID"/> of the taskID requesting permissions</param> /// <param name="taskID"><see cref="UUID"/> of the taskID requesting permissions</param>
/// <param name="permissions"><see cref="OpenMetaverse.ScriptPermission"/> list of permissions to allow</param> /// <param name="permissions"><see cref="ScriptPermission"/> list of permissions to allow</param>
public void ScriptQuestionReply(Simulator simulator, UUID itemID, UUID taskID, ScriptPermission permissions) public void ScriptQuestionReply(Simulator simulator, UUID itemID, UUID taskID, ScriptPermission permissions)
{ {
ScriptAnswerYesPacket yes = new ScriptAnswerYesPacket ScriptAnswerYesPacket yes = new ScriptAnswerYesPacket

View file

@ -426,6 +426,7 @@ namespace OpenMetaverse
Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, AgentCachedTextureResponseHandler); Client.Network.RegisterCallback(PacketType.AgentCachedTextureResponse, AgentCachedTextureResponseHandler);
Client.Network.RegisterCallback(PacketType.RebakeAvatarTextures, RebakeAvatarTexturesHandler); Client.Network.RegisterCallback(PacketType.RebakeAvatarTextures, RebakeAvatarTexturesHandler);
Client.Objects.ObjectUpdate += Objects_AttachmentUpdate;
Client.Network.Disconnected += Network_OnDisconnected; Client.Network.Disconnected += Network_OnDisconnected;
Client.Network.SimChanged += Network_OnSimChanged; Client.Network.SimChanged += Network_OnSimChanged;
} }
@ -2716,6 +2717,37 @@ namespace OpenMetaverse
Client.Network.CurrentSim.Caps.CapabilitiesReceived += Simulator_OnCapabilitiesReceived; Client.Network.CurrentSim.Caps.CapabilitiesReceived += Simulator_OnCapabilitiesReceived;
} }
private void Objects_AttachmentUpdate(object sender, PrimEventArgs e)
{
Primitive prim = e.Prim;
if (Client.Self.LocalID == 0
|| prim.ParentID != Client.Self.LocalID
|| prim.NameValues == null
|| !prim.IsAttachment
|| !e.IsNew)
{
return;
}
// Updates Attachment points as soon as the data arrives
for (int i = 0; i < prim.NameValues.Length; ++i)
{
if (prim.NameValues[i].Name != "AttachItemID")
{
continue;
}
UUID inventoryID = new UUID(prim.NameValues[i].Value.ToString());
if (Attachments.TryGetValue(inventoryID, out AttachmentPoint attachmentPoint))
{
Attachments.AddOrUpdate(inventoryID, attachmentPoint, (id, value) => prim.PrimData.AttachmentPoint);
}
break;
}
}
private void Simulator_OnCapabilitiesReceived(object sender, CapabilitiesReceivedEventArgs e) private void Simulator_OnCapabilitiesReceived(object sender, CapabilitiesReceivedEventArgs e)
{ {
e.Simulator.Caps.CapabilitiesReceived -= Simulator_OnCapabilitiesReceived; e.Simulator.Caps.CapabilitiesReceived -= Simulator_OnCapabilitiesReceived;

View file

@ -214,7 +214,7 @@ namespace OpenMetaverse
/// <returns>String with the file name of the static cached asset</returns> /// <returns>String with the file name of the static cached asset</returns>
private string StaticFileName(UUID assetID) private string StaticFileName(UUID assetID)
{ {
return Settings.RESOURCE_DIR + Path.DirectorySeparatorChar + "static_assets" + Path.DirectorySeparatorChar + assetID; return Path.Combine(Settings.RESOURCE_DIR, "static_assets", assetID.ToString());
} }
/// <summary> /// <summary>

View file

@ -953,7 +953,7 @@ namespace OpenMetaverse
Client.Network.SendPacket(request, transfer.Simulator); Client.Network.SendPacket(request, transfer.Simulator);
} }
#region Uploads #region Uploads
/// <summary> /// <summary>
/// Used to force asset data into the PendingUpload property, ie: for raw terrain uploads /// Used to force asset data into the PendingUpload property, ie: for raw terrain uploads
/// </summary> /// </summary>

View file

@ -28,10 +28,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenMetaverse.Http;
using OpenMetaverse.Packets; using OpenMetaverse.Packets;
using OpenMetaverse.Interfaces; using OpenMetaverse.Interfaces;
using OpenMetaverse.Messages.Linden; using OpenMetaverse.Messages.Linden;
@ -169,7 +167,7 @@ namespace OpenMetaverse
public struct ClassifiedAd public struct ClassifiedAd
{ {
public UUID ClassifiedID; public UUID ClassifiedID;
public uint Catagory; public uint Category;
public UUID ParcelID; public UUID ParcelID;
public uint ParentEstate; public uint ParentEstate;
public UUID SnapShotID; public UUID SnapShotID;
@ -681,7 +679,7 @@ namespace OpenMetaverse
Client.Network.SendPacket(request); Client.Network.SendPacket(request);
} }
// Get any remaining names after left after the full requests // Get any remaining names left after the full requests
if (ids.Count > n * m) if (ids.Count > n * m)
{ {
request = new UUIDNameRequestPacket request = new UUIDNameRequestPacket
@ -1533,7 +1531,7 @@ namespace OpenMetaverse
Price = p.Data.PriceForListing, Price = p.Data.PriceForListing,
ParentEstate = p.Data.ParentEstate, ParentEstate = p.Data.ParentEstate,
ClassifiedFlags = p.Data.ClassifiedFlags, ClassifiedFlags = p.Data.ClassifiedFlags,
Catagory = p.Data.Category Category = p.Data.Category
}; };
OnClassifiedInfoReply(new ClassifiedInfoReplyEventArgs(ret.ClassifiedID, ret)); OnClassifiedInfoReply(new ClassifiedInfoReplyEventArgs(ret.ClassifiedID, ret));

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022-2024, Sjofn, LLC. * Copyright (c) 2022-2025, Sjofn, LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -60,7 +60,7 @@ namespace LibreMetaverse
#region GET requests #region GET requests
public async Task GetRequestAsync(Uri uri, CancellationToken cancellationToken, public async Task GetRequestAsync(Uri uri, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
using (var request = new HttpRequestMessage(HttpMethod.Get, uri)) using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{ {
@ -68,17 +68,12 @@ namespace LibreMetaverse
} }
} }
public async Task GetRequestAsync(Uri uri, CancellationToken cancellationToken, DownloadCompleteHandler completeHandler)
{
await GetRequestAsync(uri, cancellationToken, completeHandler, null, null);
}
#endregion GET requests #endregion GET requests
#region POST requests #region POST requests
public async Task PostRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken, public async Task PostRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
using (var request = new HttpRequestMessage(HttpMethod.Post, uri)) using (var request = new HttpRequestMessage(HttpMethod.Post, uri))
{ {
@ -88,14 +83,8 @@ namespace LibreMetaverse
} }
} }
public async Task PostRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await PostRequestAsync(uri, contentType, payload, cancellationToken, completeHandler, null, null);
}
public async Task PostRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken, public async Task PostRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
SerializeData(format, payload, out var serialized, out var contentType); SerializeData(format, payload, out var serialized, out var contentType);
using (var request = new HttpRequestMessage(HttpMethod.Post, uri)) using (var request = new HttpRequestMessage(HttpMethod.Post, uri))
@ -113,18 +102,12 @@ namespace LibreMetaverse
} }
} }
public async Task PostRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await PostRequestAsync(uri, format, payload, cancellationToken, completeHandler, null, null);
}
#endregion POST requests #endregion POST requests
#region PUT requests #region PUT requests
public async Task PutRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken, public async Task PutRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
using (var request = new HttpRequestMessage(HttpMethod.Put, uri)) using (var request = new HttpRequestMessage(HttpMethod.Put, uri))
{ {
@ -134,14 +117,8 @@ namespace LibreMetaverse
} }
} }
public async Task PutRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await PutRequestAsync(uri, contentType, payload, cancellationToken, completeHandler, null, null);
}
public async Task PutRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken, public async Task PutRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
SerializeData(format, payload, out var serialized, out var contentType); SerializeData(format, payload, out var serialized, out var contentType);
using (var request = new HttpRequestMessage(HttpMethod.Put, uri)) using (var request = new HttpRequestMessage(HttpMethod.Put, uri))
@ -152,18 +129,12 @@ namespace LibreMetaverse
} }
} }
public async Task PutRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await PutRequestAsync(uri, format, payload, cancellationToken, completeHandler, null, null);
}
#endregion PUT requests #endregion PUT requests
#region PATCH requests #region PATCH requests
public async Task PatchRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken, public async Task PatchRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
#if (NETSTANDARD2_1_OR_GREATER || NET) #if (NETSTANDARD2_1_OR_GREATER || NET)
using (var request = new HttpRequestMessage(HttpMethod.Patch, uri)) using (var request = new HttpRequestMessage(HttpMethod.Patch, uri))
@ -177,14 +148,8 @@ namespace LibreMetaverse
} }
} }
public async Task PatchRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await PatchRequestAsync(uri, contentType, payload, cancellationToken, completeHandler, null, null);
}
public async Task PatchRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken, public async Task PatchRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
SerializeData(format, payload, out var serialized, out var contentType); SerializeData(format, payload, out var serialized, out var contentType);
#if (NETSTANDARD2_1_OR_GREATER || NET) #if (NETSTANDARD2_1_OR_GREATER || NET)
@ -199,18 +164,12 @@ namespace LibreMetaverse
} }
} }
public async Task PatchRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await PatchRequestAsync(uri, format, payload, cancellationToken, completeHandler, null, null);
}
#endregion PATCH requests #endregion PATCH requests
#region DELETE requests #region DELETE requests
public async Task DeleteRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken, public async Task DeleteRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
using (var request = new HttpRequestMessage(HttpMethod.Delete, uri)) using (var request = new HttpRequestMessage(HttpMethod.Delete, uri))
{ {
@ -220,14 +179,8 @@ namespace LibreMetaverse
} }
} }
public async Task DeleteRequestAsync(Uri uri, string contentType, byte[] payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await DeleteRequestAsync(uri, contentType, payload, cancellationToken, completeHandler, null, null);
}
public async Task DeleteRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken, public async Task DeleteRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler, ConnectedHandler connectedHandler) DownloadCompleteHandler completeHandler, DownloadProgressHandler progressHandler = default, ConnectedHandler connectedHandler = default)
{ {
SerializeData(format, payload, out var serialized, out var contentType); SerializeData(format, payload, out var serialized, out var contentType);
using (var request = new HttpRequestMessage(HttpMethod.Delete, uri)) using (var request = new HttpRequestMessage(HttpMethod.Delete, uri))
@ -238,12 +191,6 @@ namespace LibreMetaverse
} }
} }
public async Task DeleteRequestAsync(Uri uri, OSDFormat format, OSD payload, CancellationToken cancellationToken,
DownloadCompleteHandler completeHandler)
{
await DeleteRequestAsync(uri, format, payload, cancellationToken, completeHandler, null, null);
}
#endregion DELETE requests #endregion DELETE requests
/// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// ///

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2019-2024, Sjofn LLC. * Copyright (c) 2019-2025, Sjofn LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -36,6 +36,7 @@ using OpenMetaverse.StructuredData;
using OpenMetaverse.Interfaces; using OpenMetaverse.Interfaces;
using OpenMetaverse.Http; using OpenMetaverse.Http;
using System.Net.Http; using System.Net.Http;
using LibreMetaverse;
namespace OpenMetaverse namespace OpenMetaverse
{ {
@ -319,12 +320,21 @@ namespace OpenMetaverse
_EventQueueClient.Start(); _EventQueueClient.Start();
} }
if (_Caps.TryGetValue("SimulatorFeatures", out var simFeaturesCap))
{
Logger.DebugLog($"Retrieving Simulator Features", Simulator.Client);
Simulator.Features = new SimulatorFeatures(Simulator);
_ = Simulator.Client.HttpCapsClient.GetRequestAsync(
simFeaturesCap, _HttpCts.Token, Simulator.Features.SetFeatures);
}
OnCapabilitiesReceived(Simulator); OnCapabilitiesReceived(Simulator);
} }
} }
catch (LitJson.JsonException) catch (LitJson.JsonException)
{ {
Logger.Log("Invalid caps response; '" + System.Text.Encoding.UTF8.GetString(responseData) + "' for seed request.", Helpers.LogLevel.Warning, Simulator.Client); Logger.Log($"Invalid caps response; '{System.Text.Encoding.UTF8.GetString(responseData)}' for seed request.",
Helpers.LogLevel.Warning, Simulator.Client);
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2025, Sjofn LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -30,9 +31,10 @@ using OpenMetaverse.Messages.Linden;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using OpenMetaverse.StructuredData;
using System.Text; using System.Text;
using System.Threading.Tasks;
using LibreMetaverse;
using OpenMetaverse.StructuredData;
namespace OpenMetaverse namespace OpenMetaverse
{ {
@ -350,6 +352,78 @@ namespace OpenMetaverse
#endregion #endregion
#region Public Methods #region Public Methods
/// <summary>
/// Updates estate-level configuration settings for the current region.
/// Attempts to send the update via the "EstateChangeInfo" HTTP caps if available
/// or you can enable a fallback to the legacy method.
/// </summary>
/// <param name="estateName">The name of the estate.</param>
/// <param name="sunhourvalue">The sun hour value (0.0–24.0, scaled internally).</param>
/// <param name="issunfixed">Whether the sun position is fixed.</param>
/// <param name="isexternallyvisible">Whether the estate is visible externally.</param>
/// <param name="allowdirectteleport">Allow direct teleport to the estate.</param>
/// <param name="denyanonymous">Deny access to anonymous users.</param>
/// <param name="denyageunverified">Deny access to age-unverified users.</param>
/// <param name="blockbots">Block bots from entering the estate.</param>
/// <param name="allowvoicechat">Allow voice chat in the estate.</param>
/// <param name="overridepublicaccess">Override public access settings.</param>
/// <param name="overrideenvironment">Override environment settings.</param>
public async Task<Task> UpdateEstateInfo(
string estateName, float sunhourvalue, bool issunfixed, bool isexternallyvisible,
bool allowdirectteleport, bool denyanonymous, bool denyageunverified, bool blockbots,
bool allowvoicechat, bool overridepublicaccess, bool overrideenvironment)
{
Uri uri = Client.Network.CurrentSim.Caps.CapabilityURI("EstateChangeInfo");
if (uri == null)
{
Logger.Log("using UpdateEstateInfoFallback one day we will have working caps for this", Helpers.LogLevel.Info);
List<string> config = new List<string>();
config.Add(estateName);
Int64 bitmask = 0;
if (issunfixed) bitmask |= 1 << 4;
if (isexternallyvisible) bitmask |= 1 << 15;
if (allowdirectteleport) bitmask |= 1 << 20;
if (denyanonymous) bitmask |= 1 << 23;
if (denyageunverified) bitmask |= 1 << 30;
if (blockbots) bitmask |= 1 << 31;
if (allowvoicechat) bitmask |= 1 << 28;
if (overridepublicaccess) bitmask |= 1 << 5;
if (overrideenvironment) bitmask |= 1 << 9;
config.Add(bitmask.ToString(CultureInfo.InvariantCulture));
config.Add((sunhourvalue * 1024.0f).ToString(CultureInfo.InvariantCulture));
EstateOwnerMessage("estatechangeinfo", config);
return Task.CompletedTask;
}
OSDMap body = new OSDMap();
body["estate_name"] = estateName;
body["sun_hour"] = (sunhourvalue * 1024.0f);
body["is_sun_fixed"] = issunfixed;
body["is_externally_visible"] = isexternallyvisible;
body["allow_direct_teleport"] = allowdirectteleport;
body["deny_anonymous"] = denyanonymous;
body["deny_age_unverified"] = denyageunverified;
body["block_bots"] = blockbots;
body["allow_voice_chat"] = allowvoicechat;
body["override_public_access"] = overridepublicaccess;
body["override_environment"] = overrideenvironment;
body["invoice"] = UUID.Random();
string requestBody = OSDParser.SerializeLLSDXmlString(body);
try
{
var content = new StringContent(requestBody, Encoding.UTF8, "application/llsd+xml");
var reply = await Client.HttpCapsClient.PostAsync(uri.ToString(), content);
_ = await reply.Content.ReadAsStringAsync();
Logger.Log($"EstateChangeInfo update {reply.StatusCode.ToString()}", Helpers.LogLevel.Info);
}
catch (Exception e)
{
Logger.Log($"EstateChangeInfo error {e.Message}", Helpers.LogLevel.Error);
}
return Task.CompletedTask;
}
/// <summary> /// <summary>
/// Requests estate information such as top scripts and colliders /// Requests estate information such as top scripts and colliders
/// </summary> /// </summary>
@ -359,13 +433,21 @@ namespace OpenMetaverse
/// <param name="filter"></param> /// <param name="filter"></param>
public void LandStatRequest(int parcelLocalID, LandStatReportType reportType, uint requestFlags, string filter) public void LandStatRequest(int parcelLocalID, LandStatReportType reportType, uint requestFlags, string filter)
{ {
LandStatRequestPacket p = new LandStatRequestPacket(); LandStatRequestPacket p = new LandStatRequestPacket
p.AgentData.AgentID = Client.Self.AgentID; {
p.AgentData.SessionID = Client.Self.SessionID; AgentData =
p.RequestData.Filter = Utils.StringToBytes(filter); {
p.RequestData.ParcelLocalID = parcelLocalID; AgentID = Client.Self.AgentID,
p.RequestData.ReportType = (uint)reportType; SessionID = Client.Self.SessionID
p.RequestData.RequestFlags = requestFlags; },
RequestData =
{
Filter = Utils.StringToBytes(filter),
ParcelLocalID = parcelLocalID,
ReportType = (uint)reportType,
RequestFlags = requestFlags
}
};
Client.Network.SendPacket(p); Client.Network.SendPacket(p);
} }
@ -432,11 +514,19 @@ namespace OpenMetaverse
} }
else else
{ {
SimWideDeletesPacket simDelete = new SimWideDeletesPacket(); SimWideDeletesPacket simDelete = new SimWideDeletesPacket
simDelete.AgentData.AgentID = Client.Self.AgentID; {
simDelete.AgentData.SessionID = Client.Self.SessionID; AgentData =
simDelete.DataBlock.TargetID = Target; {
simDelete.DataBlock.Flags = (uint)flag; AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID
},
DataBlock =
{
TargetID = Target,
Flags = (uint)flag
}
};
Client.Network.SendPacket(simDelete); Client.Network.SendPacket(simDelete);
} }
} }
@ -458,17 +548,27 @@ namespace OpenMetaverse
/// <param name="listParams">List of parameters to include</param> /// <param name="listParams">List of parameters to include</param>
public void EstateOwnerMessage(string method, List<string> listParams) public void EstateOwnerMessage(string method, List<string> listParams)
{ {
EstateOwnerMessagePacket estate = new EstateOwnerMessagePacket(); EstateOwnerMessagePacket estate = new EstateOwnerMessagePacket
estate.AgentData.AgentID = Client.Self.AgentID; {
estate.AgentData.SessionID = Client.Self.SessionID; AgentData =
estate.AgentData.TransactionID = UUID.Zero; {
estate.MethodData.Invoice = UUID.Random(); AgentID = Client.Self.AgentID,
estate.MethodData.Method = Utils.StringToBytes(method); SessionID = Client.Self.SessionID,
estate.ParamList = new EstateOwnerMessagePacket.ParamListBlock[listParams.Count]; TransactionID = UUID.Zero
},
MethodData =
{
Invoice = UUID.Random(),
Method = Utils.StringToBytes(method)
},
ParamList = new EstateOwnerMessagePacket.ParamListBlock[listParams.Count]
};
for (int i = 0; i < listParams.Count; i++) for (int i = 0; i < listParams.Count; i++)
{ {
estate.ParamList[i] = new EstateOwnerMessagePacket.ParamListBlock(); estate.ParamList[i] = new EstateOwnerMessagePacket.ParamListBlock
estate.ParamList[i].Parameter = Utils.StringToBytes(listParams[i]); {
Parameter = Utils.StringToBytes(listParams[i])
};
} }
Client.Network.SendPacket((Packet)estate); Client.Network.SendPacket((Packet)estate);
} }
@ -570,79 +670,70 @@ namespace OpenMetaverse
EstateOwnerMessage("restart", "-1"); EstateOwnerMessage("restart", "-1");
} }
public async Task<KeyValuePair<bool, string>> extendedSetRegionInfo( [Obsolete("Use SetRegionInfo with new arguments or SetRegionInfoUdp")]
bool blockTerraform, bool blockFly, bool blockFlyover, bool allowDamage, bool allowLandResell, public void SetRegionInfo(bool blockTerraform, bool blockFly, bool allowDamage, bool allowLandResell,
int agentLimit, float primBonus, RegionMaturity maturityLevel, bool blockObjectPush, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus,
bool allowParcelChanges, bool blockParcelSearch) RegionMaturity maturity)
{ {
if (Client == null) SetRegionInfo(blockTerraform, blockFly, false, allowDamage, allowLandResell, restrictPushing,
{ allowParcelJoinDivide, agentLimit, objectBonus, false, maturity);
return new KeyValuePair<bool, string>(false, "No client object");
}
else if (Client.Network == null)
{
return new KeyValuePair<bool, string>(false, "Not connected to a network");
}
else if (Client.Network.CurrentSim == null)
{
return new KeyValuePair<bool, string>(false, "Not connected to a sim");
}
else if (Client.Network.CurrentSim.Caps == null)
{
return new KeyValuePair<bool, string>(false, "No caps endpoint for current sim");
}
else if (Client.HttpCapsClient == null)
{
return new KeyValuePair<bool, string>(false, "No HttpCapsClient available");
} }
Uri uri = Client.Network.CurrentSim.Caps.CapabilityURI("DispatchRegionInfo"); public void SetRegionInfo(bool blockTerraform, bool blockFly, bool blockFlyOver, bool allowDamage,
if (uri == null) bool allowLandResell, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus,
bool blockParcelSearch, RegionMaturity maturity)
{ {
return new KeyValuePair<bool, string>(false, "Unable to get URI for DispatchRegionInfo"); if (Client.Network.CurrentSim?.Caps?.CapabilityURI("DispatchRegionInfo") == null
|| !SetRegionInfoHttp(blockTerraform, blockFly, blockFlyOver, allowDamage, allowLandResell, restrictPushing,
allowParcelJoinDivide, agentLimit, objectBonus, blockParcelSearch, maturity).Result)
{
Logger.Log("Falling back to LLUDP SetRegionInfo", Helpers.LogLevel.Info);
SetRegionInfoUdp(blockTerraform, blockFly, allowDamage, allowLandResell, restrictPushing,
allowParcelJoinDivide, agentLimit, objectBonus, maturity);
}
} }
OSDMap request = new OSDMap();
request["block_terraform"] = blockTerraform;
request["block_fly"] = blockFly;
request["block_fly_over"] = blockFlyover;
request["allow_damage"] = allowDamage;
request["allow_land_resell"] = allowLandResell;
request["agent_limit"] = agentLimit;
request["prim_bonus"] = primBonus;
request["sim_access"] = (int)maturityLevel;
request["restrict_pushobject"] = blockObjectPush;
request["allow_parcel_changes"] = allowParcelChanges;
request["block_parcel_search"] = blockParcelSearch;
string requestBody = OSDParser.SerializeLLSDXmlString(request); public async Task<bool> SetRegionInfoHttp(bool blockTerraform, bool blockFly, bool blockFlyOver, bool allowDamage,
try bool allowLandResell, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus,
bool blockParcelSearch, RegionMaturity maturity)
{
var uri = Client.Network.CurrentSim?.Caps?.CapabilityURI("DispatchRegionInfo");
if (uri != null)
{
var req = new OSDMap
{
["block_terraform"] = blockTerraform,
["block_fly"] = blockFly,
["block_fly_over"] = blockFlyOver,
["allow_damage"] = allowDamage,
["allow_land_resell"] = allowLandResell,
["agent_limit"] = agentLimit,
["prim_bonus"] = objectBonus,
["sim_access"] = maturity.ToString("D"),
["restrict_pushobject"] = restrictPushing,
["allow_parcel_changes"] = allowParcelJoinDivide,
["block_parcel_search"] = blockParcelSearch
};
using (var content = new StringContent(OSDParser.SerializeLLSDXmlString(req), Encoding.UTF8,
"application/llsd+xml"))
{
using (var reply = await Client.HttpCapsClient.PostAsync(uri, content))
{ {
var content = new StringContent(requestBody, Encoding.UTF8, "application/llsd+xml");
var reply = await Client.HttpCapsClient.PostAsync(uri.ToString(), content);
string responseContent = await reply.Content.ReadAsStringAsync();
if (reply.IsSuccessStatusCode) if (reply.IsSuccessStatusCode)
{ {
return new KeyValuePair<bool, string>(true, "ok"); return true;
} }
return new KeyValuePair<bool, string>(false, $"HTTP {reply.StatusCode}: {responseContent}");
Logger.Log($"Failed to set region info via capability: {reply.ReasonPhrase}", Helpers.LogLevel.Warning);
} }
catch (Exception e)
{
return new KeyValuePair<bool, string>(false, e.Message);
} }
} }
return false;
}
/// <summary>Estate panel "Region" tab settings</summary> /// <summary>Estate panel "Region" tab settings</summary>
/// @deprecated Use the version with RegionMaturity support public void SetRegionInfoUdp(bool blockTerraform, bool blockFly, bool allowDamage, bool allowLandResell,
public void SetRegionInfo(bool blockTerraform, bool blockFly, bool allowDamage, bool allowLandResell, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus, bool mature) bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus, RegionMaturity maturity)
{
RegionMaturity rating = mature ? RegionMaturity.Mature : RegionMaturity.PG;
SetRegionInfo(blockTerraform, blockFly, allowDamage, allowLandResell, restrictPushing, allowParcelJoinDivide, agentLimit, objectBonus, rating);
}
/// <summary>Estate panel "Region" tab settings</summary>
public void SetRegionInfo(bool blockTerraform, bool blockFly, bool allowDamage, bool allowLandResell, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus, RegionMaturity maturity)
{ {
List<string> listParams = new List<string>(); List<string> listParams = new List<string>();
@ -652,7 +743,7 @@ namespace OpenMetaverse
listParams.Add(allowLandResell ? "Y" : "N"); listParams.Add(allowLandResell ? "Y" : "N");
listParams.Add(agentLimit.ToString(CultureInfo.InvariantCulture)); listParams.Add(agentLimit.ToString(CultureInfo.InvariantCulture));
listParams.Add(objectBonus.ToString(CultureInfo.InvariantCulture)); listParams.Add(objectBonus.ToString(CultureInfo.InvariantCulture));
listParams.Add(maturity.ToString()); listParams.Add(maturity.ToString("D"));
listParams.Add(restrictPushing ? "Y" : "N"); listParams.Add(restrictPushing ? "Y" : "N");
listParams.Add(allowParcelJoinDivide ? "Y" : "N"); listParams.Add(allowParcelJoinDivide ? "Y" : "N");
EstateOwnerMessage("setregioninfo", listParams); EstateOwnerMessage("setregioninfo", listParams);
@ -699,12 +790,51 @@ namespace OpenMetaverse
/// <summary>Requests the estate covenant</summary> /// <summary>Requests the estate covenant</summary>
public void RequestCovenant() public void RequestCovenant()
{ {
EstateCovenantRequestPacket req = new EstateCovenantRequestPacket(); EstateCovenantRequestPacket req = new EstateCovenantRequestPacket
req.AgentData.AgentID = Client.Self.AgentID; {
req.AgentData.SessionID = Client.Self.SessionID; AgentData =
{
AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID
}
};
Client.Network.SendPacket(req); Client.Network.SendPacket(req);
} }
/// <summary>
/// Requests Estate Covenant notecard from <see cref="Client.Network.CurrentSim" /> asset service
/// </summary>
/// <param name="covenantId">Asset UUID for estate covenant notecard</param>
/// <param name="callback">Asset Received callback</param>
/// <seealso cref="AssetManager.RequestAssetUDP"/>
public void RequestCovenantNotecard(UUID covenantId, AssetManager.AssetReceivedCallback callback)
{
RequestCovenantNotecard(covenantId, Client.Network.CurrentSim, callback);
}
/// <summary>
/// Requests Estate Covenant notecard from asset service
/// </summary>
/// <param name="covenantId">Asset UUID for estate covenant notecard</param>
/// <param name="simulator">Requested simulator</param>
/// <param name="callback">Asset Received callback</param>
/// <seealso cref="AssetManager.RequestAssetUDP"/>
public void RequestCovenantNotecard(UUID covenantId, Simulator simulator, AssetManager.AssetReceivedCallback callback)
{
var transfer = new AssetDownload
{
ID = UUID.Random(),
AssetID = covenantId,
AssetType = AssetType.Notecard,
Priority = 101.0f,
Channel = ChannelType.Asset,
Source = SourceType.SimEstate,
Simulator = simulator,
Callback = callback
};
Client.Assets.RequestEstateAsset(transfer, EstateAssetType.Covenant);
}
/// <summary> /// <summary>
/// Upload a terrain RAW file /// Upload a terrain RAW file
/// </summary> /// </summary>
@ -713,11 +843,13 @@ namespace OpenMetaverse
/// <returns>The Id of the transfer request</returns> /// <returns>The Id of the transfer request</returns>
public UUID UploadTerrain(byte[] fileData, string fileName) public UUID UploadTerrain(byte[] fileData, string fileName)
{ {
AssetUpload upload = new AssetUpload(); AssetUpload upload = new AssetUpload
upload.AssetData = fileData; {
upload.AssetType = AssetType.Unknown; AssetData = fileData,
upload.Size = fileData.Length; AssetType = AssetType.Unknown,
upload.ID = UUID.Random(); Size = fileData.Length,
ID = UUID.Random()
};
// Tell the library we have a pending file to upload // Tell the library we have a pending file to upload
Client.Assets.SetPendingAssetUploadData(upload); Client.Assets.SetPendingAssetUploadData(upload);
@ -987,13 +1119,15 @@ namespace OpenMetaverse
foreach (LandStatReplyPacket.ReportDataBlock rep in p.ReportData) foreach (LandStatReplyPacket.ReportDataBlock rep in p.ReportData)
{ {
EstateTask task = new EstateTask(); EstateTask task = new EstateTask
task.Position = new Vector3(rep.LocationX, rep.LocationY, rep.LocationZ); {
task.Score = rep.Score; Position = new Vector3(rep.LocationX, rep.LocationY, rep.LocationZ),
task.TaskID = rep.TaskID; Score = rep.Score,
task.TaskLocalID = rep.TaskLocalID; TaskID = rep.TaskID,
task.TaskName = Utils.BytesToString(rep.TaskName); TaskLocalID = rep.TaskLocalID,
task.OwnerName = Utils.BytesToString(rep.OwnerName); TaskName = Utils.BytesToString(rep.TaskName),
OwnerName = Utils.BytesToString(rep.OwnerName)
};
Tasks.Add(task.TaskID, task); Tasks.Add(task.TaskID, task);
} }
@ -1030,14 +1164,16 @@ namespace OpenMetaverse
foreach (LandStatReplyMessage.ReportDataBlock rep in m.ReportDataBlocks) foreach (LandStatReplyMessage.ReportDataBlock rep in m.ReportDataBlocks)
{ {
EstateTask task = new EstateTask(); EstateTask task = new EstateTask
task.Position = rep.Location; {
task.Score = rep.Score; Position = rep.Location,
task.MonoScore = rep.MonoScore; Score = rep.Score,
task.TaskID = rep.TaskID; MonoScore = rep.MonoScore,
task.TaskLocalID = rep.TaskLocalID; TaskID = rep.TaskID,
task.TaskName = rep.TaskName; TaskLocalID = rep.TaskLocalID,
task.OwnerName = rep.OwnerName; TaskName = rep.TaskName,
OwnerName = rep.OwnerName
};
Tasks.Add(task.TaskID, task); Tasks.Add(task.TaskID, task);
} }

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2022, Sjofn, LLC. * Copyright (c) 2022-2025, Sjofn, LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -27,7 +27,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using OpenMetaverse.StructuredData; using OpenMetaverse.StructuredData;
@ -160,16 +159,15 @@ namespace OpenMetaverse
public uint GlobalY; public uint GlobalY;
/// <summary>Get the Local X position of the item</summary> /// <summary>Get the Local X position of the item</summary>
public uint LocalX { get { return GlobalX % Simulator.DefaultRegionSizeX; } } public uint LocalX => GlobalX % Simulator.DefaultRegionSizeX;
/// <summary>Get the Local Y position of the item</summary> /// <summary>Get the Local Y position of the item</summary>
public uint LocalY { get { return GlobalY % Simulator.DefaultRegionSizeY; } } public uint LocalY => GlobalY % Simulator.DefaultRegionSizeY;
/// <summary>Get the Handle of the region</summary> /// <summary>Get the Handle of the region</summary>
public ulong RegionHandle public ulong RegionHandle =>
{ Utils.UIntsToLong((uint)(GlobalX - (GlobalX % Simulator.DefaultRegionSizeX)),
get { return Utils.UIntsToLong((uint)(GlobalX - (GlobalX % Simulator.DefaultRegionSizeX)), (uint)(GlobalY - (GlobalY % Simulator.DefaultRegionSizeY)));
(uint)(GlobalY - (GlobalY % Simulator.DefaultRegionSizeY))); }
}
} }
/// <summary> /// <summary>
@ -578,7 +576,7 @@ namespace OpenMetaverse
} }
/// <summary> /// <summary>
/// Retrieves <see cref="GridRegion"/> information using the region name /// Retrieves <see cref="GridRegion"/> information using the region handle
/// </summary> /// </summary>
/// <remarks>This function will block until it can find the region or gives up</remarks> /// <remarks>This function will block until it can find the region or gives up</remarks>
/// <param name="handle">Region Handle of requested <see cref="GridRegion"/></param> /// <param name="handle">Region Handle of requested <see cref="GridRegion"/></param>
@ -595,10 +593,7 @@ namespace OpenMetaverse
return true; return true;
} }
uint globalX, Utils.LongToUInts(handle, out var globalX, out var globalY);
globalY;
Utils.LongToUInts(handle, out globalX, out globalY);
const uint regionWidthUnits = 256; const uint regionWidthUnits = 256;
ushort gridX = (ushort)(globalX / regionWidthUnits); ushort gridX = (ushort)(globalX / regionWidthUnits);
ushort gridY = (ushort)(globalY / regionWidthUnits); ushort gridY = (ushort)(globalY / regionWidthUnits);
@ -663,7 +658,7 @@ namespace OpenMetaverse
void Callback(object sender, GridRegionEventArgs e) void Callback(object sender, GridRegionEventArgs e)
{ {
if (e.Region.Name.ToLowerInvariant() == name.ToLowerInvariant()) if (string.Equals(e.Region.Name, name, StringComparison.InvariantCultureIgnoreCase))
{ {
regionEvent.Set(); regionEvent.Set();
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2025, Sjofn, LLC
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -33,7 +34,6 @@ using OpenMetaverse.Packets;
using OpenMetaverse.StructuredData; using OpenMetaverse.StructuredData;
using OpenMetaverse.Messages.Linden; using OpenMetaverse.Messages.Linden;
using OpenMetaverse.Interfaces; using OpenMetaverse.Interfaces;
using OpenMetaverse.Http;
namespace OpenMetaverse namespace OpenMetaverse
{ {
@ -253,13 +253,13 @@ namespace OpenMetaverse
if (OwnerID == UUID.Zero || AttachmentID == UUID.Zero) if (OwnerID == UUID.Zero || AttachmentID == UUID.Zero)
return Utils.EmptyBytes; return Utils.EmptyBytes;
OpenMetaverse.StructuredData.OSDMap att = new OpenMetaverse.StructuredData.OSDMap OSDMap att = new OSDMap
{ {
{"item_id", OpenMetaverse.StructuredData.OSD.FromUUID(AttachmentID)}, {"item_id", OSD.FromUUID(AttachmentID)},
{"owner_id", OpenMetaverse.StructuredData.OSD.FromUUID(OwnerID)} {"owner_id", OSD.FromUUID(OwnerID)}
}; };
return OpenMetaverse.StructuredData.OSDParser.SerializeLLSDXmlBytes(att); return OSDParser.SerializeLLSDXmlBytes(att);
/* /*
//I guess this is how this works, no gaurentees //I guess this is how this works, no gaurentees
@ -845,7 +845,7 @@ namespace OpenMetaverse
/// <summary>Thread sync lock object</summary> /// <summary>Thread sync lock object</summary>
private readonly object m_BannedAgentsLock = new object(); private readonly object m_BannedAgentsLock = new object();
/// <summary>Raised when another agent invites our avatar to join a group</summary> /// <summary>Raised when a group ban is processed</summary>
public event EventHandler<BannedAgentsEventArgs> BannedAgents public event EventHandler<BannedAgentsEventArgs> BannedAgents
{ {
add { lock (m_BannedAgentsLock) { m_BannedAgents += value; } } add { lock (m_BannedAgentsLock) { m_BannedAgents += value; } }
@ -854,21 +854,20 @@ namespace OpenMetaverse
#endregion Delegates #endregion Delegates
/// <summary>A reference to the current <see cref="GridClient"/> instance</summary> /// <summary>A reference to the current <see cref="GridClient"/> instance</summary>
private GridClient Client; private readonly GridClient Client;
/// <summary>Currently-active group members requests</summary> /// <summary>Currently-active group members requests</summary>
private List<UUID> GroupMembersRequests; private readonly List<UUID> GroupMembersRequests;
/// <summary>Currently-active group roles requests</summary> /// <summary>Currently-active group roles requests</summary>
private List<UUID> GroupRolesRequests; private readonly List<UUID> GroupRolesRequests;
/// <summary>Currently-active group role-member requests</summary> /// <summary>Currently-active group role-member requests</summary>
private List<UUID> GroupRolesMembersRequests; private readonly List<UUID> GroupRolesMembersRequests;
/// <summary>Dictionary keeping group members while request is in progress</summary> /// <summary>Dictionary keeping group members while request is in progress</summary>
private LockingDictionary<UUID, Dictionary<UUID, GroupMember>> TempGroupMembers; private readonly LockingDictionary<UUID, Dictionary<UUID, GroupMember>> TempGroupMembers;
/// <summary>Dictionary keeping mebmer/role mapping while request is in progress</summary> /// <summary>Dictionary keeping member/role mapping while request is in progress</summary>
private LockingDictionary<UUID, List<KeyValuePair<UUID, UUID>>> TempGroupRolesMembers; private readonly LockingDictionary<UUID, List<KeyValuePair<UUID, UUID>>> TempGroupRolesMembers;
/// <summary>Dictionary keeping GroupRole information while request is in progress</summary> /// <summary>Dictionary keeping GroupRole information while request is in progress</summary>
private LockingDictionary<UUID, Dictionary<UUID, GroupRole>> TempGroupRoles; private readonly LockingDictionary<UUID, Dictionary<UUID, GroupRole>> TempGroupRoles;
/// <summary>Caches group name lookups</summary> /// <summary>Caches group name lookups</summary>
public LockingDictionary<UUID, string> GroupName2KeyCache; public LockingDictionary<UUID, string> GroupName2KeyCache;
@ -890,7 +889,7 @@ namespace OpenMetaverse
Client.Self.IM += Self_IM; Client.Self.IM += Self_IM;
Client.Network.RegisterEventCallback("AgentGroupDataUpdate", new Caps.EventQueueCallback(AgentGroupDataUpdateMessageHandler)); Client.Network.RegisterEventCallback("AgentGroupDataUpdate", AgentGroupDataUpdateMessageHandler);
// deprecated in simulator v1.27 // deprecated in simulator v1.27
Client.Network.RegisterCallback(PacketType.AgentDropGroup, AgentDropGroupHandler); Client.Network.RegisterCallback(PacketType.AgentDropGroup, AgentDropGroupHandler);
Client.Network.RegisterCallback(PacketType.GroupTitlesReply, GroupTitlesReplyHandler); Client.Network.RegisterCallback(PacketType.GroupTitlesReply, GroupTitlesReplyHandler);
@ -908,10 +907,10 @@ namespace OpenMetaverse
Client.Network.RegisterCallback(PacketType.EjectGroupMemberReply, EjectGroupMemberReplyHandler); Client.Network.RegisterCallback(PacketType.EjectGroupMemberReply, EjectGroupMemberReplyHandler);
Client.Network.RegisterCallback(PacketType.GroupNoticesListReply, GroupNoticesListReplyHandler); Client.Network.RegisterCallback(PacketType.GroupNoticesListReply, GroupNoticesListReplyHandler);
Client.Network.RegisterEventCallback("AgentDropGroup", new Caps.EventQueueCallback(AgentDropGroupMessageHandler)); Client.Network.RegisterEventCallback("AgentDropGroup", AgentDropGroupMessageHandler);
} }
void Self_IM(object sender, InstantMessageEventArgs e) private void Self_IM(object sender, InstantMessageEventArgs e)
{ {
if (m_GroupInvitation == null || e.IM.Dialog != InstantMessageDialog.GroupInvitation) return; if (m_GroupInvitation == null || e.IM.Dialog != InstantMessageDialog.GroupInvitation) return;
@ -930,7 +929,6 @@ namespace OpenMetaverse
} }
} }
#region Public Methods #region Public Methods
/// <summary> /// <summary>
@ -1044,9 +1042,10 @@ namespace OpenMetaverse
public UUID RequestGroupMembers(UUID group) public UUID RequestGroupMembers(UUID group)
{ {
UUID requestID = UUID.Random(); UUID requestID = UUID.Random();
Uri cap = null; Uri cap = Client.Network.CurrentSim?.Caps?.CapabilityURI("GroupMemberData");
if ((cap = Client.Network.CurrentSim?.Caps?.CapabilityURI("GroupMemberData")) != null) // Request from Capability
if (cap != null)
{ {
OSDMap payload = new OSDMap(1) { ["group_id"] = @group }; OSDMap payload = new OSDMap(1) { ["group_id"] = @group };
Task req = Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, payload, CancellationToken.None, Task req = Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, payload, CancellationToken.None,
@ -1058,7 +1057,7 @@ namespace OpenMetaverse
GroupMembersHandlerCaps(requestID, result); GroupMembersHandlerCaps(requestID, result);
}); });
} }
else else // fallback to LLUDP
{ {
lock (GroupMembersRequests) GroupMembersRequests.Add(requestID); lock (GroupMembersRequests) GroupMembersRequests.Add(requestID);
@ -1190,9 +1189,9 @@ namespace OpenMetaverse
/// <summary>Invites a user to a group</summary> /// <summary>Invites a user to a group</summary>
/// <param name="group">The group to invite to</param> /// <param name="group">The group to invite to</param>
/// <param name="roles">A list of roles to invite a person to</param> /// <param name="roles">A list of roles to invite an agent to</param>
/// <param name="personkey">Key of person to invite</param> /// <param name="requestedAgent">Key of agent to invite</param>
public void Invite(UUID group, List<UUID> roles, UUID personkey) public void Invite(UUID group, List<UUID> roles, UUID requestedAgent)
{ {
InviteGroupRequestPacket igp = new InviteGroupRequestPacket InviteGroupRequestPacket igp = new InviteGroupRequestPacket
{ {
@ -1208,7 +1207,7 @@ namespace OpenMetaverse
{ {
igp.InviteData[i] = new InviteGroupRequestPacket.InviteDataBlock igp.InviteData[i] = new InviteGroupRequestPacket.InviteDataBlock
{ {
InviteeID = personkey, InviteeID = requestedAgent,
RoleID = roles[i] RoleID = roles[i]
}; };
} }
@ -1276,7 +1275,7 @@ namespace OpenMetaverse
} }
/// <summary> /// <summary>
/// Save wheather agent wants to accept group notices and list this group in their profile /// Set agent's group preferences
/// </summary> /// </summary>
/// <param name="groupID">Group <see cref="UUID"/></param> /// <param name="groupID">Group <see cref="UUID"/></param>
/// <param name="acceptNotices">Accept notices from this group</param> /// <param name="acceptNotices">Accept notices from this group</param>
@ -1307,11 +1306,18 @@ namespace OpenMetaverse
/// <param name="id">group ID (UUID) to join.</param> /// <param name="id">group ID (UUID) to join.</param>
public void RequestJoinGroup(UUID id) public void RequestJoinGroup(UUID id)
{ {
JoinGroupRequestPacket join = new JoinGroupRequestPacket(); JoinGroupRequestPacket join = new JoinGroupRequestPacket
join.AgentData.AgentID = Client.Self.AgentID; {
join.AgentData.SessionID = Client.Self.SessionID; AgentData =
{
join.GroupData.GroupID = id; AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID
},
GroupData =
{
GroupID = id
}
};
Client.Network.SendPacket(join); Client.Network.SendPacket(join);
} }
@ -1324,7 +1330,7 @@ namespace OpenMetaverse
/// <param name="group">Group struct containing the new group info</param> /// <param name="group">Group struct containing the new group info</param>
public void RequestCreateGroup(Group group) public void RequestCreateGroup(Group group)
{ {
OpenMetaverse.Packets.CreateGroupRequestPacket cgrp = new CreateGroupRequestPacket CreateGroupRequestPacket cgrp = new CreateGroupRequestPacket
{ {
AgentData = new CreateGroupRequestPacket.AgentDataBlock AgentData = new CreateGroupRequestPacket.AgentDataBlock
{ {
@ -1352,24 +1358,24 @@ namespace OpenMetaverse
/// <param name="group">Group struct to update.</param> /// <param name="group">Group struct to update.</param>
public void UpdateGroup(UUID id, Group group) public void UpdateGroup(UUID id, Group group)
{ {
OpenMetaverse.Packets.UpdateGroupInfoPacket cgrp = new UpdateGroupInfoPacket UpdateGroupInfoPacket cgrp = new UpdateGroupInfoPacket
{ {
AgentData = new UpdateGroupInfoPacket.AgentDataBlock AgentData = new UpdateGroupInfoPacket.AgentDataBlock
{ {
AgentID = Client.Self.AgentID, AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID SessionID = Client.Self.SessionID
} },
}; GroupData = new UpdateGroupInfoPacket.GroupDataBlock
cgrp.GroupData = new UpdateGroupInfoPacket.GroupDataBlock
{ {
GroupID = id, GroupID = id,
AllowPublish = @group.AllowPublish, AllowPublish = group.AllowPublish,
Charter = Utils.StringToBytes(@group.Charter), Charter = Utils.StringToBytes(group.Charter),
InsigniaID = @group.InsigniaID, InsigniaID = group.InsigniaID,
MaturePublish = @group.MaturePublish, MaturePublish = group.MaturePublish,
MembershipFee = @group.MembershipFee, MembershipFee = group.MembershipFee,
OpenEnrollment = @group.OpenEnrollment, OpenEnrollment = group.OpenEnrollment,
ShowInList = @group.ShowInList ShowInList = group.ShowInList
}
}; };
Client.Network.SendPacket(cgrp); Client.Network.SendPacket(cgrp);
@ -1380,16 +1386,16 @@ namespace OpenMetaverse
/// <param name="member">Avatar's key to eject</param> /// <param name="member">Avatar's key to eject</param>
public void EjectUser(UUID group, UUID member) public void EjectUser(UUID group, UUID member)
{ {
OpenMetaverse.Packets.EjectGroupMemberRequestPacket eject = new EjectGroupMemberRequestPacket EjectGroupMemberRequestPacket eject = new EjectGroupMemberRequestPacket
{ {
AgentData = new EjectGroupMemberRequestPacket.AgentDataBlock AgentData = new EjectGroupMemberRequestPacket.AgentDataBlock
{ {
AgentID = Client.Self.AgentID, AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID SessionID = Client.Self.SessionID
} },
GroupData = new EjectGroupMemberRequestPacket.GroupDataBlock {GroupID = group},
EjectData = new EjectGroupMemberRequestPacket.EjectDataBlock[1]
}; };
eject.GroupData = new EjectGroupMemberRequestPacket.GroupDataBlock {GroupID = @group};
eject.EjectData = new EjectGroupMemberRequestPacket.EjectDataBlock[1];
eject.EjectData[0] = new EjectGroupMemberRequestPacket.EjectDataBlock {EjecteeID = member}; eject.EjectData[0] = new EjectGroupMemberRequestPacket.EjectDataBlock {EjecteeID = member};
Client.Network.SendPacket(eject); Client.Network.SendPacket(eject);
@ -1399,7 +1405,7 @@ namespace OpenMetaverse
/// <param name="role">Modified role to be updated</param> /// <param name="role">Modified role to be updated</param>
public void UpdateRole(GroupRole role) public void UpdateRole(GroupRole role)
{ {
OpenMetaverse.Packets.GroupRoleUpdatePacket gru = GroupRoleUpdatePacket gru =
new GroupRoleUpdatePacket new GroupRoleUpdatePacket
{ {
AgentData = AgentData =
@ -1427,7 +1433,7 @@ namespace OpenMetaverse
/// <param name="role">Role to create</param> /// <param name="role">Role to create</param>
public void CreateRole(UUID group, GroupRole role) public void CreateRole(UUID group, GroupRole role)
{ {
OpenMetaverse.Packets.GroupRoleUpdatePacket gru = GroupRoleUpdatePacket gru =
new GroupRoleUpdatePacket new GroupRoleUpdatePacket
{ {
AgentData = AgentData =
@ -1455,7 +1461,7 @@ namespace OpenMetaverse
/// <param name="roleID">Role to delete</param> /// <param name="roleID">Role to delete</param>
public void DeleteRole(UUID group, UUID roleID) public void DeleteRole(UUID group, UUID roleID)
{ {
OpenMetaverse.Packets.GroupRoleUpdatePacket gru = GroupRoleUpdatePacket gru =
new GroupRoleUpdatePacket new GroupRoleUpdatePacket
{ {
AgentData = AgentData =
@ -1484,7 +1490,7 @@ namespace OpenMetaverse
/// <param name="member">Avatar's Key to remove</param> /// <param name="member">Avatar's Key to remove</param>
public void RemoveFromRole(UUID group, UUID role, UUID member) public void RemoveFromRole(UUID group, UUID role, UUID member)
{ {
OpenMetaverse.Packets.GroupRoleChangesPacket grc = GroupRoleChangesPacket grc =
new GroupRoleChangesPacket new GroupRoleChangesPacket
{ {
AgentData = AgentData =
@ -1511,7 +1517,7 @@ namespace OpenMetaverse
/// <param name="member">Avatar's ID to assign to role</param> /// <param name="member">Avatar's ID to assign to role</param>
public void AddToRole(UUID group, UUID role, UUID member) public void AddToRole(UUID group, UUID role, UUID member)
{ {
OpenMetaverse.Packets.GroupRoleChangesPacket grc = GroupRoleChangesPacket grc =
new GroupRoleChangesPacket new GroupRoleChangesPacket
{ {
AgentData = AgentData =
@ -1536,7 +1542,7 @@ namespace OpenMetaverse
/// <param name="group">Group ID to fetch notices for</param> /// <param name="group">Group ID to fetch notices for</param>
public void RequestGroupNoticesList(UUID group) public void RequestGroupNoticesList(UUID group)
{ {
OpenMetaverse.Packets.GroupNoticesListRequestPacket gnl = GroupNoticesListRequestPacket gnl =
new GroupNoticesListRequestPacket new GroupNoticesListRequestPacket
{ {
AgentData = AgentData =
@ -1553,7 +1559,7 @@ namespace OpenMetaverse
/// <param name="noticeID">ID of group notice</param> /// <param name="noticeID">ID of group notice</param>
public void RequestGroupNotice(UUID noticeID) public void RequestGroupNotice(UUID noticeID)
{ {
OpenMetaverse.Packets.GroupNoticeRequestPacket gnr = GroupNoticeRequestPacket gnr =
new GroupNoticeRequestPacket new GroupNoticeRequestPacket
{ {
AgentData = AgentData =
@ -1618,43 +1624,21 @@ namespace OpenMetaverse
Client.Network.SendPacket(p); Client.Network.SendPacket(p);
} }
/// <summary>
/// Gets the URI of the cpability for handling group bans
/// </summary>
/// <param name="groupID">Group ID</param>
/// <returns>null, if the feature is not supported, or URI of the capability</returns>
public Uri GetGroupAPIUri(UUID groupID)
{
Uri ret = null;
if (Client.Network.Connected)
{
ret = Client.Network.CurrentSim?.Caps?.CapabilityURI("GroupAPIv1");
if (ret != null)
{
ret = new Uri($"{ret}?group_id={groupID.ToString()}");
}
}
return ret;
}
/// <summary> /// <summary>
/// Request a list of residents banned from joining a group /// Request a list of residents banned from joining a group
/// </summary> /// </summary>
/// <param name="groupID">UUID of the group</param> /// <param name="groupID">UUID of the group</param>
/// <param name="callback">Callback on request completion</param> /// <param name="callback">Callback on request completion</param>
public async Task RequestBannedAgents(UUID groupID, EventHandler<BannedAgentsEventArgs> callback = null) /// <param name="cancellationToken"></param>
public async Task RequestBannedAgents(UUID groupID, EventHandler<BannedAgentsEventArgs> callback = null,
CancellationToken cancellationToken = default)
{ {
Uri uri = GetGroupAPIUri(groupID); var uri = new UriBuilder(Client.Network.CurrentSim.Caps.CapabilityURI("GroupAPIv1"))
if (uri == null) { return; } {Query = $"group_id={groupID}"}.Uri;
await Client.HttpCapsClient.GetRequestAsync(uri, cancellationToken, (response, data, error) =>
Uri cap = Client.Network.CurrentSim.Caps.CapabilityURI("GroupReqBanned");
await Client.HttpCapsClient.GetRequestAsync(cap, CancellationToken.None, (response, data, error) =>
{ {
try try
{ {
if (error != null) { throw error; } if (error != null) { throw error; }
OSD result = OSDParser.Deserialize(data); OSD result = OSDParser.Deserialize(data);
@ -1677,7 +1661,7 @@ namespace OpenMetaverse
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Log("Failed to get a list of banned group members: " + ex.Message, Helpers.LogLevel.Warning, Client); Logger.Log($"Failed to fetch group ban list for {groupID}: {ex.Message}", Helpers.LogLevel.Warning, Client);
var ret = new BannedAgentsEventArgs(groupID, false, null); var ret = new BannedAgentsEventArgs(groupID, false, null);
OnBannedAgents(ret); OnBannedAgents(ret);
if (callback != null) if (callback != null)
@ -1689,17 +1673,6 @@ namespace OpenMetaverse
}); });
} }
/// <summary>
/// Request that group of agents be banned or unbanned from the group
/// </summary>
/// <param name="groupID">Group ID</param>
/// <param name="action">Ban/Unban action</param>
/// <param name="agents">Array of agents UUIDs to ban</param>
public void RequestBanAction(UUID groupID, GroupBanAction action, UUID[] agents)
{
RequestBanAction(groupID, action, agents, null);
}
/// <summary> /// <summary>
/// Request that group of agents be banned or unbanned from the group /// Request that group of agents be banned or unbanned from the group
/// </summary> /// </summary>
@ -1707,26 +1680,28 @@ namespace OpenMetaverse
/// <param name="action">Ban/Unban action</param> /// <param name="action">Ban/Unban action</param>
/// <param name="agents">Array of agents UUIDs to ban</param> /// <param name="agents">Array of agents UUIDs to ban</param>
/// <param name="callback">Callback</param> /// <param name="callback">Callback</param>
public void RequestBanAction(UUID groupID, GroupBanAction action, UUID[] agents, EventHandler<EventArgs> callback) /// <param name="cancellationToken"></param>
public async Task RequestBanAction(UUID groupID, GroupBanAction action, UUID[] agents, EventHandler<EventArgs> callback = null,
CancellationToken cancellationToken = default)
{ {
Uri baseCap = GetGroupAPIUri(groupID); var uri = new UriBuilder(Client.Network.CurrentSim.Caps.CapabilityURI("GroupAPIv1")) {Query = $"group_id={groupID}"}.Uri;
if (baseCap == null)
{
Logger.Log("Unable to get Cap url for GroupAPIv1", Helpers.LogLevel.Warning, Client);
}
OSDMap payload = new OSDMap { ["ban_action"] = (int)action }; OSDMap payload = new OSDMap { ["ban_action"] = (int)action };
OSDArray banIDs = new OSDArray(agents.Length); OSDArray banIDs = new OSDArray(agents.Length);
for (int i = 0; i < agents.Length; i++) foreach (var agent in agents)
{ {
UUID agent = agents[i];
banIDs.Add(agent); banIDs.Add(agent);
} }
payload["ban_ids"] = banIDs; payload["ban_ids"] = banIDs;
Task req = Client.HttpCapsClient.PostRequestAsync(baseCap, OSDFormat.Xml, payload, CancellationToken.None, await Client.HttpCapsClient.PostRequestAsync(uri, OSDFormat.Xml, payload, cancellationToken,
(response, data, error) => (response, data, error) =>
{ {
if (error != null) { return; } if (error != null)
{
Logger.Log($"Failed to ban members from {groupID}: {error.Message}", Helpers.LogLevel.Warning, Client);
return;
}
if (callback != null) if (callback != null)
{ {
try { callback(this, EventArgs.Empty); } try { callback(this, EventArgs.Empty); }
@ -1746,14 +1721,16 @@ namespace OpenMetaverse
Dictionary<UUID, Group> currentGroups = new Dictionary<UUID, Group>(); Dictionary<UUID, Group> currentGroups = new Dictionary<UUID, Group>();
for (int i = 0; i < msg.GroupDataBlock.Length; i++) for (int i = 0; i < msg.GroupDataBlock.Length; i++)
{ {
Group group = new Group(); Group group = new Group
group.ID = msg.GroupDataBlock[i].GroupID; {
group.InsigniaID = msg.GroupDataBlock[i].GroupInsigniaID; ID = msg.GroupDataBlock[i].GroupID,
group.Name = msg.GroupDataBlock[i].GroupName; InsigniaID = msg.GroupDataBlock[i].GroupInsigniaID,
group.Contribution = msg.GroupDataBlock[i].Contribution; Name = msg.GroupDataBlock[i].GroupName,
group.AcceptNotices = msg.GroupDataBlock[i].AcceptNotices; Contribution = msg.GroupDataBlock[i].Contribution,
group.Powers = msg.GroupDataBlock[i].GroupPowers; AcceptNotices = msg.GroupDataBlock[i].AcceptNotices,
group.ListInProfile = msg.NewGroupDataBlock[i].ListInProfile; Powers = msg.GroupDataBlock[i].GroupPowers,
ListInProfile = msg.NewGroupDataBlock[i].ListInProfile
};
currentGroups.Add(group.ID, group); currentGroups.Add(group.ID, group);
@ -1803,24 +1780,25 @@ namespace OpenMetaverse
{ {
Packet packet = e.Packet; Packet packet = e.Packet;
GroupProfileReplyPacket profile = (GroupProfileReplyPacket)packet; GroupProfileReplyPacket profile = (GroupProfileReplyPacket)packet;
Group group = new Group(); Group group = new Group
{
group.ID = profile.GroupData.GroupID; ID = profile.GroupData.GroupID,
group.AllowPublish = profile.GroupData.AllowPublish; AllowPublish = profile.GroupData.AllowPublish,
group.Charter = Utils.BytesToString(profile.GroupData.Charter); Charter = Utils.BytesToString(profile.GroupData.Charter),
group.FounderID = profile.GroupData.FounderID; FounderID = profile.GroupData.FounderID,
group.GroupMembershipCount = profile.GroupData.GroupMembershipCount; GroupMembershipCount = profile.GroupData.GroupMembershipCount,
group.GroupRolesCount = profile.GroupData.GroupRolesCount; GroupRolesCount = profile.GroupData.GroupRolesCount,
group.InsigniaID = profile.GroupData.InsigniaID; InsigniaID = profile.GroupData.InsigniaID,
group.MaturePublish = profile.GroupData.MaturePublish; MaturePublish = profile.GroupData.MaturePublish,
group.MembershipFee = profile.GroupData.MembershipFee; MembershipFee = profile.GroupData.MembershipFee,
group.MemberTitle = Utils.BytesToString(profile.GroupData.MemberTitle); MemberTitle = Utils.BytesToString(profile.GroupData.MemberTitle),
group.Money = profile.GroupData.Money; Money = profile.GroupData.Money,
group.Name = Utils.BytesToString(profile.GroupData.Name); Name = Utils.BytesToString(profile.GroupData.Name),
group.OpenEnrollment = profile.GroupData.OpenEnrollment; OpenEnrollment = profile.GroupData.OpenEnrollment,
group.OwnerRole = profile.GroupData.OwnerRole; OwnerRole = profile.GroupData.OwnerRole,
group.Powers = (GroupPowers)profile.GroupData.PowersMask; Powers = (GroupPowers)profile.GroupData.PowersMask,
group.ShowInList = profile.GroupData.ShowInList; ShowInList = profile.GroupData.ShowInList
};
OnGroupProfile(new GroupProfileEventArgs(group)); OnGroupProfile(new GroupProfileEventArgs(group));
} }
@ -1836,23 +1814,16 @@ namespace OpenMetaverse
Packet packet = e.Packet; Packet packet = e.Packet;
GroupNoticesListReplyPacket reply = (GroupNoticesListReplyPacket)packet; GroupNoticesListReplyPacket reply = (GroupNoticesListReplyPacket)packet;
List<GroupNoticesListEntry> notices = new List<GroupNoticesListEntry>(); List<GroupNoticesListEntry> notices = reply.Data.Select(entry => new GroupNoticesListEntry
foreach (GroupNoticesListReplyPacket.DataBlock entry in reply.Data)
{
GroupNoticesListEntry notice =
new GroupNoticesListEntry
{ {
FromName = Utils.BytesToString(entry.FromName), FromName = Utils.BytesToString(entry.FromName),
Subject = Utils.BytesToString(entry.Subject), Subject = Utils.BytesToString(entry.Subject),
NoticeID = entry.NoticeID, NoticeID = entry.NoticeID,
Timestamp = entry.Timestamp, Timestamp = entry.Timestamp,
HasAttachment = entry.HasAttachment, HasAttachment = entry.HasAttachment,
AssetType = (AssetType) entry.AssetType AssetType = (AssetType)entry.AssetType
}; })
.ToList();
notices.Add(notice);
}
OnGroupNoticesListReply(new GroupNoticesListReplyEventArgs(reply.AgentData.GroupID, notices)); OnGroupNoticesListReply(new GroupNoticesListReplyEventArgs(reply.AgentData.GroupID, notices));
} }
@ -2085,9 +2056,10 @@ namespace OpenMetaverse
/// <param name="e">The EventArgs object containing the packet data</param> /// <param name="e">The EventArgs object containing the packet data</param>
protected void GroupActiveProposalItemHandler(object sender, PacketReceivedEventArgs e) protected void GroupActiveProposalItemHandler(object sender, PacketReceivedEventArgs e)
{ {
//GroupActiveProposalItemReplyPacket proposal = (GroupActiveProposalItemReplyPacket)packet; GroupActiveProposalItemReplyPacket proposal = (GroupActiveProposalItemReplyPacket)e.Packet;
// TODO: Create a proposal struct to represent the fields in a proposal item // NOTE: Second Life server removed this many years back.
Logger.Log($"Received GroupActiveProposalItemReplyPacket: {proposal}", Helpers.LogLevel.Info);
} }
/// <summary>Process an incoming packet and raise the appropriate events</summary> /// <summary>Process an incoming packet and raise the appropriate events</summary>
@ -2095,9 +2067,10 @@ namespace OpenMetaverse
/// <param name="e">The EventArgs object containing the packet data</param> /// <param name="e">The EventArgs object containing the packet data</param>
protected void GroupVoteHistoryItemHandler(object sender, PacketReceivedEventArgs e) protected void GroupVoteHistoryItemHandler(object sender, PacketReceivedEventArgs e)
{ {
//GroupVoteHistoryItemReplyPacket history = (GroupVoteHistoryItemReplyPacket)packet; GroupVoteHistoryItemReplyPacket history = (GroupVoteHistoryItemReplyPacket)e.Packet;
// TODO: This was broken in the official viewer when I was last trying to work on it // NOTE: Second Life server removed this many years back.
Logger.Log($"Received GroupVoteHistoryItemReplyPacket: {history}", Helpers.LogLevel.Info);
} }
/// <summary>Process an incoming packet and raise the appropriate events</summary> /// <summary>Process an incoming packet and raise the appropriate events</summary>
@ -2235,7 +2208,7 @@ namespace OpenMetaverse
/// <param name="groups">The current groups your agent is a member of</param> /// <param name="groups">The current groups your agent is a member of</param>
public CurrentGroupsEventArgs(Dictionary<UUID, Group> groups) public CurrentGroupsEventArgs(Dictionary<UUID, Group> groups)
{ {
this.Groups = groups; Groups = groups;
} }
} }
@ -2249,7 +2222,7 @@ namespace OpenMetaverse
/// <param name="groupNames">The Group names dictionary</param> /// <param name="groupNames">The Group names dictionary</param>
public GroupNamesEventArgs(Dictionary<UUID, string> groupNames) public GroupNamesEventArgs(Dictionary<UUID, string> groupNames)
{ {
this.GroupNames = groupNames; GroupNames = groupNames;
} }
} }
@ -2274,9 +2247,9 @@ namespace OpenMetaverse
/// <param name="members">The membership list of the group</param> /// <param name="members">The membership list of the group</param>
public GroupMembersReplyEventArgs(UUID requestID, UUID groupID, Dictionary<UUID, GroupMember> members) public GroupMembersReplyEventArgs(UUID requestID, UUID groupID, Dictionary<UUID, GroupMember> members)
{ {
this.RequestID = requestID; RequestID = requestID;
this.GroupID = groupID; GroupID = groupID;
this.Members = members; Members = members;
} }
} }
@ -2300,9 +2273,9 @@ namespace OpenMetaverse
/// <param name="roles">The dictionary containing the roles</param> /// <param name="roles">The dictionary containing the roles</param>
public GroupRolesDataReplyEventArgs(UUID requestID, UUID groupID, Dictionary<UUID, GroupRole> roles) public GroupRolesDataReplyEventArgs(UUID requestID, UUID groupID, Dictionary<UUID, GroupRole> roles)
{ {
this.RequestID = requestID; RequestID = requestID;
this.GroupID = groupID; GroupID = groupID;
this.Roles = roles; Roles = roles;
} }
} }
@ -2326,9 +2299,9 @@ namespace OpenMetaverse
/// <param name="rolesMembers">The member to roles map</param> /// <param name="rolesMembers">The member to roles map</param>
public GroupRolesMembersReplyEventArgs(UUID requestID, UUID groupID, List<KeyValuePair<UUID, UUID>> rolesMembers) public GroupRolesMembersReplyEventArgs(UUID requestID, UUID groupID, List<KeyValuePair<UUID, UUID>> rolesMembers)
{ {
this.RequestID = requestID; RequestID = requestID;
this.GroupID = groupID; GroupID = groupID;
this.RolesMembers = rolesMembers; RolesMembers = rolesMembers;
} }
} }
@ -2352,9 +2325,9 @@ namespace OpenMetaverse
/// <param name="titles">The titles</param> /// <param name="titles">The titles</param>
public GroupTitlesReplyEventArgs(UUID requestID, UUID groupID, Dictionary<UUID, GroupTitle> titles) public GroupTitlesReplyEventArgs(UUID requestID, UUID groupID, Dictionary<UUID, GroupTitle> titles)
{ {
this.RequestID = requestID; RequestID = requestID;
this.GroupID = groupID; GroupID = groupID;
this.Titles = titles; Titles = titles;
} }
} }
@ -2372,8 +2345,8 @@ namespace OpenMetaverse
/// <param name="summary">The summary data</param> /// <param name="summary">The summary data</param>
public GroupAccountSummaryReplyEventArgs(UUID groupID, GroupAccountSummary summary) public GroupAccountSummaryReplyEventArgs(UUID groupID, GroupAccountSummary summary)
{ {
this.GroupID = groupID; GroupID = groupID;
this.Summary = summary; Summary = summary;
} }
} }
@ -2391,13 +2364,13 @@ namespace OpenMetaverse
/// <summary>Construct a new instance of the GroupCreatedReplyEventArgs class</summary> /// <summary>Construct a new instance of the GroupCreatedReplyEventArgs class</summary>
/// <param name="groupID">The ID of the group</param> /// <param name="groupID">The ID of the group</param>
/// <param name="success">the success or faulure of the request</param> /// <param name="success">the success or failure of the request</param>
/// <param name="messsage">A string containing additional information</param> /// <param name="message">A string containing additional information</param>
public GroupCreatedReplyEventArgs(UUID groupID, bool success, string messsage) public GroupCreatedReplyEventArgs(UUID groupID, bool success, string message)
{ {
this.GroupID = groupID; GroupID = groupID;
this.Success = success; Success = success;
this.Message = messsage; Message = message;
} }
} }
@ -2415,8 +2388,8 @@ namespace OpenMetaverse
/// <param name="success">true of the request was successful</param> /// <param name="success">true of the request was successful</param>
public GroupOperationEventArgs(UUID groupID, bool success) public GroupOperationEventArgs(UUID groupID, bool success)
{ {
this.GroupID = groupID; GroupID = groupID;
this.Success = success; Success = success;
} }
} }
@ -2463,7 +2436,7 @@ namespace OpenMetaverse
/// <param name="group">The group profile</param> /// <param name="group">The group profile</param>
public GroupProfileEventArgs(Group group) public GroupProfileEventArgs(Group group)
{ {
this.Group = group; Group = group;
} }
} }
@ -2492,10 +2465,10 @@ namespace OpenMetaverse
public GroupInvitationEventArgs(Simulator simulator, UUID agentID, string agentName, string message) public GroupInvitationEventArgs(Simulator simulator, UUID agentID, string agentName, string message)
{ {
this.Simulator = simulator; Simulator = simulator;
this.AgentID = agentID; AgentID = agentID;
this.FromName = agentName; FromName = agentName;
this.Message = message; Message = message;
} }
} }
@ -2515,9 +2488,9 @@ namespace OpenMetaverse
public BannedAgentsEventArgs(UUID groupID, bool success, Dictionary<UUID, DateTime> bannedAgents) public BannedAgentsEventArgs(UUID groupID, bool success, Dictionary<UUID, DateTime> bannedAgents)
{ {
this.GroupID = groupID; GroupID = groupID;
this.Success = success; Success = success;
this.BannedAgents = bannedAgents; BannedAgents = bannedAgents;
} }
} }

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2007-2008, openmetaverse.co * Copyright (c) 2007-2008, openmetaverse.co
* Copyright (c) 2024, Sjofn LLC. * Copyright (c) 2024-2025, Sjofn LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -360,7 +360,7 @@ namespace OpenMetaverse.Imaging
SKBitmap bitmap = null; SKBitmap bitmap = null;
lock (ResourceSync) lock (ResourceSync)
{ {
using (Stream stream = Helpers.GetResourceStream(fileName, Settings.RESOURCE_DIR)) using (Stream stream = Helpers.GetResourceStream(fileName, Path.Combine(Settings.RESOURCE_DIR, "static_assets")))
{ {
if (stream != null) if (stream != null)
{ {

View file

@ -148,7 +148,7 @@ namespace OpenMetaverse.Imaging
tga[di++] = c.Green; tga[di++] = c.Green;
tga[di++] = c.Red; tga[di++] = c.Red;
tga[di++] = (byte)(image.AlphaType > SKAlphaType.Opaque ? 0x0 : c.Alpha); tga[di++] = (byte)(image.AlphaType > SKAlphaType.Opaque ? c.Alpha : 0x0);
} }
} }

View file

@ -190,6 +190,32 @@ namespace OpenMetaverse
Items = new ConcurrentDictionary<UUID, InventoryNode>(); Items = new ConcurrentDictionary<UUID, InventoryNode>();
} }
/// <summary>
/// Returns all links of that link the specific <paramref name="assertId"/>.
/// </summary>
/// <param name="assertId">An inventory items assert UUID.</param>
/// <returns></returns>
public List<InventoryNode> FindAllLinks(UUID assertId)
{
List<InventoryNode> links = new List<InventoryNode>();
lock (RootNode.Nodes.SyncRoot)
{
links = Items.Values.Where(node => IsLinkOf(node, assertId)).ToList();
}
return links;
}
private static bool IsLinkOf(InventoryNode node, UUID assertId)
{
if (node.Data is InventoryItem item && item.AssetType == AssetType.Link)
{
return item.ActualUUID == assertId;
}
return false;
}
public List<InventoryBase> GetContents(InventoryFolder folder) public List<InventoryBase> GetContents(InventoryFolder folder)
{ {
return GetContents(folder.UUID); return GetContents(folder.UUID);
@ -487,7 +513,6 @@ namespace OpenMetaverse
} }
#endregion Operators #endregion Operators
} }
#region EventArgs classes #region EventArgs classes

View file

@ -443,7 +443,11 @@ namespace OpenMetaverse
[NonSerialized] [NonSerialized]
private readonly GridClient Client; private readonly GridClient Client;
[NonSerialized] [NonSerialized]
public Inventory _Store; private Inventory _Store;
public Inventory GetStore
{
get { return _Store; }
}
private object _CallbacksLock = new object(); private object _CallbacksLock = new object();
private uint _CallbackPos; private uint _CallbackPos;
private readonly Dictionary<uint, ItemCreatedCallback> _ItemCreatedCallbacks = new Dictionary<uint, ItemCreatedCallback>(); private readonly Dictionary<uint, ItemCreatedCallback> _ItemCreatedCallbacks = new Dictionary<uint, ItemCreatedCallback>();
@ -529,7 +533,7 @@ namespace OpenMetaverse
InventoryItem item = null; InventoryItem item = null;
await RequestFetchInventoryHttpAsync(itemId, ownerId, token, list => await RequestFetchInventoryHttpAsync(itemId, ownerId, token, list =>
{ {
item = list.First(); item = list.FirstOrDefault();
}); });
return item; return item;
} }
@ -1780,6 +1784,7 @@ namespace OpenMetaverse
/// <param name="folderID">Put newly created link in folder with this UUID</param> /// <param name="folderID">Put newly created link in folder with this UUID</param>
/// <param name="bse">Inventory item or folder</param> /// <param name="bse">Inventory item or folder</param>
/// <param name="callback">Method to call upon creation of the link</param> /// <param name="callback">Method to call upon creation of the link</param>
/// <param name="cancellationToken"></param>
public void CreateLink(UUID folderID, InventoryBase bse, ItemCreatedCallback callback, CancellationToken cancellationToken = default) public void CreateLink(UUID folderID, InventoryBase bse, ItemCreatedCallback callback, CancellationToken cancellationToken = default)
{ {
if (bse is InventoryFolder folder) if (bse is InventoryFolder folder)
@ -1798,6 +1803,7 @@ namespace OpenMetaverse
/// <param name="folderID">Put newly created link in folder with this UUID</param> /// <param name="folderID">Put newly created link in folder with this UUID</param>
/// <param name="item">Original inventory item</param> /// <param name="item">Original inventory item</param>
/// <param name="callback">Method to call upon creation of the link</param> /// <param name="callback">Method to call upon creation of the link</param>
/// <param name="cancellationToken"></param>
public void CreateLink(UUID folderID, InventoryItem item, ItemCreatedCallback callback, CancellationToken cancellationToken = default) public void CreateLink(UUID folderID, InventoryItem item, ItemCreatedCallback callback, CancellationToken cancellationToken = default)
{ {
CreateLink(folderID, item.UUID, item.Name, item.Description, item.InventoryType, UUID.Random(), callback, cancellationToken); CreateLink(folderID, item.UUID, item.Name, item.Description, item.InventoryType, UUID.Random(), callback, cancellationToken);
@ -1809,6 +1815,7 @@ namespace OpenMetaverse
/// <param name="folderID">Put newly created link in folder with this UUID</param> /// <param name="folderID">Put newly created link in folder with this UUID</param>
/// <param name="folder">Original inventory folder</param> /// <param name="folder">Original inventory folder</param>
/// <param name="callback">Method to call upon creation of the link</param> /// <param name="callback">Method to call upon creation of the link</param>
/// <param name="cancellationToken"></param>
public void CreateLink(UUID folderID, InventoryFolder folder, ItemCreatedCallback callback, CancellationToken cancellationToken = default) public void CreateLink(UUID folderID, InventoryFolder folder, ItemCreatedCallback callback, CancellationToken cancellationToken = default)
{ {
CreateLink(folderID, folder.UUID, folder.Name, "", InventoryType.Folder, UUID.Random(), callback, cancellationToken); CreateLink(folderID, folder.UUID, folder.Name, "", InventoryType.Folder, UUID.Random(), callback, cancellationToken);
@ -1824,6 +1831,7 @@ namespace OpenMetaverse
/// <param name="invType">Inventory Type</param> /// <param name="invType">Inventory Type</param>
/// <param name="transactionID">Transaction UUID</param> /// <param name="transactionID">Transaction UUID</param>
/// <param name="callback">Method to call upon creation of the link</param> /// <param name="callback">Method to call upon creation of the link</param>
/// <param name="cancellationToken"></param>
public void CreateLink(UUID folderID, UUID itemID, string name, string description, public void CreateLink(UUID folderID, UUID itemID, string name, string description,
InventoryType invType, UUID transactionID, ItemCreatedCallback callback, CancellationToken cancellationToken = default) InventoryType invType, UUID transactionID, ItemCreatedCallback callback, CancellationToken cancellationToken = default)
{ {

View file

@ -26,15 +26,15 @@
<Optimize>True</Optimize> <Optimize>True</Optimize>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CoreJ2K.Skia" Version="1.1.1.48" /> <PackageReference Include="CoreJ2K.Skia" Version="1.1.2.50" />
<PackageReference Include="log4net" Version="3.1.0" /> <PackageReference Include="log4net" Version="3.2.0" />
<PackageReference Include="MessagePack" Version="3.1.4" /> <PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.6" /> <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.8" />
<PackageReference Include="OggVorbisEncoder" Version="1.2.2" /> <PackageReference Include="OggVorbisEncoder" Version="1.2.2" />
<PackageReference Include="Pfim" Version="0.11.3" /> <PackageReference Include="Pfim" Version="0.11.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" /> <PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Threading.Channels" Version="9.0.6" /> <PackageReference Include="System.Threading.Channels" Version="9.0.8" />
<PackageReference Include="XmlRpcCore" Version="3.1.3" /> <PackageReference Include="XmlRpcCore" Version="3.1.3" />
<PackageReference Include="zlib.net-mutliplatform" Version="1.0.8" /> <PackageReference Include="zlib.net-mutliplatform" Version="1.0.8" />
</ItemGroup> </ItemGroup>
@ -42,6 +42,14 @@
<ProjectReference Include="..\LibreMetaverse.StructuredData\LibreMetaverse.StructuredData.csproj" /> <ProjectReference Include="..\LibreMetaverse.StructuredData\LibreMetaverse.StructuredData.csproj" />
<ProjectReference Include="..\LibreMetaverse.Types\LibreMetaverse.Types.csproj" /> <ProjectReference Include="..\LibreMetaverse.Types\LibreMetaverse.Types.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="linden\**" Pack="true">
<PackagePath>contentFiles\any\any\linden;content\linden</PackagePath>
<PackageCopyToOutput>true</PackageCopyToOutput>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\LICENSE.txt" Pack="true" PackagePath="\" /> <None Include="..\LICENSE.txt" Pack="true" PackagePath="\" />
<None Include="..\README.md" Pack="true" PackagePath="\" /> <None Include="..\README.md" Pack="true" PackagePath="\" />

381
LibreMetaverse/LslSyntax.cs Normal file
View file

@ -0,0 +1,381 @@
/*
* Copyright (c) 2025, Sjofn LLC.
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.co nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using Logger = OpenMetaverse.Logger;
namespace LibreMetaverse
{
public class LslSyntax
{
public enum LslCategory
{
Function,
Control,
Event,
Datatype,
Constant,
Flow,
Unknown = -1,
}
public struct LslKeyword
{
public LslCategory Category;
public string Keyword;
public string Tooltip;
public bool Deprecated;
public bool GodMode;
}
private const string KEYWORDS_DEFAULT = "keywords_lsl_default.xml";
private const string VERSION_KEY = "llsd-lsl-syntax-version";
private const string SYNTAX_FEATURE_IDENTIFIER = "LSLSyntaxId";
private const string SYNTAX_CAPABILITY_IDENTIFIER = "LSLSyntax";
private static Dictionary<string, LslKeyword> _keywords = new Dictionary<string, LslKeyword>();
public static FrozenDictionary<string, LslKeyword> Keywords => _keywords.ToFrozenDictionary();
private GridClient _client;
private UUID _syntaxId;
#region EVENTS
/// <summary>The event subscribers, null if no subscribers</summary>
private EventHandler _syntaxChanged;
///<summary>Raises the SyntaxChanged Event</summary>
protected void OnSyntaxChanged()
{
EventHandler handler = _syntaxChanged;
handler?.Invoke(this, null);
}
/// <summary>Thread sync lock object</summary>
private readonly object _syntaxChangedLock = new object();
/// <summary>Raised when the syntax tokens are updated</summary>
public event EventHandler SyntaxChanged
{
add { lock (_syntaxChangedLock) { _syntaxChanged += value; } }
remove { lock (_syntaxChangedLock) { _syntaxChanged -= value; } }
}
#endregion EVENTS
public LslSyntax()
{
var keywordFile = Path.Combine(Settings.RESOURCE_DIR, KEYWORDS_DEFAULT);
try
{
using (FileStream fs = new FileStream(keywordFile, FileMode.Open, FileAccess.Read))
{
ParseFile(fs);
}
}
catch (FileNotFoundException)
{
Logger.Log($"Failed to find {keywordFile}.", Helpers.LogLevel.Warning);
}
catch (IOException e)
{
Logger.Log($"Failed to read {keywordFile}: {e.Message}", Helpers.LogLevel.Warning);
}
}
public LslSyntax(GridClient client)
{
Register(client);
}
public void Register(GridClient client)
{
_client = client;
var keywordFile = Path.Combine(Settings.RESOURCE_DIR, KEYWORDS_DEFAULT);
if (_client.Network.Connected && _client.Network.CurrentSim.Features != null)
{
var syntaxId = _client.Network.CurrentSim.Features.Get(SYNTAX_FEATURE_IDENTIFIER);
if (syntaxId != null && syntaxId.Type == OSDType.UUID)
{
var file = Path.Combine(_client.Settings.ASSET_CACHE_DIR, $"keywords_lsl_{syntaxId}.xml");
if (File.Exists(file))
{
keywordFile = file;
}
else
{
var uri = _client.Network.CurrentSim.Caps.CapabilityURI(SYNTAX_CAPABILITY_IDENTIFIER);
if (uri != null)
{
Task fetch = _client.Network.CurrentSim.Client.HttpCapsClient.GetRequestAsync(uri,
CancellationToken.None, (response, data, error) =>
{
if (error != null)
{
Logger.Log($"Failed to retrieve syntax file. Error: {error.Message}",
Helpers.LogLevel.Warning, _client);
return;
}
if (!response.IsSuccessStatusCode)
{
Logger.Log(
$"Failed to retrieve syntax file. Status: {response.StatusCode} {response.ReasonPhrase}",
Helpers.LogLevel.Warning, _client);
return;
}
OSD features = OSDParser.Deserialize(data);
if (features.Type != OSDType.Map)
{
Logger.Log("Invalid format for syntax file. Root element is not a map.",
Helpers.LogLevel.Warning);
return;
}
Parse((OSDMap)features);
_syntaxId = syntaxId;
using (FileStream writer =
new FileStream(
Path.Combine(_client.Settings.ASSET_CACHE_DIR,
$"keywords_lsl_{syntaxId}.xml"),
FileMode.Create, FileAccess.Write))
{
var bytes = OSDParser.SerializeLLSDXmlBytes(features);
writer.Write(bytes, 0, bytes.Length);
}
});
fetch.Wait(TimeSpan.FromSeconds(20));
return;
}
}
}
}
try
{
using (FileStream fs = new FileStream(keywordFile, FileMode.Open, FileAccess.Read))
{
ParseFile(fs);
}
}
catch (FileNotFoundException)
{
Logger.Log($"Failed to find {keywordFile}.", Helpers.LogLevel.Warning);
}
catch (IOException e)
{
Logger.Log($"Failed to read {keywordFile}: {e.Message}", Helpers.LogLevel.Warning);
}
_client.Network.SimChanged += Network_OnSimChanged;
}
private void Network_OnSimChanged(object sender, SimChangedEventArgs e)
{
_client.Network.CurrentSim.Caps.CapabilitiesReceived += Simulator_OnCapabilitiesReceived;
}
private void Simulator_OnCapabilitiesReceived(object sender, CapabilitiesReceivedEventArgs e)
{
e.Simulator.Caps.CapabilitiesReceived -= Simulator_OnCapabilitiesReceived;
var syntaxId = e.Simulator.Features.Get(SYNTAX_FEATURE_IDENTIFIER);
var uri = e.Simulator.Caps.CapabilityURI(SYNTAX_CAPABILITY_IDENTIFIER);
if (uri == null || syntaxId == null || syntaxId.Type != OSDType.UUID) { return; }
if (syntaxId.AsUUID() == _syntaxId) { return; }
_ = e.Simulator.Client.HttpCapsClient.GetRequestAsync(uri, CancellationToken.None, (response, data, error) =>
{
if (error != null)
{
Logger.Log($"Failed to retrieve syntax file. Error: {error.Message}", Helpers.LogLevel.Warning, _client);
return;
}
if (!response.IsSuccessStatusCode)
{
Logger.Log($"Failed to retrieve syntax file. Status: {response.StatusCode} {response.ReasonPhrase}",
Helpers.LogLevel.Warning, _client);
return;
}
OSD features = OSDParser.Deserialize(data);
if (features.Type != OSDType.Map)
{
Logger.Log("Invalid format for syntax file. Root element is not a map.", Helpers.LogLevel.Warning);
return;
}
Parse((OSDMap)features);
_syntaxId = syntaxId;
using (FileStream writer =
new FileStream(Path.Combine(_client.Settings.ASSET_CACHE_DIR, $"keywords_lsl_{syntaxId}.xml"),
FileMode.Create, FileAccess.Write))
{
var bytes = OSDParser.SerializeLLSDXmlBytes(features);
writer.Write(bytes, 0, bytes.Length);
}
});
}
private void ParseFile(Stream stream)
{
using (XmlTextReader reader = new XmlTextReader(stream))
{
var deserialized = OSDParser.DeserializeLLSDXml(reader);
if (deserialized.Type != OSDType.Map)
{
Logger.Log("Invalid format for syntax file. Root element is not a map.", Helpers.LogLevel.Warning);
return;
}
Parse((OSDMap)deserialized);
}
}
private void Parse(OSDMap map)
{
if (!map.TryGetValue(VERSION_KEY, out var version))
{
Logger.Log("Syntax file does not contain a version key. Contents may not parse correctly.",
Helpers.LogLevel.Warning);
}
else if (version.AsInteger() != 2)
{
Logger.Log($"Syntax file version {version.AsInteger()} is incompatible. Contents may not parse correctly.",
Helpers.LogLevel.Warning);
}
int tokens = 0, added = 0;
var keywords = new Dictionary<string, LslKeyword>();
try
{
var groupEnumerator = map.GetEnumerator();
while (groupEnumerator.MoveNext())
{
var group = (string)groupEnumerator.Key;
if (group == VERSION_KEY) { continue; }
var items = (OSDMap)groupEnumerator.Value;
var enumerator = items.GetEnumerator();
while (enumerator.MoveNext())
{
++tokens;
StringBuilder tooltip = new StringBuilder();
LslCategory category = LslCategory.Unknown;
var key = (string)enumerator.Key;
var attr = (OSDMap)enumerator.Value;
tooltip.AppendLine(attr["tooltip"].AsString());
switch (group)
{
case "controls":
category = LslCategory.Control;
break;
case "types":
category = LslCategory.Datatype;
break;
case "constants":
category = LslCategory.Constant;
tooltip.Append($" Type: {attr["type"]}-{attr["value"]}");
break;
case "events":
category = LslCategory.Event;
tooltip.Append($"{key} ({ParseArguments(attr["arguments"])})");
break;
case "functions":
category = LslCategory.Function;
tooltip.AppendLine(
$"{attr["return"]} {key} ({ParseArguments(attr["arguments"])})");
tooltip.Append(
$"Energy: {(attr.ContainsKey("energy") ? attr["energy"].AsString() : "0.0")}");
if (attr.TryGetValue("sleep", out var sleep))
tooltip.Append($", Sleep: {sleep}");
break;
}
if (category == LslCategory.Unknown) { continue; }
var kw = new LslKeyword
{
Category = category,
Keyword = key,
Deprecated = attr.ContainsKey("deprecated") && attr["deprecated"].AsBoolean(),
GodMode = attr.ContainsKey("god-mode") && attr["god-mode"].AsBoolean(),
Tooltip = tooltip.ToString(),
};
keywords.Add(kw.Keyword, kw);
++added;
}
}
}
catch (Exception e)
{
Logger.Log($"Syntax parser exception: {e.Message}", Helpers.LogLevel.Warning);
}
Logger.Log($"Parsed Syntax file, added {added}/{tokens} tokens.", Helpers.LogLevel.Debug);
lock(_keywords) { _keywords = keywords; }
OnSyntaxChanged();
}
private string ParseArguments(OSD args)
{
var str = new StringBuilder();
if (args.Type == OSDType.Array)
{
foreach (var osd in (OSDArray)args)
{
if (osd.Type == OSDType.Map)
{
var map = (OSDMap)osd;
var left = map.Count;
var enumerable = map.GetEnumerator();
while (enumerable.MoveNext())
{
var value = (OSD)enumerable.Value;
str.Append($"{value.AsString()} {(string)enumerable.Key}");
if (left-- > 1)
{
str.Append(", ");
}
}
}
}
}
return str.ToString();
}
}
}

View file

@ -25,17 +25,18 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
using LibreMetaverse;
using OpenMetaverse.Interfaces;
using OpenMetaverse.Messages.Linden;
using OpenMetaverse.Packets;
using OpenMetaverse.StructuredData;
using SkiaSharp;
using System; using System;
using System.Threading;
using System.Reflection;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenMetaverse.Packets; using System.Reflection;
using OpenMetaverse.Interfaces; using System.Threading;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Messages.Linden;
using System.Threading.Tasks; using System.Threading.Tasks;
using LibreMetaverse;
namespace OpenMetaverse namespace OpenMetaverse
{ {
@ -616,6 +617,102 @@ namespace OpenMetaverse
Update(client, client.Network.CurrentSim, wantReply); Update(client, client.Network.CurrentSim, wantReply);
} }
public void UpdateParcelAccessList(
GridClient Client,
AccessList flags,
List<ParcelManager.ParcelAccessEntry> entries)
{
if(Client == null) throw new InvalidOperationException("Client is null");
if(Client.Network == null) throw new InvalidOperationException("Client.Network is null");
if(Client.Network.CurrentSim == null) throw new InvalidOperationException("Client.Network.CurrentSim is null");
Simulator simulator = Client.Network.CurrentSim;
if (simulator == null) throw new InvalidOperationException("No current simulator");
int parcelLocalId = LocalID;
if (parcelLocalId == 0) throw new InvalidOperationException("Parcel LocalID is zero");
if (entries.Count == 0)
{
// send an empty block to clear the list
Guid sendemptyGuid = Guid.NewGuid();
var packet = new ParcelAccessListUpdatePacket
{
AgentData = {
AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID
},
Data = {
Flags = (uint)flags,
LocalID = parcelLocalId,
TransactionID = new UUID(sendemptyGuid.ToString()),
SequenceID = 1,
Sections = 0
},
List = { }
};
packet.List = new ParcelAccessListUpdatePacket.ListBlock[1];
packet.List[0] = new ParcelAccessListUpdatePacket.ListBlock
{
ID = UUID.Zero,
Time = 0,
Flags = 0
};
simulator.SendPacket(packet);
return;
}
const int PARCEL_MAX_ENTRIES_PER_PACKET = 48; // Set to your actual max
int count = entries.Count;
int numSections = (int)Math.Ceiling((double)count / PARCEL_MAX_ENTRIES_PER_PACKET);
int sequenceId = 1;
Guid transactionUUID = Guid.NewGuid();
int entryIndex = 0;
for (int section = 0; section < numSections; section++)
{
var packet = new ParcelAccessListUpdatePacket
{
AgentData = {
AgentID = Client.Self.AgentID,
SessionID = Client.Self.SessionID
},
Data = {
Flags = (uint)flags,
LocalID = parcelLocalId,
TransactionID = new UUID(transactionUUID.ToString()),
SequenceID = sequenceId,
Sections = numSections
},
List = new ParcelAccessListUpdatePacket.ListBlock[
Math.Min(PARCEL_MAX_ENTRIES_PER_PACKET, count - entryIndex)
]
};
for (int i = 0; i < packet.List.Length; i++, entryIndex++)
{
var entry = entries[entryIndex];
packet.List[i] = new ParcelAccessListUpdatePacket.ListBlock
{
ID = entry.AgentID,
Time = (int)(entry.Time.Subtract(new DateTime(1970, 1, 1))).TotalSeconds,
Flags = (uint)entry.Flags
};
}
// If there are no entries, send an empty block
if (packet.List.Length == 0)
{
packet.List = new ParcelAccessListUpdatePacket.ListBlock[1];
packet.List[0] = new ParcelAccessListUpdatePacket.ListBlock
{
ID = UUID.Zero,
Time = 0,
Flags = 0
};
}
simulator.SendPacket(packet);
sequenceId++;
}
}
/// <summary> /// <summary>
/// Update the simulator with any local changes to this Parcel object /// Update the simulator with any local changes to this Parcel object
/// </summary> /// </summary>

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2025, Sjofn LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -58,7 +59,7 @@ namespace OpenMetaverse.Rendering
/// Load a skeleton from a given file. /// Load a skeleton from a given file.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// We use xml scema validation on top of the xml de-serializer, since the schema has /// We use xml schema validation on top of the xml de-serializer, since the schema has
/// some stricter checks than the de-serializer provides. E.g. the vector attributes /// some stricter checks than the de-serializer provides. E.g. the vector attributes
/// are guaranteed to hold only 3 float values. This reduces the need for error checking /// are guaranteed to hold only 3 float values. This reduces the need for error checking
/// while working with the loaded skeleton. /// while working with the loaded skeleton.
@ -68,7 +69,7 @@ namespace OpenMetaverse.Rendering
public static LindenSkeleton Load(string fileName) public static LindenSkeleton Load(string fileName)
{ {
if (fileName == null) if (fileName == null)
fileName = System.IO.Path.Combine(Settings.RESOURCE_DIR, "avatar_skeleton.xml"); fileName = System.IO.Path.Combine(Settings.RESOURCE_DIR, "character", "avatar_skeleton.xml");
LindenSkeleton result; LindenSkeleton result;

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2006-2016, openmetaverse.co
* Copyright (c) 2021-2022, Sjofn LLC. * Copyright (c) 2021-2025, Sjofn LLC.
* All rights reserved. * All rights reserved.
* *
* - Redistribution and use in source and binary forms, with or without * - Redistribution and use in source and binary forms, with or without
@ -25,6 +25,7 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
using System.IO;
using OpenMetaverse.Packets; using OpenMetaverse.Packets;
namespace OpenMetaverse namespace OpenMetaverse
@ -49,7 +50,7 @@ namespace OpenMetaverse
public const string ADITI_LOGIN_SERVER = "https://login.aditi.lindenlab.com/cgi-bin/login.cgi"; public const string ADITI_LOGIN_SERVER = "https://login.aditi.lindenlab.com/cgi-bin/login.cgi";
/// <summary>The relative directory where external resources are kept</summary> /// <summary>The relative directory where external resources are kept</summary>
public static string RESOURCE_DIR = "openmetaverse_data"; public static string RESOURCE_DIR = "linden";
/// <summary>Login server to connect to</summary> /// <summary>Login server to connect to</summary>
public string LOGIN_SERVER = AGNI_LOGIN_SERVER; public string LOGIN_SERVER = AGNI_LOGIN_SERVER;
@ -286,7 +287,7 @@ namespace OpenMetaverse
public bool USE_ASSET_CACHE = true; public bool USE_ASSET_CACHE = true;
/// <summary>Path to store cached texture data</summary> /// <summary>Path to store cached texture data</summary>
public string ASSET_CACHE_DIR = RESOURCE_DIR + "/cache"; public string ASSET_CACHE_DIR = Path.Combine(RESOURCE_DIR, "cache");
/// <summary>Maximum size cached files are allowed to take on disk (bytes)</summary> /// <summary>Maximum size cached files are allowed to take on disk (bytes)</summary>
public long ASSET_CACHE_MAX_SIZE = 1024 * 1024 * 1024; // 1GB public long ASSET_CACHE_MAX_SIZE = 1024 * 1024 * 1024; // 1GB

View file

@ -31,6 +31,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Net; using System.Net;
using LibreMetaverse;
using OpenMetaverse.Packets; using OpenMetaverse.Packets;
namespace OpenMetaverse namespace OpenMetaverse
@ -263,6 +264,8 @@ namespace OpenMetaverse
public UUID ID = UUID.Zero; public UUID ID = UUID.Zero;
/// <summary>The capabilities for this simulator</summary> /// <summary>The capabilities for this simulator</summary>
public Caps Caps; public Caps Caps;
/// <summary>Simulator Features available for this simulator</summary>
public SimulatorFeatures Features;
/// <summary>Unique identified for this region generated via it's coordinates on the world map</summary> /// <summary>Unique identified for this region generated via it's coordinates on the world map</summary>
public ulong Handle; public ulong Handle;
/// <summary>Simulator land size in X direction in meters</summary> /// <summary>Simulator land size in X direction in meters</summary>

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2025, Sjofn LLC.
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.co nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net.Http;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
namespace LibreMetaverse
{
/// <summary>
/// Stores the Simulator Features provided by the SimulatorFeatures sim capability
/// </summary>
public class SimulatorFeatures
{
private OSDMap _featureMap = new OSDMap();
private readonly Simulator _simulator;
internal SimulatorFeatures(Simulator simulator)
{
_simulator = simulator;
}
/// <summary>
/// Returns true if Simulator Features has a given feature and corresponding true value
/// </summary>
/// <param name="feature"></param>
/// <returns>true if feature exists and is true</returns>
public bool Has(string feature)
{
return _featureMap.TryGetValue(feature, out var value) && value.AsBoolean();
}
/// <summary>
/// Returns the value of a specified Simulator Feature
/// </summary>
/// <param name="feature"></param>
/// <returns>Returns value of the specified Simulator Feature, null of not found.</returns>
public OSD Get(string feature)
{
return _featureMap.TryGetValue(feature, out var value) ? value : null;
}
/// <summary>
/// Set SimulatorFeatures from <see cref="OSDMap"/>
/// </summary>
/// <param name="response"></param>
/// <param name="responseData"></param>
/// <param name="error"></param>
/// <exception cref="ArgumentException">OSD type is not a map</exception>
public void SetFeatures(HttpResponseMessage response, byte[] responseData, Exception error)
{
if (error != null)
{
Logger.Log($"Failed to retrieve simulator features. Error: {error.Message}",
Helpers.LogLevel.Warning, _simulator.Client);
return;
}
if (!response.IsSuccessStatusCode)
{
Logger.Log($"Failed to retrieve simulator features. Status: {response.StatusCode} {response.ReasonPhrase}",
Helpers.LogLevel.Warning, _simulator.Client);
return;
}
OSD features = OSDParser.Deserialize(responseData);
if (features.Type != OSDType.Map)
{
Logger.Log($"Simulator Features response was not valid LLSD map",
Helpers.LogLevel.Warning, _simulator.Client);
return;
}
_featureMap = (OSDMap)features;
}
}
}

View file

@ -0,0 +1,359 @@
Creative Commons Legal Code
Attribution-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(f) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined below) for the purposes of this
License.
c. "Creative Commons Compatible License" means a license that is listed
at https://creativecommons.org/compatiblelicenses that has been
approved by Creative Commons as being essentially equivalent to this
License, including, at a minimum, because that license: (i) contains
terms that have the same purpose, meaning and effect as the License
Elements of this License; and, (ii) explicitly permits the relicensing
of adaptations of works made available under that license under this
License or a Creative Commons jurisdiction license with the same
License Elements as this License.
d. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
e. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, ShareAlike.
f. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
g. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
h. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
i. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
j. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
k. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor waives the
exclusive right to collect such royalties for any exercise by You
of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(c), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(c), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under the
terms of: (i) this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
License. If you license the Adaptation under one of the licenses
mentioned in (iv), you must comply with the terms of that license. If
you license the Adaptation under the terms of any of the licenses
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
comply with the terms of the Applicable License generally and the
following provisions: (I) You must include a copy of, or the URI for,
the Applicable License with every copy of each Adaptation You
Distribute or Publicly Perform; (II) You may not offer or impose any
terms on the Adaptation that restrict the terms of the Applicable
License or the ability of the recipient of the Adaptation to exercise
the rights granted to that recipient under the terms of the Applicable
License; (III) You must keep intact all notices that refer to the
Applicable License and to the disclaimer of warranties with every copy
of the Work as included in the Adaptation You Distribute or Publicly
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
You may not impose any effective technological measures on the
Adaptation that restrict the ability of a recipient of the Adaptation
from You to exercise the rights granted to that recipient under the
terms of the Applicable License. This Section 4(b) applies to the
Adaptation as incorporated in a Collection, but this does not require
the Collection apart from the Adaptation itself to be made subject to
the terms of the Applicable License.
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and (iv) , consistent with Ssection 3(b), in the case of an
Adaptation, a credit identifying the use of the Work in the Adaptation
(e.g., "French translation of the Work by Original Author," or
"Screenplay based on original Work by Original Author"). The credit
required by this Section 4(c) may be implemented in any reasonable
manner; provided, however, that in the case of a Adaptation or
Collection, at a minimum such credit will appear, if a credit for all
contributing authors of the Adaptation or Collection appears, then as
part of these credits and in a manner at least as prominent as the
credits for the other contributing authors. For the avoidance of
doubt, You may only use the credit required by this Section for the
purpose of attribution in the manner set out above and, by exercising
Your rights under this License, You may not implicitly or explicitly
assert or imply any connection with, sponsorship or endorsement by the
Original Author, Licensor and/or Attribution Parties, as appropriate,
of You or Your use of the Work, without the separate, express prior
written permission of the Original Author, Licensor and/or Attribution
Parties.
d. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of the License.
Creative Commons may be contacted at https://creativecommons.org/.

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="US-ASCII" standalone="yes"?>
<!--
Default values for "Look At" controls.
Various events compete for avatar attention. I.E. control where the
user's avatar looks.
The avatar's head will turn to look towards the source of those events
as much as is possible.
If the head cannot turn due to an animation override, the eyes will still
track those events.
Avatar attention will be taken by events of equal of greater priorities
and will last for timeout number of seconds before reverting to idle.
Negative timeout values indicate attentions that never time out.
Avatar sex determines which set of attention settings are used.
Within each set, the following named attentions relate to the indicated events:
IDLE: Tracks the mouse pointer movement.
AUTO LISTEN: Tracks nearby chat.
FREELOOK: Tracks "target objects" and a case of mouse moved in 3rd person mode.
RESPOND: Tracks the beginning of typing.
HOVER: Tracks objects the mouse lingers over when "show hover tooltips" is on.
CONVERSATION: Tracks avatars and other objects clicked on.
SEOECT: Tracks objects grabbed and being moved.
FOCUS: Freezes during avatar customization and when focused on object or point.
MOUSELOOK: Tracks center of view when in mouselook view mode.
-->
<linden_attentions version="1.0">
<gender name="Masculine">
<param attention="idle" priority="1.0" timeout="3.0" />
<param attention="auto_listen" priority="3.0" timeout="4.0" />
<param attention="freelook" priority="2.0" timeout="2.0" />
<param attention="respond" priority="3.0" timeout="4.0" />
<param attention="hover" priority="4.0" timeout="1.0" />
<param attention="conversation" priority="0.0" timeout="-1" />
<param attention="select" priority="6.0" timeout="-1" />
<param attention="focus" priority="6.0" timeout="-1" />
<param attention="mouselook" priority="7.0" timeout="-1" />
</gender>
<gender name="Feminine">
<param attention="idle" priority="1.0" timeout="3.0" />
<param attention="auto_listen" priority="3.0" timeout="4.0" />
<param attention="freelook" priority="2.0" timeout="2.0" />
<param attention="respond" priority="3.0" timeout="4.0" />
<param attention="hover" priority="4.0" timeout="1.0" />
<param attention="conversation" priority="0.0" timeout="-1" />
<param attention="select" priority="6.0" timeout="-1" />
<param attention="focus" priority="6.0" timeout="-1" />
<param attention="mouselook" priority="7.0" timeout="-1" />
</gender>
</linden_attentions>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="US-ASCII" standalone="yes"?>
<!--
Default values for "Look At" controls.
Various events compete for avatar attention. I.E. control where the
user's avatar looks.
The avatar's head will turn to look towards the source of those events
as much as is possible.
If the head cannot turn due to an animation override, the eyes will still
track those events.
Avatar attention will be taken by events of equal of greater priorities
and will last for timeout number of seconds before reverting to idle.
Negative timeout values indicate attentions that never time out.
Avatar sex determines which set of attention settings are used.
Within each set, the following named attentions relate to the indicated events:
IDLE: Tracks the mouse pointer movement.
AUTO LISTEN: Tracks nearby chat.
FREELOOK: Tracks "target objects" and a case of mouse moved in 3rd person mode.
RESPOND: Tracks the beginning of typing.
HOVER: Tracks objects the mouse lingers over when "show hover tooltips" is on.
CONVERSATION: Tracks avatars and other objects clicked on.
SEOECT: Tracks objects grabbed and being moved.
FOCUS: Freezes during avatar customization and when focused on object or point.
MOUSELOOK: Tracks center of view when in mouselook view mode.
-->
<linden_attentions version="1.0">
<gender name="Masculine">
<param attention="idle" priority="1.0" timeout="3.0" />
<param attention="auto_listen" priority="3.0" timeout="4.0" />
<param attention="freelook" priority="2.0" timeout="2.0" />
<param attention="respond" priority="3.0" timeout="3.0" />
<param attention="hover" priority="4.0" timeout="1.0" />
<param attention="conversation" priority="5.0" timeout="3.0" />
<param attention="select" priority="3.5" timeout="-1" />
<param attention="focus" priority="3.5" timeout="20.0" />
<param attention="mouselook" priority="7.0" timeout="-1" />
</gender>
<gender name="Feminine">
<param attention="idle" priority="1.0" timeout="3.0" />
<param attention="auto_listen" priority="3.0" timeout="4.0" />
<param attention="freelook" priority="2.0" timeout="2.0" />
<param attention="respond" priority="3.0" timeout="6.0" />
<param attention="hover" priority="4.0" timeout="1.0" />
<param attention="conversation" priority="5.0" timeout="6.0" />
<param attention="select" priority="2.5" timeout="-1" />
<param attention="focus" priority="2.5" timeout="10.0" />
<param attention="mouselook" priority="7.0" timeout="-1" />
</gender>
</linden_attentions>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,232 @@
<linden_skeleton num_bones="133" num_collision_volumes="26" version="2.0">
<bone aliases="hip avatar_mPelvis" connected="false" end="0.000 0.000 0.084" group="Torso" name="mPelvis" pivot="0.000000 0.000000 1.067015" pos="0.000 0.000 1.067" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true">
<collision_volume end="0.030 0.000 0.095" group="Collision" name="PELVIS" pos="-0.01 0 -0.02" rot="0.000000 8.00000 0.000000" scale="0.12 0.16 0.17" support="base" reposition="true"/>
<collision_volume end="-0.100 0.000 0.000" group="Collision" name="BUTT" pos="-0.06 0 -0.1" rot="0.000000 0.00000 0.000000" scale="0.1 0.1 0.1" support="base" reposition="true"/>
<bone aliases="avatar_mSpine1" connected="true" end="0.000 0.000 -0.084" group="Spine" name="mSpine1" pivot="0.000000 0.000000 0.084073" pos="0.000 0.000 0.084" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mSpine2" connected="true" end="0.000 0.000 0.084" group="Spine" name="mSpine2" pivot="0.000000 0.000000 -0.084073" pos="0.000 0.000 -0.084" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="abdomen avatar_mTorso" connected="true" end="-0.015 0.000 0.205" group="Torso" name="mTorso" pivot="0.000000 0.000000 0.084073" pos="0.000 0.000 0.084" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.028 0.000 0.094" group="Collision" name="BELLY" pos="0.028 0 0.04" rot="0.000000 8.00000 0.000000" scale="0.09 0.13 0.15" support="base" reposition="true"/>
<collision_volume end="0.000 0.100 0.000" group="Collision" name="LEFT_HANDLE" pos="0.0 0.10 0.058" rot="0.000000 0.00000 0.000000" scale="0.05 0.05 0.05" support="base" reposition="true"/>
<collision_volume end="0.000 -0.100 0.000" group="Collision" name="RIGHT_HANDLE" pos="0.0 -0.10 0.058" rot="0.000000 0.00000 0.000000" scale="0.05 0.05 0.05" support="base" reposition="true"/>
<collision_volume end="-0.100 0.000 0.000" group="Collision" name="LOWER_BACK" pos="0.0 0.0 0.023" rot="0.000000 0.00000 0.000000" scale="0.09 0.13 0.15" support="base" reposition="true"/>
<bone aliases="avatar_mSpine3" connected="true" end="0.015 0.000 -0.205" group="Spine" name="mSpine3" pivot="-0.015368 0.000000 0.204877" pos="-0.015 0.000 0.205" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mSpine4" connected="true" end="-0.015 0.000 0.205" group="Spine" name="mSpine4" pivot="0.015368 0.000000 -0.204877" pos="0.015 0.000 -0.205" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="chest avatar_mChest" connected="true" end="-0.010 0.000 0.250" group="Torso" name="mChest" pivot="-0.015368 0.000000 0.204877" pos="-0.015 0.000 0.205" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="-0.096 0.000 0.152" group="Collision" name="CHEST" pos="0.028 0 0.07" rot="0.000000 -10.00000 0.000000" scale="0.11 0.15 0.2" support="base" reposition="true"/>
<collision_volume end="0.080 0.000 -0.006" group="Collision" name="LEFT_PEC" pos="0.119 0.082 0.042" rot="0.000000 4.29000 0.000000" scale="0.05 0.05 0.05" support="base" reposition="true"/>
<collision_volume end="0.080 0.000 -0.006" group="Collision" name="RIGHT_PEC" pos="0.119 -0.082 0.042" rot="0.000000 4.29000 0.000000" scale="0.05 0.05 0.05" support="base" reposition="true"/>
<collision_volume end="-0.100 0.000 0.000" group="Collision" name="UPPER_BACK" pos="0.0 0.0 0.017" rot="0.000000 0.00000 0.000000" scale="0.09 0.13 0.15" support="base" reposition="true"/>
<bone aliases="neck avatar_mNeck" connected="true" end="0.000 0.000 0.077" group="Torso" name="mNeck" pivot="-0.009507 0.000000 0.251108" pos="-0.010 0.000 0.251" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 0.000 0.080" group="Collision" name="NECK" pos="0.0 0 0.02" rot="0.000000 0.000000 0.000000" scale="0.05 0.06 0.08" support="base"/>
<bone aliases="head avatar_mHead" connected="true" end="0.000 0.000 0.079" group="Torso" name="mHead" pivot="0.000000 -0.000000 0.075630" pos="0.000 -0.000 0.076" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 0.000 0.100" group="Collision" name="HEAD" pos="0.02 0 0.07" rot="0.000000 0.000000 0.000000" scale="0.11 0.09 0.12" support="base" reposition="true"/>
<bone aliases="figureHair avatar_mSkull" connected="false" end="0.000 0.000 0.033" group="Extra" name="mSkull" pivot="0.000000 0.000000 0.079000" pos="0.000 0.000 0.079" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true"/>
<bone aliases="avatar_mEyeRight" connected="false" end="0.025 0.000 0.000" group="Extra" name="mEyeRight" pivot="0.098466 -0.036000 0.079000" pos="0.098 -0.036 0.079" rot="0.000000 0.000000 -0.000000" scale="1.000 1.000 1.000" support="base" reposition="true"/>
<bone aliases="avatar_mEyeLeft" connected="false" end="0.025 0.000 0.000" group="Extra" name="mEyeLeft" pivot="0.098461 0.036000 0.079000" pos="0.098 0.036 0.079" rot="0.000000 -0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true"/>
<bone aliases="avatar_mFaceRoot" connected="false" end="0.020 0.000 0.000" group="Face" name="mFaceRoot" pivot="0.025000 0.000000 0.045000" pos="0.025 0.000 0.045" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mFaceEyeAltRight" connected="false" end="0.025 0.000 0.000" group="Face" name="mFaceEyeAltRight" pivot="0.073466 -0.036000 0.0339300" pos="0.073 -0.036 0.034" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyeAltLeft" connected="false" end="0.025 0.000 0.000" group="Face" name="mFaceEyeAltLeft" pivot="0.073461 0.036000 0.0339300" pos="0.073 0.036 0.034" rot="0.000000 0.000000 0.000000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceForeheadLeft" connected="false" end="0.024 0.004 0.018" group="Face" name="mFaceForeheadLeft" pivot="0.061 0.035 0.083" pos="0.061 0.035 0.083" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceForeheadRight" connected="false" end="0.024 -0.004 0.018" group="Face" name="mFaceForeheadRight" pivot="0.061 -0.035 0.083" pos="0.061 -0.035 0.083" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyebrowOuterLeft" connected="false" end="0.023 0.013 0.000" group="Eyes" name="mFaceEyebrowOuterLeft" pivot="0.064 0.051 0.048" pos="0.064 0.051 0.048" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyebrowCenterLeft" connected="false" end="0.027 0.000 0.000" group="Eyes" name="mFaceEyebrowCenterLeft" pivot="0.070 0.043 0.056" pos="0.070 0.043 0.056" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyebrowInnerLeft" connected="false" end="0.026 0.000 0.000" group="Eyes" name="mFaceEyebrowInnerLeft" pivot="0.075 0.022 0.051" pos="0.075 0.022 0.051" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyebrowOuterRight" connected="false" end="0.023 -0.013 0.000" group="Eyes" name="mFaceEyebrowOuterRight" pivot="0.064 -0.051 0.048" pos="0.064 -0.051 0.048" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyebrowCenterRight" connected="false" end="0.027 0.000 0.000" group="Eyes" name="mFaceEyebrowCenterRight" pivot="0.070 -0.043 0.056" pos="0.070 -0.043 0.056" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyebrowInnerRight" connected="false" end="0.026 0.000 0.000" group="Eyes" name="mFaceEyebrowInnerRight" pivot="0.075 -0.022 0.051" pos="0.075 -0.022 0.051" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyeLidUpperLeft" connected="false" end="0.027 0.000 0.005" group="Eyes" name="mFaceEyeLidUpperLeft" pivot="0.073 0.036 0.034" pos="0.073 0.036 0.034" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyeLidLowerLeft" connected="false" end="0.024 0.000 -0.007" group="Eyes" name="mFaceEyeLidLowerLeft" pivot="0.073 0.036 0.034" pos="0.073 0.036 0.034" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyeLidUpperRight" connected="false" end="0.027 0.000 0.005" group="Eyes" name="mFaceEyeLidUpperRight" pivot="0.073 -0.036 0.034" pos="0.073 -0.036 0.034" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyeLidLowerRight" connected="false" end="0.024 0.000 -0.007" group="Eyes" name="mFaceEyeLidLowerRight" pivot="0.073 -0.036 0.034" pos="0.073 -0.036 0.034" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEar1Left" connected="false" end="-0.019 0.018 0.025" group="Ears" name="mFaceEar1Left" pivot="0.000 0.080 0.002" pos="0.000 0.080 0.002" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mFaceEar2Left" connected="true" end="0.000 0.000 0.033" group="Ears" name="mFaceEar2Left" pivot="-0.019 0.018 0.025" pos="-0.019 0.018 0.025" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
<bone aliases="avatar_mFaceEar1Right" connected="false" end="-0.019 -0.018 0.025" group="Ears" name="mFaceEar1Right" pivot="0.000 -0.080 0.002" pos="0.000 -0.080 0.002" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mFaceEar2Right" connected="true" end="0.000 0.000 0.033" group="Ears" name="mFaceEar2Right" pivot="-0.019 -0.018 0.025" pos="-0.019 -0.018 0.025" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
<bone aliases="avatar_mFaceNoseLeft" connected="false" end="0.015 0.004 0.000" group="Face" name="mFaceNoseLeft" pivot="0.086 0.015 -0.004" pos="0.086 0.015 -0.004" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceNoseCenter" connected="false" end="0.025 0.000 0.000" group="Face" name="mFaceNoseCenter" pivot="0.102 0.000 0.000" pos="0.102 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceNoseRight" connected="false" end="0.015 -0.004 0.000" group="Face" name="mFaceNoseRight" pivot="0.086 -0.015 -0.004" pos="0.086 -0.015 -0.004" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceCheekLowerLeft" connected="false" end="0.013 0.030 0.000" group="Face" name="mFaceCheekLowerLeft" pivot="0.050 0.034 -0.031" pos="0.050 0.034 -0.031" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceCheekUpperLeft" connected="false" end="0.022 0.015 0.000" group="Face" name="mFaceCheekUpperLeft" pivot="0.070 0.034 -0.005" pos="0.070 0.034 -0.005" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceCheekLowerRight" connected="false" end="0.013 -0.030 0.000" group="Face" name="mFaceCheekLowerRight" pivot="0.050 -0.034 -0.031" pos="0.050 -0.034 -0.031" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceCheekUpperRight" connected="false" end="0.022 -0.015 0.000" group="Face" name="mFaceCheekUpperRight" pivot="0.070 -0.034 -0.005" pos="0.070 -0.034 -0.005" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceJaw" connected="false" end="0.059 0.000 -0.039" group="Mouth" name="mFaceJaw" pivot="-0.001 0.000 -0.015" pos="-0.001 0.000 -0.015" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mFaceChin" connected="false" end="0.021 0.000 -0.018" group="Mouth" name="mFaceChin" pivot="0.074 0.000 -0.054" pos="0.074 0.000 -0.054" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceTeethLower" connected="false" end="0.035 0.000 0.000" group="Mouth" name="mFaceTeethLower" pivot="0.021 0.000 -0.039" pos="0.021 0.000 -0.039" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mFaceLipLowerLeft" connected="false" end="0.034 0.017 0.005" group="Lips" name="mFaceLipLowerLeft" pivot="0.045 0.000 0.000" pos="0.045 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceLipLowerRight" connected="false" end="0.034 -0.017 0.005" group="Lips" name="mFaceLipLowerRight" pivot="0.045 0.000 0.000" pos="0.045 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceLipLowerCenter" connected="false" end="0.040 0.000 0.002" group="Lips" name="mFaceLipLowerCenter" pivot="0.045 0.000 0.000" pos="0.045 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceTongueBase" connected="false" end="0.022 0.000 0.007" group="Mouth" name="mFaceTongueBase" pivot="0.039 0.000 0.005" pos="0.039 0.000 0.005" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mFaceTongueTip" connected="true" end="0.010 0.000 0.000" group="Mouth" name="mFaceTongueTip" pivot="0.022 0.000 0.007" pos="0.022 0.000 0.007" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
<bone aliases="avatar_mFaceJawShaper" connected="false" end="-0.017 0.000 0.000" group="Face" name="mFaceJawShaper" pivot="0.000 0.000 0.000" pos="0.000 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceForeheadCenter" connected="false" end="0.036 0.000 0.000" group="Face" name="mFaceForeheadCenter" pivot="0.069 0.000 0.065" pos="0.069 0.000 0.065" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceNoseBase" connected="false" end="0.014 0.000 0.000" group="Nose" name="mFaceNoseBase" pivot="0.094 0.000 -0.016" pos="0.094 0.000 -0.016" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceTeethUpper" connected="false" end="0.035 0.000 0.000" group="Mouth" name="mFaceTeethUpper" pivot="0.020 0.000 -0.030" pos="0.020 0.000 -0.030" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mFaceLipUpperLeft" connected="false" end="0.041 0.015 0.000" group="Lips" name="mFaceLipUpperLeft" pivot="0.045 0.000 -0.003" pos="0.045 0.000 -0.003" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceLipUpperRight" connected="false" end="0.041 -0.015 0.000" group="Lips" name="mFaceLipUpperRight" pivot="0.045 0.000 -0.003" pos="0.045 0.000 -0.003" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceLipCornerLeft" connected="false" end="0.045 0.051 0.000" group="Lips" name="mFaceLipCornerLeft" pivot="0.028 -0.019 -0.010" pos="0.028 -0.019 -0.010" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceLipCornerRight" connected="false" end="0.045 -0.051 0.000" group="Lips" name="mFaceLipCornerRight" pivot="0.028 0.019 -0.010" pos="0.028 0.019 -0.010" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceLipUpperCenter" connected="false" end="0.043 0.000 0.002" group="Lips" name="mFaceLipUpperCenter" pivot="0.045 0.000 -0.003" pos="0.045 0.000 -0.003" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
<bone aliases="avatar_mFaceEyecornerInnerLeft" connected="false" end="0.016 0.000 0.000" group="Face" name="mFaceEyecornerInnerLeft" pivot="0.075 0.017 0.032" pos="0.075 0.017 0.032" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceEyecornerInnerRight" connected="false" end="0.016 0.000 0.000" group="Face" name="mFaceEyecornerInnerRight" pivot="0.075 -0.017 0.032" pos="0.075 -0.017 0.032" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mFaceNoseBridge" connected="false" end="0.015 0.000 0.008" group="Nose" name="mFaceNoseBridge" pivot="0.091 0.000 0.020" pos="0.091 0.000 0.020" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
<bone aliases="lCollar avatar_mCollarLeft" connected="false" end="0.000 0.079 0.000" group="Arms" name="mCollarLeft" pivot="-0.020927 0.084665 0.165396" pos="-0.021 0.085 0.165" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 0.100 0.000" group="Collision" name="L_CLAVICLE" pos="0.02 0 0.02" rot="0.000000 0.00000 0.000000" scale="0.07 0.14 0.05" support="base" reposition="true"/>
<bone aliases="lShldr avatar_mShoulderLeft" connected="true" end="0.000 0.247 0.000" group="Arms" name="mShoulderLeft" pivot="0.000000 0.079000 -0.000000" pos="0.000 0.079 -0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 0.130 -0.003" group="Collision" name="L_UPPER_ARM" pos="0.0 0.12 0.01" rot="-5.000000 0.00000 0.000000" scale="0.05 0.17 0.05" support="base" reposition="true"/>
<bone aliases="lForeArm avatar_mElbowLeft" connected="true" end="0.000 0.205 0.000" group="Arms" name="mElbowLeft" pivot="0.000000 0.248000 0.000000" pos="0.000 0.248 0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 0.100 -0.001" group="Collision" name="L_LOWER_ARM" pos="0.0 0.1 0.0" rot="-3.000000 0.00000 0.000000" scale="0.04 0.14 0.04" support="base" reposition="true"/>
<bone aliases="lHand avatar_mWristLeft" connected="true" end="0.000 0.060 0.000" group="Arms" name="mWristLeft" pivot="-0.000000 0.204846 0.000000" pos="-0.000 0.205 0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.005 0.049 -0.001" group="Collision" name="L_HAND" pos="0.01 0.05 0.0" rot="-3.000000 0.00000 -10.000000" scale="0.05 0.08 0.03" support="base"/>
<bone aliases="avatar_mHandMiddle1Left" connected="false" end="-0.001 0.040 -0.006" group="Hand" name="mHandMiddle1Left" pivot="0.013 0.101 0.015" pos="0.013 0.101 0.015" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandMiddle2Left" connected="true" end="-0.001 0.049 -0.008" group="Hand" name="mHandMiddle2Left" pivot="-0.001 0.040 -0.006" pos="-0.001 0.040 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandMiddle3Left" connected="true" end="-0.002 0.033 -0.006" group="Hand" name="mHandMiddle3Left" pivot="-0.001 0.049 -0.008" pos="-0.001 0.049 -0.008" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandIndex1Left" connected="false" end="0.017 0.036 -0.006" group="Hand" name="mHandIndex1Left" pivot="0.038 0.097 0.015" pos="0.038 0.097 0.015" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandIndex2Left" connected="true" end="0.014 0.032 -0.006" group="Hand" name="mHandIndex2Left" pivot="0.017 0.036 -0.006" pos="0.017 0.036 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandIndex3Left" connected="true" end="0.011 0.025 -0.004" group="Hand" name="mHandIndex3Left" pivot="0.014 0.032 -0.006" pos="0.014 0.032 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandRing1Left" connected="false" end="-0.013 0.038 -0.008" group="Hand" name="mHandRing1Left" pivot="-0.010 0.099 0.009" pos="-0.010 0.099 0.009" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandRing2Left" connected="true" end="-0.013 0.040 -0.009" group="Hand" name="mHandRing2Left" pivot="-0.013 0.038 -0.008" pos="-0.013 0.038 -0.008" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandRing3Left" connected="true" end="-0.010 0.028 -0.006" group="Hand" name="mHandRing3Left" pivot="-0.013 0.040 -0.009" pos="-0.013 0.040 -0.009" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandPinky1Left" connected="false" end="-0.024 0.025 -0.006" group="Hand" name="mHandPinky1Left" pivot="-0.031 0.095 0.003" pos="-0.031 0.095 0.003" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandPinky2Left" connected="true" end="-0.015 0.018 -0.004" group="Hand" name="mHandPinky2Left" pivot="-0.024 0.025 -0.006" pos="-0.024 0.025 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandPinky3Left" connected="true" end="-0.013 0.016 -0.004" group="Hand" name="mHandPinky3Left" pivot="-0.015 0.018 -0.004" pos="-0.015 0.018 -0.004" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandThumb1Left" connected="false" end="0.028 0.032 0.000" group="Hand" name="mHandThumb1Left" pivot="0.031 0.026 0.004" pos="0.031 0.026 0.004" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandThumb2Left" connected="true" end="0.023 0.031 0.000" group="Hand" name="mHandThumb2Left" pivot="0.028 0.032 -0.001" pos="0.028 0.032 -0.001" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandThumb3Left" connected="true" end="0.015 0.025 0.000" group="Hand" name="mHandThumb3Left" pivot="0.023 0.031 -0.001" pos="0.023 0.031 -0.001" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
<bone aliases="rCollar avatar_mCollarRight" connected="false" end="0.000 -0.079 0.000" group="Arms" name="mCollarRight" pivot="-0.020927 -0.085000 0.165396" pos="-0.021 -0.085 0.165" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 -0.100 0.000" group="Collision" name="R_CLAVICLE" pos="0.02 0 0.02" rot="0.000000 0.00000 0.000000" scale="0.07 0.14 0.05" support="base" reposition="true"/>
<bone aliases="rShldr avatar_mShoulderRight" connected="true" end="0.000 -0.247 0.000" group="Arms" name="mShoulderRight" pivot="0.000000 -0.079418 -0.000000" pos="0.000 -0.079 -0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 -0.130 -0.003" group="Collision" name="R_UPPER_ARM" pos="0.0 -0.12 0.01" rot="5.000000 0.00000 0.000000" scale="0.05 0.17 0.05" support="base" reposition="true"/>
<bone aliases="rForeArm avatar_mElbowRight" connected="true" end="0.000 -0.205 0.000" group="Arms" name="mElbowRight" pivot="0.000000 -0.248000 -0.000000" pos="0.000 -0.248 -0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.000 -0.100 -0.001" group="Collision" name="R_LOWER_ARM" pos="0.0 -0.1 0.0" rot="3.000000 0.00000 0.000000" scale="0.04 0.14 0.04" support="base" reposition="true"/>
<bone aliases="rHand avatar_mWristRight" connected="true" end="0.000 -0.060 0.000" group="Arms" name="mWristRight" pivot="-0.000000 -0.205000 -0.000000" pos="0.000 -0.205 -0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.005 -0.049 -0.001" group="Collision" name="R_HAND" pos="0.01 -0.05 0.0" rot="3.000000 0.00000 10.000000" scale="0.05 0.08 0.03" support="base"/>
<bone aliases="avatar_mHandMiddle1Right" connected="false" end="-0.001 -0.040 -0.006" group="Hand" name="mHandMiddle1Right" pivot="0.013 -0.101 0.015" pos="0.013 -0.101 0.015" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandMiddle2Right" connected="true" end="-0.001 -0.049 -0.008" group="Hand" name="mHandMiddle2Right" pivot="-0.001 -0.040 -0.006" pos="-0.001 -0.040 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandMiddle3Right" connected="true" end="-0.002 -0.033 -0.006" group="Hand" name="mHandMiddle3Right" pivot="-0.001 -0.049 -0.008" pos="-0.001 -0.049 -0.008" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandIndex1Right" connected="false" end="0.017 -0.036 -0.006" group="Hand" name="mHandIndex1Right" pivot="0.038 -0.097 0.015" pos="0.038 -0.097 0.015" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandIndex2Right" connected="true" end="0.014 -0.032 -0.006" group="Hand" name="mHandIndex2Right" pivot="0.017 -0.036 -0.006" pos="0.017 -0.036 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandIndex3Right" connected="true" end="0.011 -0.025 -0.004" group="Hand" name="mHandIndex3Right" pivot="0.014 -0.032 -0.006" pos="0.014 -0.032 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandRing1Right" connected="false" end="-0.013 -0.038 -0.008" group="Hand" name="mHandRing1Right" pivot="-0.010 -0.099 0.009" pos="-0.010 -0.099 0.009" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandRing2Right" connected="true" end="-0.013 -0.040 -0.009" group="Hand" name="mHandRing2Right" pivot="-0.013 -0.038 -0.008" pos="-0.013 -0.038 -0.008" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandRing3Right" connected="true" end="-0.010 -0.028 -0.006" group="Hand" name="mHandRing3Right" pivot="-0.013 -0.040 -0.009" pos="-0.013 -0.040 -0.009" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandPinky1Right" connected="false" end="-0.024 -0.025 -0.006" group="Hand" name="mHandPinky1Right" pivot="-0.031 -0.095 0.003" pos="-0.031 -0.095 0.003" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandPinky2Right" connected="true" end="-0.015 -0.018 -0.004" group="Hand" name="mHandPinky2Right" pivot="-0.024 -0.025 -0.006" pos="-0.024 -0.025 -0.006" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandPinky3Right" connected="true" end="-0.013 -0.016 -0.004" group="Hand" name="mHandPinky3Right" pivot="-0.015 -0.018 -0.004" pos="-0.015 -0.018 -0.004" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
<bone aliases="avatar_mHandThumb1Right" connected="false" end="0.028 -0.032 0.000" group="Hand" name="mHandThumb1Right" pivot="0.031 -0.026 0.004" pos="0.031 -0.026 0.004" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandThumb2Right" connected="true" end="0.023 -0.031 0.000" group="Hand" name="mHandThumb2Right" pivot="0.028 -0.032 -0.001" pos="0.028 -0.032 -0.001" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHandThumb3Right" connected="true" end="0.015 -0.025 0.000" group="Hand" name="mHandThumb3Right" pivot="0.023 -0.031 -0.001" pos="0.023 -0.031 -0.001" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
<bone aliases="avatar_mWingsRoot" connected="false" end="-0.061 0.000 0.000" group="Wing" name="mWingsRoot" pivot="-0.014 0.000 0.000" pos="-0.014 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mWing1Left" connected="false" end="-0.168 0.169 0.067" group="Wing" name="mWing1Left" pivot="-0.099 0.105 0.181" pos="-0.099 0.105 0.181" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mWing2Left" connected="true" end="-0.181 0.183 0.000" group="Wing" name="mWing2Left" pivot="-0.168 0.169 0.067" pos="-0.168 0.169 0.067" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mWing3Left" connected="true" end="-0.171 0.173 0.000" group="Wing" name="mWing3Left" pivot="-0.181 0.183 0.000" pos="-0.181 0.183 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mWing4Left" connected="true" end="-0.146 0.132 0.000" group="Wing" name="mWing4Left" pivot="-0.171 0.173 0.000" pos="-0.171 0.173 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mWing4FanLeft" connected="true" end="-0.068 0.062 -0.159" group="Wing" name="mWing4FanLeft" pivot="-0.171 0.173 0.000" pos="-0.171 0.173 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
<bone aliases="avatar_mWing1Right" connected="false" end="-0.168 -0.169 0.067" group="Wing" name="mWing1Right" pivot="-0.099 -0.105 0.181" pos="-0.099 -0.105 0.181" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mWing2Right" connected="true" end="-0.181 -0.183 0.000" group="Wing" name="mWing2Right" pivot="-0.168 -0.169 0.067" pos="-0.168 -0.169 0.067" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mWing3Right" connected="true" end="-0.171 -0.173 0.000" group="Wing" name="mWing3Right" pivot="-0.181 -0.183 0.000" pos="-0.181 -0.183 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mWing4Right" connected="true" end="-0.146 -0.132 0.000" group="Wing" name="mWing4Right" pivot="-0.171 -0.173 0.000" pos="-0.171 -0.173 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
<bone aliases="avatar_mWing4FanRight" connected="true" end="-0.068 -0.062 -0.159" group="Wing" name="mWing4FanRight" pivot="-0.171 -0.173 0.000" pos="-0.171 -0.173 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
</bone>
<bone aliases="rThigh avatar_mHipRight" connected="false" end="-0.001 0.049 -0.491" group="Legs" name="mHipRight" pivot="0.033620 -0.128806 -0.041086" pos="0.034 -0.129 -0.041" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true">
<collision_volume end="0.000 0.000 -0.200" group="Collision" name="R_UPPER_LEG" pos="-0.02 0.05 -0.22" rot="0.000000 0.00000 0.000000" scale="0.09 0.09 0.32" support="base" reposition="true"/>
<bone aliases="rShin avatar_mKneeRight" connected="true" end="-0.029 0.000 -0.469" group="Legs" name="mKneeRight" pivot="-0.000780 0.048635 -0.490922" pos="-0.001 0.049 -0.491" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="-0.010 0.000 -0.150" group="Collision" name="R_LOWER_LEG" pos="-0.02 0.0 -0.2" rot="0.000000 0.00000 0.000000" scale="0.06 0.06 0.25" support="base" reposition="true"/>
<bone aliases="rFoot avatar_mAnkleRight" connected="true" end="0.112 0.000 -0.061" group="Legs" name="mAnkleRight" pivot="-0.028869 0.000000 -0.468494" pos="-0.029 0.000 -0.468" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.089 0.000 -0.026" group="Collision" name="R_FOOT" pos="0.077 0.0 -0.041" rot="0.000000 10.00000 0.000000" scale="0.13 0.05 0.05" support="base" reposition="true"/>
<bone aliases="avatar_mFootRight" connected="true" end="0.105 -0.010 0.000" group="Extra" name="mFootRight" pivot="0.111956 -0.000000 -0.060637" pos="0.112 -0.000 -0.061" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true">
<bone aliases="avatar_mToeRight" connected="false" end="0.020 0.000 0.000" group="Extra" name="mToeRight" pivot="0.105399 -0.010408 -0.000104" pos="0.109 0.000 0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base"/>
</bone>
</bone>
</bone>
</bone>
<bone aliases="lThigh avatar_mHipLeft" connected="false" end="-0.001 -0.046 -0.491" group="Legs" name="mHipLeft" pivot="0.033757 0.126765 -0.040998" pos="0.034 0.127 -0.041" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true">
<collision_volume end="0.000 0.000 -0.200" group="Collision" name="L_UPPER_LEG" pos="-0.02 -0.05 -0.22" rot="0.000000 0.00000 0.000000" scale="0.09 0.09 0.32" support="base" reposition="true"/>
<bone aliases="lShin avatar_mKneeLeft" connected="true" end="-0.029 0.001 -0.469" group="Legs" name="mKneeLeft" pivot="-0.000887 -0.045568 -0.491053" pos="-0.001 -0.046 -0.491" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="-0.010 0.000 -0.150" group="Collision" name="L_LOWER_LEG" pos="-0.02 0.0 -0.2" rot="0.000000 0.00000 0.000000" scale="0.06 0.06 0.25" support="base" reposition="true"/>
<bone aliases="lFoot avatar_mAnkleLeft" connected="true" end="0.112 0.000 -0.061" group="Legs" name="mAnkleLeft" pivot="-0.028887 0.001378 -0.468449" pos="-0.029 0.001 -0.468" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base">
<collision_volume end="0.089 0.000 -0.026" group="Collision" name="L_FOOT" pos="0.077 0.0 -0.041" rot="0.000000 10.00000 0.000000" scale="0.13 0.05 0.05" support="base" reposition="true"/>
<bone aliases="avatar_mFootLeft" connected="true" end="0.105 0.008 0.001" group="Extra" name="mFootLeft" pivot="0.111956 -0.000000 -0.060620" pos="0.112 -0.000 -0.061" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base" reposition="true">
<bone aliases="avatar_mToeLeft" connected="false" end="0.020 0.000 0.000" group="Extra" name="mToeLeft" pivot="0.105387 0.008270 0.000871" pos="0.109 0.000 0.000" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" support="base"/>
</bone>
</bone>
</bone>
</bone>
<bone aliases="avatar_mTail1" connected="false" end="-0.197 0.000 0.000" group="Tail" name="mTail1" pivot="-0.116 0.000 0.047" pos="-0.116 0.000 0.047" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mTail2" connected="true" end="-0.168 0.000 0.000" group="Tail" name="mTail2" pivot="-0.197 0.000 0.000" pos="-0.197 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mTail3" connected="true" end="-0.142 0.000 0.000" group="Tail" name="mTail3" pivot="-0.168 0.000 0.000" pos="-0.168 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mTail4" connected="true" end="-0.112 0.000 0.000" group="Tail" name="mTail4" pivot="-0.142 0.000 0.000" pos="-0.142 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mTail5" connected="true" end="-0.094 0.000 0.000" group="Tail" name="mTail5" pivot="-0.112 0.000 0.000" pos="-0.112 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mTail6" connected="true" end="-0.089 0.000 0.000" group="Tail" name="mTail6" pivot="-0.094 0.000 0.000" pos="-0.094 0.000 0.000" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
</bone>
</bone>
<bone aliases="avatar_mGroin" connected="false" end="0.004 0.000 -0.066" group="Groin" name="mGroin" pivot="0.064 0.000 -0.097" pos="0.064 0.000 -0.097" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended"/>
<bone aliases="avatar_mHindLimbsRoot" connected="false" end="-0.204 0.000 0.000" group="Limb" name="mHindLimbsRoot" pivot="-0.200 0.000 0.084" pos="-0.200 0.000 0.084" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHindLimb1Left" connected="false" end="0.002 -0.046 -0.491" group="Limb" name="mHindLimb1Left" pivot="-0.204 0.129 -0.125" pos="-0.204 0.129 -0.125" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mHindLimb2Left" connected="true" end="-0.030 -0.003 -0.468" group="Limb" name="mHindLimb2Left" pivot="0.002 -0.046 -0.491" pos="0.002 -0.046 -0.491" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHindLimb3Left" connected="true" end="0.112 0.000 -0.061" group="Limb" name="mHindLimb3Left" pivot="-0.030 -0.003 -0.468" pos="-0.030 -0.003 -0.468" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHindLimb4Left" connected="true" end="0.105 0.008 0.000" group="Limb" name="mHindLimb4Left" pivot="0.112 0.000 -0.061" pos="0.112 0.000 -0.061" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
<bone aliases="avatar_mHindLimb1Right" connected="false" end="0.002 0.046 -0.491" group="Limb" name="mHindLimb1Right" pivot="-0.204 -0.129 -0.125" pos="-0.204 -0.129 -0.125" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true">
<bone aliases="avatar_mHindLimb2Right" connected="true" end="-0.030 0.003 -0.468" group="Limb" name="mHindLimb2Right" pivot="0.002 0.046 -0.491" pos="0.002 0.046 -0.491" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHindLimb3Right" connected="true" end="0.112 0.000 -0.061" group="Limb" name="mHindLimb3Right" pivot="-0.030 0.003 -0.468" pos="-0.030 0.003 -0.468" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended">
<bone aliases="avatar_mHindLimb4Right" connected="true" end="0.105 -0.008 0.000" group="Limb" name="mHindLimb4Right" pivot="0.112 0.000 -0.061" pos="0.112 0.000 -0.061" rot="0.000 0.000 0.000" scale="1.00 1.00 1.00" support="extended" reposition="true"/>
</bone>
</bone>
</bone>
</bone>
</bone>
</linden_skeleton>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more