From 0efa6b2327d0050b78dee7a4f65ffe8d3a360bac Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 16 Sep 2026 13:50:30 +0200
Subject: [PATCH 1/8] DecalHelpers.CleanUpDecals
---
SecretAPI/Features/DecalHelpers.cs | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/SecretAPI/Features/DecalHelpers.cs b/SecretAPI/Features/DecalHelpers.cs
index ce5f76e..acd9fac 100644
--- a/SecretAPI/Features/DecalHelpers.cs
+++ b/SecretAPI/Features/DecalHelpers.cs
@@ -1,10 +1,12 @@
namespace SecretAPI.Features;
using System;
+using CommandSystem.Commands.RemoteAdmin.Cleanup;
using Decals;
using InventorySystem.Items;
using InventorySystem.Items.Autosync;
using InventorySystem.Items.Firearms.Modules;
+using LabApi.Features.Wrappers;
using Mirror;
using RelativePositioning;
using UnityEngine;
@@ -86,6 +88,21 @@ public static void SpawnDecalFromDirection(Vector3 position, Vector3 direction,
public static void SpawnDecalFromDirection(Vector3 position, Quaternion direction, DecalPoolType type = DecalPoolType.Blood)
=> GetDecalMessage(position, position - (direction * Vector3.forward), type).SendToAuthenticated();
+ ///
+ /// Cleans up decals for all players.
+ ///
+ /// The to cleanup.
+ /// The amount of that decal to cleanup.
+ public static void CleanupDecals(DecalPoolType decalType, int amount = int.MaxValue) => new DecalCleanupMessage(decalType, amount).SendToAuthenticated();
+
+ ///
+ /// Cleans up decals for a specific player.
+ ///
+ /// The player to clean up decals for.
+ /// The to cleanup.
+ /// The amount of that decal to cleanup.
+ public static void CleanUpDecals(this Player player, DecalPoolType decalType, int amount = int.MaxValue) => new DecalCleanupMessage(decalType, amount).SendToHubsConditionally(hub => hub == player.ReferenceHub);
+
private static AutoSyncData GetAutoSyncData()
{
if (autoSyncData != null)
From f20ef458ab77c90072059b0922a9121a105e95f0 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 16 Sep 2026 18:00:47 +0200
Subject: [PATCH 2/8] CopyDll
---
Directory.Build.targets | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 38f22b4..d961ed5 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -19,4 +19,9 @@
+
+
+
+
+
\ No newline at end of file
From 202167387b8271d338b6ad284ba57d04e5788fbd Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 21 Sep 2026 19:11:37 +0200
Subject: [PATCH 3/8] IRegister: Support IPriority
---
SecretAPI/Features/IPriority.cs | 2 +-
SecretAPI/Features/IRegister.cs | 13 ++++++++++---
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/SecretAPI/Features/IPriority.cs b/SecretAPI/Features/IPriority.cs
index 6f0666b..24214c6 100644
--- a/SecretAPI/Features/IPriority.cs
+++ b/SecretAPI/Features/IPriority.cs
@@ -1,7 +1,7 @@
namespace SecretAPI.Features;
///
-/// Handles IPriority.
+/// Handles priority on certain things.
///
public interface IPriority
{
diff --git a/SecretAPI/Features/IRegister.cs b/SecretAPI/Features/IRegister.cs
index 91bb494..a5a5bab 100644
--- a/SecretAPI/Features/IRegister.cs
+++ b/SecretAPI/Features/IRegister.cs
@@ -2,11 +2,13 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
///
/// Interface used to define a type that should auto register.
///
+/// This supports , will default to 0 if not implemented.
// TODO: Source generate this
public interface IRegister
{
@@ -25,7 +27,7 @@ public interface IRegister
///
public void TryUnregister()
{
- // default empty to prevent breaking change
+ // default to empty
}
///
@@ -38,6 +40,7 @@ public static void RegisterAll(Assembly? assembly = null)
registerables.TryAdd(assembly, new());
+ List registers = new();
foreach (Type type in assembly.GetTypes())
{
if (type.IsAbstract || type.IsInterface)
@@ -46,10 +49,14 @@ public static void RegisterAll(Assembly? assembly = null)
if (!typeof(IRegister).IsAssignableFrom(type))
continue;
- object obj = Activator.CreateInstance(type);
- if (obj is not IRegister register)
+ if (Activator.CreateInstance(type) is not IRegister register)
continue;
+ registers.Add(register);
+ }
+
+ foreach (IRegister register in registers.OrderBy(x => x is IPriority priority ? priority.Priority : 0))
+ {
registerables[assembly].Add(register);
register.TryRegister();
}
From 645e75e0f253a9b9f210def2bc5c01953a891099 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 21 Sep 2026 19:11:45 +0200
Subject: [PATCH 4/8] Obsolete Registry
---
SecretAPI/Features/Registry.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/SecretAPI/Features/Registry.cs b/SecretAPI/Features/Registry.cs
index 2aae151..8103a70 100644
--- a/SecretAPI/Features/Registry.cs
+++ b/SecretAPI/Features/Registry.cs
@@ -1,11 +1,13 @@
namespace SecretAPI.Features;
+using System;
using System.Collections.Generic;
///
/// Handles the registration of objects and ids.
///
/// The type of the registry.
+[Obsolete("This will be removed in 4.0")]
public static class Registry
{
///
From 99f7451383167e4b410b82a0a92a0c412f80de6c Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 21 Sep 2026 19:46:25 +0200
Subject: [PATCH 5/8] CustomButtonSetting::TimesPressed
---
.../Features/UserSettings/CustomButtonSetting.cs | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
index df65faa..b79dc4e 100644
--- a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
@@ -56,6 +56,11 @@ protected CustomButtonSetting(int? id, string label, string buttonText, float? h
///
public bool EverPressed => LastPressWatch.IsRunning;
+ ///
+ /// Gets the amount of times the button has been pressed.
+ ///
+ public uint TimesPressed { get; private set; }
+
///
/// Gets or sets the text of the button.
///
@@ -82,6 +87,13 @@ public float RequiredHoldTime
}
}
+ ///
+ protected override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ TimesPressed++;
+ }
+
///
/// Sends an update to that or has updated.
///
From afc7e84bf308e9b9cf7e3e8e9f21bd5ccb05d886 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:35:11 +0200
Subject: [PATCH 6/8] Extra condition to CustomPlayerEffect.Register(Type)
---
SecretAPI/Features/Effects/CustomPlayerEffect.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/SecretAPI/Features/Effects/CustomPlayerEffect.cs b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
index 13699b4..394d3a3 100644
--- a/SecretAPI/Features/Effects/CustomPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
@@ -55,6 +55,12 @@ public static void Register(Type type)
}
#pragma warning disable CS0618 // Type or member is obsolete
+ if (EffectsToRegister.Contains(type))
+ {
+ Logger.Error("[CustomPlayerEffect.Register] {type} is already registered!");
+ return;
+ }
+
EffectsToRegister.Add(type);
}
From 323d7ca2a99fb00cef71eeedaa2ce1ff026dccaa Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Tue, 13 Oct 2026 23:36:33 +0200
Subject: [PATCH 7/8] Version Bump: 3.5.0-beta1
---
Directory.Build.props | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index 24401c1..52706c7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
enable
- 3.4.0
+ 3.5.0-beta1
From 9f25e813660a40ff9643b6a9c554ab15c77e96cf Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Tue, 20 Oct 2026 14:53:50 +0200
Subject: [PATCH 8/8] Fix: Validation logic is incorrect + Invalid settings
being registered in DefinedSettings
---
SecretAPI.Examples/ExampleEntry.cs | 2 ++
SecretAPI/Features/UserSettings/CustomSetting.cs | 9 ++++++---
.../Features/Settings/ReportDefinedSettingPatch.cs | 8 ++++----
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/SecretAPI.Examples/ExampleEntry.cs b/SecretAPI.Examples/ExampleEntry.cs
index 1839237..8c0afc1 100644
--- a/SecretAPI.Examples/ExampleEntry.cs
+++ b/SecretAPI.Examples/ExampleEntry.cs
@@ -2,10 +2,12 @@
using System;
using HarmonyLib;
+using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
using SecretAPI.Examples.Settings;
using SecretAPI.Extensions;
using SecretAPI.Features.UserSettings;
+using UserSettings.ServerSpecific;
///
/// Defines the entry for the plugin.
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index 5715e44..2e51bc7 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -164,7 +164,7 @@ public static void Register(IEnumerable settings)
{
foreach (CustomSetting setting in settings)
{
- if (ValidateSettingInternal(setting, null))
+ if (ValidateSettingInternal(setting, null, null))
CustomSettings.Add(setting);
}
}
@@ -346,9 +346,12 @@ public static void SendSettingsToPlayer(Player player, int? version = null)
///
/// The to validate.
/// The to validate.
+ /// The current .
/// Whether the setting is validated and considered fine.
- internal static bool ValidateSettingInternal(CustomSetting? setting, ServerSpecificSettingBase? settingBase)
+ internal static bool ValidateSettingInternal(CustomSetting? setting, ServerSpecificSettingBase? settingBase, ServerSpecificSettingBase[]? definedSettings)
{
+ definedSettings ??= ServerSpecificSettingsSync.DefinedSettings;
+
if (settingBase == null && setting == null)
{
Logger.Error("[CustomSetting.TryValidateSetting] Failed to validate null setting! " + new StackTrace(1));
@@ -362,7 +365,7 @@ internal static bool ValidateSettingInternal(CustomSetting? setting, ServerSpeci
string validateSettingName = setting?.GetType().FullName ?? settingBase?.Label ?? "[Unknown]";
int? id = setting?.Id ?? settingBase?.SettingId;
- string? sharedIdInfo = CustomSettings.FirstOrDefault(s => s.Id == id)?.GetType().FullName ?? ServerSpecificSettingsSync.DefinedSettings.FirstOrDefault(s => s.SettingId == id && s is not SSGroupHeader)?.Label;
+ string? sharedIdInfo = CustomSettings.FirstOrDefault(s => s.Id == id && setting != s)?.GetType().FullName ?? definedSettings.FirstOrDefault(s => s.SettingId == id && s != settingBase && s is not SSGroupHeader)?.Label;
if (sharedIdInfo != null)
{
Logger.Error($"[CustomSetting.TryValidateSetting] {validateSettingName} is being registered with a duplicate ID ({id}) shared by {sharedIdInfo} {new StackTrace(1)}");
diff --git a/SecretAPI/Patches/Features/Settings/ReportDefinedSettingPatch.cs b/SecretAPI/Patches/Features/Settings/ReportDefinedSettingPatch.cs
index 30ec0c5..acfd1b5 100644
--- a/SecretAPI/Patches/Features/Settings/ReportDefinedSettingPatch.cs
+++ b/SecretAPI/Patches/Features/Settings/ReportDefinedSettingPatch.cs
@@ -8,22 +8,22 @@
using UserSettings.ServerSpecific;
///
-/// Logs issues with .
+/// Logs issues with and prevents them being registered.
///
[HarmonyPatchCategory(nameof(CustomSetting))]
[HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.DefinedSettings), MethodType.Setter)]
internal static class ReportDefinedSettingPatch
{
- private static void Postfix(ref ServerSpecificSettingBase[] value)
+ private static void Prefix(ref ServerSpecificSettingBase[] value)
{
- // OLD : value.ForEach(setting => CustomSetting.ValidateSettingInternal(null, setting));
List settings = ListPool.Shared.Rent();
foreach (ServerSpecificSettingBase setting in value)
{
- if (CustomSetting.ValidateSettingInternal(null, setting))
+ if (CustomSetting.ValidateSettingInternal(null, setting, value))
settings.Add(setting);
}
value = settings.ToArray();
+ ListPool.Shared.Return(settings);
}
}
\ No newline at end of file