diff --git a/LibreMetaverse/NetworkManager.cs b/LibreMetaverse/NetworkManager.cs
index d94b10d..98c84ab 100644
--- a/LibreMetaverse/NetworkManager.cs
+++ b/LibreMetaverse/NetworkManager.cs
@@ -1334,7 +1334,7 @@ namespace OpenMetaverse
IPEndPoint endPoint = new IPEndPoint(ip, port);
- if (FindSimulator(endPoint) != null) return;
+ if (FindSimulator(endPoint) != null) continue;
if (Connect(ip, port, handle, false, null, t.RegionSizeX, t.RegionSizeY) == null)
{
diff --git a/LibreMetaverse/Settings.cs b/LibreMetaverse/Settings.cs
index 3cf976c..acd65d7 100644
--- a/LibreMetaverse/Settings.cs
+++ b/LibreMetaverse/Settings.cs
@@ -181,7 +181,7 @@ namespace OpenMetaverse
/// Enable/disable storing terrain heightmaps in the
/// TerrainManager
- public bool STORE_LAND_PATCHES = false;
+ public bool STORE_LAND_PATCHES = true;
/// Enable/disable sending agent updates
public bool SEND_AGENT_UPDATES = true;
@@ -208,7 +208,7 @@ namespace OpenMetaverse
/// Should we connect to multiple sims? This will allow
/// viewing into neighboring simulators and sim crossings
/// (Experimental)
- public bool MULTIPLE_SIMS = false;
+ public bool MULTIPLE_SIMS = true;
/// If true, all object update packets will be decoded in to
/// native objects. If false, only updates for our own agent will be
diff --git a/SecondBotEvents/Commands/Avatars.cs b/SecondBotEvents/Commands/Avatars.cs
index e6e8b20..e88af55 100644
--- a/SecondBotEvents/Commands/Avatars.cs
+++ b/SecondBotEvents/Commands/Avatars.cs
@@ -80,11 +80,27 @@ namespace SecondBotEvents.Commands
List BetterNearMe = [];
Utils.LongToUInts(GetClient().Network.CurrentSim.Handle, out uint mySimX, out uint mySimY);
- Vector3 myGlobalPos = new Vector3(mySimX + GetClient().Self.SimPosition.X, mySimY + GetClient().Self.SimPosition.Y, GetClient().Self.SimPosition.Z);
+ Vector3 myLocalPos = GetClient().Self.SimPosition;
- foreach (var sim in GetClient().Network.Simulators)
+ List sims = [];
+ try
{
+ lock (GetClient().Network.Simulators)
+ {
+ sims = [.. GetClient().Network.Simulators];
+ }
+ }
+ catch
+ {
+ sims = [GetClient().Network.CurrentSim];
+ }
+
+ foreach (var sim in sims)
+ {
+ if (sim == null || !sim.Connected) continue;
Utils.LongToUInts(sim.Handle, out uint simX, out uint simY);
+ float offsetX = (long)simX - (long)mySimX;
+ float offsetY = (long)simY - (long)mySimY;
HashSet processedAvatars = [];
List avcopy = [.. sim.ObjectsAvatars.Values];
@@ -100,21 +116,20 @@ namespace SecondBotEvents.Commands
localPos = parentObj.Position + (av.Position * parentObj.Rotation);
}
- Vector3 globalPos = new Vector3(simX + localPos.X, simY + localPos.Y, localPos.Z);
- double dist = Math.Round(Vector3.Distance(globalPos, myGlobalPos), 2);
+ float relX = offsetX + localPos.X;
+ float relY = offsetY + localPos.Y;
+ Vector3 relPos = new Vector3(relX, relY, localPos.Z);
+ double dist = Math.Round(Vector3.Distance(relPos, myLocalPos), 2);
if (dist > 150) continue;
- float relX = (simX - mySimX) + localPos.X;
- float relY = (simY - mySimY) + localPos.Y;
-
NearMeDetails details = new()
{
id = av.ID.ToString(),
name = av.Name,
x = (int)Math.Round(relX),
y = (int)Math.Round(relY),
- z = (int)Math.Round(globalPos.Z),
+ z = (int)Math.Round(localPos.Z),
range = dist
};
BetterNearMe.Add(details);
@@ -126,14 +141,13 @@ namespace SecondBotEvents.Commands
if (avID == GetClient().Self.AgentID || processedAvatars.Contains(avID)) continue;
Vector3 localPos = kvp.Value;
- Vector3 globalPos = new Vector3(simX + localPos.X, simY + localPos.Y, localPos.Z);
- double dist = Math.Round(Vector3.Distance(globalPos, myGlobalPos), 2);
+ float relX = offsetX + localPos.X;
+ float relY = offsetY + localPos.Y;
+ Vector3 relPos = new Vector3(relX, relY, localPos.Z);
+ double dist = Math.Round(Vector3.Distance(relPos, myLocalPos), 2);
if (dist > 150) continue;
- float relX = (simX - mySimX) + localPos.X;
- float relY = (simY - mySimY) + localPos.Y;
-
string avName = master.DataStoreService.GetAvatarName(avID);
if (avName == "lookup" || string.IsNullOrEmpty(avName)) avName = "Unknown";
@@ -143,7 +157,7 @@ namespace SecondBotEvents.Commands
name = avName,
x = (int)Math.Round(relX),
y = (int)Math.Round(relY),
- z = (int)Math.Round(globalPos.Z),
+ z = (int)Math.Round(localPos.Z),
range = dist
};
BetterNearMe.Add(details);
diff --git a/SecondBotEvents/Commands/Chat.cs b/SecondBotEvents/Commands/Chat.cs
index 1ea7778..571a100 100644
--- a/SecondBotEvents/Commands/Chat.cs
+++ b/SecondBotEvents/Commands/Chat.cs
@@ -98,6 +98,17 @@ namespace SecondBotEvents.Commands
}
master.DataStoreService.OpenChatWindow(false, avataruuid, avataruuid);
master.BotClient.SendIM(avataruuid, message);
+ string senderName = "{BOT} " + GetClient().Self.Name;
+ master.DataStoreService.RecordAvatarIm(avataruuid, senderName, message);
+ if (master.DiscordService != null && master.DiscordService.isRunning())
+ {
+ string avName = master.DataStoreService.GetAvatarName(avataruuid);
+ if (string.IsNullOrEmpty(avName) || avName == "lookup")
+ {
+ avName = avatar;
+ }
+ master.DiscordService.AvatarIMChat(avataruuid, avName, message, GetClient().Self.Name);
+ }
return BasicReply("ok", [avatar, message]);
}
diff --git a/SecondBotEvents/Commands/Group.cs b/SecondBotEvents/Commands/Group.cs
index a0de98d..44dd7f7 100644
--- a/SecondBotEvents/Commands/Group.cs
+++ b/SecondBotEvents/Commands/Group.cs
@@ -495,6 +495,8 @@ namespace SecondBotEvents.Commands
return Failure("Unknown group", [group]);
}
GetClient().Self.InstantMessageGroup(groupUUID, message);
+ string senderName = "{BOT} " + GetClient().Self.Name;
+ master.DataStoreService.RecordGroupChat(groupUUID, senderName, message);
return BasicReply("Sending");
}
[About("fetchs a list of all pending group invitations")]
diff --git a/SecondBotEvents/Commands/Info.cs b/SecondBotEvents/Commands/Info.cs
index a1273c9..8394050 100644
--- a/SecondBotEvents/Commands/Info.cs
+++ b/SecondBotEvents/Commands/Info.cs
@@ -1,10 +1,19 @@
using System.Text.Json;
using OpenMetaverse;
+using OpenMetaverse.Assets;
+using OpenMetaverse.Imaging;
+using OpenMetaverse.Rendering;
+using OpenMetaverse.StructuredData;
+using CoreJ2K;
using SecondBotEvents.Services;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Threading;
+using System.Xml.Linq;
namespace SecondBotEvents.Commands
{
@@ -251,19 +260,1880 @@ namespace SecondBotEvents.Commands
string name = prim.Properties?.Name;
if (string.IsNullOrEmpty(name)) name = "Object #" + prim.LocalID;
+ int primCount = GetClient().Network.CurrentSim.ObjectsPrimitives.Values.Count(p => p.ID == prim.ID || p.ParentID == prim.LocalID);
+
+ prim.Rotation.GetEulerAngles(out float roll, out float pitch, out float yaw);
+ float rotX = (float)Math.Round(roll * (180.0f / (float)Math.PI), 2);
+ float rotY = (float)Math.Round(pitch * (180.0f / (float)Math.PI), 2);
+ float rotZ = (float)Math.Round(yaw * (180.0f / (float)Math.PI), 2);
+
+ UUID primOwner = prim.Properties?.OwnerID ?? UUID.Zero;
+ bool canBotModify = false;
+ if (primOwner != UUID.Zero)
+ {
+ if (primOwner == GetClient().Self.AgentID)
+ {
+ canBotModify = true;
+ }
+ else if (GetClient().Friends.FriendList.TryGetValue(primOwner, out FriendInfo friend) && friend.CanModifyTheirObjects)
+ {
+ canBotModify = true;
+ }
+ }
+
reply.Add(new {
name = name,
+ description = prim.Properties?.Description ?? "",
uuid = prim.ID.ToString(),
local_id = prim.LocalID,
dist = Math.Round(dist, 1),
pos = prim.Position.ToString(),
- is_sitting = (prim.LocalID == sittingOn)
+ pos_x = Math.Round(prim.Position.X, 3),
+ pos_y = Math.Round(prim.Position.Y, 3),
+ pos_z = Math.Round(prim.Position.Z, 3),
+ scale_x = Math.Round(prim.Scale.X, 3),
+ scale_y = Math.Round(prim.Scale.Y, 3),
+ scale_z = Math.Round(prim.Scale.Z, 3),
+ rot_x = rotX,
+ rot_y = rotY,
+ rot_z = rotZ,
+ flag_physical = (prim.Flags & PrimFlags.Physics) != 0,
+ flag_temporary = (prim.Flags & PrimFlags.Temporary) != 0,
+ flag_phantom = (prim.Flags & PrimFlags.Phantom) != 0,
+ physics_shape = prim.PhysicsProps != null ? prim.PhysicsProps.PhysicsShapeType.ToString() : "Prim",
+ has_light = prim.Light != null,
+ light_color = prim.Light != null ? prim.Light.Color.ToString() : "",
+ light_intensity = prim.Light != null ? Math.Round(prim.Light.Intensity, 2) : 0,
+ light_radius = prim.Light != null ? Math.Round(prim.Light.Radius, 2) : 0,
+ light_falloff = prim.Light != null ? Math.Round(prim.Light.Falloff, 2) : 0,
+ has_flexi = prim.Flexible != null,
+ flexi_softness = prim.Flexible != null ? prim.Flexible.Softness : 0,
+ flexi_gravity = prim.Flexible != null ? Math.Round(prim.Flexible.Gravity, 2) : 0,
+ flexi_drag = prim.Flexible != null ? Math.Round(prim.Flexible.Drag, 2) : 0,
+ flexi_wind = prim.Flexible != null ? Math.Round(prim.Flexible.Wind, 2) : 0,
+ flexi_tension = prim.Flexible != null ? Math.Round(prim.Flexible.Tension, 2) : 0,
+ is_sitting = (prim.LocalID == sittingOn),
+ owner_id = prim.Properties?.OwnerID.ToString() ?? "00000000-0000-0000-0000-000000000000",
+ creator_id = prim.Properties?.CreatorID.ToString() ?? "00000000-0000-0000-0000-000000000000",
+ last_owner_id = prim.Properties?.LastOwnerID.ToString() ?? "00000000-0000-0000-0000-000000000000",
+ group_id = prim.Properties?.GroupID.ToString() ?? "00000000-0000-0000-0000-000000000000",
+ click_action = prim.ClickAction.ToString(),
+ prims = primCount > 0 ? primCount : 1,
+ can_bot_modify = canBotModify,
+ perm_anyone_move = (prim.Properties != null && (prim.Properties.Permissions.EveryoneMask & PermissionMask.Move) != 0),
+ perm_anyone_copy = (prim.Properties != null && (prim.Properties.Permissions.EveryoneMask & PermissionMask.Copy) != 0),
+ perm_next_modify = (prim.Properties != null && (prim.Properties.Permissions.NextOwnerMask & PermissionMask.Modify) != 0),
+ perm_next_copy = (prim.Properties != null && (prim.Properties.Permissions.NextOwnerMask & PermissionMask.Copy) != 0),
+ perm_next_transfer = (prim.Properties != null && (prim.Properties.Permissions.NextOwnerMask & PermissionMask.Transfer) != 0),
+ sale_price = prim.Properties?.SalePrice ?? 0,
+ sale_type = prim.Properties?.SaleType.ToString() ?? "Not",
+ tex_default_id = prim.Textures?.DefaultTexture?.TextureID.ToString() ?? "00000000-0000-0000-0000-000000000000",
+ tex_glow = prim.Textures?.DefaultTexture != null ? Math.Round(prim.Textures.DefaultTexture.Glow, 3) : 0,
+ tex_transparency = prim.Textures?.DefaultTexture != null ? Math.Round((1.0f - prim.Textures.DefaultTexture.RGBA.A) * 100.0f, 1) : 0,
+ tex_shiny = prim.Textures?.DefaultTexture?.Shiny.ToString() ?? "None",
+ tex_bump = prim.Textures?.DefaultTexture?.Bump.ToString() ?? "None",
+ tex_fullbright = prim.Textures?.DefaultTexture?.Fullbright ?? false,
+ tex_mapping = prim.Textures?.DefaultTexture?.TexMapType.ToString() ?? "Default",
+ tex_repeat_u = prim.Textures?.DefaultTexture != null ? Math.Round(prim.Textures.DefaultTexture.RepeatU, 4) : 1.0,
+ tex_repeat_v = prim.Textures?.DefaultTexture != null ? Math.Round(prim.Textures.DefaultTexture.RepeatV, 4) : 1.0,
+ tex_offset_u = prim.Textures?.DefaultTexture != null ? Math.Round(prim.Textures.DefaultTexture.OffsetU, 4) : 0.0,
+ tex_offset_v = prim.Textures?.DefaultTexture != null ? Math.Round(prim.Textures.DefaultTexture.OffsetV, 4) : 0.0,
+ tex_rotation = prim.Textures?.DefaultTexture != null ? Math.Round(prim.Textures.DefaultTexture.Rotation * (180.0f / (float)Math.PI), 2) : 0.0,
+ tex_color = prim.Textures?.DefaultTexture != null ? $"#{((byte)(prim.Textures.DefaultTexture.RGBA.R * 255)):X2}{((byte)(prim.Textures.DefaultTexture.RGBA.G * 255)):X2}{((byte)(prim.Textures.DefaultTexture.RGBA.B * 255)):X2}" : "#FFFFFF",
+ material_type = prim.PrimData.Material.ToString(),
+ prim_type = prim.Type.ToString(),
+ sculpt_type = prim.Sculpt != null ? prim.Sculpt.Type.ToString() : "None",
+ sculpt_uuid = prim.Sculpt != null ? prim.Sculpt.SculptTexture.ToString() : "00000000-0000-0000-0000-000000000000",
+ phys_density = prim.PhysicsProps != null ? Math.Round(prim.PhysicsProps.Density, 1) : 1000.0,
+ phys_friction = prim.PhysicsProps != null ? Math.Round(prim.PhysicsProps.Friction, 2) : 0.6,
+ phys_gravity = prim.PhysicsProps != null ? Math.Round(prim.PhysicsProps.GravityMultiplier, 2) : 1.0,
+ phys_restitution = prim.PhysicsProps != null ? Math.Round(prim.PhysicsProps.Restitution, 2) : 0.5,
+ light_cutoff = prim.Light != null ? Math.Round(prim.Light.Cutoff, 2) : 0.0
});
}
}
return BasicReply(JsonSerializer.Serialize(reply, JsonOptions.UnsafeRelaxed));
}
+
+ [About("Fetches contents (Task Inventory) inside a specified object")]
+ [ArgHints("target", "The UUID or LocalID of the object", "STRING")]
+ [CmdTypeGet()]
+ public object GetObjectContents(string target)
+ {
+ if (GetClient().Network.CurrentSim == null) return Failure("Not in a sim");
+
+ string cleanTarget = (target ?? "").Trim('"', ' ', '\t', '\r', '\n');
+ uint localID = 0;
+ UUID objectID = UUID.Zero;
+
+ if (UUID.TryParse(cleanTarget, out objectID))
+ {
+ var prim = GetClient().Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ID == objectID);
+ if (prim != null)
+ {
+ localID = prim.LocalID;
+ }
+ }
+ else if (uint.TryParse(cleanTarget, out localID))
+ {
+ if (GetClient().Network.CurrentSim.ObjectsPrimitives.TryGetValue(localID, out Primitive prim))
+ {
+ objectID = prim.ID;
+ }
+ }
+
+ // Fallback search across all simulator prims
+ if (objectID != UUID.Zero && localID == 0)
+ {
+ foreach (var p in GetClient().Network.CurrentSim.ObjectsPrimitives.Values)
+ {
+ if (p.ID == objectID)
+ {
+ localID = p.LocalID;
+ break;
+ }
+ }
+ }
+ else if (localID != 0 && objectID == UUID.Zero)
+ {
+ if (GetClient().Network.CurrentSim.ObjectsPrimitives.TryGetValue(localID, out Primitive prim))
+ {
+ objectID = prim.ID;
+ }
+ }
+
+ if (objectID == UUID.Zero || localID == 0)
+ {
+ return Failure($"Object '{cleanTarget}' not found in current sim (UUID={objectID}, LocalID={localID})");
+ }
+
+ var items = GetClient().Inventory.GetTaskInventory(objectID, localID, TimeSpan.FromSeconds(6));
+ if (items == null) return BasicReply("[]");
+
+ var replyList = items.Select(item => {
+ string permsStr = "";
+ string itemType = item.GetType().Name.Replace("Inventory", "");
+ if (item is InventoryItem invItem)
+ {
+ var p = invItem.Permissions.NextOwnerMask;
+ var noMod = (p & PermissionMask.Modify) == PermissionMask.None;
+ var noCopy = (p & PermissionMask.Copy) == PermissionMask.None;
+ var noXfer = (p & PermissionMask.Transfer) == PermissionMask.None;
+
+ List restr = new List();
+ if (noMod) restr.Add("no modify");
+ if (noCopy) restr.Add("no copy");
+ if (noXfer) restr.Add("no transfer");
+ if (restr.Count > 0) permsStr = " (" + string.Join(") (", restr) + ")";
+
+ if (invItem.InventoryType != InventoryType.Unknown)
+ {
+ itemType = invItem.InventoryType.ToString();
+ }
+ else if (invItem.AssetType != AssetType.Unknown)
+ {
+ itemType = invItem.AssetType.ToString();
+ }
+ }
+
+ string itemName = item.Name ?? "";
+ try { itemName = Uri.UnescapeDataString(itemName); } catch {}
+
+ return new {
+ name = itemName,
+ uuid = item.UUID.ToString(),
+ type = itemType,
+ perms = permsStr
+ };
+ }).ToList();
+
+ return BasicReply(JsonSerializer.Serialize(replyList, JsonOptions.UnsafeRelaxed));
+ }
+
+ [About("Fetches all prims in the linkset for a specified object")]
+ [ArgHints("target", "The UUID or LocalID of the object", "STRING")]
+ [CmdTypeGet()]
+ public object GetLinksetPrims(string target)
+ {
+ if (GetClient().Network.CurrentSim == null) return Failure("Not in a sim");
+
+ string cleanTarget = (target ?? "").Trim('"', ' ', '\t', '\r', '\n');
+ uint localID = 0;
+ UUID objectID = UUID.Zero;
+
+ if (UUID.TryParse(cleanTarget, out objectID))
+ {
+ var prim = GetClient().Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ID == objectID);
+ if (prim != null) localID = prim.LocalID;
+ }
+ else if (uint.TryParse(cleanTarget, out localID))
+ {
+ if (GetClient().Network.CurrentSim.ObjectsPrimitives.TryGetValue(localID, out Primitive prim))
+ {
+ objectID = prim.ID;
+ }
+ }
+
+ if (objectID == UUID.Zero && localID == 0) return Failure($"Object '{cleanTarget}' not found");
+
+ Primitive targetPrim = GetClient().Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ID == objectID || p.LocalID == localID);
+ if (targetPrim == null) return Failure("Target prim not found");
+
+ Primitive rootPrim = targetPrim;
+ if (targetPrim.ParentID != 0)
+ {
+ var parent = GetClient().Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.LocalID == targetPrim.ParentID);
+ if (parent != null) rootPrim = parent;
+ }
+
+ var linksetPrims = GetClient().Network.CurrentSim.ObjectsPrimitives.Values
+ .Where(p => p.ID == rootPrim.ID || p.ParentID == rootPrim.LocalID)
+ .OrderBy(p => p.ID == rootPrim.ID ? 0 : 1)
+ .ThenBy(p => p.LocalID)
+ .ToList();
+
+ var missingProps = linksetPrims.Where(p => p.Properties == null).Select(p => p.LocalID).ToArray();
+ if (missingProps.Length > 0)
+ {
+ GetClient().Objects.SelectObjects(GetClient().Network.CurrentSim, missingProps);
+ Thread.Sleep(400);
+ }
+
+ uint sittingOn = GetClient().Self.SittingOn;
+ int linkNum = 1;
+ List