SecondBot/SecondBotEvents/Services/RLVService.cs
RebeccaNod1 adf4e30cd2 Dashboard & Group Management Integration
- [LibreMetaverse] Enhanced GroupInvitationEventArgs with GroupID.
- [InteractionService] Added persistence for group invites when auto-accept is disabled.
- [DataStoreService] Implemented pending group invitation tracking and management.
- [DataStoreService] Enhanced local chat recording with Shout/Whisper/OwnerSay type tags.
- [BotClientService] Improved logout stability with explicit logout call and delay.
- [RLVService] Exposed active RLV rules list for external monitoring.
- [Commands] Added GroupInviteRespond, GetPendingGroupInvites, and RLVStatus API commands.
- [Commands] Overhauled NearmeWithDetails to support multi-simulator tracking and better position resolution for seated avatars.
2026-05-05 16:01:58 -06:00

1133 lines
46 KiB
C#

using Discord;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using Org.BouncyCastle.Asn1.Ocsp;
using RestSharp;
using SecondBotEvents.Commands;
using SecondBotEvents.Config;
using SecondBotEvents.RLV;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
namespace SecondBotEvents.Services
{
public class RLVService : BotServices
{
protected new RlvConfig myConfig;
protected bool botConnected = false;
protected CurrentOutfitFolder CurrentOutfitFolder = null;
public RLVService(EventsSecondBot setMaster) : base(setMaster)
{
myConfig = new RlvConfig(master.fromEnv, master.fromFolder);
this.instance = setMaster;
if (myConfig.GetEnabled() == false)
{
return;
}
}
public override string Status()
{
if (myConfig == null)
{
return "No Config";
}
else if (myConfig.GetHideStatusOutput() == true)
{
return "hidden";
}
else if (myConfig.GetEnabled() == false)
{
return "- Not requested -";
}
else if (botConnected == false)
{
return "Waiting for bot";
}
return "Active";
}
protected void BotClientRestart(object o, BotClientNotice e)
{
if (e.isStart == false)
{
return;
}
LogFormater.Info("RLV service [Attached to new client]");
GetClient().Network.LoggedOut += BotLoggedOut;
GetClient().Network.SimConnected += BotLoggedIn;
}
protected void BotLoggedOut(object o, LoggedOutEventArgs e)
{
GetClient().Network.SimConnected += BotLoggedIn;
botConnected = false;
LogFormater.Info("RLV service [Logged out]");
removeEvents();
}
protected void BotLoggedIn(object o, SimConnectedEventArgs e)
{
GetClient().Network.SimConnected -= BotLoggedIn;
GetClient().Self.ChatFromSimulator += BotChatEvents;
CurrentOutfitFolder = master.CurrentOutfitFolder;
botConnected = true;
// attach events
RLVWearables = [];
RLVAttachments =
[
new RLVAttachment() {Name = "none", Point = AttachmentPoint.Default}
];
foreach (WearableType wear in Enum.GetValues(typeof(WearableType)))
{
if (wear.Equals(WearableType.Invalid)) continue;
string wearstr = Utils.EnumToText(wear);
RLVWearables.Add(new RLVWearable() { Name = wearstr.ToLower(), Type = wear });
}
foreach (AttachmentPoint pt in Enum.GetValues(typeof(AttachmentPoint)))
{
string ptstr = Utils.EnumToText(pt);
if (pt.Equals(AttachmentPoint.Default) || ptstr.StartsWith("HUD")) continue;
RLVAttachments.Add(new RLVAttachment { Name = ptstr.ToLower(), Point = pt });
}
// auto load the #RLV folder
var rootContent = GetClient().Inventory.Store.GetContents(GetClient().Inventory.Store.RootFolder.UUID);
UUID RLVfolder = UUID.Zero;
foreach (var baseItem in rootContent)
{
if (baseItem is InventoryFolder folder && folder.Name == "#RLV")
{
RLVfolder = folder.UUID;
break;
}
}
if(RLVfolder == UUID.Zero)
{
LogFormater.Info("RLV service [#RLV folder is not setup]");
}
}
protected UUID GetFolderFromPath(string path)
{
InventoryNode A = FindFolder(path);
if(A != null)
{
return A.Data.UUID;
}
return UUID.Zero;
}
protected void removeEvents()
{
botConnected = false;
if (GetClient() != null)
{
// remove events
GetClient().Self.ChatFromSimulator -= BotChatEvents;
}
}
public override void Start(bool updateEnabled = false, bool setEnabledTo = false)
{
if(updateEnabled)
{
myConfig.setEnabled(setEnabledTo);
}
if (myConfig.GetEnabled() == false)
{
LogFormater.Info("Events service [Disabled]");
return;
}
Stop();
running = true;
master.BotClientNoticeEvent += BotClientRestart;
}
public override void Stop()
{
if (myConfig.GetEnabled() == false)
{
return;
}
running = false;
master.BotClientNoticeEvent -= BotClientRestart;
removeEvents();
}
#region Events
/// <summary>The event subscribers. null if no subcribers</summary>
private EventHandler<RLVEventArgs> m_RLVRuleChanged;
/// <summary>Raises the RLVRuleChanged event</summary>
/// <param name="e">An RLVRuleChangedEventArgs object containing the
/// data returned from the data server</param>
protected virtual void OnRLVRuleChanged(RLVEventArgs e)
{
EventHandler<RLVEventArgs> handler = m_RLVRuleChanged;
try
{
handler?.Invoke(this, e);
}
catch (Exception) { }
}
/// <summary>Thread sync lock object</summary>
private readonly object m_RLVRuleChangedLock = new();
/// <summary>Triggered when an RLVRuleChangedUpdate packet is received,
/// telling us what our avatar is currently wearing
/// <see cref="RequestRLVRuleChanged"/> request.</summary>
public event EventHandler<RLVEventArgs> RLVRuleChanged
{
add { lock (m_RLVRuleChangedLock) { m_RLVRuleChanged += value; } }
remove { lock (m_RLVRuleChangedLock) { m_RLVRuleChanged -= value; } }
}
#endregion
#region Helper classes, methods, structs and enums
public struct RLVWearable
{
public string Name { get; set; }
public WearableType Type { get; set; }
}
public struct RLVAttachment
{
public string Name { get; set; }
public AttachmentPoint Point { get; set; }
}
public List<RLVWearable> RLVWearables;
public List<RLVAttachment> RLVAttachments;
public WearableType WearableFromString(string type)
{
var found = RLVWearables.FindAll(w => w.Name == type);
return found.Count == 1 ? found[0].Type : WearableType.Invalid;
}
public AttachmentPoint AttachmentPointFromString(string point)
{
var found = RLVAttachments.FindAll(a => a.Name == point);
return found.Count == 1 ? found[0].Point : AttachmentPoint.Default;
}
#endregion Helper classes, structs and enums
public bool Enabled
{
get
{
return myConfig.GetEnabled();
}
set
{
myConfig.setEnabled(value);
if (value)
StartTimer();
else
StopTimer();
}
}
GridClient client => instance.BotClient.GetClient();
EventsSecondBot instance = null;
readonly Regex rlv_regex = new(@"(?<behaviour>[^:=]+)(:(?<option>[^=]*))?=(?<param>\w+)", RegexOptions.Compiled);
readonly List<RLVRule> rules = [];
public List<RLVRule> Rules => rules;
System.Timers.Timer CleanupTimer;
void StartTimer()
{
StopTimer();
CleanupTimer = new System.Timers.Timer()
{
Enabled = true,
Interval = 120 * 1000 // two minutes
};
CleanupTimer.Elapsed += CleanupTimer_Elapsed;
}
private void CleanupTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var objects = new List<UUID>();
lock (rules)
{
foreach (var rule in rules.Where(rule => !objects.Contains(rule.Sender)))
{
objects.Add(rule.Sender);
}
}
foreach (var obj in objects.Where(obj => !client.Network.CurrentSim.ObjectsPrimitives.Values.Any(p => p.ID == obj)))
{
Clear(obj);
}
}
private void StopTimer()
{
if (CleanupTimer != null)
{
CleanupTimer.Enabled = false;
CleanupTimer.Dispose();
CleanupTimer = null;
}
}
protected void BotChatEvents(object o, ChatEventArgs e)
{
if (e.Message.StartsWith("@") == false)
{
return;
}
if (e.Type != ChatType.OwnerSay)
{
return;
}
TryProcessCMD(e);
}
public bool TryProcessCMD(ChatEventArgs e)
{
if (!Enabled || !e.Message.StartsWith("@")) return false;
if (e.Message == "@clear")
{
Clear(e.SourceID);
return true;
}
foreach (var cmd in e.Message.Substring(1).Split(','))
{
var m = rlv_regex.Match(cmd);
if (!m.Success) continue;
var rule = new RLVRule
{
Behaviour = m.Groups["behaviour"].ToString().ToLower(),
Option = m.Groups["option"].ToString(),
Param = m.Groups["param"].ToString().ToLower(),
Sender = e.SourceID,
SenderName = e.FromName
};
//UUID AV = UUID.Parse("289c3e36-69b3-40c5-9229-0c6a5d230766");
//master.BotClient.SendIM(AV, "rule="+rule.Behaviour+" option="+rule.Option+" Param="+rule.Param);
Logger.DebugLog(rule.ToString());
if (rule.Param == "rem") rule.Param = "y";
if (rule.Param == "add") rule.Param = "n";
switch (rule.Param)
{
case "n":
lock (rules)
{
var existing = rules.Find(r =>
r.Behaviour == rule.Behaviour &&
r.Sender == rule.Sender &&
r.Option == rule.Option);
if (existing != null)
{
rules.Remove(existing);
}
rules.Add(rule);
OnRLVRuleChanged(new RLVEventArgs(rule));
}
continue;
case "y":
lock (rules)
{
if (rule.Option == "")
{
rules.RemoveAll(r => r.Behaviour == rule.Behaviour && r.Sender == rule.Sender);
}
else
{
rules.RemoveAll(r => r.Behaviour == rule.Behaviour && r.Sender == rule.Sender && r.Option == rule.Option);
}
}
OnRLVRuleChanged(new RLVEventArgs(rule));
continue;
}
switch (rule.Behaviour)
{
case "version":
int chan = 0;
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
Respond(chan, "RestrainedLife viewer v1.23 (SecondBot Events)");
}
break;
case "versionnew":
chan = 0;
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
Respond(chan, "RestrainedLove viewer v1.23 (SecondBot Events)");
}
break;
case "versionnum":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
Respond(chan, "1230100");
}
break;
case "getgroup":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
UUID gid = client.Self.ActiveGroup;
if (instance.DataStoreService.GetGroups().ContainsKey(gid) == true)
{
Respond(chan, instance.DataStoreService.GetGroups()[gid]);
}
}
break;
case "setgroup":
{
if (rule.Param == "force")
{
foreach (var g in instance.DataStoreService.GetGroups())
{
if (g.Value.ToLower() == rule.Option)
{
client.Groups.ActivateGroup(g.Key);
}
}
}
}
break;
case "getpath":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
var attachment = client.Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ParentID == client.Self.LocalID && p.ID == rule.Sender);
if (attachment != null && client.Inventory.Store.Items.ContainsKey(CurrentOutfitFolder.GetAttachmentItem(attachment)))
{
var item = client.Inventory.Store.Items[CurrentOutfitFolder.GetAttachmentItem(attachment)];
var path = FindFullInventoryPath(item, "").Substring(5);
Respond(chan, path);
}
}
break;
case "getpathnew":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
if (UUID.TryParse(rule.Option, out UUID uuid) && uuid != UUID.Zero)
{
var attachment = client.Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ParentID == client.Self.LocalID && p.ID == uuid);
if (attachment != null && client.Inventory.Store.Items.ContainsKey(CurrentOutfitFolder.GetAttachmentItem(attachment)))
{
var item = client.Inventory.Store.Items[CurrentOutfitFolder.GetAttachmentItem(attachment)];
var path = FindFullInventoryPath(item, "");
if (path.StartsWith("#RLV"))
{
Respond(chan, "getpathnew: " + path.Substring(5));
}
else
{
Respond(chan, "getpathnew: ");
}
}
else
{
Respond(chan, "getpathnew: ");
}
}
}
break;
case "getsitid":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
if (client.Network.CurrentSim.ObjectsAvatars.TryGetValue(client.Self.LocalID, out Avatar me))
{
if (me.ParentID != 0)
{
if (client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(me.ParentID, out Primitive seat))
{
Respond(chan, seat.ID.ToString());
break;
}
}
}
Respond(chan, UUID.Zero.ToString());
}
break;
case "getstatusall":
case "getstatus":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
string sep = "/";
string filter = "";
if (!string.IsNullOrEmpty(rule.Option))
{
var parts = rule.Option.Split(';');
if (parts.Length > 1 && parts[1].Length > 0)
{
sep = parts[1].Substring(0, 1);
}
if (parts.Length > 0 && parts[0].Length > 0)
{
filter = parts[0].ToLower();
}
}
lock (rules)
{
string res = "";
rules
.FindAll(r => (rule.Behaviour == "getstatusall" || r.Sender == rule.Sender) && r.Behaviour.Contains(filter))
.ForEach(objRule =>
{
res += sep + objRule.Behaviour;
if (!string.IsNullOrEmpty(objRule.Option))
{
res += ":" + objRule.Option;
}
});
Respond(chan, res);
}
}
break;
case "sit":
UUID sitTarget = UUID.Zero;
if (rule.Param == "force")
{
if (rule.Option != "")
{
UUID.TryParse(rule.Option, out sitTarget);
}
string[] args = [sitTarget.ToString()];
SignedCommand C = new(master.CommandsService, "rlv", "Sit", null, args, 0, "", false, 0, "", false);
master.CommandsService.RunCommand("RLV",C);
}
break;
case "unsit":
if (rule.Param == "force")
{
// stand
SignedCommand C = new(master.CommandsService, "rlv", "Stand", null, [], 0, "", false, 0, "", false);
master.CommandsService.RunCommand("RLV", C);
}
break;
case "setrot":
double rot = 0.0;
if (rule.Param == "force" && double.TryParse(rule.Option, System.Globalization.NumberStyles.Float, Utils.EnUsCulture, out rot))
{
client.Self.Movement.UpdateFromHeading(Math.PI / 2d - rot, true);
}
break;
case "tpto":
if (rule.Param == "force")
{
var coord = rule.Option.Split('/', ';');
try
{
if (coord.Length ==
3) // 3 params is a global coordinate teleport: @tpto:<X>/<Y>/<Z>=force
{
float gx = float.Parse(coord[0], Utils.EnUsCulture);
float gy = float.Parse(coord[1], Utils.EnUsCulture);
float z = float.Parse(coord[2], Utils.EnUsCulture);
ulong h = Helpers.GlobalPosToRegionHandle(gx, gy, out float x, out float y);
client.Self.RequestTeleport(h, new Vector3(x, y, z));
}
else if (
coord.Length == 4 ||
coord.Length ==
5) // 4/5 params is a region-local coordinate teleport (no ;lookat support): @tpto:<region_name>/<X_local>/<Y_local>/<Z_local>[;lookat]=force
{
string n = coord[0];
float x = float.Parse(coord[1], Utils.EnUsCulture);
float y = float.Parse(coord[2], Utils.EnUsCulture);
float z = float.Parse(coord[3], Utils.EnUsCulture);
client.Grid.GetGridRegion(n, GridLayerType.Objects, out GridRegion r);
client.Self.RequestTeleport(r.RegionHandle, new Vector3(x, y, z));
}
}
catch
{
}
}
break;
#region #RLV folder and outfit manipulation
case "getoutfit":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
var wearables = client.Appearance.GetWearablesByType();
string res = "";
// Do we have a specific wearable to check, ie @getoutfit:socks=99
if (!string.IsNullOrEmpty(rule.Option))
{
res = wearables.ContainsKey(WearableFromString(rule.Option)) ? "1" : "0";
}
else
{
foreach (var t in RLVWearables)
{
if (wearables.ContainsKey(t.Type))
{
res += "1";
}
else
{
res += "0";
}
}
}
Respond(chan, res);
}
break;
case "getattach":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
string res = "";
// With this corrected code:
var attachments = client.Network.CurrentSim.ObjectsPrimitives.Values
.Where(p => p.ParentID == client.Self.LocalID)
.ToList();
if (attachments.Count > 0)
{
var myPoints = new List<AttachmentPoint>(attachments.Count);
foreach (var p in attachments)
{
if (!myPoints.Contains(p.PrimData.AttachmentPoint))
{
myPoints.Add(p.PrimData.AttachmentPoint);
}
}
// Do we want to check one single attachment
if (!string.IsNullOrEmpty(rule.Option))
{
res = myPoints.Contains(AttachmentPointFromString(rule.Option)) ? "1" : "0";
}
else
{
foreach (var a in RLVAttachments)
{
if (myPoints.Contains(a.Point))
{
res += "1";
}
else
{
res += "0";
}
}
}
}
Respond(chan, res);
}
break;
case "remattach":
case "detach":
if (rule.Param == "force")
{
if (!string.IsNullOrEmpty(rule.Option))
{
var point = RLVAttachments.Find(a => a.Name == rule.Option);
if (point.Name == rule.Option)
{
var attachment = client.Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ParentID == client.Self.LocalID && p.PrimData.AttachmentPoint == point.Point);
if (attachment != null && client.Inventory.Store.Items.ContainsKey(CurrentOutfitFolder.GetAttachmentItem(attachment)))
{
master.CurrentOutfitFolder.Detach((InventoryItem)client.Inventory.Store.Items[CurrentOutfitFolder.GetAttachmentItem(attachment)].Data);
}
}
else
{
InventoryNode folder = FindFolder(rule.Option);
if (folder != null)
{
var outfit = (from item in folder.Nodes.Values where CurrentOutfitFolder.CanBeWorn(item.Data) select (InventoryItem)(item.Data)).ToList();
master.CurrentOutfitFolder.RemoveFromOutfit(outfit);
}
}
}
else
{
foreach (var attachment in client.Network.CurrentSim.ObjectsPrimitives.Values.Where(p => p.ParentID == client.Self.LocalID))
{
if (client.Inventory.Store.Items.ContainsKey(CurrentOutfitFolder.GetAttachmentItem(attachment)))
{
master.CurrentOutfitFolder.Detach((InventoryItem)client.Inventory.Store.Items[CurrentOutfitFolder.GetAttachmentItem(attachment)].Data);
}
}
}
}
break;
case "detachall":
if (rule.Param == "force")
{
if (!string.IsNullOrEmpty(rule.Option))
{
InventoryNode folder = FindFolder(rule.Option);
if (folder != null)
{
List<InventoryItem> allItems = [];
AllSubfolderWearables(folder, ref allItems);
List<InventoryItem> allSubfolderWorn = allItems.Where(n => CurrentOutfitFolder.CanBeWorn(n)).ToList();
master.CurrentOutfitFolder.RemoveFromOutfit(allSubfolderWorn);
}
}
}
break;
case "detachallthis":
if (rule.Param == "force")
{
var attachment = client.Network.CurrentSim.ObjectsPrimitives.Values.FirstOrDefault(p => p.ParentID == client.Self.LocalID && p.ID == rule.Sender);
if (attachment != null && client.Inventory.Store.Items.ContainsKey(CurrentOutfitFolder.GetAttachmentItem(attachment)))
{
var folder = client.Inventory.Store.Items[CurrentOutfitFolder.GetAttachmentItem(attachment)].Parent;
if (folder != null)
{
List<InventoryItem> outfit = new List<InventoryItem>();
GetAllItems(folder.Data.UUID, true, ref outfit);
master.CurrentOutfitFolder.RemoveFromOutfit(outfit);
}
}
}
break;
case "remoutfit":
if (rule.Param == "force")
{
if (!string.IsNullOrEmpty(rule.Option))
{
var w = RLVWearables.Find(a => a.Name == rule.Option);
if (w.Name == rule.Option)
{
var items = master.CurrentOutfitFolder.GetWornAt(w.Type);
master.CurrentOutfitFolder.RemoveFromOutfit(items);
}
}
}
break;
case "attach":
case "attachoverorreplace":
case "attachover":
case "attachall":
case "attachallover":
if (rule.Param == "force")
{
if (!string.IsNullOrEmpty(rule.Option))
{
InventoryNode folder = FindFolder(rule.Option);
if (folder != null)
{
List<InventoryItem> outfit = [];
if (rule.Behaviour == "attachall" || rule.Behaviour == "attachallover")
{
GetAllItems(folder.Data.UUID, true, ref outfit);
}
else
{
GetAllItems(folder.Data.UUID, false, ref outfit);
}
if (rule.Behaviour == "attachover" || rule.Behaviour == "attachallover")
{
master.CurrentOutfitFolder.AddToOutfit(outfit, false);
}
else
{
master.CurrentOutfitFolder.AddToOutfit(outfit, true);
}
}
}
}
break;
case "getinv":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
string res = string.Empty;
InventoryNode folder = FindFolder(rule.Option);
if (folder != null)
{
res = folder.Nodes.Values.Where(
f => f.Data is InventoryFolder && !f.Data.Name.StartsWith(".")).Aggregate(
res, (current, f) => current + (f.Data.Name + ","));
}
Respond(chan, res.TrimEnd(','));
}
break;
case "getinvworn":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
string res = string.Empty;
InventoryNode root = FindFolder(rule.Option);
if (root != null)
{
res += "|" + GetWornIndicator(root) + ",";
res = root.Nodes.Values.Where(
n => n.Data is InventoryFolder && !n.Data.Name.StartsWith(".")).Aggregate(
res, (current, n) => current + (n.Data.Name + "|" + GetWornIndicator(n) + ","));
}
Respond(chan, res.TrimEnd(','));
}
break;
case "findfolder":
case "findfolders":
if (int.TryParse(rule.Param, out chan) && chan > 0)
{
StringBuilder response = new();
string[] keywordsArray = rule.Option.Split(["&&"], StringSplitOptions.None);
if (keywordsArray.Any())
{
List<InventoryNode> matching_nodes = FindFoldersKeyword(keywordsArray);
if (matching_nodes.Any())
{
if (rule.Behaviour == "findfolder")
{
InventoryNode bestCandidate = null;
int bestCandidateSlashCount = -1;
foreach (var match in matching_nodes)
{
string fullPath = FindFullInventoryPath(match, "");
int numSlashes = fullPath.Count(ch => ch == '/');
if (bestCandidate == null || numSlashes > bestCandidateSlashCount)
{
bestCandidateSlashCount = numSlashes;
bestCandidate = match;
}
}
string bestCandidatePath = bestCandidate.Data.Name;
if (bestCandidatePath.Substring(0, 5).ToLower() == @"#rlv/")
{
bestCandidatePath = bestCandidatePath.Substring(5);
}
response.Append(bestCandidatePath);
}
else
{
foreach (var node in matching_nodes)
{
string fullPath = FindFullInventoryPath(node, "");
if (fullPath.Length > 4 && fullPath.Substring(0, 5).ToLower() == @"#rlv/")
{
fullPath = fullPath.Substring(5);
}
response.Append(fullPath + ",");
}
}
}
}
Respond(chan, response.ToString().TrimEnd(','));
}
break;
#endregion #RLV folder and outfit manipulation
}
}
return true;
}
private void GetAllItems(UUID folder, bool recursive, ref List<InventoryItem> items)
{
List<InventoryBase> itemsfound = GetClient().Inventory.FolderContents(folder, GetClient().Self.AgentID, true, true, InventorySortOrder.ByName, TimeSpan.FromSeconds(15));
foreach (InventoryBase item in itemsfound)
{
if ((item is InventoryWearable) || (item is InventoryObject))
{
items.Add((InventoryItem)item);
}
else if ((item is InventoryFolder) && (recursive == true))
{
GetAllItems(item.UUID, true, ref items);
}
}
}
private void Respond(int chan, string msg)
{
client.Self.Chat(msg, chan, ChatType.Normal);
}
public void AllSubfolderWearables(InventoryNode root, ref List<InventoryItem> WearableItems)
{
foreach (var n in root.Nodes.Values)
{
if (!n.Data.Name.StartsWith("."))
{
if (n.Data is InventoryFolder)
{
AllSubfolderWearables(n, ref WearableItems);
}
else
{
WearableItems.Add((InventoryItem)n.Data);
}
}
}
}
protected string GetWornIndicator(InventoryNode node)
{
var currentOutfit = new List<AppearanceManager.WearableData>(client.Appearance.GetWearables());
// With this corrected code:
var attachments = client.Network.CurrentSim.ObjectsPrimitives.Values
.Where(p => p.ParentID == client.Self.LocalID)
.ToList();
int myItemsCount = 0;
int myItemsWornCount = 0;
foreach (var n in node.Nodes.Values)
{
if (CurrentOutfitFolder.CanBeWorn(n.Data) && !n.Data.Name.StartsWith("."))
{
myItemsCount++;
if ((n.Data is InventoryWearable wearable
&& CurrentOutfitFolder.IsWorn(currentOutfit, wearable))
|| CurrentOutfitFolder.IsAttached(attachments, (InventoryItem)n.Data))
{
myItemsWornCount++;
}
}
}
var allItems = new List<InventoryItem>();
foreach (var n in node.Nodes.Values)
{
if (n.Data is InventoryFolder && !n.Data.Name.StartsWith("."))
{
AllSubfolderWearables(n, ref allItems);
}
}
int allItemsCount = 0;
int allItemsWornCount = 0;
foreach (var n in allItems.Where(n =>
CurrentOutfitFolder.CanBeWorn(n) && !n.Name.StartsWith(".")))
{
allItemsCount++;
if ((n is InventoryWearable && CurrentOutfitFolder.IsWorn(currentOutfit, n)) ||
CurrentOutfitFolder.IsAttached(attachments, n))
{
allItemsWornCount++;
}
}
return WornIndicator(myItemsCount, myItemsWornCount) + WornIndicator(allItemsCount, allItemsWornCount);
}
protected string WornIndicator(int nrMyItem, int nrWorn)
{
string first = "0";
if (nrMyItem > 0)
{
if (nrMyItem == nrWorn)
{
first = "3";
}
else if (nrWorn > 0)
{
first = "2";
}
else
{
first = "1";
}
}
return first;
}
public InventoryNode RLVRootFolder()
{
return client.Inventory.Store.RootNode.Nodes.Values.FirstOrDefault(
rn => rn.Data.Name == "#RLV" && rn.Data is InventoryFolder);
}
public string FindFullInventoryPath(InventoryNode input, string pathConstruct)
{
if (input.Parent == null)
{
return pathConstruct.TrimEnd('/');
}
else
{
pathConstruct = input.Data.Name + "/" + pathConstruct;
return FindFullInventoryPath(input.Parent, pathConstruct);
}
}
public InventoryNode FindFolder(string path)
{
var root = RLVRootFolder();
return root == null ? null : FindFolderInternal(root, "/",
"/" + Regex.Replace(path, @"^[/\s]*(.*)[/\s]*", @"$1").ToLower());
}
protected InventoryNode FindFolderInternal(InventoryNode currentNode, string currentPath, string desiredPath)
{
if (desiredPath == currentPath || desiredPath == (currentPath + "/"))
{
return currentNode;
}
return currentNode.Nodes.Values.Select(
n => FindFolderInternal(n, (currentPath == "/"
? currentPath
: currentPath + "/") + n.Data.Name.ToLower(), desiredPath))
.FirstOrDefault(res => res != null);
}
public List<InventoryNode> FindFoldersKeyword(string[] keywords)
{
var matchingNodes = new List<InventoryNode>();
var root = RLVRootFolder();
if (root != null)
{
FindFoldersKeywordsInternal(root, keywords, [], ref matchingNodes);
}
return matchingNodes;
}
protected void FindFoldersKeywordsInternal(InventoryNode currentNode, string[] keywords,
List<string> currentPathParts, ref List<InventoryNode> matchingNodes)
{
if (currentNode.Data is InventoryFolder &&
!currentNode.Data.Name.StartsWith(".") &&
!currentNode.Data.Name.StartsWith("~") &&
keywords.All(currentPathParts.Contains))
{
matchingNodes.Add(currentNode);
}
foreach (var node in currentNode.Nodes.Values)
{
if (!(node.Data is InventoryFolder)) continue;
currentPathParts.Add(node.Data.Name.ToLower());
FindFoldersKeywordsInternal(node, keywords, currentPathParts, ref matchingNodes);
currentPathParts.RemoveAt(currentPathParts.Count - 1);
}
}
public void Clear(UUID id)
{
lock (rules)
{
rules.RemoveAll(r => r.Sender == id);
}
}
public bool RestictionActive(string behaviour)
{
if (!Enabled) return false;
return rules.FindAll(r => r.Behaviour == behaviour && string.IsNullOrEmpty(r.Option)).Count > 0;
}
public bool RestictionActive(string behaviour, string exception)
{
if (!Enabled) return false;
var set = rules.FindAll(r => r.Behaviour == behaviour);
return set.Count > 0 &&
set.FindAll(r => r.Option == exception).Count == 0 &&
set.FindAll(r => string.IsNullOrEmpty(r.Option)).Count > 0;
}
public List<string> GetOptions(string behaviour)
{
List<string> ret = [];
foreach (var rule in rules.FindAll(r => r.Behaviour == behaviour && !string.IsNullOrEmpty(r.Option))
.Where(rule => !ret.Contains(rule.Option)))
{
ret.Add(rule.Option);
}
return ret;
}
public bool AllowDetach(AttachmentInfo a)
{
if (!Enabled || a == null) return true;
return rules.FindAll(r => r.Behaviour == "detach" && r.Sender == a.Prim.ID).Count <= 0;
}
public bool AllowDetach(InventoryItem item)
{
if (!Enabled || item == null) return true;
// With this corrected code:
var primitives = client.Network.CurrentSim.ObjectsPrimitives.Values
.Where(p => p.ParentID == client.Self.LocalID)
.ToList();
foreach (var att in primitives.Where(att => CurrentOutfitFolder.GetAttachmentItem(att) == item.UUID))
{
if (rules.FindAll(r => r.Behaviour == "detach" && r.Sender == att.ID).Count > 0)
{
return false;
}
break;
}
return true;
}
public bool AutoAcceptTP(UUID agent)
{
if (!Enabled || agent == UUID.Zero) return false;
return rules.FindAll(r => r.Behaviour == "accepttp" && (r.Option == "" || r.Option == agent.ToString())).Count > 0;
}
}
}