diff --git a/Dockerfile b/Dockerfile
index 1c5ecc2..2a43216 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -89,7 +89,9 @@ ENV basic_Username='' \
events_HideStatusOutput='false' \
homebound_HideStatusOutput='false' \
http_HideStatusOutput='false' \
- interaction_HideStatusOutput='false'
+ interaction_HideStatusOutput='false' \
+ rlv_Enabled='false' \
+ rlv_HideStatusOutput='false'
EXPOSE 80
diff --git a/LibreMetaverse/Settings.cs b/LibreMetaverse/Settings.cs
index 4c20650..2bcdc62 100644
--- a/LibreMetaverse/Settings.cs
+++ b/LibreMetaverse/Settings.cs
@@ -338,7 +338,7 @@ namespace OpenMetaverse
/// You will get no output on the console. This behavior can be overriden by creating
/// a logger configuration file for log4net
///
- public static Helpers.LogLevel LOG_LEVEL = Helpers.LogLevel.Error;
+ public static Helpers.LogLevel LOG_LEVEL = Helpers.LogLevel.None;
/// Attach avatar names to log messages
public bool LOG_NAMES = true;
diff --git a/SecondBotEvents/Commands/Self.cs b/SecondBotEvents/Commands/Self.cs
index 3091edd..3c0dde4 100644
--- a/SecondBotEvents/Commands/Self.cs
+++ b/SecondBotEvents/Commands/Self.cs
@@ -86,17 +86,17 @@ namespace SecondBotEvents.Commands
[About("Makes the bot sit on the ground or on a object if it can see it")]
[ReturnHints("ok")]
[ReturnHintsFailure("Invaild object UUID")]
- [ArgHints("target", "ground or a object UUID")]
+ [ArgHints("target", "a object UUID anything or nothing will also work")]
public object Sit(string target)
{
- if (target == "ground")
+ if ((target == "ground") || (target == UUID.Zero.ToString()))
{
GetClient().Self.SitOnGround();
return BasicReply("ok", new [] { target });
}
if(UUID.TryParse(target,out UUID objectuuid) == false)
{
- return Failure("Invaild object UUID", new [] { target });
+ return Failure("Invaild object uuid and not ground");
}
GetClient().Self.RequestSit(objectuuid, Vector3.Zero);
return BasicReply("ok", new [] { target });
diff --git a/SecondBotEvents/Config/InteractionConfig.cs b/SecondBotEvents/Config/InteractionConfig.cs
index 0f10f13..9847317 100644
--- a/SecondBotEvents/Config/InteractionConfig.cs
+++ b/SecondBotEvents/Config/InteractionConfig.cs
@@ -27,7 +27,7 @@ namespace SecondBotEvents.Config
public bool GetAcceptTeleports()
{
- return ReadSettingAsBool("AcceptTeleports", true);
+ return ReadSettingAsBool("AcceptTeleports", false);
}
public bool GetAcceptGroupInvites()
diff --git a/SecondBotEvents/Config/RlvConfig.cs b/SecondBotEvents/Config/RlvConfig.cs
index a96fefb..14c97b1 100644
--- a/SecondBotEvents/Config/RlvConfig.cs
+++ b/SecondBotEvents/Config/RlvConfig.cs
@@ -11,18 +11,8 @@ namespace SecondBotEvents.Config
{
filename = "rlv";
settings.Add("Enabled");
- settings.Add("AllowRlvViaIm");
- settings.Add("AllowRlvViaObjectOwnerSay");
settings.Add("HideStatusOutput");
}
- public bool GetAllowRlvViaIm()
- {
- return ReadSettingAsBool("AllowRlvViaIm", false);
- }
- public bool GetAllowRlvViaObjectOwnerSay()
- {
- return ReadSettingAsBool("AllowRlvViaObjectOwnerSay", false);
- }
public bool GetEnabled()
{
diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs
index ce113b8..f08c41b 100644
--- a/SecondBotEvents/EventsSecondBot.cs
+++ b/SecondBotEvents/EventsSecondBot.cs
@@ -39,6 +39,9 @@ namespace SecondBotEvents
public EventsService EventsService { get { return (EventsService)GetService("EventsService"); } }
public DialogService DialogService { get { return (DialogService)GetService("DialogService"); } }
+ public CurrentOutfitFolder CurrentOutfitFolder { get { return (CurrentOutfitFolder)GetService("CurrentOutfitFolder"); } }
+ public RLVService RLV { get { return (RLVService)GetService("RLVService"); } }
+
public CustomCommandsService CustomCommandsService { get { return (CustomCommandsService)GetService("CustomCommandsService"); } }
@@ -145,6 +148,8 @@ namespace SecondBotEvents
lastStatus = new Dictionary();
RegisterService("BotClientService", false);
RegisterService("DataStoreService");
+ RegisterService("RLVService");
+ RegisterService("CurrentOutfitFolder");
RegisterService("HttpService");
RegisterService("CommandsService");
RegisterService("DiscordService");
diff --git a/SecondBotEvents/RLV/RLVAttachmentInfo.cs b/SecondBotEvents/RLV/RLVAttachmentInfo.cs
new file mode 100644
index 0000000..c250a6b
--- /dev/null
+++ b/SecondBotEvents/RLV/RLVAttachmentInfo.cs
@@ -0,0 +1,19 @@
+using OpenMetaverse;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SecondBotEvents.RLV
+{
+ public class AttachmentInfo
+ {
+ public Primitive Prim;
+ public InventoryItem Item;
+ public UUID InventoryID;
+ public UUID PrimID;
+ public bool MarkedAttached;
+ public AttachmentPoint Point => Prim != null ? Prim.PrimData.AttachmentPoint : AttachmentPoint.Default;
+ }
+}
diff --git a/SecondBotEvents/RLV/RLVEventArgs.cs b/SecondBotEvents/RLV/RLVEventArgs.cs
new file mode 100644
index 0000000..92124df
--- /dev/null
+++ b/SecondBotEvents/RLV/RLVEventArgs.cs
@@ -0,0 +1,34 @@
+/**
+ * Radegast Metaverse Client
+ * Copyright(c) 2009-2014, Radegast Development Team
+ * Copyright(c) 2016-2020, Sjofn, LLC
+ * All rights reserved.
+ *
+ * Radegast is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.If not, see.
+ */
+
+using System;
+
+namespace SecondBotEvents.RLV
+{
+ public class RLVEventArgs : EventArgs
+ {
+ public RLVRule Rule { set; get; }
+
+ public RLVEventArgs(RLVRule rule)
+ {
+ Rule = rule;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecondBotEvents/RLV/RLVRule.cs b/SecondBotEvents/RLV/RLVRule.cs
new file mode 100644
index 0000000..3b8907c
--- /dev/null
+++ b/SecondBotEvents/RLV/RLVRule.cs
@@ -0,0 +1,39 @@
+/**
+ * Radegast Metaverse Client
+ * Copyright(c) 2009-2014, Radegast Development Team
+ * Copyright(c) 2016-2020, Sjofn, LLC
+ * All rights reserved.
+ *
+ * Radegast is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.If not, see.
+ */
+
+using OpenMetaverse;
+
+namespace SecondBotEvents.RLV
+{
+ public class RLVRule
+ {
+ public string Behaviour { set; get; }
+ public string Option { set; get; }
+ public string Param { set; get; }
+ public UUID Sender { set; get; }
+ public string SenderName { set; get; }
+
+ public override string ToString()
+ {
+ return string.Format("{0}: {1}:{2}={3} [{4}]", SenderName, Behaviour, Option, Param, Sender);
+ }
+ }
+}
+
diff --git a/SecondBotEvents/Services/CurrentOutfitFolder.cs b/SecondBotEvents/Services/CurrentOutfitFolder.cs
new file mode 100644
index 0000000..f2d5879
--- /dev/null
+++ b/SecondBotEvents/Services/CurrentOutfitFolder.cs
@@ -0,0 +1,626 @@
+using OpenMetaverse;
+using SecondBotEvents.Config;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using static SecondBotEvents.Services.RLVService;
+
+
+namespace SecondBotEvents.Services
+{
+ public class CurrentOutfitFolder : BotServices
+ {
+ #region Fields
+ private bool InitiCOF = false;
+ private bool AppearanceSent = false;
+ private bool COFReady = false;
+ private bool InitialUpdateDone = false;
+ public Dictionary Content = new Dictionary();
+ public InventoryFolder COF;
+ protected bool botConnected = false;
+
+ #endregion Fields
+
+ #region Construction and disposal
+ public CurrentOutfitFolder(EventsSecondBot setMaster) : base(setMaster)
+ {
+ }
+
+ public override string Status()
+ {
+ if (botConnected == false)
+ {
+ return "Waiting for bot";
+ }
+ return "Active";
+ }
+
+ public override void Start()
+ {
+ Stop();
+ running = true;
+ master.BotClientNoticeEvent += BotClientRestart;
+ LogFormater.Info("COF inventor [Starting]");
+ }
+
+ public override void Stop()
+ {
+ if (running == true)
+ {
+ LogFormater.Info("COF inventor [Stopping]");
+ }
+ running = false;
+ }
+
+ protected void BotClientRestart(object o, BotClientNotice e)
+ {
+ LogFormater.Info("COF inventory [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("COF inventory [Logged out]");
+ UnregisterClientEvents();
+ }
+
+ protected void BotLoggedIn(object o, SimConnectedEventArgs e)
+ {
+ GetClient().Network.SimConnected -= BotLoggedIn;
+ LogFormater.Info("COF inventory [starting]");
+ RegisterClientEvents();
+ }
+ #endregion Construction and disposal
+
+ #region Event handling
+ private void RegisterClientEvents()
+ {
+ GetClient().Network.EventQueueRunning += Network_EventQueueRunning;
+ GetClient().Inventory.FolderUpdated += Inventory_FolderUpdated;
+ GetClient().Inventory.ItemReceived += Inventory_ItemReceived;
+ GetClient().Appearance.AppearanceSet += Appearance_AppearanceSet;
+ GetClient().Objects.KillObject += Objects_KillObject;
+ }
+
+ private void UnregisterClientEvents()
+ {
+ GetClient().Network.EventQueueRunning -= Network_EventQueueRunning;
+ GetClient().Inventory.FolderUpdated -= Inventory_FolderUpdated;
+ GetClient().Inventory.ItemReceived -= Inventory_ItemReceived;
+ GetClient().Appearance.AppearanceSet -= Appearance_AppearanceSet;
+ GetClient().Objects.KillObject -= Objects_KillObject;
+ lock (Content) Content.Clear();
+ InitiCOF = false;
+ AppearanceSent = false;
+ COFReady = false;
+ InitialUpdateDone = false;
+ }
+
+ private void Appearance_AppearanceSet(object sender, AppearanceSetEventArgs e)
+ {
+ AppearanceSent = true;
+ if (COFReady)
+ {
+ InitialUpdate();
+ }
+ }
+
+ private void Inventory_ItemReceived(object sender, ItemReceivedEventArgs e)
+ {
+ var links = ContentLinks();
+ bool partOfCOF = links.Any(cofItem => cofItem.AssetUUID == e.Item.UUID);
+
+ if (partOfCOF)
+ {
+ lock (Content)
+ {
+ Content[e.Item.UUID] = e.Item;
+ }
+ }
+
+ if (Content.Count != links.Count) return;
+ COFReady = true;
+ if (AppearanceSent)
+ {
+ InitialUpdate();
+ }
+ lock (Content)
+ {
+ foreach (var lk in from link in Content.Values
+ where link.InventoryType == InventoryType.Wearable
+ select (InventoryWearable)link into w
+ select links.Find(l => l.AssetUUID == w.UUID))
+ { }
+ }
+ }
+
+ private readonly object FolderSync = new object();
+
+ private void Inventory_FolderUpdated(object sender, FolderUpdatedEventArgs e)
+ {
+ if (COF == null) return;
+
+ if (e.FolderID == COF.UUID && e.Success)
+ {
+ COF = (InventoryFolder)GetClient().Inventory.Store[COF.UUID];
+ lock (FolderSync)
+ {
+ lock (Content) Content.Clear();
+
+
+ List items = new List();
+ List owners = new List();
+
+ foreach (var link in ContentLinks())
+ {
+ //if (Client.Inventory.Store.Contains(link.AssetUUID))
+ //{
+ // continue;
+ //}
+ items.Add(link.AssetUUID);
+ owners.Add(GetClient().Self.AgentID);
+ }
+
+ if (items.Count > 0)
+ {
+ GetClient().Inventory.RequestFetchInventory(items, owners);
+ }
+ }
+ }
+ }
+
+ private void Objects_KillObject(object sender, KillObjectEventArgs e)
+ {
+ if (GetClient().Network.CurrentSim != e.Simulator) return;
+
+ Primitive prim = null;
+ if (GetClient().Network.CurrentSim.ObjectsPrimitives.TryGetValue(e.ObjectLocalID, out prim))
+ {
+ UUID invItem = GetAttachmentItem(prim);
+ if (invItem != UUID.Zero)
+ {
+ RemoveLink(invItem);
+ }
+ }
+ }
+
+ private void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
+ {
+ if (e.Simulator == GetClient().Network.CurrentSim && !InitiCOF)
+ {
+ InitiCOF = true;
+ InitCOF();
+ }
+ }
+ #endregion Event handling
+
+ #region Private methods
+
+ private void RequestDescendants(UUID folderID)
+ {
+ GetClient().Inventory.RequestFolderContents(folderID, GetClient().Self.AgentID, true, true, InventorySortOrder.ByDate);
+ }
+
+ private void InitCOF()
+ {
+ var rootContent = GetClient().Inventory.Store.GetContents(GetClient().Inventory.Store.RootFolder.UUID);
+ foreach (var baseItem in rootContent)
+ {
+ if (baseItem is InventoryFolder folder && folder.PreferredType == FolderType.CurrentOutfit)
+ {
+ COF = folder;
+ break;
+ }
+ }
+
+ if (COF == null)
+ {
+ CreateCOF();
+ }
+ else
+ {
+ RequestDescendants(COF.UUID);
+ }
+ }
+
+ private void CreateCOF()
+ {
+ UUID cofID = GetClient().Inventory.CreateFolder(GetClient().Inventory.Store.RootFolder.UUID, "Current Outfit", FolderType.CurrentOutfit);
+ if (GetClient().Inventory.Store.Items.ContainsKey(cofID) && GetClient().Inventory.Store.Items[cofID].Data is InventoryFolder folder)
+ {
+ COF = folder;
+ COFReady = true;
+ if (AppearanceSent)
+ {
+ InitialUpdate();
+ }
+ }
+ }
+
+ private void InitialUpdate()
+ {
+ if (InitialUpdateDone) return;
+ InitialUpdateDone = true;
+ lock (Content)
+ {
+ var myAtt = GetClient().Network.CurrentSim.ObjectsPrimitives.FindAll(p => p.ParentID == GetClient().Self.LocalID);
+
+ foreach (var item in Content.Values
+ .Where(item => item is InventoryObject || item is InventoryAttachment)
+ .Where(item => !IsAttached(myAtt, item)))
+ {
+ GetClient().Appearance.Attach(item, AttachmentPoint.Default, false);
+ }
+ }
+ }
+ #endregion Private methods
+
+ #region Public methods
+ ///
+ /// Get COF contents
+ ///
+ /// List if InventoryItems that can be part of appearance (attachments, wearables)
+ public List ContentLinks()
+ {
+ var ret = new List();
+ if (COF == null) return ret;
+
+ GetClient().Inventory.Store.GetContents(COF)
+ .FindAll(b => CanBeWorn(b) && ((InventoryItem)b).AssetType == AssetType.Link)
+ .ForEach(item => ret.Add((InventoryItem)item));
+
+ return ret;
+ }
+
+ ///
+ /// Get inventory ID of a prim
+ ///
+ /// Prim to check
+ /// Inventory ID of the object. UUID.Zero if not found
+ public static UUID GetAttachmentItem(Primitive prim)
+ {
+ if (prim.NameValues == null) return UUID.Zero;
+
+ for (var i = 0; i < prim.NameValues.Length; i++)
+ {
+ if (prim.NameValues[i].Name == "AttachItemID")
+ {
+ return (UUID)prim.NameValues[i].Value.ToString();
+ }
+ }
+ return UUID.Zero;
+ }
+
+ ///
+ /// Is an inventory item currently attached
+ ///
+ /// List of root prims that are attached to our avatar
+ /// Inventory item to check
+ /// True if the inventory item is attached to avatar
+ public static bool IsAttached(List attachments, InventoryItem item)
+ {
+ return attachments.Any(prim => GetAttachmentItem(prim) == item.UUID);
+ }
+
+ ///
+ /// Checks if inventory item of Wearable type is worn
+ ///
+ /// Current outfit
+ /// Item to check
+ /// True if the item is worn
+ public static bool IsWorn(List currentlyWorn, InventoryItem item)
+ {
+ return currentlyWorn.Any(worn => worn.ItemID == item.UUID);
+ }
+
+ ///
+ /// Can this inventory type be worn
+ ///
+ /// Item to check
+ /// True if the inventory item can be worn
+ public static bool CanBeWorn(InventoryBase item)
+ {
+ return item is InventoryWearable || item is InventoryAttachment || item is InventoryObject;
+ }
+
+ ///
+ /// Attach an inventory item
+ ///
+ /// Item to be attached
+ /// Attachment point
+ /// Replace existing attachment at that point first?
+ public void Attach(InventoryItem item, AttachmentPoint point, bool replace)
+ {
+ GetClient().Appearance.Attach(item, point, replace);
+ AddLink(item);
+ }
+
+ ///
+ /// Creates a new COF link
+ ///
+ /// Original item to be linked from COF
+ public void AddLink(InventoryItem item)
+ {
+ if (item.InventoryType == InventoryType.Wearable && !IsBodyPart(item))
+ {
+ var w = (InventoryWearable)item;
+ int layer = 0;
+ string desc = $"@{(int)w.WearableType}{layer:00}";
+ AddLink(item, desc);
+ }
+ else
+ {
+ AddLink(item, string.Empty);
+ }
+ }
+
+ ///
+ /// Creates a new COF link
+ ///
+ /// Original item to be linked from COF
+ /// Description for the link
+ public void AddLink(InventoryItem item, string newDescription)
+ {
+ if (COF == null) return;
+
+ bool linkExists = null != ContentLinks().Find(itemLink => itemLink.AssetUUID == item.UUID);
+
+ if (!linkExists)
+ {
+ GetClient().Inventory.CreateLink(COF.UUID, item.UUID, item.Name, newDescription,
+ AssetType.Link, item.InventoryType, UUID.Random(),
+ (success, newItem) =>
+ {
+ if (success)
+ {
+ GetClient().Inventory.RequestFetchInventory(newItem.UUID, newItem.OwnerID);
+ }
+ });
+ }
+ }
+
+ ///
+ /// Remove a link to specified inventory item
+ ///
+ /// ID of the target inventory item for which we want link to be removed
+ public void RemoveLink(UUID itemID)
+ {
+ RemoveLink(new List(1) { itemID });
+ }
+
+ ///
+ /// Remove a link to specified inventory item
+ ///
+ /// List of IDs of the target inventory item for which we want link to be removed
+ public void RemoveLink(List itemIDs)
+ {
+ if (COF == null) return;
+
+ foreach (var links in itemIDs.Select(itemID => ContentLinks()
+ .FindAll(itemLink => itemLink.AssetUUID == itemID)))
+ {
+ links.ForEach(item => GetClient().Inventory.RemoveItem(item.UUID));
+ }
+ }
+
+ ///
+ /// Remove attachment
+ ///
+ /// >Inventory item to be detached
+ public void Detach(InventoryItem item)
+ {
+ var realItem = RealInventoryItem(item);
+ if (!master.RLV.AllowDetach(realItem)) return;
+
+ GetClient().Appearance.Detach(item);
+ RemoveLink(item.UUID);
+ }
+
+ public List GetWornAt(WearableType type)
+ {
+ var ret = new List();
+ ContentLinks().ForEach(link =>
+ {
+ var item = RealInventoryItem(link);
+ if (!(item is InventoryWearable wearable)) return;
+
+ if (wearable.WearableType == type)
+ {
+ ret.Add(wearable);
+ }
+ });
+
+ return ret;
+ }
+
+ ///
+ /// Resolves inventory links and returns a real inventory item that
+ /// the link is pointing to
+ ///
+ ///
+ ///
+ public InventoryItem RealInventoryItem(InventoryItem item)
+ {
+ if (item.IsLink() && GetClient().Inventory.Store.Contains(item.AssetUUID)
+ && GetClient().Inventory.Store[item.AssetUUID] is InventoryItem invItem)
+ {
+ return invItem;
+ }
+
+ return item;
+ }
+
+ ///
+ /// Replaces the current outfit and updates COF links accordingly
+ ///
+ /// List of new wearables and attachments that comprise the new outfit
+ public void ReplaceOutfit(List newOutfit)
+ {
+ // Resolve inventory links
+ var outfit = newOutfit.Select(RealInventoryItem).ToList();
+
+ // Remove links to all exiting items
+ var toRemove = new List();
+ ContentLinks().ForEach(item =>
+ {
+ if (IsBodyPart(item))
+ {
+ WearableType linkType = ((InventoryWearable)RealInventoryItem(item)).WearableType;
+ bool hasBodyPart = newOutfit.Select(RealInventoryItem).Where(IsBodyPart).Any(newItem =>
+ ((InventoryWearable)newItem).WearableType == linkType);
+
+ if (hasBodyPart)
+ {
+ toRemove.Add(item.UUID);
+ }
+ }
+ else
+ {
+ toRemove.Add(item.UUID);
+ }
+ });
+
+ foreach (var item in toRemove)
+ {
+ GetClient().Inventory.RemoveItem(item);
+ }
+
+ // Add links to new items
+ var newItems = outfit.FindAll(CanBeWorn);
+ foreach (var item in newItems)
+ {
+ AddLink(item);
+ }
+
+ GetClient().Appearance.ReplaceOutfit(outfit, false);
+ ThreadPool.QueueUserWorkItem(sync =>
+ {
+ Thread.Sleep(2000);
+ GetClient().Appearance.RequestSetAppearance(true);
+ });
+ }
+
+ ///
+ /// Add items to current outfit
+ ///
+ /// Item to add
+ /// Should existing wearable of the same type be removed
+ public void AddToOutfit(InventoryItem item, bool replace)
+ {
+ AddToOutfit(new List(1) { item }, replace);
+ }
+
+ ///
+ /// Add items to current outfit
+ ///
+ /// List of items to add
+ /// Should existing wearable of the same type be removed
+ public void AddToOutfit(List items, bool replace)
+ {
+ var current = ContentLinks();
+ var toRemove = new List();
+
+ // Resolve inventory links and remove wearables of the same type from COF
+ var outfit = new List();
+
+ foreach (var item in items)
+ {
+ var realItem = RealInventoryItem(item);
+ if (replace && realItem is InventoryWearable wearable)
+ {
+ foreach (var link in current)
+ {
+ var currentItem = RealInventoryItem(link);
+ if (link.AssetUUID == item.UUID)
+ {
+ toRemove.Add(link.UUID);
+ }
+ else
+ {
+ var w = currentItem as InventoryWearable;
+ if (w?.WearableType == wearable.WearableType)
+ {
+ toRemove.Add(link.UUID);
+ }
+ }
+ }
+ }
+
+ outfit.Add(realItem);
+ }
+
+ foreach (var item in toRemove)
+ {
+ GetClient().Inventory.RemoveItem(item);
+ }
+
+ // Add links to new items
+ var newItems = outfit.FindAll(CanBeWorn);
+ foreach (var item in newItems)
+ {
+ AddLink(item);
+ }
+
+ GetClient().Appearance.AddToOutfit(outfit, replace);
+ ThreadPool.QueueUserWorkItem(sync =>
+ {
+ Thread.Sleep(2000);
+ GetClient().Appearance.RequestSetAppearance(true);
+ });
+ }
+
+ ///
+ /// Remove an item from the current outfit
+ ///
+ /// Item to remove
+ public void RemoveFromOutfit(InventoryItem item)
+ {
+ RemoveFromOutfit(new List(1) { item });
+ }
+
+ ///
+ /// Remove specified items from the current outfit
+ ///
+ /// List of items to remove
+ public void RemoveFromOutfit(List items)
+ {
+ // Resolve inventory links
+ var outfit = items.Select(RealInventoryItem).Where(realItem => master.RLV.AllowDetach(realItem)).ToList();
+
+ // Remove links to all items that were removed
+ var toRemove = outfit.FindAll(item => CanBeWorn(item) && !IsBodyPart(item)).Select(item => item.UUID).ToList();
+ RemoveLink(toRemove);
+
+ GetClient().Appearance.RemoveFromOutfit(outfit);
+ }
+
+ public bool IsBodyPart(InventoryItem item)
+ {
+ var realItem = RealInventoryItem(item);
+ if (!(realItem is InventoryWearable wearable)) return false;
+
+ var t = wearable.WearableType;
+ return t == WearableType.Shape ||
+ t == WearableType.Skin ||
+ t == WearableType.Eyes ||
+ t == WearableType.Hair;
+ }
+
+ ///
+ /// Force rebaking textures
+ ///
+ public void RebakeTextures()
+ {
+ GetClient().Appearance.RequestSetAppearance(true);
+ }
+
+ #endregion Public methods
+ }
+}
diff --git a/SecondBotEvents/Services/HomeboundService.cs b/SecondBotEvents/Services/HomeboundService.cs
index 8d475ee..d73755c 100644
--- a/SecondBotEvents/Services/HomeboundService.cs
+++ b/SecondBotEvents/Services/HomeboundService.cs
@@ -75,7 +75,6 @@ namespace SecondBotEvents.Services
}
protected void Tick()
{
- long dif = 0;
if (evacWanted == true)
{
BotAlertMessage(null, new AlertMessageEventArgs("restart", "999", new OpenMetaverse.StructuredData.OSDMap()));
@@ -327,6 +326,12 @@ namespace SecondBotEvents.Services
return;
}
Stop();
+ if(master.RLV.Enabled == true)
+ {
+ myConfig.setEnabled(false);
+ LogFormater.Info("Homebound Service [Disabled RLV enabled]");
+ return;
+ }
running = true;
master.BotClientNoticeEvent += BotClientRestart;
LogFormater.Info("Homebound Service [Starting]");
diff --git a/SecondBotEvents/Services/RLVService.cs b/SecondBotEvents/Services/RLVService.cs
new file mode 100644
index 0000000..a3a04cf
--- /dev/null
+++ b/SecondBotEvents/Services/RLVService.cs
@@ -0,0 +1,1092 @@
+using OpenMetaverse;
+using SecondBotEvents.Config;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using RestSharp;
+using OpenMetaverse.StructuredData;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Text;
+using System;
+using SecondBotEvents.RLV;
+
+namespace SecondBotEvents.Services
+{
+ public class RLVService : BotServices
+ {
+ protected 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)
+ {
+ 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 = new List();
+ RLVAttachments = new List
+ {
+ 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 });
+ }
+ }
+
+ protected void removeEvents()
+ {
+ botConnected = false;
+ if (GetClient() != null)
+ {
+ // remove events
+ GetClient().Self.ChatFromSimulator -= BotChatEvents;
+ }
+ }
+
+
+ public override void Start()
+ {
+ 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
+ /// The event subscribers. null if no subcribers
+ private EventHandler m_RLVRuleChanged;
+
+ /// Raises the RLVRuleChanged event
+ /// An RLVRuleChangedEventArgs object containing the
+ /// data returned from the data server
+ protected virtual void OnRLVRuleChanged(RLVEventArgs e)
+ {
+ EventHandler handler = m_RLVRuleChanged;
+ try
+ {
+ handler?.Invoke(this, e);
+ }
+ catch (Exception) { }
+
+ }
+
+ /// Thread sync lock object
+ private readonly object m_RLVRuleChangedLock = new object();
+
+ /// Triggered when an RLVRuleChangedUpdate packet is received,
+ /// telling us what our avatar is currently wearing
+ /// request.
+ public event EventHandler 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 RLVWearables;
+ public List 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 Regex(@"(?[^:=]+)(:(?