From 12b31656d18d5e7f469490f95b96b6fa1eb97432 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Fri, 11 Apr 2025 22:19:18 -0400 Subject: [PATCH 01/13] flippin 4H SSS rework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only bad thing about this is the fact that the constructor for most settings is now ambiguous unless you specified every input. (not breaking tho 💰 💰 💰 💰 ) --- .../Core/UserSettings/ButtonSetting.cs | 16 ++++ .../Core/UserSettings/DropdownSetting.cs | 24 ++++++ .../Core/UserSettings/KeybindSetting.cs | 16 ++++ .../Features/Core/UserSettings/SettingBase.cs | 84 ++++++++++++++++++- .../Core/UserSettings/SettingGroup.cs | 69 +++++++++++++++ .../Core/UserSettings/TextInputSetting.cs | 22 +++++ .../Core/UserSettings/TwoButtonsSetting.cs | 17 ++++ .../Exiled.Events/Handlers/Internal/Round.cs | 4 + 8 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs index b150e01a44..40e4c8edda 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs @@ -28,12 +28,28 @@ public class ButtonSetting : SettingBase, IWrapper /// /// /// + [Obsolete("Use the constructor without the Header instead.")] public ButtonSetting(int id, string label, string buttonText, float holdTime = 0.0f, string hintDescription = null, HeaderSetting header = null, Action onChanged = null) : base(new SSButton(id, label, buttonText, holdTime, hintDescription), header, onChanged) { Base = (SSButton)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public ButtonSetting(int id, string label, string buttonText, float holdTime = 0.0f, string hintDescription = null, Action onChanged = null) + : base(new SSButton(id, label, buttonText, holdTime, hintDescription), onChanged) + { + Base = (SSButton)base.Base; + } + /// /// Initializes a new instance of the class. /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs index 88886839bd..89626b923f 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs @@ -30,6 +30,7 @@ public class DropdownSetting : SettingBase, IWrapper /// /// /// + [Obsolete("Use the constructor without the Header instead.")] public DropdownSetting( int id, string label, @@ -44,6 +45,29 @@ public DropdownSetting( Base = (SSDropdownSetting)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + public DropdownSetting( + int id, + string label, + IEnumerable options, + int defaultOptionIndex = 0, + SSDropdownSetting.DropdownEntryType dropdownEntryType = SSDropdownSetting.DropdownEntryType.Regular, + string hintDescription = null, + Action onChanged = null) + : base(new SSDropdownSetting(id, label, options.ToArray(), defaultOptionIndex, dropdownEntryType, hintDescription), onChanged) + { + Base = (SSDropdownSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs index cfb6b3bbca..9e2f6b2cf6 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs @@ -28,12 +28,28 @@ public class KeybindSetting : SettingBase, IWrapper /// /// /// + [Obsolete("Use the constructor without the Header instead.")] public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, hintDescription), header, onChanged) { Base = (SSKeybindSetting)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI = false, string hintDescription = "", Action onChanged = null) + : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, hintDescription), onChanged) + { + Base = (SSKeybindSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index b27ae1fc78..3f3184e6db 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -31,12 +31,18 @@ public class SettingBase : TypeCastObject, IWrapper internal static readonly List Settings = new(); + /// + /// A collection that contains all groups of settings that will be sent to clients. + /// + internal static readonly List Groups = new(); + /// /// Initializes a new instance of the class. /// /// A instance. /// /// + [Obsolete("Use the constructor without the Header instead.")] internal SettingBase(ServerSpecificSettingBase settingBase, HeaderSetting header, Action onChanged) { Base = settingBase; @@ -45,6 +51,18 @@ internal SettingBase(ServerSpecificSettingBase settingBase, HeaderSetting header OnChanged = onChanged; } + /// + /// Initializes a new instance of the class. + /// + /// A instance. + /// + internal SettingBase(ServerSpecificSettingBase settingBase, Action onChanged) + { + Base = settingBase; + + OnChanged = onChanged; + } + /// /// Initializes a new instance of the class. /// @@ -55,7 +73,9 @@ internal SettingBase(ServerSpecificSettingBase settingBase) if (OriginalDefinition != null) { +#pragma warning disable CS0618 // Type or member is obsolete Header = OriginalDefinition.Header; +#pragma warning restore CS0618 // Type or member is obsolete OnChanged = OriginalDefinition.OnChanged; Label = OriginalDefinition.Label; HintDescription = OriginalDefinition.HintDescription; @@ -76,6 +96,7 @@ public static IReadOnlyDictionary> Synce /// /// Gets or sets the predicate for syncing this setting when a player joins. /// + [Obsolete("Use SettingBase::Viewers instead of SettingBase.SyncOnJoin")] public static Predicate SyncOnJoin { get; set; } /// @@ -123,6 +144,7 @@ public string HintDescription /// Gets or sets the header of this setting. /// /// Can be null. + [Obsolete("Use SettingGroup instead")] public HeaderSetting Header { get; set; } /// @@ -197,7 +219,13 @@ public static T Create(ServerSpecificSettingBase settingBase) /// /// Syncs setting with all players. /// - public static void SendToAll() => ServerSpecificSettingsSync.SendToAll(); + public static void SendToAll() + { + foreach (Player player in Player.List) + { + SendToPlayer(player); + } + } /// /// Syncs setting with all players according to the specified predicate. @@ -216,7 +244,11 @@ public static void SendToAll(Func predicate) /// Syncs setting with the specified target. /// /// Target player. - public static void SendToPlayer(Player player) => ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub); + public static void SendToPlayer(Player player) + { + ServerSpecificSettingBase[] sorted = Groups.Where(group => group.Viewers == null || group.Viewers(player)).OrderByDescending(group => group.Priority).SelectMany(group => group.Settings).Select(setting => setting.Base).ToArray(); + ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, sorted); + } /// /// Syncs specific settings with the specified target. @@ -233,6 +265,7 @@ public static void SendToPlayer(Player player, IEnumerable settings /// A requirement to meet when sending settings to players. /// A of instances that were successfully registered. /// This method is used to sync new settings with players. + [Obsolete("Use RegisterGroups instead")] public static IEnumerable Register(IEnumerable settings, Func predicate = null) { IEnumerable> grouped = settings.Where(s => s != null).GroupBy(s => s.Header); @@ -250,6 +283,7 @@ public static IEnumerable Register(IEnumerable setting ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(result.Select(s => s.Base)).ToArray(); Settings.AddRange(result); + Groups.Add(new SettingGroup(result)); if (predicate == null) SendToAll(); @@ -259,13 +293,31 @@ public static IEnumerable Register(IEnumerable setting return result; } + /// + /// Registers all settings from the specified collection. + /// + /// A collection of groups of settings to register. + /// A of instances that were successfully registered. + /// This method is used to sync new settings with players. + public static IEnumerable RegisterGroups(IEnumerable settings) + { + settings = settings.Where(group => group != null).ToArray(); + SettingBase[] sorted = settings.OrderByDescending(group => group.Priority).SelectMany(group => group.Settings).ToArray(); + ServerSpecificSettingBase[] sent = sorted.Select(setting => setting.Base).ToArray(); + ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(sent).ToArray(); + Settings.AddRange(sorted); + Groups.AddRange(settings); + SendToAll(); + return sorted; + } + /// /// Removes settings from players. /// /// Determines which players will receive this update. /// Settings to remove. If null, all settings will be removed. /// A of instances that were successfully removed. - /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. + /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. public static IEnumerable Unregister(Func predicate = null, IEnumerable settings = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); @@ -283,13 +335,39 @@ public static IEnumerable Unregister(Func predicate = return list2; } + /// + /// Removes settings from players. + /// + /// Determines which players will receive this update. + /// Settings to remove. If null, all settings will be removed. + /// A of instances that were successfully removed. + /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. + public static IEnumerable Unregister(Func predicate = null, IEnumerable settings = null) + { + List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); + List list2 = new((settings?.SelectMany(group => group.Settings) ?? Settings).Where(setting => list.Remove(setting.Base))); + + ServerSpecificSettingsSync.DefinedSettings = list.ToArray(); + + if (predicate == null) + SendToAll(); + else + SendToAll(predicate); + + ListPool.Pool.Return(list); + + return list2; + } + /// /// Returns a string representation of this . /// /// A string in human-readable format. public override string ToString() { +#pragma warning disable CS0618 // Type or member is obsolete return $"{Id} ({Label}) [{HintDescription}] {{{ResponseMode}}} ^{Header}^"; +#pragma warning restore CS0618 // Type or member is obsolete } /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs new file mode 100644 index 0000000000..eb251567f1 --- /dev/null +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features.Core.UserSettings +{ + using System; + using System.Collections.Generic; + + /// + /// Represents a group of SettingBase. + /// + public class SettingGroup + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public SettingGroup(IEnumerable settings, int priority = 0, Predicate viewers = null) + { + Settings = settings; + Priority = priority; + Viewers = viewers; + } + + /// + /// Gets or sets the settings within this group. + /// + public IEnumerable Settings { get; set; } + + /// + /// Gets or sets the priority of this group when sent to players. + /// + public int Priority { get; set; } + + /// + /// Gets or sets the predicate which determines who can see this group of settings. + /// + public Predicate Viewers { get; set; } + + /// + /// Handles constructing a group containing a singular setting. Useful for standalone settings. + /// + /// A instance. + /// A SettingGroup containing the singular setting. + public static implicit operator SettingGroup(SettingBase setting) => new(new[] { setting }); + + // These two methods are required to avoid implicit IEnumerable conversions. They are used to make the registration of settings easier. The true use of these will likely only be utilized once the obsolete code is removed. + + /// + /// Handles implicitly converting a List of SettingBase into a SettingGroup. + /// + /// A of . + /// A SettingGroup containing all the SettingBases. + public static implicit operator SettingGroup(List settings) => new(settings); + + /// + /// Handles implicitly converting an Array of SettingBase into a SettingGroup. + /// + /// An of . + /// A SettingGroup containing all the SettingBases. + public static implicit operator SettingGroup(SettingBase[] settings) => new(settings); + } +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs index 69c7875ad1..b98033685c 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs @@ -28,6 +28,7 @@ public class TextInputSetting : SettingBase, IWrapper /// /// /// + [Obsolete("Use the constructor without the Header instead.")] public TextInputSetting( int id, string label, @@ -41,6 +42,27 @@ public TextInputSetting( Base = (SSTextArea)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public TextInputSetting( + int id, + string label, + SSTextArea.FoldoutMode foldoutMode = SSTextArea.FoldoutMode.NotCollapsable, + TextAlignmentOptions alignment = TextAlignmentOptions.TopLeft, + string hintDescription = null, + Action onChanged = null) + : base(new SSTextArea(id, label, foldoutMode, hintDescription, alignment), onChanged) + { + Base = (SSTextArea)base.Base; + } + /// /// Initializes a new instance of the class. /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs index 52c596ae76..adfa284e30 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs @@ -28,12 +28,29 @@ public class TwoButtonsSetting : SettingBase, IWrapper /// /// /// + [Obsolete("Use the constructor without the Header instead.")] public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) : base(new SSTwoButtonsSetting(id, label, firstOption, secondOption, defaultIsSecond, hintDescription), header, onChanged) { Base = (SSTwoButtonsSetting)base.Base; } + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond = false, string hintDescription = "", Action onChanged = null) + : base(new SSTwoButtonsSetting(id, label, firstOption, secondOption, defaultIsSecond, hintDescription), onChanged) + { + Base = (SSTwoButtonsSetting)base.Base; + } + /// /// Initializes a new instance of the class. /// diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 99212af062..5a77da2a49 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -97,8 +97,12 @@ public static void OnVerified(VerifiedEventArgs ev) { RoleAssigner.CheckLateJoin(ev.Player.ReferenceHub, ClientInstanceMode.ReadyClient); +#pragma warning disable CS0618 // Type or member is obsolete if (SettingBase.SyncOnJoin != null && SettingBase.SyncOnJoin(ev.Player)) SettingBase.SendToPlayer(ev.Player); + else if (SettingBase.SyncOnJoin == null) + SettingBase.SendToPlayer(ev.Player); +#pragma warning restore CS0618 // Type or member is obsolete // TODO: Remove if this has been fixed for https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/52 foreach (Room room in Room.List.Where(current => current.AreLightsOff)) From d809202d191d4db9d3f87f2ec29218313081594a Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Fri, 11 Apr 2025 22:26:34 -0400 Subject: [PATCH 02/13] Add ToString for SettingBase --- .../Exiled.API/Features/Core/UserSettings/SettingGroup.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index eb251567f1..6d7cb86669 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -9,6 +9,7 @@ namespace Exiled.API.Features.Core.UserSettings { using System; using System.Collections.Generic; + using System.Linq; /// /// Represents a group of SettingBase. @@ -65,5 +66,11 @@ public SettingGroup(IEnumerable settings, int priority = 0, Predica /// An of . /// A SettingGroup containing all the SettingBases. public static implicit operator SettingGroup(SettingBase[] settings) => new(settings); + + /// + /// Returns a string representation of this . + /// + /// A string in human-readable format. + public override string ToString() => $"{Priority} ({Viewers}) [{string.Join(", ", Settings.Select(s => s.ToString()))}]"; } } \ No newline at end of file From 4ad975b5f7c5a8fffb5f7941d49f0c406f357da7 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Sat, 12 Apr 2025 20:07:43 -0400 Subject: [PATCH 03/13] Extra features Removed default values from obsolete constructors, making the Ambiguous constructor reference error impossible. Added a SubGroups property to SettingGroup to handle recursive groups of settings. SubGroups defaults to null but I made sure to check it in them methods that call it. Notably SettingGroup::GetAllSettings(); that recursively gets all the SettingBase contained within it. I added checks for recursive stuff and an error for it. I also added SettingGroup::GetViewableSettingsOrdered(Player) that gets all the settings viewable by a specified player as well as ordered by priority etc... --- .../Core/UserSettings/ButtonSetting.cs | 2 +- .../Core/UserSettings/DropdownSetting.cs | 10 +-- .../Core/UserSettings/KeybindSetting.cs | 2 +- .../Features/Core/UserSettings/SettingBase.cs | 4 +- .../Core/UserSettings/SettingGroup.cs | 77 ++++++++++++++++++- .../Core/UserSettings/TextInputSetting.cs | 10 +-- .../Core/UserSettings/TwoButtonsSetting.cs | 2 +- 7 files changed, 91 insertions(+), 16 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs index 40e4c8edda..0af6302754 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/ButtonSetting.cs @@ -29,7 +29,7 @@ public class ButtonSetting : SettingBase, IWrapper /// /// [Obsolete("Use the constructor without the Header instead.")] - public ButtonSetting(int id, string label, string buttonText, float holdTime = 0.0f, string hintDescription = null, HeaderSetting header = null, Action onChanged = null) + public ButtonSetting(int id, string label, string buttonText, float holdTime, string hintDescription, HeaderSetting header, Action onChanged) : base(new SSButton(id, label, buttonText, holdTime, hintDescription), header, onChanged) { Base = (SSButton)base.Base; diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs index 89626b923f..557cee49a3 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/DropdownSetting.cs @@ -35,11 +35,11 @@ public DropdownSetting( int id, string label, IEnumerable options, - int defaultOptionIndex = 0, - SSDropdownSetting.DropdownEntryType dropdownEntryType = SSDropdownSetting.DropdownEntryType.Regular, - string hintDescription = null, - HeaderSetting header = null, - Action onChanged = null) + int defaultOptionIndex, + SSDropdownSetting.DropdownEntryType dropdownEntryType, + string hintDescription, + HeaderSetting header, + Action onChanged) : base(new SSDropdownSetting(id, label, options.ToArray(), defaultOptionIndex, dropdownEntryType, hintDescription), header, onChanged) { Base = (SSDropdownSetting)base.Base; diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs index 9e2f6b2cf6..6272b40a50 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/KeybindSetting.cs @@ -29,7 +29,7 @@ public class KeybindSetting : SettingBase, IWrapper /// /// [Obsolete("Use the constructor without the Header instead.")] - public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) + public KeybindSetting(int id, string label, KeyCode suggested, bool preventInteractionOnGUI, string hintDescription, HeaderSetting header, Action onChanged) : base(new SSKeybindSetting(id, label, suggested, preventInteractionOnGUI, hintDescription), header, onChanged) { Base = (SSKeybindSetting)base.Base; diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 3f3184e6db..8ce73c96b3 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -246,7 +246,7 @@ public static void SendToAll(Func predicate) /// Target player. public static void SendToPlayer(Player player) { - ServerSpecificSettingBase[] sorted = Groups.Where(group => group.Viewers == null || group.Viewers(player)).OrderByDescending(group => group.Priority).SelectMany(group => group.Settings).Select(setting => setting.Base).ToArray(); + ServerSpecificSettingBase[] sorted = Groups.OrderByDescending(group => group.Priority).SelectMany(group => group.GetViewableSettingsOrdered(player)).Select(setting => setting.Base).ToArray(); ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, sorted); } @@ -302,7 +302,7 @@ public static IEnumerable Register(IEnumerable setting public static IEnumerable RegisterGroups(IEnumerable settings) { settings = settings.Where(group => group != null).ToArray(); - SettingBase[] sorted = settings.OrderByDescending(group => group.Priority).SelectMany(group => group.Settings).ToArray(); + SettingBase[] sorted = settings.OrderByDescending(group => group.Priority).SelectMany(group => group.GetAllSettings()).ToArray(); ServerSpecificSettingBase[] sent = sorted.Select(setting => setting.Base).ToArray(); ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(sent).ToArray(); Settings.AddRange(sorted); diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index 6d7cb86669..f732e8b4bd 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -11,6 +11,8 @@ namespace Exiled.API.Features.Core.UserSettings using System.Collections.Generic; using System.Linq; + using Pools; + /// /// Represents a group of SettingBase. /// @@ -22,11 +24,13 @@ public class SettingGroup /// /// /// - public SettingGroup(IEnumerable settings, int priority = 0, Predicate viewers = null) + /// + public SettingGroup(IEnumerable settings, int priority = 0, Predicate viewers = null, IEnumerable subgroups = null) { Settings = settings; Priority = priority; Viewers = viewers; + SubGroups = subgroups; } /// @@ -44,6 +48,11 @@ public SettingGroup(IEnumerable settings, int priority = 0, Predica /// public Predicate Viewers { get; set; } + /// + /// Gets or sets the subgroups within this group. + /// + public IEnumerable SubGroups { get; set; } + /// /// Handles constructing a group containing a singular setting. Useful for standalone settings. /// @@ -67,6 +76,72 @@ public SettingGroup(IEnumerable settings, int priority = 0, Predica /// A SettingGroup containing all the SettingBases. public static implicit operator SettingGroup(SettingBase[] settings) => new(settings); + /// + /// Recursively gets all instances of contained within this 's Settings and SubGroups. + /// + /// An of . + public IEnumerable GetAllSettings() + { + try + { + List settings = new(); + settings.AddRange(Settings); + if (SubGroups == null) + return settings; + List recursiveCheck = ListPool.Pool.Get(); + recursiveCheck.Add(this); + foreach (SettingGroup group in SubGroups) + { + if (recursiveCheck.Contains(group)) + throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); + settings.AddRange(group.GetAllSettings()); + } + + ListPool.Pool.Return(recursiveCheck); + return settings; + } + catch (Exception ex) + { + Log.Error($"{nameof(GetAllSettings)}: {ex}"); + return Array.Empty(); + } + } + + /// + /// Recursively gets all instances of contained within this 's Settings and SubGroups that can be viewed by a specified player. + /// + /// + /// An of . + public IEnumerable GetViewableSettingsOrdered(Player viewer) + { + try + { + if (viewer == null) + return Array.Empty(); + List settings = new(); + if (Viewers == null || Viewers(viewer)) + settings.AddRange(Settings); + if (SubGroups == null) + return settings; + List recursiveCheck = ListPool.Pool.Get(); + recursiveCheck.Add(this); + foreach (SettingGroup group in SubGroups.Where(group => group.Viewers == null || group.Viewers(viewer)).OrderByDescending(group => group.Priority)) + { + if (recursiveCheck.Contains(group)) + throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); + settings.AddRange(group.GetAllSettings()); + } + + ListPool.Pool.Return(recursiveCheck); + return settings; + } + catch (Exception ex) + { + Log.Error($"{nameof(GetViewableSettingsOrdered)}: {ex}"); + return Array.Empty(); + } + } + /// /// Returns a string representation of this . /// diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs index b98033685c..31cf34ba2a 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/TextInputSetting.cs @@ -32,11 +32,11 @@ public class TextInputSetting : SettingBase, IWrapper public TextInputSetting( int id, string label, - SSTextArea.FoldoutMode foldoutMode = SSTextArea.FoldoutMode.NotCollapsable, - TextAlignmentOptions alignment = TextAlignmentOptions.TopLeft, - string hintDescription = null, - HeaderSetting header = null, - Action onChanged = null) + SSTextArea.FoldoutMode foldoutMode, + TextAlignmentOptions alignment, + string hintDescription, + HeaderSetting header, + Action onChanged) : base(new SSTextArea(id, label, foldoutMode, hintDescription, alignment), header, onChanged) { Base = (SSTextArea)base.Base; diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs b/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs index adfa284e30..42ea36e74b 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/TwoButtonsSetting.cs @@ -29,7 +29,7 @@ public class TwoButtonsSetting : SettingBase, IWrapper /// /// [Obsolete("Use the constructor without the Header instead.")] - public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond = false, string hintDescription = "", HeaderSetting header = null, Action onChanged = null) + public TwoButtonsSetting(int id, string label, string firstOption, string secondOption, bool defaultIsSecond, string hintDescription, HeaderSetting header, Action onChanged) : base(new SSTwoButtonsSetting(id, label, firstOption, secondOption, defaultIsSecond, hintDescription), header, onChanged) { Base = (SSTwoButtonsSetting)base.Base; From 89778f4de2214eaa417569dd9bdefcff99274895 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Sat, 12 Apr 2025 20:15:25 -0400 Subject: [PATCH 04/13] bruh moment some last minute tweaks including fixing the List<> renting I was doing --- EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs | 2 +- EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 8ce73c96b3..c52eb0f94b 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -342,7 +342,7 @@ public static IEnumerable Unregister(Func predicate = /// Settings to remove. If null, all settings will be removed. /// A of instances that were successfully removed. /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. - public static IEnumerable Unregister(Func predicate = null, IEnumerable settings = null) + public static IEnumerable UnregisterGroups(Func predicate = null, IEnumerable settings = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); List list2 = new((settings?.SelectMany(group => group.Settings) ?? Settings).Where(setting => list.Remove(setting.Base))); diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index f732e8b4bd..f83e9538bc 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -88,7 +88,7 @@ public IEnumerable GetAllSettings() settings.AddRange(Settings); if (SubGroups == null) return settings; - List recursiveCheck = ListPool.Pool.Get(); + List recursiveCheck = ListPool.Pool.Get(1); recursiveCheck.Add(this); foreach (SettingGroup group in SubGroups) { @@ -123,7 +123,7 @@ public IEnumerable GetViewableSettingsOrdered(Player viewer) settings.AddRange(Settings); if (SubGroups == null) return settings; - List recursiveCheck = ListPool.Pool.Get(); + List recursiveCheck = ListPool.Pool.Get(1); recursiveCheck.Add(this); foreach (SettingGroup group in SubGroups.Where(group => group.Viewers == null || group.Viewers(viewer)).OrderByDescending(group => group.Priority)) { From c2bab23adaa6a006b1c12e0dca858bca2e7c2c0b Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Sun, 13 Apr 2025 10:15:15 -0400 Subject: [PATCH 05/13] Midnight Realization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Legit realized my recursive check didn't work last night right before I went to bed 😭 This ought to fix it though --- .../Core/UserSettings/SettingGroup.cs | 98 ++++++++++--------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index f83e9538bc..7b53c3068f 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -82,29 +82,11 @@ public SettingGroup(IEnumerable settings, int priority = 0, Predica /// An of . public IEnumerable GetAllSettings() { - try - { - List settings = new(); - settings.AddRange(Settings); - if (SubGroups == null) - return settings; - List recursiveCheck = ListPool.Pool.Get(1); - recursiveCheck.Add(this); - foreach (SettingGroup group in SubGroups) - { - if (recursiveCheck.Contains(group)) - throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); - settings.AddRange(group.GetAllSettings()); - } - - ListPool.Pool.Return(recursiveCheck); - return settings; - } - catch (Exception ex) - { - Log.Error($"{nameof(GetAllSettings)}: {ex}"); - return Array.Empty(); - } + List settings = new(); + List previousGroups = ListPool.Pool.Get(1); + InternalGetAllSettings(settings, previousGroups); + ListPool.Pool.Return(previousGroups); + return settings; } /// @@ -114,32 +96,13 @@ public IEnumerable GetAllSettings() /// An of . public IEnumerable GetViewableSettingsOrdered(Player viewer) { - try - { - if (viewer == null) - return Array.Empty(); - List settings = new(); - if (Viewers == null || Viewers(viewer)) - settings.AddRange(Settings); - if (SubGroups == null) - return settings; - List recursiveCheck = ListPool.Pool.Get(1); - recursiveCheck.Add(this); - foreach (SettingGroup group in SubGroups.Where(group => group.Viewers == null || group.Viewers(viewer)).OrderByDescending(group => group.Priority)) - { - if (recursiveCheck.Contains(group)) - throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); - settings.AddRange(group.GetAllSettings()); - } - - ListPool.Pool.Return(recursiveCheck); - return settings; - } - catch (Exception ex) - { - Log.Error($"{nameof(GetViewableSettingsOrdered)}: {ex}"); + if (viewer == null) return Array.Empty(); - } + List settings = new(); + List previousGroups = ListPool.Pool.Get(1); + InternalGetViewableSettingsOrdered(settings, previousGroups, viewer); + ListPool.Pool.Return(previousGroups); + return settings; } /// @@ -147,5 +110,44 @@ public IEnumerable GetViewableSettingsOrdered(Player viewer) /// /// A string in human-readable format. public override string ToString() => $"{Priority} ({Viewers}) [{string.Join(", ", Settings.Select(s => s.ToString()))}]"; + + /// + /// An internal recursive method to get all settings within a SettingGroup. + /// + /// A of to add new settings to. + /// A of used to verify no recursive loops. + internal void InternalGetAllSettings(List current, List previousGroups) + { + current.AddRange(Settings); + if (SubGroups == null) + return; + previousGroups.Add(this); + foreach (SettingGroup group in SubGroups) + { + if (previousGroups.Contains(group)) + throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); + group.InternalGetAllSettings(current, previousGroups); + } + } + + /// + /// An internal recursive method to get all settings viewable by a specified player, ordered, within a SettingGroup. + /// + /// A of to add new settings to. + /// A of used to verify no recursive loops. + /// A that filters the settings. + internal void InternalGetViewableSettingsOrdered(List current, List previousGroups, Player viewer) + { + current.AddRange(Settings); + if (SubGroups == null) + return; + previousGroups.Add(this); + foreach (SettingGroup group in SubGroups.Where(group => group.Viewers == null || group.Viewers(viewer)).OrderByDescending(group => group.Priority)) + { + if (previousGroups.Contains(group)) + throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); + group.InternalGetViewableSettingsOrdered(current, previousGroups, viewer); + } + } } } \ No newline at end of file From e4c871b62076811d483a9386ddf58bcd56838164 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Fri, 18 Apr 2025 15:03:53 -0400 Subject: [PATCH 06/13] Fix Unregister, make SendToPlayer(); check if settings are in DefinedSettings, Add an IReadOnlyCollection of Groups if a plugin-maker manually removes a ServerSpecificSettingsBase from ServerSpecificSettingsSync.DefinedSettings, it's on them to handle who gets what settings, otherwise making this check simply reduces points of failure --- .../Features/Core/UserSettings/SettingBase.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index c52eb0f94b..6ab0dab04c 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -93,6 +93,11 @@ public static IReadOnlyDictionary> Synce /// public static IReadOnlyCollection List => Settings; + /// + /// Gets the list of setting groups that are registered. + /// + public static IReadOnlyCollection AllGroups => Groups; + /// /// Gets or sets the predicate for syncing this setting when a player joins. /// @@ -246,7 +251,7 @@ public static void SendToAll(Func predicate) /// Target player. public static void SendToPlayer(Player player) { - ServerSpecificSettingBase[] sorted = Groups.OrderByDescending(group => group.Priority).SelectMany(group => group.GetViewableSettingsOrdered(player)).Select(setting => setting.Base).ToArray(); + ServerSpecificSettingBase[] sorted = Groups.OrderByDescending(group => group.Priority).SelectMany(group => group.GetViewableSettingsOrdered(player)).Select(setting => setting.Base).Where(ServerSpecificSettingsSync.DefinedSettings.Contains).ToArray(); ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, sorted); } @@ -339,13 +344,19 @@ public static IEnumerable Unregister(Func predicate = /// Removes settings from players. /// /// Determines which players will receive this update. - /// Settings to remove. If null, all settings will be removed. + /// Groups to remove. If null, all settings will be removed. /// A of instances that were successfully removed. /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. - public static IEnumerable UnregisterGroups(Func predicate = null, IEnumerable settings = null) + public static IEnumerable UnregisterGroups(Func predicate = null, IEnumerable groups = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); - List list2 = new((settings?.SelectMany(group => group.Settings) ?? Settings).Where(setting => list.Remove(setting.Base))); + SettingGroup[] settingGroups = groups?.ToArray(); + List list2 = new((settingGroups?.SelectMany(group => group.Settings) ?? Settings).Where(setting => list.Remove(setting.Base))); + + if (groups == null) + Groups.Clear(); + else + Groups.RemoveAll(settingGroups.Contains); ServerSpecificSettingsSync.DefinedSettings = list.ToArray(); From 8de7a8e6a0174e1d8ce2edb13ea3166c413cda9e Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Sat, 19 Apr 2025 21:00:34 -0400 Subject: [PATCH 07/13] Added Predicate to SettingBase.RegisterGroups --- .../Features/Core/UserSettings/SettingBase.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 6ab0dab04c..5d0a9bdb2f 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -302,9 +302,10 @@ public static IEnumerable Register(IEnumerable setting /// Registers all settings from the specified collection. /// /// A collection of groups of settings to register. + /// A requirement to meet when sending settings to players after registering all groups. /// A of instances that were successfully registered. /// This method is used to sync new settings with players. - public static IEnumerable RegisterGroups(IEnumerable settings) + public static IEnumerable RegisterGroups(IEnumerable settings, Predicate predicate = null) { settings = settings.Where(group => group != null).ToArray(); SettingBase[] sorted = settings.OrderByDescending(group => group.Priority).SelectMany(group => group.GetAllSettings()).ToArray(); @@ -312,7 +313,21 @@ public static IEnumerable RegisterGroups(IEnumerable ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(sent).ToArray(); Settings.AddRange(sorted); Groups.AddRange(settings); - SendToAll(); + if (predicate == null) + { + SendToAll(); + } + else + { + foreach (Player player in Player.List) + { + if (predicate(player)) + { + SendToPlayer(player); + } + } + } + return sorted; } From fb47fe29ee9d895728d4364591c43511ca833a7a Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Sat, 19 Apr 2025 21:14:58 -0400 Subject: [PATCH 08/13] Fix GetAllSettings Forgor to check predicate :( --- EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index 7b53c3068f..9d7cd708a0 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -138,7 +138,8 @@ internal void InternalGetAllSettings(List current, ListA that filters the settings. internal void InternalGetViewableSettingsOrdered(List current, List previousGroups, Player viewer) { - current.AddRange(Settings); + if (Viewers == null || Viewers(viewer)) + current.AddRange(Settings); if (SubGroups == null) return; previousGroups.Add(this); From bad9f1277c71925eb2b6573c5b6e920177d0e584 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Mon, 21 Apr 2025 19:15:41 -0400 Subject: [PATCH 09/13] Actual fix + Obsoletes --- EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 6bcf68d2da..147785b6f1 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -331,12 +331,14 @@ public static IEnumerable RegisterGroups(IEnumerable return sorted; } + /// /// Registers all settings from the specified collection to player. /// /// A collection of settings to register. /// A player that will receive settings. /// A of instances that were successfully registered. /// This method is used to sync new settings with players. + [Obsolete("Use RegisterGroups instead")] public static IEnumerable Register(IEnumerable settings, Player player) { IEnumerable> grouped = settings.Where(s => s != null).GroupBy(s => s.Header); @@ -414,10 +416,14 @@ public static IEnumerable UnregisterGroups(Func predi return list2; } + /// + /// Removes settings from players. + /// /// Determines which player will receive this update. /// Settings to remove. If null, all settings will be removed. /// A of instances that were successfully removed. /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. + [Obsolete("Use UnregisterGroups instead")] public static IEnumerable Unregister(Player player, IEnumerable settings = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); From 555cc11cc97036e246363f48f819a0b297846043 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Mon, 21 Apr 2025 19:55:18 -0400 Subject: [PATCH 10/13] Fix new method and also a typo --- EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 147785b6f1..7a48cfb19d 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -101,7 +101,7 @@ public static IReadOnlyDictionary> Synce /// /// Gets or sets the predicate for syncing this setting when a player joins. /// - [Obsolete("Use SettingBase::Viewers instead of SettingBase.SyncOnJoin")] + [Obsolete("Use SettingGroup::Viewers instead of SettingBase.SyncOnJoin")] public static Predicate SyncOnJoin { get; set; } /// @@ -356,6 +356,7 @@ public static IEnumerable Register(IEnumerable setting ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(result.Select(s => s.Base)).ToArray(); Settings.AddRange(result); + Groups.Add(new SettingGroup(result)); SendToPlayer(player); From 063f26f6da0bd2e29246e18ce2052404de9fdfc6 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Mon, 21 Apr 2025 20:24:50 -0400 Subject: [PATCH 11/13] Rework obsolete register method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yknow, I really though that checking DefinedSettings for the setting bases before sending them was a good idea, but SOMEHOW SettingBase::Base doesn't return the value stored in ServerSpecificSettingSync.DefinedSettings!?!?!? 😭 Anyways, removed that, added a specified group for the obsolete methods to register into, then made all SettingBase non-group register/unregister method reference that group. Only way this breaks is if some goofy goobers plugin unregisters all settings, clients would see stuff created via group method, but that's why we obsolete stuff, it doesn't affect pre-change behavior so as long as devs have a few brain cells (crazy request ik) we should be good --- .../Features/Core/UserSettings/SettingBase.cs | 13 ++++++++----- .../Features/Core/UserSettings/SettingGroup.cs | 5 +++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index 7a48cfb19d..4b61a3e107 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -34,7 +34,7 @@ public class SettingBase : TypeCastObject, IWrapper /// A collection that contains all groups of settings that will be sent to clients. /// - internal static readonly List Groups = new(); + internal static readonly List Groups = new() { SettingGroup.ObsoleteGroup }; /// /// Initializes a new instance of the class. @@ -251,7 +251,7 @@ public static void SendToAll(Func predicate) /// Target player. public static void SendToPlayer(Player player) { - ServerSpecificSettingBase[] sorted = Groups.OrderByDescending(group => group.Priority).SelectMany(group => group.GetViewableSettingsOrdered(player)).Select(setting => setting.Base).Where(ServerSpecificSettingsSync.DefinedSettings.Contains).ToArray(); + ServerSpecificSettingBase[] sorted = Groups.OrderByDescending(group => group.Priority).SelectMany(group => group.GetViewableSettingsOrdered(player)).Select(setting => setting.Base).ToArray(); ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, sorted); } @@ -288,7 +288,7 @@ public static IEnumerable Register(IEnumerable setting ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(result.Select(s => s.Base)).ToArray(); Settings.AddRange(result); - Groups.Add(new SettingGroup(result)); + SettingGroup.ObsoleteGroup.Settings = SettingGroup.ObsoleteGroup.Settings.Concat(result); if (predicate == null) SendToAll(); @@ -356,7 +356,7 @@ public static IEnumerable Register(IEnumerable setting ServerSpecificSettingsSync.DefinedSettings = (ServerSpecificSettingsSync.DefinedSettings ?? Array.Empty()).Concat(result.Select(s => s.Base)).ToArray(); Settings.AddRange(result); - Groups.Add(new SettingGroup(result)); + SettingGroup.ObsoleteGroup.Settings = SettingGroup.ObsoleteGroup.Settings.Concat(result); SendToPlayer(player); @@ -370,10 +370,12 @@ public static IEnumerable Register(IEnumerable setting /// Settings to remove. If null, all settings will be removed. /// A of instances that were successfully removed. /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. + [Obsolete("Use SettingGroups instead")] public static IEnumerable Unregister(Func predicate = null, IEnumerable settings = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); List list2 = new((settings ?? Settings).Where(setting => list.Remove(setting.Base))); + SettingGroup.ObsoleteGroup.Settings = SettingGroup.ObsoleteGroup.Settings.Except(settings ?? Settings); ServerSpecificSettingsSync.DefinedSettings = list.ToArray(); @@ -424,11 +426,12 @@ public static IEnumerable UnregisterGroups(Func predi /// Settings to remove. If null, all settings will be removed. /// A of instances that were successfully removed. /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. - [Obsolete("Use UnregisterGroups instead")] + [Obsolete("Use SettingGroups instead")] public static IEnumerable Unregister(Player player, IEnumerable settings = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); List list2 = new((settings ?? Settings).Where(setting => list.Remove(setting.Base))); + SettingGroup.ObsoleteGroup.Settings = SettingGroup.ObsoleteGroup.Settings.Except(settings ?? Settings); ServerSpecificSettingsSync.DefinedSettings = list.ToArray(); diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index 9d7cd708a0..38689a6586 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -53,6 +53,11 @@ public SettingGroup(IEnumerable settings, int priority = 0, Predica /// public IEnumerable SubGroups { get; set; } + /// + /// Gets a SettingGroup that all values from obsolete SettingBase methods register to. Helps track unregistering settings. + /// + internal static SettingGroup ObsoleteGroup { get; } = new(new SettingBase[] { }); + /// /// Handles constructing a group containing a singular setting. Useful for standalone settings. /// From d32b25fff8e7e54bc9509734c39f70ba84e6277c Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Fri, 25 Apr 2025 16:40:40 -0400 Subject: [PATCH 12/13] Fix docs from change in dev --- EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs index b03f376023..ff3e54395a 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingBase.cs @@ -425,8 +425,8 @@ public static IEnumerable UnregisterGroups(Func predi /// Determines which player will receive this update. /// Settings to remove. If null, all settings will be removed. /// A of instances that were successfully removed. + /// /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. [Obsolete("Use SettingGroups instead")] - /// This method is used to unsync settings from players. Using it with provides an opportunity to update synced settings. public static IEnumerable Unregister(Player player, IEnumerable settings = null) { List list = ListPool.Pool.Get(ServerSpecificSettingsSync.DefinedSettings); From ca088d82d5dc235c87facf4c3a53cbb485270171 Mon Sep 17 00:00:00 2001 From: Trevlouw Date: Tue, 29 Apr 2025 14:35:03 -0400 Subject: [PATCH 13/13] Change in comment as far as I can tell this shouldn't mess with the recursive check but make it more accurate. But now you can have multiple of the same group in SubGroups if you'd like (without them being recursive ofc) --- EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs index 38689a6586..8c147e7e3f 100644 --- a/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs +++ b/EXILED/Exiled.API/Features/Core/UserSettings/SettingGroup.cs @@ -133,6 +133,8 @@ internal void InternalGetAllSettings(List current, List @@ -154,6 +156,8 @@ internal void InternalGetViewableSettingsOrdered(List current, List throw new InvalidOperationException("SettingGroups cannot reference themselves within subgroups."); group.InternalGetViewableSettingsOrdered(current, previousGroups, viewer); } + + previousGroups.Remove(this); } } } \ No newline at end of file