From aae9613fcadace6171daf7fdd257f4bcf3fefdd5 Mon Sep 17 00:00:00 2001
From: Misfiy <85962933+obvEve@users.noreply.github.com>
Date: Tue, 6 Jan 2026 14:53:37 +0100
Subject: [PATCH 01/37] 3.0
---
SecretAPI.Examples/ExampleEntry.cs | 1 -
SecretAPI/Extensions/CollectionExtensions.cs | 51 ++++----
SecretAPI/Extensions/HarmonyExtensions.cs | 85 ++++++-------
SecretAPI/Extensions/MirrorExtensions.cs | 20 ----
SecretAPI/Extensions/PlayerExtensions.cs | 120 +++++++++----------
SecretAPI/Extensions/RoleExtensions.cs | 57 ---------
SecretAPI/SecretAPI.csproj | 3 +
7 files changed, 130 insertions(+), 207 deletions(-)
delete mode 100644 SecretAPI/Extensions/RoleExtensions.cs
diff --git a/SecretAPI.Examples/ExampleEntry.cs b/SecretAPI.Examples/ExampleEntry.cs
index 7381b08..32ee205 100644
--- a/SecretAPI.Examples/ExampleEntry.cs
+++ b/SecretAPI.Examples/ExampleEntry.cs
@@ -5,7 +5,6 @@
using HarmonyLib;
using LabApi.Loader.Features.Plugins;
using SecretAPI.Examples.Settings;
- using SecretAPI.Extensions;
using SecretAPI.Features.UserSettings;
///
diff --git a/SecretAPI/Extensions/CollectionExtensions.cs b/SecretAPI/Extensions/CollectionExtensions.cs
index 28e1887..83af57e 100644
--- a/SecretAPI/Extensions/CollectionExtensions.cs
+++ b/SecretAPI/Extensions/CollectionExtensions.cs
@@ -11,37 +11,38 @@
///
public static class CollectionExtensions
{
- ///
- /// Gets a random value from the collection.
- ///
/// The collection to pull from.
/// The Type contained by the collection.
- /// A random value, default value when empty collection.
- /// Will occur if the collection is empty.
- public static T GetRandomValue(this IEnumerable collection)
+ extension(IEnumerable collection)
{
- TryGetRandomValue(collection, out T? value);
- return value!;
- }
-
- ///
- /// Tries to get a random value from .
- ///
- /// The to try and get a random value from.
- /// The value that was found. Default if none could be found.
- /// The type contained within the .
- /// Whether a non-null value was found.
- public static bool TryGetRandomValue(this IEnumerable collection, [NotNullWhen(true)] out T? value)
- {
- IList list = collection as IList ?? collection.ToList();
- if (list.Count == 0)
+ ///
+ /// Gets a random value from the collection.
+ ///
+ /// A random value, default value when empty collection.
+ /// Will occur if the collection is empty.
+ public T GetRandomValue()
{
- value = default;
- return false;
+ TryGetRandomValue(collection, out T? value);
+ return value!;
}
- value = list[Random.Range(0, list.Count)];
- return value != null;
+ ///
+ /// Tries to get a random value from .
+ ///
+ /// The value that was found. Default if none could be found.
+ /// Whether a non-null value was found.
+ public bool TryGetRandomValue([NotNullWhen(true)] out T? value)
+ {
+ IList list = collection as IList ?? collection.ToList();
+ if (list.Count == 0)
+ {
+ value = default;
+ return false;
+ }
+
+ value = list[Random.Range(0, list.Count)];
+ return value != null;
+ }
}
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/HarmonyExtensions.cs b/SecretAPI/Extensions/HarmonyExtensions.cs
index 2f623b1..29d7451 100644
--- a/SecretAPI/Extensions/HarmonyExtensions.cs
+++ b/SecretAPI/Extensions/HarmonyExtensions.cs
@@ -13,55 +13,56 @@
///
public static class HarmonyExtensions
{
- ///
- /// Patches all methods with the proper .
- ///
/// The harmony to use for the patch.
- /// The category to patch.
- /// The assembly to find patches in.
- public static void PatchCategory(this Harmony harmony, string category, Assembly? assembly = null)
+ extension(Harmony harmony)
{
- assembly ??= Assembly.GetCallingAssembly();
-
- assembly.GetTypes().Where(type =>
- {
- IEnumerable categories = type.GetCustomAttributes();
- return categories.Any(c => c.Category == category);
- })
- .Do(type => SafePatch(harmony, type));
- }
-
- ///
- /// Patches all patches that don't have a .
- ///
- /// The harmony to use for the patch.
- /// The assembly to look for patches.
- public static void PatchAllNoCategory(this Harmony harmony, Assembly? assembly = null)
- {
- assembly ??= Assembly.GetCallingAssembly();
+ ///
+ /// Patches all methods with the proper .
+ ///
+ /// The category to patch.
+ /// The assembly to find patches in.
+ public void PatchCategory(string category, Assembly? assembly = null)
+ {
+ assembly ??= Assembly.GetCallingAssembly();
- assembly.GetTypes().Where(type =>
- {
- IEnumerable categories = type.GetCustomAttributes();
- return !categories.Any();
- })
- .Do(type => SafePatch(harmony, type));
- }
+ assembly.GetTypes().Where(type =>
+ {
+ IEnumerable categories = type.GetCustomAttributes();
+ return categories.Any(c => c.Category == category);
+ })
+ .Do(type => SafePatch(harmony, type));
+ }
- ///
- /// Attempts to safely patch a , logging any errors.
- ///
- /// The harmony to use for the patch.
- /// The to attempt to patch.
- public static void SafePatch(this Harmony harmony, Type type)
- {
- try
+ ///
+ /// Patches all patches that don't have a .
+ ///
+ /// The assembly to look for patches.
+ public void PatchAllNoCategory(Assembly? assembly = null)
{
- harmony.CreateClassProcessor(type).Patch();
+ assembly ??= Assembly.GetCallingAssembly();
+
+ assembly.GetTypes().Where(type =>
+ {
+ IEnumerable categories = type.GetCustomAttributes();
+ return !categories.Any();
+ })
+ .Do(type => SafePatch(harmony, type));
}
- catch (Exception ex)
+
+ ///
+ /// Attempts to safely patch a , logging any errors.
+ ///
+ /// The to attempt to patch.
+ public void SafePatch(Type type)
{
- Logger.Error($"[HarmonyExtensions] failed to safely patch {harmony.Id} ({type.FullName}): {ex}");
+ try
+ {
+ harmony.CreateClassProcessor(type).Patch();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"[HarmonyExtensions] failed to safely patch {harmony.Id} ({type.FullName}): {ex}");
+ }
}
}
}
diff --git a/SecretAPI/Extensions/MirrorExtensions.cs b/SecretAPI/Extensions/MirrorExtensions.cs
index 126321e..30aea4e 100644
--- a/SecretAPI/Extensions/MirrorExtensions.cs
+++ b/SecretAPI/Extensions/MirrorExtensions.cs
@@ -11,26 +11,6 @@
///
public static class MirrorExtensions
{
- ///
- /// Sends a fake cassie message to a player.
- ///
- /// The target to send the cassie message to.
- /// The message to send.
- /// Whether the cassie is held.
- /// Whether the cassie is noisy.
- /// Whether there is subtitles on the cassie.
- /// The custom subtitles to use for the cassie.
- [Obsolete("Due to NW changes to Cassie, this is no longer functional.")]
- public static void SendFakeCassieMessage(
- this Player target,
- string message,
- bool isHeld = false,
- bool isNoisy = true,
- bool isSubtitles = true,
- string customSubtitles = "")
- {
- }
-
///
/// Send a fake rpc message to a player.
///
diff --git a/SecretAPI/Extensions/PlayerExtensions.cs b/SecretAPI/Extensions/PlayerExtensions.cs
index 032b3bf..d2a6737 100644
--- a/SecretAPI/Extensions/PlayerExtensions.cs
+++ b/SecretAPI/Extensions/PlayerExtensions.cs
@@ -2,9 +2,6 @@
{
using CustomPlayerEffects;
using Interactables.Interobjects.DoorUtils;
- using InventorySystem;
- using InventorySystem.Items;
- using InventorySystem.Items.Usables.Scp330;
using LabApi.Features.Wrappers;
using SecretAPI.Enums;
@@ -13,74 +10,73 @@
///
public static class PlayerExtensions
{
- ///
- /// Gets an effect of a player based on the effect name.
- ///
/// The player to get effect from.
- /// Name of the effect to find.
- /// The effect.
- public static StatusEffectBase GetEffect(this Player player, string name)
- => player.ReferenceHub.playerEffectsController.TryGetEffect(name, out StatusEffectBase? effect) ? effect : null!;
-
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The player to check.
- /// The requester to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public static bool HasDoorPermission(this Player player, IDoorPermissionRequester requester, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ extension(Player player)
{
- if (checkFlags.HasFlag(DoorPermissionCheck.Bypass) && player.IsBypassEnabled)
- return true;
-
- if (checkFlags.HasFlag(DoorPermissionCheck.Role) && player.RoleBase is IDoorPermissionProvider roleProvider && requester.PermissionsPolicy.CheckPermissions(roleProvider.GetPermissions(requester)))
- return true;
+ ///
+ /// Gets an effect of a player based on the effect name.
+ ///
+ /// Name of the effect to find.
+ /// The effect.
+ public StatusEffectBase GetEffect(string name)
+ => player.ReferenceHub.playerEffectsController.TryGetEffect(name, out StatusEffectBase? effect) ? effect : null!;
- foreach (Item item in player.Items)
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The requester to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasDoorPermission(IDoorPermissionRequester requester, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
{
- bool isCurrent = item == player.CurrentItem;
- if (!checkFlags.HasFlag(DoorPermissionCheck.CurrentItem) && isCurrent)
- continue;
-
- if (!checkFlags.HasFlag(DoorPermissionCheck.InventoryExcludingCurrent) && !isCurrent)
- continue;
+ if (checkFlags.HasFlag(DoorPermissionCheck.Bypass) && player.IsBypassEnabled)
+ return true;
- if (item.Base is IDoorPermissionProvider itemProvider && requester.PermissionsPolicy.CheckPermissions(itemProvider.GetPermissions(requester)))
+ if (checkFlags.HasFlag(DoorPermissionCheck.Role) && player.RoleBase is IDoorPermissionProvider roleProvider && requester.PermissionsPolicy.CheckPermissions(roleProvider.GetPermissions(requester)))
return true;
- }
- return false;
- }
+ foreach (Item item in player.Items)
+ {
+ bool isCurrent = item == player.CurrentItem;
+ if (!checkFlags.HasFlag(DoorPermissionCheck.CurrentItem) && isCurrent)
+ continue;
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The player to check.
- /// The door to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public static bool HasDoorPermission(this Player player, Door door, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- => player.HasDoorPermission(door.Base, checkFlags);
+ if (!checkFlags.HasFlag(DoorPermissionCheck.InventoryExcludingCurrent) && !isCurrent)
+ continue;
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The player to check.
- /// The locker chamber to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public static bool HasLockerChamberPermission(this Player player, LockerChamber chamber, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- => player.HasDoorPermission(chamber.Base, checkFlags);
+ if (item.Base is IDoorPermissionProvider itemProvider && requester.PermissionsPolicy.CheckPermissions(itemProvider.GetPermissions(requester)))
+ return true;
+ }
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The player to check.
- /// The generator to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public static bool HasGeneratorPermission(this Player player, Generator generator, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- => player.HasDoorPermission(generator.Base, checkFlags);
+ return false;
+ }
+
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The door to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasDoorPermission(Door door, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ => player.HasDoorPermission(door.Base, checkFlags);
+
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The locker chamber to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasLockerChamberPermission(LockerChamber chamber, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ => player.HasDoorPermission(chamber.Base, checkFlags);
+
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The generator to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasGeneratorPermission(Generator generator, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ => player.HasDoorPermission(generator.Base, checkFlags);
+ }
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/RoleExtensions.cs b/SecretAPI/Extensions/RoleExtensions.cs
deleted file mode 100644
index e436281..0000000
--- a/SecretAPI/Extensions/RoleExtensions.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-namespace SecretAPI.Extensions
-{
- using System;
- using System.Diagnostics.CodeAnalysis;
- using InventorySystem;
- using LabApi.Features.Extensions;
- using PlayerRoles;
- using Respawning.Objectives;
- using UnityEngine;
-
- ///
- /// Extensions related to .
- ///
- [Obsolete("This no longer provides anything that basegame/LabAPI does not")]
- public static class RoleExtensions
- {
- ///
- /// Tries to get a role base from a .
- ///
- /// The to get base of.
- /// The found.
- /// The .
- /// The role base found, else null.
- [Obsolete("Use LabApi.Features.Extensions.RoleExtensions.TryGetRoleBase")]
- public static bool TryGetRoleBase(this RoleTypeId roleTypeId, [NotNullWhen(true)] out T? role)
- => LabApi.Features.Extensions.RoleExtensions.TryGetRoleBase(roleTypeId, out role);
-
- ///
- /// Gets the color of a .
- ///
- /// The role to get color of.
- /// The color found, if not found then white.
- [Obsolete("Use Respawning.Objectives.GetRoleColor")]
- public static Color GetColor(this RoleTypeId roleTypeId)
- => roleTypeId.GetRoleColor();
-
- ///
- /// Tries to get a random spawn point from a .
- ///
- /// The role to get spawn from.
- /// The position found.
- /// The rotation found.
- /// Whether a spawnpoint was found.
- [Obsolete("Use LabApi.Features.Extensions.RoleExtensions.TryGetRandomSpawnPoint")]
- public static bool GetRandomSpawnPosition(this RoleTypeId role, out Vector3 position, out float horizontalRot)
- => role.TryGetRandomSpawnPoint(out position, out horizontalRot);
-
- ///
- /// Gets the inventory of the specified .
- ///
- /// The .
- /// The found.
- [Obsolete("Use LabApi.Features.Extensions.RoleExtensions.GetInventory")]
- public static InventoryRoleInfo GetInventory(this RoleTypeId role)
- => LabApi.Features.Extensions.RoleExtensions.GetInventory(role);
- }
-}
\ No newline at end of file
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index ba3d4b3..c4335e7 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -29,13 +29,16 @@
+
+
From 22ec81f241ae790e5a1ae8ed20945880e7f41a21 Mon Sep 17 00:00:00 2001
From: evelyn <85962933+Misfiy@users.noreply.github.com>
Date: Wed, 7 Jan 2026 09:27:51 +0100
Subject: [PATCH 02/37] Update Attribute to Attributes namespace
---
SecretAPI.Examples/Patches/ExamplePatch.cs | 2 +-
SecretAPI/{Attribute => Attributes}/CallOnLoadAttribute.cs | 2 +-
SecretAPI/{Attribute => Attributes}/CallOnUnloadAttribute.cs | 2 +-
SecretAPI/{Attribute => Attributes}/HarmonyPatchCategory.cs | 2 +-
SecretAPI/Extensions/HarmonyExtensions.cs | 2 +-
SecretAPI/Patches/Features/SendSettingsPlayerSync.cs | 2 +-
SecretAPI/Patches/Features/SendSettingsServerSync.cs | 2 +-
SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs | 2 +-
SecretAPI/Patches/Features/SettingsSyncValidateFix.cs | 2 +-
SecretAPI/SecretApi.cs | 2 +-
10 files changed, 10 insertions(+), 10 deletions(-)
rename SecretAPI/{Attribute => Attributes}/CallOnLoadAttribute.cs (98%)
rename SecretAPI/{Attribute => Attributes}/CallOnUnloadAttribute.cs (97%)
rename SecretAPI/{Attribute => Attributes}/HarmonyPatchCategory.cs (95%)
diff --git a/SecretAPI.Examples/Patches/ExamplePatch.cs b/SecretAPI.Examples/Patches/ExamplePatch.cs
index fde590e..167939b 100644
--- a/SecretAPI.Examples/Patches/ExamplePatch.cs
+++ b/SecretAPI.Examples/Patches/ExamplePatch.cs
@@ -1,6 +1,6 @@
namespace SecretAPI.Examples.Patches
{
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
///
/// An example harmony patch.
diff --git a/SecretAPI/Attribute/CallOnLoadAttribute.cs b/SecretAPI/Attributes/CallOnLoadAttribute.cs
similarity index 98%
rename from SecretAPI/Attribute/CallOnLoadAttribute.cs
rename to SecretAPI/Attributes/CallOnLoadAttribute.cs
index cb55356..32bcd34 100644
--- a/SecretAPI/Attribute/CallOnLoadAttribute.cs
+++ b/SecretAPI/Attributes/CallOnLoadAttribute.cs
@@ -1,4 +1,4 @@
-namespace SecretAPI.Attribute
+namespace SecretAPI.Attributes
{
using System;
using System.Collections.Generic;
diff --git a/SecretAPI/Attribute/CallOnUnloadAttribute.cs b/SecretAPI/Attributes/CallOnUnloadAttribute.cs
similarity index 97%
rename from SecretAPI/Attribute/CallOnUnloadAttribute.cs
rename to SecretAPI/Attributes/CallOnUnloadAttribute.cs
index b886d55..65da120 100644
--- a/SecretAPI/Attribute/CallOnUnloadAttribute.cs
+++ b/SecretAPI/Attributes/CallOnUnloadAttribute.cs
@@ -1,4 +1,4 @@
-namespace SecretAPI.Attribute
+namespace SecretAPI.Attributes
{
using System;
using System.Reflection;
diff --git a/SecretAPI/Attribute/HarmonyPatchCategory.cs b/SecretAPI/Attributes/HarmonyPatchCategory.cs
similarity index 95%
rename from SecretAPI/Attribute/HarmonyPatchCategory.cs
rename to SecretAPI/Attributes/HarmonyPatchCategory.cs
index a27b953..853129d 100644
--- a/SecretAPI/Attribute/HarmonyPatchCategory.cs
+++ b/SecretAPI/Attributes/HarmonyPatchCategory.cs
@@ -1,4 +1,4 @@
-namespace SecretAPI.Attribute
+namespace SecretAPI.Attributes
{
using System;
using SecretAPI.Extensions;
diff --git a/SecretAPI/Extensions/HarmonyExtensions.cs b/SecretAPI/Extensions/HarmonyExtensions.cs
index 29d7451..f26b436 100644
--- a/SecretAPI/Extensions/HarmonyExtensions.cs
+++ b/SecretAPI/Extensions/HarmonyExtensions.cs
@@ -6,7 +6,7 @@
using System.Reflection;
using HarmonyLib;
using LabApi.Features.Console;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
///
/// Handles patching.
diff --git a/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs b/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs
index dcc3c58..d7b94cb 100644
--- a/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs
+++ b/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs
@@ -2,7 +2,7 @@
{
using HarmonyLib;
using LabApi.Features.Wrappers;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
using SecretAPI.Features.UserSettings;
using UserSettings.ServerSpecific;
diff --git a/SecretAPI/Patches/Features/SendSettingsServerSync.cs b/SecretAPI/Patches/Features/SendSettingsServerSync.cs
index cb2fd52..e84d1ff 100644
--- a/SecretAPI/Patches/Features/SendSettingsServerSync.cs
+++ b/SecretAPI/Patches/Features/SendSettingsServerSync.cs
@@ -1,7 +1,7 @@
namespace SecretAPI.Patches.Features
{
using HarmonyLib;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
using SecretAPI.Features.UserSettings;
using UserSettings.ServerSpecific;
diff --git a/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs b/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
index f17b81c..a1b1e74 100644
--- a/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
+++ b/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
@@ -1,7 +1,7 @@
namespace SecretAPI.Patches.Features
{
using HarmonyLib;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
using SecretAPI.Features.UserSettings;
using UserSettings.ServerSpecific;
diff --git a/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs b/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
index 89e08a9..39cf781 100644
--- a/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
+++ b/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
@@ -1,7 +1,7 @@
namespace SecretAPI.Patches.Features
{
using HarmonyLib;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
using SecretAPI.Features.UserSettings;
using UserSettings.ServerSpecific;
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 6272099..72634a7 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -6,7 +6,7 @@
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using LabApi.Loader.Features.Plugins.Enums;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
///
/// Main class handling loading API.
From b544af7cb45f62b0faaceb0957bf2490b7dd7879 Mon Sep 17 00:00:00 2001
From: evelyn <85962933+Misfiy@users.noreply.github.com>
Date: Wed, 7 Jan 2026 09:31:11 +0100
Subject: [PATCH 03/37] Namespace fix
---
SecretAPI/Features/Effects/CustomPlayerEffect.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SecretAPI/Features/Effects/CustomPlayerEffect.cs b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
index 6231060..8f9b080 100644
--- a/SecretAPI/Features/Effects/CustomPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
@@ -4,7 +4,7 @@
using System.Collections.Generic;
using CustomPlayerEffects;
using LabApi.Features.Wrappers;
- using SecretAPI.Attribute;
+ using SecretAPI.Attributes;
using SecretAPI.Extensions;
using UnityEngine;
using UnityEngine.SceneManagement;
From 6c765bd48303869a68f01e506094ff03c997587a Mon Sep 17 00:00:00 2001
From: Misfiy <85962933+obvEve@users.noreply.github.com>
Date: Sun, 18 Jan 2026 20:42:41 +0100
Subject: [PATCH 04/37] Change DoorPermissionCheck to use bitwise
---
SecretAPI/Enums/DoorPermissionCheck.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/SecretAPI/Enums/DoorPermissionCheck.cs b/SecretAPI/Enums/DoorPermissionCheck.cs
index 0070b60..ad78cac 100644
--- a/SecretAPI/Enums/DoorPermissionCheck.cs
+++ b/SecretAPI/Enums/DoorPermissionCheck.cs
@@ -21,22 +21,22 @@ public enum DoorPermissionCheck
///
/// Used to consider .
///
- Bypass = 1,
+ Bypass = 1 << 0,
///
/// Used to consider the player's .
///
- Role = 2,
+ Role = 1 << 1,
///
/// Used to consider the player's .
///
- CurrentItem = 4,
+ CurrentItem = 1 << 2,
///
/// Used to consider the player's inventory, not including the item they are holding.
///
- InventoryExcludingCurrent = 8,
+ InventoryExcludingCurrent = 1 << 3,
///
/// Used to consider the player's ENTIRE inventory.
From cc8060499d8795d2e2a2c4f1748ecf3c94182110 Mon Sep 17 00:00:00 2001
From: Evelyn <85962933+obvEve@users.noreply.github.com>
Date: Fri, 27 Mar 2026 16:33:09 +0100
Subject: [PATCH 05/37] SSSS additions (#85)
* SSSS additions for sending values
* SSSS additions!!
* Do all settings properly
* SSSS stuff
* Fix: Harmony is not created early enough, causing patches to fail
* Improve & Fix docs
* feat: SSSS Improvements/Additions (#87)
* feat: SettingResponseType enum | CustomSetting:LastUpdateType
* CustomDropdownSetting & CustomTwoButtonSetting -> HasValueChanged
* Improve documentation
---
.../UserSettings/CustomButtonSetting.cs | 29 +++++-
.../UserSettings/CustomDropdownSetting.cs | 53 +++++++++-
.../Features/UserSettings/CustomHeader.cs | 2 +
.../UserSettings/CustomKeybindSetting.cs | 6 +-
.../UserSettings/CustomPlainTextSetting.cs | 64 +++++++++++--
.../Features/UserSettings/CustomSetting.cs | 67 +++++++++++--
.../UserSettings/CustomSliderSetting.cs | 96 +++++++++++++++++--
.../UserSettings/CustomTextAreaSetting.cs | 9 ++
.../UserSettings/CustomTwoButtonSetting.cs | 66 ++++++++++++-
.../UserSettings/SettingResponseType.cs | 23 +++++
SecretAPI/SecretApi.cs | 3 +-
11 files changed, 383 insertions(+), 35 deletions(-)
create mode 100644 SecretAPI/Features/UserSettings/SettingResponseType.cs
diff --git a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
index 3076fb3..7ef676c 100644
--- a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
@@ -40,13 +40,34 @@ protected CustomButtonSetting(int? id, string label, string buttonText, float? h
public TimeSpan LastPress => Base.SyncLastPress.Elapsed;
///
- /// Gets the text of the button.
+ /// Gets or sets the text of the button.
///
- public string Text => Base.ButtonText;
+ public string Text
+ {
+ get => Base.ButtonText;
+ set
+ {
+ Base.ButtonText = value;
+ SendButtonUpdate();
+ }
+ }
+
+ ///
+ /// Gets or sets the amount of time to hold the button in seconds.
+ ///
+ public float RequiredHoldTime
+ {
+ get => Base.HoldTimeSeconds;
+ set
+ {
+ Base.HoldTimeSeconds = value;
+ SendButtonUpdate();
+ }
+ }
///
- /// Gets the amount of time to hold the button in seconds.
+ /// Sends an update to that or has updated.
///
- public float HoldTime => Base.HoldTimeSeconds;
+ private void SendButtonUpdate() => Base.SendButtonUpdate(Text, RequiredHoldTime, false, IsKnownOwnerHub);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
index 00626a1..ec18e97 100644
--- a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
@@ -27,20 +27,27 @@ protected CustomDropdownSetting(SSDropdownSetting setting)
/// The default option (int index).
/// The entry type.
/// The hint to show.
+ /// The .
+ /// See .
protected CustomDropdownSetting(
int? id,
string label,
string[] options,
int defaultOptionIndex = 0,
SSDropdownSetting.DropdownEntryType entryType = SSDropdownSetting.DropdownEntryType.Regular,
- string? hint = null)
- : this(new SSDropdownSetting(id, label, options, defaultOptionIndex, entryType, hint))
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSDropdownSetting(id, label, options, defaultOptionIndex, entryType, hint, collectionId, isServerSetting))
{
}
///
public new SSDropdownSetting Base { get; }
+ ///
+ public override bool HasValueChanged => LastSelectedIndex != SyncedIndex;
+
///
/// Gets the index the client is claiming to have selected.
///
@@ -58,12 +65,52 @@ protected CustomDropdownSetting(
public string[] Options
{
get => Base.Options;
- set => Base.Options = value;
+ set
+ {
+ Base.Options = value;
+ SendDropdownUpdate();
+ }
}
///
/// Gets the selected option as string.
///
public string SelectedOption => Options[ValidatedSelectedIndex];
+
+ ///
+ /// Gets the .
+ ///
+ /// Refer to https://github.com/HubertMoszka/Server-Specific-Settings-System/blob/main/SSDropdownSetting.cs#L151 for proper documentation.
+ public SSDropdownSetting.DropdownEntryType EntryType => Base.EntryType;
+
+ ///
+ /// Gets the last selected index, or -1 if none was selected previously.
+ ///
+ public int LastSelectedIndex { get; private set; } = -1;
+
+ ///
+ /// Gets the selected option prior to the most recent call as a string, or null if none was selected previously.
+ ///
+ public string? LastSelectedOption => LastSelectedIndex < 0 || LastSelectedIndex >= Options.Length ? null : Options[LastSelectedIndex];
+
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// The new ID selected.
+ public void SendServerUpdate(int selectionId) => Base.SendValueUpdate(selectionId, false, IsKnownOwnerHub);
+
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+
+ if (LastUpdateType != SettingResponseType.Initial)
+ LastSelectedIndex = SyncedIndex;
+ }
+
+ ///
+ /// Sends an update to that has been updated.
+ ///
+ private void SendDropdownUpdate() => Base.SendDropdownUpdate(Options, false, IsKnownOwnerHub);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomHeader.cs b/SecretAPI/Features/UserSettings/CustomHeader.cs
index bc7c7b5..d638121 100644
--- a/SecretAPI/Features/UserSettings/CustomHeader.cs
+++ b/SecretAPI/Features/UserSettings/CustomHeader.cs
@@ -1,5 +1,6 @@
namespace SecretAPI.Features.UserSettings
{
+ using System;
using global::UserSettings.ServerSpecific;
///
@@ -21,6 +22,7 @@ public CustomHeader(string label, bool reducedPadding = false, string? hint = nu
///
/// Gets a for Gameplay purposes.
///
+ [Obsolete("3.0 will remove this - Please handle your setting header yourself!")]
public static CustomHeader Gameplay { get; } = new("Gameplay", hint: "Features that affect gameplay");
///
diff --git a/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs b/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
index 1b8d094..32ae2aa 100644
--- a/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
@@ -27,14 +27,16 @@ protected CustomKeybindSetting(SSKeybindSetting setting)
/// Whether to prevent interaction in a GUI.
/// Whether to allow spectators to trigger.
/// The hint to show.
+ /// The .
protected CustomKeybindSetting(
int? id,
string label,
KeyCode suggestedKey = KeyCode.None,
bool preventInteractionOnGui = true,
bool allowSpectatorTrigger = true,
- string? hint = null)
- : this(new SSKeybindSetting(id, label, suggestedKey, preventInteractionOnGui, allowSpectatorTrigger, hint))
+ string? hint = null,
+ byte collectionId = byte.MaxValue)
+ : this(new SSKeybindSetting(id, label, suggestedKey, preventInteractionOnGui, allowSpectatorTrigger, hint, collectionId))
{
}
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
index 963f93d..2c6b2b5 100644
--- a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -1,5 +1,6 @@
namespace SecretAPI.Features.UserSettings
{
+ using System;
using global::UserSettings.ServerSpecific;
using TMPro;
@@ -41,24 +42,75 @@ protected CustomPlainTextSetting(
///
public new SSPlaintextSetting Base { get; }
+ ///
+ /// Gets the input text prior to the most recent call.
+ ///
+ public string LastInputText
+ {
+ get => field ??= string.Empty;
+ private set;
+ }
+
///
/// Gets the synced input text.
///
public string InputText => Base.SyncInputText;
///
- /// Gets the content type.
+ /// Gets or sets the content type.
///
- public TMP_InputField.ContentType ContentType => Base.ContentType;
+ public TMP_InputField.ContentType ContentType
+ {
+ get => Base.ContentType;
+ set
+ {
+ Base.ContentType = value;
+ SendPlaintextUpdate();
+ }
+ }
///
- /// Gets the placeholder.
+ /// Gets or sets the placeholder.
///
- public string Placeholder => Base.Placeholder;
+ public string Placeholder
+ {
+ get => Base.Placeholder;
+ set
+ {
+ Base.Placeholder = value;
+ SendPlaintextUpdate();
+ }
+ }
+
+ ///
+ /// Gets or sets the character limit.
+ ///
+ public int CharacterLimit
+ {
+ get => Base.CharacterLimit;
+ set
+ {
+ Base.CharacterLimit = value;
+ SendPlaintextUpdate();
+ }
+ }
+
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// The new text.
+ public void SendServerUpdate(string text) => Base.SendValueUpdate(text, false, IsKnownOwnerHub);
+
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ LastInputText = InputText;
+ }
///
- /// Gets the character limit.
+ /// Sends an update to the that or has changed values.
///
- public int CharacterLimit => Base.CharacterLimit;
+ private void SendPlaintextUpdate() => Base.SendPlaintextUpdate(Placeholder, (ushort)Math.Clamp(CharacterLimit, ushort.MinValue, ushort.MaxValue), ContentType, false, IsKnownOwnerHub);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index 9f65019..2c97fad 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -21,7 +21,7 @@ public abstract class CustomSetting : ISetting
static CustomSetting()
{
- SecretApi.Harmony?.PatchCategory(nameof(CustomSetting));
+ SecretApi.Harmony?.PatchCategory(nameof(CustomSetting), SecretApi.Assembly);
ServerSpecificSettingsSync.SendOnJoinFilter = null;
ServerSpecificSettingsSync.DefinedSettings ??= []; // fix null ref
@@ -66,22 +66,42 @@ protected CustomSetting(ServerSpecificSettingBase setting)
public abstract CustomHeader Header { get; }
///
- /// Gets or sets a value indicating whether the setting is server side only.
+ /// Gets an enum indicating the type of the last update.
///
- /// The setting value cannot be updated from client side and can be used to indicate server features being toggled.
- public bool IsServerOnly
+ /// When used inside of it will indicate the current status.
+ public SettingResponseType LastUpdateType { get; private set; } = SettingResponseType.None;
+
+ ///
+ /// Gets a value indicating whether the current value received is different to that prior to the most recent call.
+ ///
+ public virtual bool HasValueChanged { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the setting is server side.
+ ///
+ /// This will result in client not saving the setting values and allows the server to change the setting .
+ public bool IsServerSetting
{
get => Base.IsServerOnly;
set => Base.IsServerOnly = value;
}
+ ///
+ /// Gets a value indicating whether the setting is the default and not tied to a .
+ ///
+ public bool IsDefaultSetting => KnownOwner == null;
+
///
/// Gets or sets the current label.
///
public string Label
{
get => Base.Label;
- set => Base.Label = value;
+ set
+ {
+ Base.Label = value;
+ SendSettingUpdate();
+ }
}
///
@@ -90,7 +110,11 @@ public string Label
public string DescriptionHint
{
get => Base.HintDescription;
- set => Base.HintDescription = value;
+ set
+ {
+ Base.HintDescription = value;
+ SendSettingUpdate();
+ }
}
///
@@ -138,13 +162,13 @@ public bool IsShared
/// Unregisters collection of settings.
///
/// The settings to unregister.
- public static void UnRegister(params CustomSetting[] settings) => CustomSettings.RemoveAll(s => settings.Contains(s));
+ public static void UnRegister(params CustomSetting[] settings) => CustomSettings.RemoveAll(settings.Contains);
///
/// Unregisters a collection of settings.
///
/// The settings to unregister.
- public static void UnRegister(IEnumerable settings) => CustomSettings.RemoveAll(s => settings.Contains(s));
+ public static void UnRegister(IEnumerable settings) => CustomSettings.RemoveAll(settings.Contains);
///
/// Tries to get player specific setting.
@@ -252,6 +276,24 @@ public static void SendSettingsToPlayer(Player player, int? version = null)
ListPool.Shared.Return(ordered);
}
+ ///
+ /// Checks whether a is equal to .
+ ///
+ /// The to check.
+ /// Whether is equal to Owner .
+ internal bool IsKnownOwnerHub(ReferenceHub? hub) => hub && KnownOwner?.ReferenceHub == hub;
+
+ ///
+ /// Called before , adding & .
+ ///
+ /// This will not have the current status.
+ protected internal virtual void HandleBeforeSettingUpdate()
+ {
+ LastUpdateType = LastUpdateType == SettingResponseType.None
+ ? SettingResponseType.Initial
+ : SettingResponseType.Update;
+ }
+
///
/// Resyncs the setting to its owner.
///
@@ -287,6 +329,7 @@ protected virtual void PersonalizeSetting()
///
/// Called when client sends a new value on the setting.
///
+ /// You can use to get the current update type.
protected abstract void HandleSettingUpdate();
private static void RemoveStoredPlayer(Player player) => ReceivedPlayerSettings.Remove(player);
@@ -304,10 +347,11 @@ private static void OnSettingsUpdated(ReferenceHub hub, ServerSpecificSettingBas
// validate setting existence and then write data from client
CustomSetting newSettingPlayer = EnsurePlayerSpecificSetting(player, setting);
+ newSettingPlayer.HandleBeforeSettingUpdate();
+
NetworkWriterPooled valueWriter = NetworkWriterPool.Get();
settingBase.SerializeValue(valueWriter);
newSettingPlayer.Base.DeserializeValue(new NetworkReader(valueWriter.buffer));
-
NetworkWriterPool.Return(valueWriter);
newSettingPlayer.HandleSettingUpdate();
@@ -326,5 +370,10 @@ private static CustomSetting EnsurePlayerSpecificSetting(Player player, CustomSe
return currentSetting;
}
+
+ ///
+ /// Sends an update to that or has changed.
+ ///
+ private void SendSettingUpdate() => Base.SendUpdate(Label, DescriptionHint, false, IsKnownOwnerHub);
}
}
diff --git a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
index 98b7e4e..c64031d 100644
--- a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
@@ -1,6 +1,7 @@
namespace SecretAPI.Features.UserSettings
{
using global::UserSettings.ServerSpecific;
+ using UnityEngine;
///
/// Wrapper for .
@@ -29,6 +30,8 @@ protected CustomSliderSetting(SSSliderSetting setting)
/// Value to string format.
/// The final display format.
/// The hint to display.
+ /// The .
+ /// See .
protected CustomSliderSetting(
int? id,
string label,
@@ -38,7 +41,9 @@ protected CustomSliderSetting(
bool integer = false,
string valueToStringFormat = "0.##",
string finalDisplayFormat = "{0}",
- string? hint = null)
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
: this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, integer, valueToStringFormat, finalDisplayFormat, hint))
{
}
@@ -46,6 +51,19 @@ protected CustomSliderSetting(
///
public new SSSliderSetting Base { get; }
+ ///
+ public override bool HasValueChanged => !Mathf.Approximately(LastSelectedValueFloat, SelectedValueFloat);
+
+ ///
+ /// Gets the selected value prior to the most recent call as a float.
+ ///
+ public float LastSelectedValueFloat { get; private set; }
+
+ ///
+ /// Gets the selected value prior to the most recent call as an int.
+ ///
+ public int LastSelectedValueInt => Mathf.RoundToInt(LastSelectedValueFloat);
+
///
/// Gets the synced value selected as a float.
///
@@ -56,13 +74,43 @@ protected CustomSliderSetting(
///
public int SelectedValueInt => Base.SyncIntValue;
+ ///
+ /// Gets or sets the value to string format.
+ ///
+ public string ValueToStringFormat
+ {
+ get => Base.ValueToStringFormat;
+ set
+ {
+ Base.ValueToStringFormat = value;
+ SendSliderUpdate();
+ }
+ }
+
+ ///
+ /// Gets or sets the final display format.
+ ///
+ public string FinalDisplayFormat
+ {
+ get => Base.FinalDisplayFormat;
+ set
+ {
+ Base.FinalDisplayFormat = value;
+ SendSliderUpdate();
+ }
+ }
+
///
/// Gets or sets the minimum value of the setting.
///
public float MinimumValue
{
get => Base.MinValue;
- set => Base.MinValue = value;
+ set
+ {
+ Base.MinValue = value;
+ SendSliderUpdate();
+ }
}
///
@@ -71,17 +119,51 @@ public float MinimumValue
public float MaximumValue
{
get => Base.MaxValue;
- set => Base.MaxValue = value;
+ set
+ {
+ Base.MaxValue = value;
+ SendSliderUpdate();
+ }
+ }
+
+ ///
+ /// Gets or sets the default value of the setting.
+ ///
+ public float DefaultValue
+ {
+ get => Base.DefaultValue;
+ set => Base.DefaultValue = value;
}
///
- /// Gets the default value of the setting.
+ /// Gets or sets a value indicating whether to use integer. False will use float.
///
- public float DefaultValue => Base.DefaultValue;
+ public bool UseInteger
+ {
+ get => Base.Integer;
+ set
+ {
+ Base.Integer = value;
+ SendSliderUpdate();
+ }
+ }
+
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// The new value that this is set to.
+ public void SendServerUpdate(float value) => Base.SendValueUpdate(value, false, IsKnownOwnerHub);
+
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ LastSelectedValueFloat = SelectedValueFloat;
+ }
///
- /// Gets a value indicating whether to use integer. False will use float.
+ /// Sends an update that any of the slider values have been updated.
///
- public bool UseInteger => Base.Integer;
+ private void SendSliderUpdate() => Base.SendSliderUpdate(MinimumValue, MaximumValue, UseInteger, ValueToStringFormat, FinalDisplayFormat, false, IsKnownOwnerHub);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs b/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
index ea4b4d8..e70c67c 100644
--- a/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
@@ -39,6 +39,15 @@ protected CustomTextAreaSetting(
///
public new SSTextArea Base { get; }
+ ///
+ /// Gets or sets the current content. This is equal to .
+ ///
+ public string Content
+ {
+ get => Label;
+ set => Label = value;
+ }
+
///
/// Gets the foldout mode.
///
diff --git a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
index 4a48a2a..d73cc61 100644
--- a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
@@ -26,14 +26,58 @@ protected CustomTwoButtonSetting(SSTwoButtonsSetting button)
/// The second option.
/// Whether the second option should be default. Default: false.
/// The hint to show.
- protected CustomTwoButtonSetting(int? id, string label, string optionA, string optionB, bool defaultIsB = false, string? hint = null)
- : this(new SSTwoButtonsSetting(id, label, optionA, optionB, defaultIsB, hint))
+ /// The .
+ /// See .
+ protected CustomTwoButtonSetting(
+ int? id,
+ string label,
+ string optionA,
+ string optionB,
+ bool defaultIsB = false,
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSTwoButtonsSetting(id, label, optionA, optionB, defaultIsB, hint, collectionId, isServerSetting))
{
}
///
public new SSTwoButtonsSetting Base { get; }
+ ///
+ /// Gets or sets the current text for the first option.
+ ///
+ public string OptionA
+ {
+ get => Base.OptionA;
+ set
+ {
+ Base.OptionA = value;
+ SendOptionsUpdate();
+ }
+ }
+
+ ///
+ /// Gets or sets the current text for the second option.
+ ///
+ public string OptionB
+ {
+ get => Base.OptionB;
+ set
+ {
+ Base.OptionB = value;
+ SendOptionsUpdate();
+ }
+ }
+
+ ///
+ public override bool HasValueChanged => WasLastOptionB != IsOptionB;
+
+ ///
+ /// Gets a value indicating whether the value prior to the most recent call was Option B.
+ ///
+ public bool WasLastOptionB { get; private set; }
+
///
/// Gets a value indicating whether the selected option is currently the first.
///
@@ -48,5 +92,23 @@ protected CustomTwoButtonSetting(int? id, string label, string optionA, string o
/// Gets a value indicating whether the selected option is currently set to the default.
///
public bool IsDefault => Base.DefaultIsB ? IsOptionB : IsOptionA;
+
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// Whether the setting is set to B value now.
+ public void SendServerUpdate(bool isB) => Base.SendValueUpdate(isB, false, IsKnownOwnerHub);
+
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ WasLastOptionB = IsOptionB;
+ }
+
+ ///
+ /// Sends an update to the that or has changed values.
+ ///
+ private void SendOptionsUpdate() => Base.SendTwoButtonUpdate(OptionA, OptionB, false, IsKnownOwnerHub);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/SettingResponseType.cs b/SecretAPI/Features/UserSettings/SettingResponseType.cs
new file mode 100644
index 0000000..ea3f5db
--- /dev/null
+++ b/SecretAPI/Features/UserSettings/SettingResponseType.cs
@@ -0,0 +1,23 @@
+namespace SecretAPI.Features.UserSettings
+{
+ ///
+ /// The type of response.
+ ///
+ public enum SettingResponseType
+ {
+ ///
+ /// Indicates that no response has been recorded.
+ ///
+ None,
+
+ ///
+ /// Indicates that this is the initial response.
+ ///
+ Initial,
+
+ ///
+ /// Indicates that this is an update, changing the value.
+ ///
+ Update,
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 72634a7..7f58deb 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -38,7 +38,7 @@ public class SecretApi : Plugin
///
/// Gets the harmony to use for the API.
///
- internal static Harmony? Harmony { get; private set; }
+ internal static Harmony? Harmony { get; } = new("SecretAPI" + DateTime.Now);
///
/// Gets the Assembly of the API.
@@ -48,7 +48,6 @@ public class SecretApi : Plugin
///
public override void Enable()
{
- Harmony = new Harmony("SecretAPI" + DateTime.Now);
CallOnLoadAttribute.Load(Assembly);
}
From d0692146668f5dda7f9c0b353e3b8573119658a3 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Fri, 27 Mar 2026 16:35:05 +0100
Subject: [PATCH 06/37] Fix build (Please Exiled stop changing it)
---
.github/workflows/nuget.yml | 6 +++---
.github/workflows/pull_request.yml | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index 468425b..6c30cde 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -26,9 +26,9 @@ jobs:
Invoke-WebRequest -Uri ${{ env.REFERENCES_URL }} -OutFile "${{ github.workspace }}/References.zip"
Expand-Archive -Path "${{ github.workspace }}/References.zip" -DestinationPath ${{ env.REFERENCES_PATH }}
- - name: Rename Assembly-CSharp-Publicized to Assembly-CSharp
- shell: pwsh
- run: Rename-Item -Path "${{ env.REFERENCES_PATH }}\Assembly-CSharp-Publicized.dll" -NewName "Assembly-CSharp.dll"
+# - name: Rename Assembly-CSharp-Publicized to Assembly-CSharp
+# shell: pwsh
+# run: Rename-Item -Path "${{ env.REFERENCES_PATH }}\Assembly-CSharp-Publicized.dll" -NewName "Assembly-CSharp.dll"
- name: Build and Pack NuGet
env:
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 526d686..2de576e 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -26,9 +26,9 @@ jobs:
Invoke-WebRequest -Uri ${{ env.REFERENCES_URL }} -OutFile "${{ github.workspace }}/References.zip"
Expand-Archive -Path "${{ github.workspace }}/References.zip" -DestinationPath ${{ env.REFERENCES_PATH }}
- - name: Rename Assembly-CSharp-Publicized to Assembly-CSharp
- shell: pwsh
- run: Rename-Item -Path "${{ env.REFERENCES_PATH }}\Assembly-CSharp-Publicized.dll" -NewName "Assembly-CSharp.dll"
+# - name: Rename Assembly-CSharp-Publicized to Assembly-CSharp
+# shell: pwsh
+# run: Rename-Item -Path "${{ env.REFERENCES_PATH }}\Assembly-CSharp-Publicized.dll" -NewName "Assembly-CSharp.dll"
- name: Build
env:
From a771508db6795b5c529d1c889164f8535f4b54eb Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Fri, 27 Mar 2026 16:38:38 +0100
Subject: [PATCH 07/37] docs: Change & symbol to and
---
SecretAPI/Features/UserSettings/CustomSetting.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index 2c97fad..ef52e5a 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -284,7 +284,7 @@ public static void SendSettingsToPlayer(Player player, int? version = null)
internal bool IsKnownOwnerHub(ReferenceHub? hub) => hub && KnownOwner?.ReferenceHub == hub;
///
- /// Called before , adding & .
+ /// Called before , adding and .
///
/// This will not have the current status.
protected internal virtual void HandleBeforeSettingUpdate()
From 2284ecd2b1ee8ca91b2b5aacb1396645661e7ea4 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Fri, 27 Mar 2026 16:40:57 +0100
Subject: [PATCH 08/37] Update csproj to obvEve instead of Misfiy & Bump
version to 3.0.0
---
SecretAPI/SecretAPI.csproj | 8 ++++----
SecretAPI/SecretApi.cs | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index c4335e7..d1740d4 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -4,18 +4,18 @@
net48
latest
enable
- 2.0.3
+ 3.0.0
true
-
+
true
true
- Misfiy
+ obvEve
SecretAPI
API to extend SCP:SL LabAPI
git
- https://github.com/Misfiy/SecretAPI
+ https://github.com/obvEve/SecretAPI
README.md
MIT
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 7f58deb..40abb82 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -20,7 +20,7 @@ public class SecretApi : Plugin
public override string Description => "API for SCP:SL";
///
- public override string Author => "@misfiy / @obvEvelyn";
+ public override string Author => "@obvEve";
///
public override LoadPriority Priority => LoadPriority.Highest;
From f57caf868bdcc48136199e3fa3ded71276581bc0 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Fri, 27 Mar 2026 17:48:08 +0100
Subject: [PATCH 09/37] Minor code improvements
---
SecretAPI/Features/Effects/CustomPlayerEffect.cs | 11 ++---------
.../Features/UserSettings/CustomPlainTextSetting.cs | 8 ++++++--
SecretAPI/Features/UserSettings/CustomSetting.cs | 2 +-
.../Features/UserSettings/CustomSliderSetting.cs | 2 +-
SecretAPI/Features/UserSettings/ISetting.cs | 2 +-
SecretAPI/SecretApi.cs | 4 ++--
6 files changed, 13 insertions(+), 16 deletions(-)
diff --git a/SecretAPI/Features/Effects/CustomPlayerEffect.cs b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
index 8f9b080..a74b743 100644
--- a/SecretAPI/Features/Effects/CustomPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
@@ -27,14 +27,7 @@ public abstract class CustomPlayerEffect : StatusEffectBase
///
/// Gets the with this effect.
///
- public Player Owner { get; private set; } = null!;
-
- ///
- public override void Start()
- {
- Owner = Player.Get(Hub);
- base.Start();
- }
+ public Player Owner => field ??= Player.Get(Hub);
///
public override string ToString() => $"{GetType().Name}: Owner ({Owner}) - Intensity ({Intensity}) - Duration {Duration}";
@@ -45,7 +38,7 @@ public override void Start()
[CallOnLoad]
internal static void Initialize()
{
- SecretApi.Harmony?.PatchCategory(nameof(CustomPlayerEffect), SecretApi.Assembly);
+ SecretApi.Harmony.PatchCategory(nameof(CustomPlayerEffect), SecretApi.Assembly);
EffectsToRegister.Add(typeof(TemporaryDamageImmunity));
EffectsToRegister.Add(typeof(StaminaUsageDisablerEffect));
EffectsToRegister.Add(typeof(SprintDisablerEffect));
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
index 2c6b2b5..a5ba7cc 100644
--- a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -28,14 +28,18 @@ protected CustomPlainTextSetting(SSPlaintextSetting setting)
/// The max allowed characters.
/// The content type.
/// The hint to display for the setting.
+ /// The .
+ /// See .
protected CustomPlainTextSetting(
int? id,
string label,
string placeholder = "...",
int characterLimit = 64,
TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard,
- string? hint = null)
- : this(new SSPlaintextSetting(id, label, placeholder, characterLimit, contentType, hint))
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSPlaintextSetting(id, label, placeholder, characterLimit, contentType, hint, collectionId, isServerSetting))
{
}
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index ef52e5a..1190e7d 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -21,7 +21,7 @@ public abstract class CustomSetting : ISetting
static CustomSetting()
{
- SecretApi.Harmony?.PatchCategory(nameof(CustomSetting), SecretApi.Assembly);
+ SecretApi.Harmony.PatchCategory(nameof(CustomSetting), SecretApi.Assembly);
ServerSpecificSettingsSync.SendOnJoinFilter = null;
ServerSpecificSettingsSync.DefinedSettings ??= []; // fix null ref
diff --git a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
index c64031d..1831934 100644
--- a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
@@ -44,7 +44,7 @@ protected CustomSliderSetting(
string? hint = null,
byte collectionId = byte.MaxValue,
bool isServerSetting = false)
- : this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, integer, valueToStringFormat, finalDisplayFormat, hint))
+ : this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, integer, valueToStringFormat, finalDisplayFormat, hint, collectionId, isServerSetting))
{
}
diff --git a/SecretAPI/Features/UserSettings/ISetting.cs b/SecretAPI/Features/UserSettings/ISetting.cs
index 629936d..6c35959 100644
--- a/SecretAPI/Features/UserSettings/ISetting.cs
+++ b/SecretAPI/Features/UserSettings/ISetting.cs
@@ -6,7 +6,7 @@
/// Interface for to handle the Base.
///
/// The setting being wrapped.
- public interface ISetting
+ public interface ISetting
where T : ServerSpecificSettingBase
{
///
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 40abb82..f41521c 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -38,7 +38,7 @@ public class SecretApi : Plugin
///
/// Gets the harmony to use for the API.
///
- internal static Harmony? Harmony { get; } = new("SecretAPI" + DateTime.Now);
+ internal static Harmony Harmony { get; } = new("SecretAPI" + DateTime.Now);
///
/// Gets the Assembly of the API.
@@ -54,7 +54,7 @@ public override void Enable()
///
public override void Disable()
{
- Harmony?.UnpatchAll(Harmony.Id);
+ Harmony.UnpatchAll(Harmony.Id);
}
}
}
\ No newline at end of file
From 2afdab894ec6190fe1d585456977fe73278d8014 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Fri, 27 Mar 2026 21:08:31 +0100
Subject: [PATCH 10/37] CustomPlainTextSetting::LastInputText -> Remove
validation as basegame already does it
---
SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
index a5ba7cc..2c75305 100644
--- a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -49,11 +49,7 @@ protected CustomPlainTextSetting(
///
/// Gets the input text prior to the most recent call.
///
- public string LastInputText
- {
- get => field ??= string.Empty;
- private set;
- }
+ public string LastInputText { get; private set; } = string.Empty;
///
/// Gets the synced input text.
From 92cd31cf5195716b523244f3a12b840947328172 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 30 Mar 2026 13:20:15 +0200
Subject: [PATCH 11/37] Change version to 3.0.0-beta1
---
SecretAPI/SecretAPI.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index d1740d4..48282bb 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -4,7 +4,7 @@
net48
latest
enable
- 3.0.0
+ 3.0.0-beta1
true
From 83c2c49867fd28f48e90416f880b0cc7fb0c6a73 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 30 Mar 2026 13:37:14 +0200
Subject: [PATCH 12/37] Fix? --output on nuget.yml dotnet pack
---
.github/workflows/nuget.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index 6c30cde..1be9c83 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -33,7 +33,7 @@ jobs:
- name: Build and Pack NuGet
env:
SL_REFERENCES: ${{ env.REFERENCES_PATH }}
- run: dotnet pack -c Release --output ${NUGET_PACKAGED_PATH}
+ run: dotnet pack -c Release --output ${{ NUGET_PACKAGED_PATH }}
- name: Push NuGet package
run: |
From 21eacc04fc3e415ff7573536e27c6bb3a89265b1 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 30 Mar 2026 13:37:48 +0200
Subject: [PATCH 13/37] Remove CustomHeader::Gameplay
---
SecretAPI/Features/UserSettings/CustomHeader.cs | 6 ------
1 file changed, 6 deletions(-)
diff --git a/SecretAPI/Features/UserSettings/CustomHeader.cs b/SecretAPI/Features/UserSettings/CustomHeader.cs
index d638121..8822806 100644
--- a/SecretAPI/Features/UserSettings/CustomHeader.cs
+++ b/SecretAPI/Features/UserSettings/CustomHeader.cs
@@ -19,12 +19,6 @@ public CustomHeader(string label, bool reducedPadding = false, string? hint = nu
Base = new SSGroupHeader(label, reducedPadding, hint);
}
- ///
- /// Gets a for Gameplay purposes.
- ///
- [Obsolete("3.0 will remove this - Please handle your setting header yourself!")]
- public static CustomHeader Gameplay { get; } = new("Gameplay", hint: "Features that affect gameplay");
-
///
/// Gets a for Example purposes.
///
From d9656967806801316c13149de519a497bf5f2a8b Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 30 Mar 2026 13:44:35 +0200
Subject: [PATCH 14/37] File-scoped namespaces
---
.editorconfig | 3 +-
SecretAPI.Examples/ExampleEntry.cs | 61 +-
SecretAPI.Examples/Patches/ExamplePatch.cs | 37 +-
.../Settings/ExampleDropdownSetting.cs | 73 +-
.../Settings/ExampleKeybindSetting.cs | 53 +-
SecretAPI/Attributes/CallOnLoadAttribute.cs | 115 ++-
SecretAPI/Attributes/CallOnUnloadAttribute.cs | 67 +-
SecretAPI/Attributes/HarmonyPatchCategory.cs | 39 +-
SecretAPI/Enums/DoorPermissionCheck.cs | 107 ++-
SecretAPI/Extensions/CollectionExtensions.cs | 77 +--
SecretAPI/Extensions/HarmonyExtensions.cs | 113 ++-
SecretAPI/Extensions/MirrorExtensions.cs | 105 ++-
SecretAPI/Extensions/PlayerExtensions.cs | 137 ++--
SecretAPI/Extensions/ReflectionExtensions.cs | 81 ++-
SecretAPI/Extensions/RoomExtensions.cs | 61 +-
SecretAPI/Extensions/Scp914Extensions.cs | 69 +-
.../Features/Effects/CustomPlayerEffect.cs | 105 ++-
.../Effects/CustomTickingPlayerEffect.cs | 79 ++-
.../Features/Effects/SprintDisablerEffect.cs | 45 +-
.../Effects/StaminaUsageDisablerEffect.cs | 29 +-
.../Effects/TemporaryDamageImmunity.cs | 31 +-
SecretAPI/Features/IPriority.cs | 17 +-
SecretAPI/Features/IRegister.cs | 105 ++-
SecretAPI/Features/PrefabManager.cs | 83 ++-
SecretAPI/Features/PrefabStore.cs | 87 ++-
SecretAPI/Features/Registry.cs | 33 +-
.../UserSettings/CustomButtonSetting.cs | 115 ++-
.../UserSettings/CustomDropdownSetting.cs | 197 +++---
.../Features/UserSettings/CustomHeader.cs | 44 +-
.../UserSettings/CustomKeybindSetting.cs | 85 ++-
.../UserSettings/CustomPlainTextSetting.cs | 189 +++--
.../Features/UserSettings/CustomSetting.cs | 653 +++++++++---------
.../UserSettings/CustomSliderSetting.cs | 275 ++++----
.../UserSettings/CustomTextAreaSetting.cs | 101 ++-
.../UserSettings/CustomTwoButtonSetting.cs | 185 +++--
SecretAPI/Features/UserSettings/ISetting.cs | 25 +-
.../UserSettings/SettingResponseType.cs | 33 +-
.../Features/SendSettingsPlayerSync.cs | 35 +-
.../Features/SendSettingsServerSync.cs | 33 +-
.../Features/SettingsOriginalDefinitionFix.cs | 39 +-
.../Features/SettingsSyncValidateFix.cs | 39 +-
SecretAPI/SecretApi.cs | 93 ++-
42 files changed, 1955 insertions(+), 1998 deletions(-)
diff --git a/.editorconfig b/.editorconfig
index 5bb1630..5ab906b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,4 +4,5 @@ csharp_style_var_when_type_is_apparent = false:error
csharp_style_var_elsewhere = false:error
csharp_using_directive_placement = inside_namespace:error
csharp_prefer_braces = false
-dotnet_code_quality_unused_parameters = non_public:none
\ No newline at end of file
+dotnet_code_quality_unused_parameters = non_public:none
+csharp_style_namespace_declarations = file_scoped:error
\ No newline at end of file
diff --git a/SecretAPI.Examples/ExampleEntry.cs b/SecretAPI.Examples/ExampleEntry.cs
index 32ee205..6dd27b0 100644
--- a/SecretAPI.Examples/ExampleEntry.cs
+++ b/SecretAPI.Examples/ExampleEntry.cs
@@ -1,41 +1,38 @@
-namespace SecretAPI.Examples
+namespace SecretAPI.Examples;
+
+using System;
+using LabApi.Loader.Features.Plugins;
+using SecretAPI.Examples.Settings;
+using SecretAPI.Features.UserSettings;
+
+///
+/// Defines the entry for the plugin.
+///
+public class ExampleEntry : Plugin
{
- using System;
- using System.Reflection;
- using HarmonyLib;
- using LabApi.Loader.Features.Plugins;
- using SecretAPI.Examples.Settings;
- using SecretAPI.Features.UserSettings;
-
- ///
- /// Defines the entry for the plugin.
- ///
- public class ExampleEntry : Plugin
- {
- ///
- public override string Name { get; } = "SecretAPI.Examples";
+ ///
+ public override string Name { get; } = "SecretAPI.Examples";
- ///
- public override string Description { get; } = "An example plugin";
+ ///
+ public override string Description { get; } = "An example plugin";
- ///
- public override string Author { get; } = "@misfiy / @obvEvelyn";
+ ///
+ public override string Author { get; } = "@misfiy / @obvEvelyn";
- ///
- public override Version Version { get; } = typeof(SecretApi).Assembly.GetName().Version;
+ ///
+ public override Version Version { get; } = typeof(SecretApi).Assembly.GetName().Version;
- ///
- public override Version RequiredApiVersion { get; } = new(LabApi.Features.LabApiProperties.CompiledVersion);
+ ///
+ public override Version RequiredApiVersion { get; } = new(LabApi.Features.LabApiProperties.CompiledVersion);
- ///
- public override void Enable()
- {
- CustomSetting.Register(new ExampleKeybindSetting(), new ExampleDropdownSetting());
- }
+ ///
+ public override void Enable()
+ {
+ CustomSetting.Register(new ExampleKeybindSetting(), new ExampleDropdownSetting());
+ }
- ///
- public override void Disable()
- {
- }
+ ///
+ public override void Disable()
+ {
}
}
\ No newline at end of file
diff --git a/SecretAPI.Examples/Patches/ExamplePatch.cs b/SecretAPI.Examples/Patches/ExamplePatch.cs
index 167939b..29cd386 100644
--- a/SecretAPI.Examples/Patches/ExamplePatch.cs
+++ b/SecretAPI.Examples/Patches/ExamplePatch.cs
@@ -1,24 +1,23 @@
-namespace SecretAPI.Examples.Patches
-{
- using SecretAPI.Attributes;
+namespace SecretAPI.Examples.Patches;
+
+using SecretAPI.Attributes;
- ///
- /// An example harmony patch.
- ///
- [HarmonyPatchCategory(nameof(ExampleEntry))]
- /*[HarmonyPatch]*/
- public static class ExamplePatch
+///
+/// An example harmony patch.
+///
+[HarmonyPatchCategory(nameof(ExampleEntry))]
+/*[HarmonyPatch]*/
+public static class ExamplePatch
+{
+ // gets called before the original method is called
+ private static bool Prefix()
{
- // gets called before the original method is called
- private static bool Prefix()
- {
- // prevent original method from running
- return false;
- }
+ // prevent original method from running
+ return false;
+ }
- // gets called after the original method is called
- private static void Postfix()
- {
- }
+ // gets called after the original method is called
+ private static void Postfix()
+ {
}
}
\ No newline at end of file
diff --git a/SecretAPI.Examples/Settings/ExampleDropdownSetting.cs b/SecretAPI.Examples/Settings/ExampleDropdownSetting.cs
index c47c8d6..f3a17a0 100644
--- a/SecretAPI.Examples/Settings/ExampleDropdownSetting.cs
+++ b/SecretAPI.Examples/Settings/ExampleDropdownSetting.cs
@@ -1,44 +1,43 @@
-namespace SecretAPI.Examples.Settings
+namespace SecretAPI.Examples.Settings;
+
+using LabApi.Features.Console;
+using LabApi.Features.Permissions;
+using SecretAPI.Features.UserSettings;
+
+///
+/// Example version of .
+///
+public class ExampleDropdownSetting : CustomDropdownSetting
{
- using LabApi.Features.Console;
- using LabApi.Features.Permissions;
- using SecretAPI.Features.UserSettings;
+ private static readonly string[] ExampleOptions = ["hi", "test", "yum", "fish", "nugget"];
+ private static readonly string[] ExampleSupporterOptions = ["bucket", "lava", "wanted", "globe"];
///
- /// Example version of .
+ /// Initializes a new instance of the class.
///
- public class ExampleDropdownSetting : CustomDropdownSetting
+ public ExampleDropdownSetting()
+ : base(901, "Example dropdown", ExampleOptions)
+ {
+ }
+
+ ///
+ public override CustomHeader Header => CustomHeader.Examples;
+
+ ///
+ protected override CustomSetting CreateDuplicate() => new ExampleDropdownSetting();
+
+ ///
+ protected override void PersonalizeSetting()
+ {
+ if (KnownOwner == null || !KnownOwner.HasAnyPermission("example.supporter"))
+ return;
+
+ Options = ExampleSupporterOptions;
+ }
+
+ ///
+ protected override void HandleSettingUpdate()
{
- private static readonly string[] ExampleOptions = ["hi", "test", "yum", "fish", "nugget"];
- private static readonly string[] ExampleSupporterOptions = ["bucket", "lava", "wanted", "globe"];
-
- ///
- /// Initializes a new instance of the class.
- ///
- public ExampleDropdownSetting()
- : base(901, "Example dropdown", ExampleOptions)
- {
- }
-
- ///
- public override CustomHeader Header => CustomHeader.Examples;
-
- ///
- protected override CustomSetting CreateDuplicate() => new ExampleDropdownSetting();
-
- ///
- protected override void PersonalizeSetting()
- {
- if (KnownOwner == null || !KnownOwner.HasAnyPermission("example.supporter"))
- return;
-
- Options = ExampleSupporterOptions;
- }
-
- ///
- protected override void HandleSettingUpdate()
- {
- Logger.Info($"{KnownOwner?.DisplayName ?? "(Null Owner - What went wrong?)"} selected {SelectedOption} (Index {ValidatedSelectedIndex}/{Options.Length - 1})");
- }
+ Logger.Info($"{KnownOwner?.DisplayName ?? "(Null Owner - What went wrong?)"} selected {SelectedOption} (Index {ValidatedSelectedIndex}/{Options.Length - 1})");
}
}
\ No newline at end of file
diff --git a/SecretAPI.Examples/Settings/ExampleKeybindSetting.cs b/SecretAPI.Examples/Settings/ExampleKeybindSetting.cs
index a51f541..df726cb 100644
--- a/SecretAPI.Examples/Settings/ExampleKeybindSetting.cs
+++ b/SecretAPI.Examples/Settings/ExampleKeybindSetting.cs
@@ -1,38 +1,37 @@
-namespace SecretAPI.Examples.Settings
-{
- using LabApi.Features.Wrappers;
- using SecretAPI.Features.UserSettings;
- using UnityEngine;
+namespace SecretAPI.Examples.Settings;
+
+using LabApi.Features.Wrappers;
+using SecretAPI.Features.UserSettings;
+using UnityEngine;
+///
+/// Example setting for keybinds.
+///
+public class ExampleKeybindSetting : CustomKeybindSetting
+{
///
- /// Example setting for keybinds.
+ /// Initializes a new instance of the class.
///
- public class ExampleKeybindSetting : CustomKeybindSetting
+ public ExampleKeybindSetting()
+ : base(900, "Example Kill Button", KeyCode.G, allowSpectatorTrigger: false)
{
- ///
- /// Initializes a new instance of the class.
- ///
- public ExampleKeybindSetting()
- : base(900, "Example Kill Button", KeyCode.G, allowSpectatorTrigger: false)
- {
- }
+ }
- ///
- public override CustomHeader Header => CustomHeader.Examples;
+ ///
+ public override CustomHeader Header => CustomHeader.Examples;
- ///
- protected override bool CanView(Player player) => player.RemoteAdminAccess;
+ ///
+ protected override bool CanView(Player player) => player.RemoteAdminAccess;
- ///
- protected override CustomSetting CreateDuplicate() => new ExampleKeybindSetting();
+ ///
+ protected override CustomSetting CreateDuplicate() => new ExampleKeybindSetting();
- ///
- protected override void HandleSettingUpdate()
- {
- if (!IsPressed)
- return;
+ ///
+ protected override void HandleSettingUpdate()
+ {
+ if (!IsPressed)
+ return;
- KnownOwner?.Kill();
- }
+ KnownOwner?.Kill();
}
}
\ No newline at end of file
diff --git a/SecretAPI/Attributes/CallOnLoadAttribute.cs b/SecretAPI/Attributes/CallOnLoadAttribute.cs
index 32bcd34..4ece8ce 100644
--- a/SecretAPI/Attributes/CallOnLoadAttribute.cs
+++ b/SecretAPI/Attributes/CallOnLoadAttribute.cs
@@ -1,74 +1,73 @@
-namespace SecretAPI.Attributes
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
- using SecretAPI.Features;
+namespace SecretAPI.Attributes;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using SecretAPI.Features;
+///
+/// Defines the attribute for methods to call on load.
+///
+[AttributeUsage(AttributeTargets.Method)]
+public class CallOnLoadAttribute : Attribute, IPriority
+{
///
- /// Defines the attribute for methods to call on load.
+ /// Initializes a new instance of the class.
///
- [AttributeUsage(AttributeTargets.Method)]
- public class CallOnLoadAttribute : Attribute, IPriority
+ /// The priority of the load.
+ public CallOnLoadAttribute(int priority = 0)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The priority of the load.
- public CallOnLoadAttribute(int priority = 0)
- {
- Priority = priority;
- }
+ Priority = priority;
+ }
- ///
- /// Gets the priority of the loading.
- ///
- public int Priority { get; }
+ ///
+ /// Gets the priority of the loading.
+ ///
+ public int Priority { get; }
- ///
- /// Loads and calls all .
- ///
- /// The assembly to begin this on. Null will attempt to get calling, but may fail.
- public static void Load(Assembly? assembly = null)
- {
- assembly ??= Assembly.GetCallingAssembly();
- CallAttributeMethodPriority(assembly);
- }
+ ///
+ /// Loads and calls all .
+ ///
+ /// The assembly to begin this on. Null will attempt to get calling, but may fail.
+ public static void Load(Assembly? assembly = null)
+ {
+ assembly ??= Assembly.GetCallingAssembly();
+ CallAttributeMethodPriority(assembly);
+ }
- ///
- public override bool Equals(object? obj) => obj == this;
+ ///
+ public override bool Equals(object? obj) => obj == this;
- ///
- public override int GetHashCode() => base.GetHashCode();
+ ///
+ public override int GetHashCode() => base.GetHashCode();
- ///
- /// Calls the method associated with a Attribute implementing .
- ///
- /// The assembly to handle calls from.
- /// The attribute required for this to be called.
- internal static void CallAttributeMethodPriority(Assembly assembly)
- where TAttribute : Attribute, IPriority
- {
- const BindingFlags methodFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
- Dictionary methods = new();
+ ///
+ /// Calls the method associated with a Attribute implementing .
+ ///
+ /// The assembly to handle calls from.
+ /// The attribute required for this to be called.
+ internal static void CallAttributeMethodPriority(Assembly assembly)
+ where TAttribute : Attribute, IPriority
+ {
+ const BindingFlags methodFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
+ Dictionary methods = new();
- // get all types
- foreach (Type type in assembly.GetTypes())
+ // get all types
+ foreach (Type type in assembly.GetTypes())
+ {
+ // get all static methods
+ foreach (MethodInfo method in type.GetMethods(methodFlags))
{
- // get all static methods
- foreach (MethodInfo method in type.GetMethods(methodFlags))
- {
- TAttribute? attribute = method.GetCustomAttribute();
- if (attribute == null)
- continue;
+ TAttribute? attribute = method.GetCustomAttribute();
+ if (attribute == null)
+ continue;
- methods.Add(attribute, method);
- }
+ methods.Add(attribute, method);
}
-
- foreach (KeyValuePair method in methods.OrderBy(static v => v.Key.Priority))
- method.Value.Invoke(null, null);
}
+
+ foreach (KeyValuePair method in methods.OrderBy(static v => v.Key.Priority))
+ method.Value.Invoke(null, null);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Attributes/CallOnUnloadAttribute.cs b/SecretAPI/Attributes/CallOnUnloadAttribute.cs
index 65da120..86eb13f 100644
--- a/SecretAPI/Attributes/CallOnUnloadAttribute.cs
+++ b/SecretAPI/Attributes/CallOnUnloadAttribute.cs
@@ -1,43 +1,42 @@
-namespace SecretAPI.Attributes
-{
- using System;
- using System.Reflection;
- using SecretAPI.Features;
+namespace SecretAPI.Attributes;
+
+using System;
+using System.Reflection;
+using SecretAPI.Features;
+///
+/// Defines the attribute for methods to call on unload.
+///
+[AttributeUsage(AttributeTargets.Method)]
+public class CallOnUnloadAttribute : Attribute, IPriority
+{
///
- /// Defines the attribute for methods to call on unload.
+ /// Initializes a new instance of the class.
///
- [AttributeUsage(AttributeTargets.Method)]
- public class CallOnUnloadAttribute : Attribute, IPriority
+ /// The priority of the load.
+ public CallOnUnloadAttribute(int priority = 0)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The priority of the load.
- public CallOnUnloadAttribute(int priority = 0)
- {
- Priority = priority;
- }
+ Priority = priority;
+ }
- ///
- /// Gets the priority of the loading.
- ///
- public int Priority { get; }
+ ///
+ /// Gets the priority of the loading.
+ ///
+ public int Priority { get; }
- ///
- /// Unloads and calls all .
- ///
- /// The assembly to begin this on. Null will attempt to get calling, but may fail.
- public static void Unload(Assembly? assembly = null)
- {
- assembly ??= Assembly.GetCallingAssembly();
- CallOnLoadAttribute.CallAttributeMethodPriority(assembly);
- }
+ ///
+ /// Unloads and calls all .
+ ///
+ /// The assembly to begin this on. Null will attempt to get calling, but may fail.
+ public static void Unload(Assembly? assembly = null)
+ {
+ assembly ??= Assembly.GetCallingAssembly();
+ CallOnLoadAttribute.CallAttributeMethodPriority(assembly);
+ }
- ///
- public override bool Equals(object? obj) => obj == this;
+ ///
+ public override bool Equals(object? obj) => obj == this;
- ///
- public override int GetHashCode() => base.GetHashCode();
- }
+ ///
+ public override int GetHashCode() => base.GetHashCode();
}
\ No newline at end of file
diff --git a/SecretAPI/Attributes/HarmonyPatchCategory.cs b/SecretAPI/Attributes/HarmonyPatchCategory.cs
index 853129d..4257803 100644
--- a/SecretAPI/Attributes/HarmonyPatchCategory.cs
+++ b/SecretAPI/Attributes/HarmonyPatchCategory.cs
@@ -1,26 +1,25 @@
-namespace SecretAPI.Attributes
-{
- using System;
- using SecretAPI.Extensions;
+namespace SecretAPI.Attributes;
+
+using System;
+using SecretAPI.Extensions;
+///
+/// Category handling for .
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class HarmonyPatchCategory : Attribute
+{
///
- /// Category handling for .
+ /// Initializes a new instance of the class.
///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
- public class HarmonyPatchCategory : Attribute
+ /// The category of the patch.
+ public HarmonyPatchCategory(string category)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The category of the patch.
- public HarmonyPatchCategory(string category)
- {
- Category = category;
- }
-
- ///
- /// Gets the patch category.
- ///
- public string Category { get; }
+ Category = category;
}
+
+ ///
+ /// Gets the patch category.
+ ///
+ public string Category { get; }
}
\ No newline at end of file
diff --git a/SecretAPI/Enums/DoorPermissionCheck.cs b/SecretAPI/Enums/DoorPermissionCheck.cs
index ad78cac..c1c38af 100644
--- a/SecretAPI/Enums/DoorPermissionCheck.cs
+++ b/SecretAPI/Enums/DoorPermissionCheck.cs
@@ -1,56 +1,55 @@
-namespace SecretAPI.Enums
+namespace SecretAPI.Enums;
+
+using System;
+using Interactables.Interobjects.DoorUtils;
+using LabApi.Features.Wrappers;
+using PlayerRoles;
+using PlayerStatsSystem;
+using SecretAPI.Extensions;
+
+///
+/// Flags to use for .
+///
+[Flags]
+public enum DoorPermissionCheck
{
- using System;
- using Interactables.Interobjects.DoorUtils;
- using LabApi.Features.Wrappers;
- using PlayerRoles;
- using PlayerStatsSystem;
- using SecretAPI.Extensions;
-
- ///
- /// Flags to use for .
- ///
- [Flags]
- public enum DoorPermissionCheck
- {
- ///
- /// None. Will not check anything.
- ///
- None = 0,
-
- ///
- /// Used to consider .
- ///
- Bypass = 1 << 0,
-
- ///
- /// Used to consider the player's .
- ///
- Role = 1 << 1,
-
- ///
- /// Used to consider the player's .
- ///
- CurrentItem = 1 << 2,
-
- ///
- /// Used to consider the player's inventory, not including the item they are holding.
- ///
- InventoryExcludingCurrent = 1 << 3,
-
- ///
- /// Used to consider the player's ENTIRE inventory.
- ///
- FullInventory = CurrentItem | InventoryExcludingCurrent,
-
- ///
- /// Used to consider all.
- ///
- All = -1,
-
- ///
- /// Used to mirror default base-game checks (bypass mode, role and held item).
- ///
- Default = Bypass | Role | CurrentItem,
- }
+ ///
+ /// None. Will not check anything.
+ ///
+ None = 0,
+
+ ///
+ /// Used to consider .
+ ///
+ Bypass = 1 << 0,
+
+ ///
+ /// Used to consider the player's .
+ ///
+ Role = 1 << 1,
+
+ ///
+ /// Used to consider the player's .
+ ///
+ CurrentItem = 1 << 2,
+
+ ///
+ /// Used to consider the player's inventory, not including the item they are holding.
+ ///
+ InventoryExcludingCurrent = 1 << 3,
+
+ ///
+ /// Used to consider the player's ENTIRE inventory.
+ ///
+ FullInventory = CurrentItem | InventoryExcludingCurrent,
+
+ ///
+ /// Used to consider all.
+ ///
+ All = -1,
+
+ ///
+ /// Used to mirror default base-game checks (bypass mode, role and held item).
+ ///
+ Default = Bypass | Role | CurrentItem,
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/CollectionExtensions.cs b/SecretAPI/Extensions/CollectionExtensions.cs
index 83af57e..969a9d2 100644
--- a/SecretAPI/Extensions/CollectionExtensions.cs
+++ b/SecretAPI/Extensions/CollectionExtensions.cs
@@ -1,48 +1,47 @@
-namespace SecretAPI.Extensions
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using Random = UnityEngine.Random;
+namespace SecretAPI.Extensions;
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Random = UnityEngine.Random;
- ///
- /// Extensions for collections.
- ///
- public static class CollectionExtensions
+///
+/// Extensions for collections.
+///
+public static class CollectionExtensions
+{
+ /// The collection to pull from.
+ /// The Type contained by the collection.
+ extension(IEnumerable collection)
{
- /// The collection to pull from.
- /// The Type contained by the collection.
- extension(IEnumerable collection)
+ ///
+ /// Gets a random value from the collection.
+ ///
+ /// A random value, default value when empty collection.
+ /// Will occur if the collection is empty.
+ public T GetRandomValue()
{
- ///
- /// Gets a random value from the collection.
- ///
- /// A random value, default value when empty collection.
- /// Will occur if the collection is empty.
- public T GetRandomValue()
- {
- TryGetRandomValue(collection, out T? value);
- return value!;
- }
+ TryGetRandomValue(collection, out T? value);
+ return value!;
+ }
- ///
- /// Tries to get a random value from .
- ///
- /// The value that was found. Default if none could be found.
- /// Whether a non-null value was found.
- public bool TryGetRandomValue([NotNullWhen(true)] out T? value)
+ ///
+ /// Tries to get a random value from .
+ ///
+ /// The value that was found. Default if none could be found.
+ /// Whether a non-null value was found.
+ public bool TryGetRandomValue([NotNullWhen(true)] out T? value)
+ {
+ IList list = collection as IList ?? collection.ToList();
+ if (list.Count == 0)
{
- IList list = collection as IList ?? collection.ToList();
- if (list.Count == 0)
- {
- value = default;
- return false;
- }
-
- value = list[Random.Range(0, list.Count)];
- return value != null;
+ value = default;
+ return false;
}
+
+ value = list[Random.Range(0, list.Count)];
+ return value != null;
}
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/HarmonyExtensions.cs b/SecretAPI/Extensions/HarmonyExtensions.cs
index f26b436..1eee37b 100644
--- a/SecretAPI/Extensions/HarmonyExtensions.cs
+++ b/SecretAPI/Extensions/HarmonyExtensions.cs
@@ -1,68 +1,67 @@
-namespace SecretAPI.Extensions
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
- using HarmonyLib;
- using LabApi.Features.Console;
- using SecretAPI.Attributes;
+namespace SecretAPI.Extensions;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using HarmonyLib;
+using LabApi.Features.Console;
+using SecretAPI.Attributes;
- ///
- /// Handles patching.
- ///
- public static class HarmonyExtensions
+///
+/// Handles patching.
+///
+public static class HarmonyExtensions
+{
+ /// The harmony to use for the patch.
+ extension(Harmony harmony)
{
- /// The harmony to use for the patch.
- extension(Harmony harmony)
+ ///
+ /// Patches all methods with the proper .
+ ///
+ /// The category to patch.
+ /// The assembly to find patches in.
+ public void PatchCategory(string category, Assembly? assembly = null)
{
- ///
- /// Patches all methods with the proper .
- ///
- /// The category to patch.
- /// The assembly to find patches in.
- public void PatchCategory(string category, Assembly? assembly = null)
- {
- assembly ??= Assembly.GetCallingAssembly();
+ assembly ??= Assembly.GetCallingAssembly();
- assembly.GetTypes().Where(type =>
- {
- IEnumerable categories = type.GetCustomAttributes();
- return categories.Any(c => c.Category == category);
- })
- .Do(type => SafePatch(harmony, type));
- }
+ assembly.GetTypes().Where(type =>
+ {
+ IEnumerable categories = type.GetCustomAttributes();
+ return categories.Any(c => c.Category == category);
+ })
+ .Do(type => SafePatch(harmony, type));
+ }
- ///
- /// Patches all patches that don't have a .
- ///
- /// The assembly to look for patches.
- public void PatchAllNoCategory(Assembly? assembly = null)
- {
- assembly ??= Assembly.GetCallingAssembly();
+ ///
+ /// Patches all patches that don't have a .
+ ///
+ /// The assembly to look for patches.
+ public void PatchAllNoCategory(Assembly? assembly = null)
+ {
+ assembly ??= Assembly.GetCallingAssembly();
- assembly.GetTypes().Where(type =>
- {
- IEnumerable categories = type.GetCustomAttributes();
- return !categories.Any();
- })
- .Do(type => SafePatch(harmony, type));
- }
+ assembly.GetTypes().Where(type =>
+ {
+ IEnumerable categories = type.GetCustomAttributes();
+ return !categories.Any();
+ })
+ .Do(type => SafePatch(harmony, type));
+ }
- ///
- /// Attempts to safely patch a , logging any errors.
- ///
- /// The to attempt to patch.
- public void SafePatch(Type type)
+ ///
+ /// Attempts to safely patch a , logging any errors.
+ ///
+ /// The to attempt to patch.
+ public void SafePatch(Type type)
+ {
+ try
{
- try
- {
- harmony.CreateClassProcessor(type).Patch();
- }
- catch (Exception ex)
- {
- Logger.Error($"[HarmonyExtensions] failed to safely patch {harmony.Id} ({type.FullName}): {ex}");
- }
+ harmony.CreateClassProcessor(type).Patch();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"[HarmonyExtensions] failed to safely patch {harmony.Id} ({type.FullName}): {ex}");
}
}
}
diff --git a/SecretAPI/Extensions/MirrorExtensions.cs b/SecretAPI/Extensions/MirrorExtensions.cs
index 30aea4e..4b2dc81 100644
--- a/SecretAPI/Extensions/MirrorExtensions.cs
+++ b/SecretAPI/Extensions/MirrorExtensions.cs
@@ -1,66 +1,65 @@
-namespace SecretAPI.Extensions
-{
- using System;
- using System.Reflection;
- using LabApi.Features.Console;
- using LabApi.Features.Wrappers;
- using Mirror;
+namespace SecretAPI.Extensions;
+
+using System;
+using System.Reflection;
+using LabApi.Features.Console;
+using LabApi.Features.Wrappers;
+using Mirror;
+///
+/// Extensions related to Mirror.
+///
+public static class MirrorExtensions
+{
///
- /// Extensions related to Mirror.
+ /// Send a fake rpc message to a player.
///
- public static class MirrorExtensions
+ /// The target to send the rpc to.
+ /// The network behaviour containing the rpc.
+ /// The type containing the rpc.
+ /// The name of the rpc to call.
+ /// The values to write to the writer.
+ public static void SendFakeRpcMessage(this Player target, NetworkBehaviour behaviour, Type type, string rpcName, params object[] values)
{
- ///
- /// Send a fake rpc message to a player.
- ///
- /// The target to send the rpc to.
- /// The network behaviour containing the rpc.
- /// The type containing the rpc.
- /// The name of the rpc to call.
- /// The values to write to the writer.
- public static void SendFakeRpcMessage(this Player target, NetworkBehaviour behaviour, Type type, string rpcName, params object[] values)
- {
- NetworkWriterPooled pooledWriter = NetworkWriterPool.Get();
+ NetworkWriterPooled pooledWriter = NetworkWriterPool.Get();
- foreach (object obj in values)
- ProperWrite(pooledWriter, obj);
+ foreach (object obj in values)
+ ProperWrite(pooledWriter, obj);
- RpcMessage rpcMessage = new()
- {
- netId = behaviour.netId,
- componentIndex = behaviour.ComponentIndex,
- functionHash = (ushort)ReflectionExtensions.GetLongFuncName(type, rpcName).GetStableHashCode(),
- payload = pooledWriter.ToArraySegment(),
- };
+ RpcMessage rpcMessage = new()
+ {
+ netId = behaviour.netId,
+ componentIndex = behaviour.ComponentIndex,
+ functionHash = (ushort)ReflectionExtensions.GetLongFuncName(type, rpcName).GetStableHashCode(),
+ payload = pooledWriter.ToArraySegment(),
+ };
- target.Connection.Send(rpcMessage);
- NetworkWriterPool.Return(pooledWriter);
- }
+ target.Connection.Send(rpcMessage);
+ NetworkWriterPool.Return(pooledWriter);
+ }
- ///
- /// Handles writing into a .
- ///
- /// The writer to write the object to.
- /// The object to write.
- public static void ProperWrite(this NetworkWriter writer, object obj)
+ ///
+ /// Handles writing into a .
+ ///
+ /// The writer to write the object to.
+ /// The object to write.
+ public static void ProperWrite(this NetworkWriter writer, object obj)
+ {
+ Type genericType = typeof(Writer<>).MakeGenericType(obj.GetType());
+ FieldInfo? writeField = genericType.GetField("write", BindingFlags.Static | BindingFlags.Public);
+ if (writeField == null)
{
- Type genericType = typeof(Writer<>).MakeGenericType(obj.GetType());
- FieldInfo? writeField = genericType.GetField("write", BindingFlags.Static | BindingFlags.Public);
- if (writeField == null)
- {
- Logger.Warn($"Tried to write type: {obj.GetType()} but has no NetworkWriter!");
- return;
- }
-
- object? writeDelegate = writeField.GetValue(null);
- if (writeDelegate is not Delegate del)
- {
- Logger.Warn($"Writer<{obj.GetType()}>.write is not a delegate!");
- return;
- }
+ Logger.Warn($"Tried to write type: {obj.GetType()} but has no NetworkWriter!");
+ return;
+ }
- del.DynamicInvoke(writer, obj);
+ object? writeDelegate = writeField.GetValue(null);
+ if (writeDelegate is not Delegate del)
+ {
+ Logger.Warn($"Writer<{obj.GetType()}>.write is not a delegate!");
+ return;
}
+
+ del.DynamicInvoke(writer, obj);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/PlayerExtensions.cs b/SecretAPI/Extensions/PlayerExtensions.cs
index d2a6737..b4a8a18 100644
--- a/SecretAPI/Extensions/PlayerExtensions.cs
+++ b/SecretAPI/Extensions/PlayerExtensions.cs
@@ -1,82 +1,81 @@
-namespace SecretAPI.Extensions
-{
- using CustomPlayerEffects;
- using Interactables.Interobjects.DoorUtils;
- using LabApi.Features.Wrappers;
- using SecretAPI.Enums;
+namespace SecretAPI.Extensions;
- ///
- /// Extensions related to the player.
- ///
- public static class PlayerExtensions
- {
- /// The player to get effect from.
- extension(Player player)
- {
- ///
- /// Gets an effect of a player based on the effect name.
- ///
- /// Name of the effect to find.
- /// The effect.
- public StatusEffectBase GetEffect(string name)
- => player.ReferenceHub.playerEffectsController.TryGetEffect(name, out StatusEffectBase? effect) ? effect : null!;
+using CustomPlayerEffects;
+using Interactables.Interobjects.DoorUtils;
+using LabApi.Features.Wrappers;
+using SecretAPI.Enums;
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The requester to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public bool HasDoorPermission(IDoorPermissionRequester requester, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- {
- if (checkFlags.HasFlag(DoorPermissionCheck.Bypass) && player.IsBypassEnabled)
- return true;
+///
+/// Extensions related to the player.
+///
+public static class PlayerExtensions
+{
+ /// The player to get effect from.
+ extension(Player player)
+ {
+ ///
+ /// Gets an effect of a player based on the effect name.
+ ///
+ /// Name of the effect to find.
+ /// The effect.
+ public StatusEffectBase GetEffect(string name)
+ => player.ReferenceHub.playerEffectsController.TryGetEffect(name, out StatusEffectBase? effect) ? effect : null!;
- if (checkFlags.HasFlag(DoorPermissionCheck.Role) && player.RoleBase is IDoorPermissionProvider roleProvider && requester.PermissionsPolicy.CheckPermissions(roleProvider.GetPermissions(requester)))
- return true;
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The requester to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasDoorPermission(IDoorPermissionRequester requester, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ {
+ if (checkFlags.HasFlag(DoorPermissionCheck.Bypass) && player.IsBypassEnabled)
+ return true;
- foreach (Item item in player.Items)
- {
- bool isCurrent = item == player.CurrentItem;
- if (!checkFlags.HasFlag(DoorPermissionCheck.CurrentItem) && isCurrent)
- continue;
+ if (checkFlags.HasFlag(DoorPermissionCheck.Role) && player.RoleBase is IDoorPermissionProvider roleProvider && requester.PermissionsPolicy.CheckPermissions(roleProvider.GetPermissions(requester)))
+ return true;
- if (!checkFlags.HasFlag(DoorPermissionCheck.InventoryExcludingCurrent) && !isCurrent)
- continue;
+ foreach (Item item in player.Items)
+ {
+ bool isCurrent = item == player.CurrentItem;
+ if (!checkFlags.HasFlag(DoorPermissionCheck.CurrentItem) && isCurrent)
+ continue;
- if (item.Base is IDoorPermissionProvider itemProvider && requester.PermissionsPolicy.CheckPermissions(itemProvider.GetPermissions(requester)))
- return true;
- }
+ if (!checkFlags.HasFlag(DoorPermissionCheck.InventoryExcludingCurrent) && !isCurrent)
+ continue;
- return false;
+ if (item.Base is IDoorPermissionProvider itemProvider && requester.PermissionsPolicy.CheckPermissions(itemProvider.GetPermissions(requester)))
+ return true;
}
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The door to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public bool HasDoorPermission(Door door, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- => player.HasDoorPermission(door.Base, checkFlags);
+ return false;
+ }
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The locker chamber to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public bool HasLockerChamberPermission(LockerChamber chamber, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- => player.HasDoorPermission(chamber.Base, checkFlags);
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The door to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasDoorPermission(Door door, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ => player.HasDoorPermission(door.Base, checkFlags);
- ///
- /// Checks whether a player has permission to access a .
- ///
- /// The generator to check for permissions.
- /// The to use for checking if a player has it.
- /// Whether a valid permission was found.
- public bool HasGeneratorPermission(Generator generator, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
- => player.HasDoorPermission(generator.Base, checkFlags);
- }
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The locker chamber to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasLockerChamberPermission(LockerChamber chamber, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ => player.HasDoorPermission(chamber.Base, checkFlags);
+
+ ///
+ /// Checks whether a player has permission to access a .
+ ///
+ /// The generator to check for permissions.
+ /// The to use for checking if a player has it.
+ /// Whether a valid permission was found.
+ public bool HasGeneratorPermission(Generator generator, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
+ => player.HasDoorPermission(generator.Base, checkFlags);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/ReflectionExtensions.cs b/SecretAPI/Extensions/ReflectionExtensions.cs
index 987ca0b..f367c6e 100644
--- a/SecretAPI/Extensions/ReflectionExtensions.cs
+++ b/SecretAPI/Extensions/ReflectionExtensions.cs
@@ -1,52 +1,51 @@
-namespace SecretAPI.Extensions
-{
- using System;
- using System.Linq;
- using System.Reflection;
+namespace SecretAPI.Extensions;
+
+using System;
+using System.Linq;
+using System.Reflection;
+///
+/// Extensions for reflection.
+///
+public static class ReflectionExtensions
+{
///
- /// Extensions for reflection.
+ /// Gets the long name of a function.
///
- public static class ReflectionExtensions
+ /// The type containing the method.
+ /// The method name.
+ /// The long function name.
+ /// When the method could not be found.
+ public static string GetLongFuncName(Type type, string methodName)
{
- ///
- /// Gets the long name of a function.
- ///
- /// The type containing the method.
- /// The method name.
- /// The long function name.
- /// When the method could not be found.
- public static string GetLongFuncName(Type type, string methodName)
- {
- const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
+ const BindingFlags methodFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
- MethodInfo method = type.GetMethod(methodName, methodFlags) ?? throw new InvalidOperationException($"[ReflectionExtensions.GetLongFuncName] {type.FullName}.{methodName} could not be found.");
- return GetLongFuncName(type, method);
- }
+ MethodInfo method = type.GetMethod(methodName, methodFlags) ?? throw new InvalidOperationException($"[ReflectionExtensions.GetLongFuncName] {type.FullName}.{methodName} could not be found.");
+ return GetLongFuncName(type, method);
+ }
- ///
- /// Gets the long name of a function.
- ///
- /// The type containing the method.
- /// The method to use.
- /// The long function name.
- public static string GetLongFuncName(Type type, MethodInfo method)
- {
- return $"{method.ReturnType.FullName} {type.FullName}::{method.Name}({string.Join(",", method.GetParameters().Select(x => x.ParameterType.FullName))})";
- }
+ ///
+ /// Gets the long name of a function.
+ ///
+ /// The type containing the method.
+ /// The method to use.
+ /// The long function name.
+ public static string GetLongFuncName(Type type, MethodInfo method)
+ {
+ return $"{method.ReturnType.FullName} {type.FullName}::{method.Name}({string.Join(",", method.GetParameters().Select(x => x.ParameterType.FullName))})";
+ }
- ///
- /// Copies the properties.
- ///
- /// The source of the properties to copy.
- /// Where to copy to.
- public static void CopyProperties(this object source, object destination)
+ ///
+ /// Copies the properties.
+ ///
+ /// The source of the properties to copy.
+ /// Where to copy to.
+ public static void CopyProperties(this object source, object destination)
+ {
+ Type destinationType = destination.GetType();
+ foreach (PropertyInfo property in source.GetType().GetProperties())
{
- Type destinationType = destination.GetType();
- foreach (PropertyInfo property in source.GetType().GetProperties())
- {
- destinationType.GetProperty(property.Name)?.SetValue(destination, property.GetValue(source));
- }
+ destinationType.GetProperty(property.Name)?.SetValue(destination, property.GetValue(source));
}
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/RoomExtensions.cs b/SecretAPI/Extensions/RoomExtensions.cs
index 41629af..5225af3 100644
--- a/SecretAPI/Extensions/RoomExtensions.cs
+++ b/SecretAPI/Extensions/RoomExtensions.cs
@@ -1,41 +1,40 @@
-namespace SecretAPI.Extensions
+namespace SecretAPI.Extensions;
+
+using System.Collections.Generic;
+using LabApi.Features.Wrappers;
+using MapGeneration;
+using UnityEngine;
+
+///
+/// Extensions related to rooms.
+///
+public static class RoomExtensions
{
- using System.Collections.Generic;
- using LabApi.Features.Wrappers;
- using MapGeneration;
- using UnityEngine;
+ private static readonly List KnownUnsafeRooms =
+ [
+ RoomName.HczTesla, // Instant death
+ RoomName.EzEvacShelter, // Stuck permanently
+ RoomName.EzCollapsedTunnel, // Stuck permanently
+ RoomName.HczWaysideIncinerator, // Death
+ RoomName.Hcz096, // Void
+ ];
///
- /// Extensions related to rooms.
+ /// Gets whether a room is safe to teleport to. Will consider decontamination, warhead, teslas and void rooms.
///
- public static class RoomExtensions
+ /// The room to check.
+ /// Whether the room is safe to teleport to.
+ public static bool IsSafeToTeleport(this Room room)
{
- private static readonly List KnownUnsafeRooms =
- [
- RoomName.HczTesla, // Instant death
- RoomName.EzEvacShelter, // Stuck permanently
- RoomName.EzCollapsedTunnel, // Stuck permanently
- RoomName.HczWaysideIncinerator, // Death
- RoomName.Hcz096, // Void
- ];
-
- ///
- /// Gets whether a room is safe to teleport to. Will consider decontamination, warhead, teslas and void rooms.
- ///
- /// The room to check.
- /// Whether the room is safe to teleport to.
- public static bool IsSafeToTeleport(this Room room)
- {
- if (Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
- return false;
+ if (Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
+ return false;
- if (Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
- return false;
+ if (Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
+ return false;
- if (KnownUnsafeRooms.Contains(room.Name))
- return false;
+ if (KnownUnsafeRooms.Contains(room.Name))
+ return false;
- return Physics.Raycast(room.Position, Vector3.down, out _, 2);
- }
+ return Physics.Raycast(room.Position, Vector3.down, out _, 2);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Scp914Extensions.cs b/SecretAPI/Extensions/Scp914Extensions.cs
index ad13efb..cab6b07 100644
--- a/SecretAPI/Extensions/Scp914Extensions.cs
+++ b/SecretAPI/Extensions/Scp914Extensions.cs
@@ -1,45 +1,44 @@
-namespace SecretAPI.Extensions
-{
- using System.Linq;
- using LabApi.Features.Interfaces;
- using LabApi.Features.Wrappers;
- using Scp914;
+namespace SecretAPI.Extensions;
+
+using System.Linq;
+using LabApi.Features.Interfaces;
+using LabApi.Features.Wrappers;
+using Scp914;
+///
+/// Extensions related to SCP-914.
+///
+public static class Scp914Extensions
+{
///
- /// Extensions related to SCP-914.
+ /// Process Player like 914 Without Being in 914.
///
- public static class Scp914Extensions
+ /// The player.
+ /// If it should upgrade only the held item.
+ /// The knob setting.
+ public static void Process914Player(this Player player, bool heldOnly, Scp914KnobSetting setting)
{
- ///
- /// Process Player like 914 Without Being in 914.
- ///
- /// The player.
- /// If it should upgrade only the held item.
- /// The knob setting.
- public static void Process914Player(this Player player, bool heldOnly, Scp914KnobSetting setting)
+ if (heldOnly)
{
- if (heldOnly)
- {
- Process914Item(player.CurrentItem, setting);
- return;
- }
-
- foreach (Item item in player.Items.ToList())
- Process914Item(item, setting);
+ Process914Item(player.CurrentItem, setting);
+ return;
}
- ///
- /// Processes an item in 914.
- ///
- /// The item to process.
- /// The setting to process the item on.
- public static void Process914Item(this Item? item, Scp914KnobSetting setting)
- {
- if (item == null)
- return;
+ foreach (Item item in player.Items.ToList())
+ Process914Item(item, setting);
+ }
- IScp914ItemProcessor? processor = Scp914.GetItemProcessor(item.Type);
- processor?.UpgradeItem(setting, item);
- }
+ ///
+ /// Processes an item in 914.
+ ///
+ /// The item to process.
+ /// The setting to process the item on.
+ public static void Process914Item(this Item? item, Scp914KnobSetting setting)
+ {
+ if (item == null)
+ return;
+
+ IScp914ItemProcessor? processor = Scp914.GetItemProcessor(item.Type);
+ processor?.UpgradeItem(setting, item);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/Effects/CustomPlayerEffect.cs b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
index a74b743..6fedb8d 100644
--- a/SecretAPI/Features/Effects/CustomPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
@@ -1,68 +1,67 @@
-namespace SecretAPI.Features.Effects
+namespace SecretAPI.Features.Effects;
+
+using System;
+using System.Collections.Generic;
+using CustomPlayerEffects;
+using LabApi.Features.Wrappers;
+using SecretAPI.Attributes;
+using SecretAPI.Extensions;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using Logger = LabApi.Features.Console.Logger;
+
+///
+/// Handles custom player effects.
+/// Must register to to work.
+///
+public abstract class CustomPlayerEffect : StatusEffectBase
{
- using System;
- using System.Collections.Generic;
- using CustomPlayerEffects;
- using LabApi.Features.Wrappers;
- using SecretAPI.Attributes;
- using SecretAPI.Extensions;
- using UnityEngine;
- using UnityEngine.SceneManagement;
- using Logger = LabApi.Features.Console.Logger;
+ private static bool isLoaded;
///
- /// Handles custom player effects.
- /// Must register to to work.
+ /// Gets a list of types to register (Must inherit ).
+ /// Must be , can be gotten through typeof(Scp207)
///
- public abstract class CustomPlayerEffect : StatusEffectBase
- {
- private static bool isLoaded;
+ public static List EffectsToRegister { get; } = [];
- ///
- /// Gets a list of types to register (Must inherit ).
- /// Must be , can be gotten through typeof(Scp207)
- ///
- public static List EffectsToRegister { get; } = [];
+ ///
+ /// Gets the with this effect.
+ ///
+ public Player Owner => field ??= Player.Get(Hub);
- ///
- /// Gets the with this effect.
- ///
- public Player Owner => field ??= Player.Get(Hub);
+ ///
+ public override string ToString() => $"{GetType().Name}: Owner ({Owner}) - Intensity ({Intensity}) - Duration {Duration}";
- ///
- public override string ToString() => $"{GetType().Name}: Owner ({Owner}) - Intensity ({Intensity}) - Duration {Duration}";
+ ///
+ /// Initializes the to implement .
+ ///
+ [CallOnLoad]
+ internal static void Initialize()
+ {
+ SecretApi.Harmony.PatchCategory(nameof(CustomPlayerEffect), SecretApi.Assembly);
+ EffectsToRegister.Add(typeof(TemporaryDamageImmunity));
+ EffectsToRegister.Add(typeof(StaminaUsageDisablerEffect));
+ EffectsToRegister.Add(typeof(SprintDisablerEffect));
- ///
- /// Initializes the to implement .
- ///
- [CallOnLoad]
- internal static void Initialize()
+ SceneManager.sceneLoaded += (_, _) =>
{
- SecretApi.Harmony.PatchCategory(nameof(CustomPlayerEffect), SecretApi.Assembly);
- EffectsToRegister.Add(typeof(TemporaryDamageImmunity));
- EffectsToRegister.Add(typeof(StaminaUsageDisablerEffect));
- EffectsToRegister.Add(typeof(SprintDisablerEffect));
-
- SceneManager.sceneLoaded += (_, _) =>
- {
- if (isLoaded)
- return;
+ if (isLoaded)
+ return;
- isLoaded = true;
+ isLoaded = true;
- Transform playerEffects = PrefabStore.Prefab.playerEffectsController.effectsGameObject.transform;
- foreach (Type type in EffectsToRegister)
+ Transform playerEffects = PrefabStore.Prefab.playerEffectsController.effectsGameObject.transform;
+ foreach (Type type in EffectsToRegister)
+ {
+ if (!typeof(StatusEffectBase).IsAssignableFrom(type))
{
- if (!typeof(StatusEffectBase).IsAssignableFrom(type))
- {
- Logger.Error($"[CustomPlayerEffect.Initialize] {type.FullName} is not a valid StatusEffectBase and thus could not be registered!");
- continue;
- }
-
- // register effect into prefab
- new GameObject(type.Name, type).transform.parent = playerEffects;
+ Logger.Error($"[CustomPlayerEffect.Initialize] {type.FullName} is not a valid StatusEffectBase and thus could not be registered!");
+ continue;
}
- };
- }
+
+ // register effect into prefab
+ new GameObject(type.Name, type).transform.parent = playerEffects;
+ }
+ };
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/Effects/CustomTickingPlayerEffect.cs b/SecretAPI/Features/Effects/CustomTickingPlayerEffect.cs
index 7cd15b8..b948220 100644
--- a/SecretAPI/Features/Effects/CustomTickingPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomTickingPlayerEffect.cs
@@ -1,47 +1,46 @@
-namespace SecretAPI.Features.Effects
+namespace SecretAPI.Features.Effects;
+
+using CustomPlayerEffects;
+using UnityEngine;
+
+///
+/// Custom Effect for .
+///
+public abstract class CustomTickingPlayerEffect : CustomPlayerEffect
{
- using CustomPlayerEffects;
- using UnityEngine;
+ private float timeTillTick;
+
+ ///
+ /// Gets the time that should pass between each tick.
+ ///
+ protected virtual float TimePerTick => 1;
+
+ ///
+ public override void Enabled()
+ {
+ timeTillTick = TimePerTick;
+ }
+
+ ///
+ public override void OnEffectUpdate()
+ {
+ base.OnEffectUpdate();
+
+ if (TimePerTick == 0)
+ return;
+
+ timeTillTick -= Time.deltaTime;
+ if (timeTillTick > 0)
+ return;
+
+ timeTillTick += TimePerTick;
+ OnTick();
+ }
///
- /// Custom Effect for .
+ /// Called everytime passes.
///
- public abstract class CustomTickingPlayerEffect : CustomPlayerEffect
+ public virtual void OnTick()
{
- private float timeTillTick;
-
- ///
- /// Gets the time that should pass between each tick.
- ///
- protected virtual float TimePerTick => 1;
-
- ///
- public override void Enabled()
- {
- timeTillTick = TimePerTick;
- }
-
- ///
- public override void OnEffectUpdate()
- {
- base.OnEffectUpdate();
-
- if (TimePerTick == 0)
- return;
-
- timeTillTick -= Time.deltaTime;
- if (timeTillTick > 0)
- return;
-
- timeTillTick += TimePerTick;
- OnTick();
- }
-
- ///
- /// Called everytime passes.
- ///
- public virtual void OnTick()
- {
- }
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/Effects/SprintDisablerEffect.cs b/SecretAPI/Features/Effects/SprintDisablerEffect.cs
index de3a15b..44f197d 100644
--- a/SecretAPI/Features/Effects/SprintDisablerEffect.cs
+++ b/SecretAPI/Features/Effects/SprintDisablerEffect.cs
@@ -1,30 +1,29 @@
-namespace SecretAPI.Features.Effects
-{
- using PlayerRoles.FirstPersonControl;
+namespace SecretAPI.Features.Effects;
- ///
- /// Effect that disables sprinting for a player. Sets stamina to 0 and disables regen.
- ///
- public class SprintDisablerEffect : CustomPlayerEffect, IStaminaModifier
- {
- ///
- public bool StaminaModifierActive => IsEnabled;
+using PlayerRoles.FirstPersonControl;
- ///
- public float StaminaRegenMultiplier => 0;
+///
+/// Effect that disables sprinting for a player. Sets stamina to 0 and disables regen.
+///
+public class SprintDisablerEffect : CustomPlayerEffect, IStaminaModifier
+{
+ ///
+ public bool StaminaModifierActive => IsEnabled;
- ///
- public override EffectClassification Classification => EffectClassification.Negative;
+ ///
+ public float StaminaRegenMultiplier => 0;
- ///
- public override void Enabled()
- {
- Owner.StaminaRemaining = 0;
- }
+ ///
+ public override EffectClassification Classification => EffectClassification.Negative;
- ///
- public override void Disabled()
- {
- }
+ ///
+ public override void Enabled()
+ {
+ Owner.StaminaRemaining = 0;
+ }
+
+ ///
+ public override void Disabled()
+ {
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs b/SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs
index 8e28c6e..d20d6a3 100644
--- a/SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs
+++ b/SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs
@@ -1,19 +1,18 @@
-namespace SecretAPI.Features.Effects
-{
- using PlayerRoles.FirstPersonControl;
+namespace SecretAPI.Features.Effects;
+
+using PlayerRoles.FirstPersonControl;
- ///
- /// Effect that disables stamina usage.
- ///
- public class StaminaUsageDisablerEffect : CustomPlayerEffect, IStaminaModifier
- {
- ///
- public bool StaminaModifierActive => IsEnabled;
+///
+/// Effect that disables stamina usage.
+///
+public class StaminaUsageDisablerEffect : CustomPlayerEffect, IStaminaModifier
+{
+ ///
+ public bool StaminaModifierActive => IsEnabled;
- ///
- public float StaminaUsageMultiplier => 0;
+ ///
+ public float StaminaUsageMultiplier => 0;
- ///
- public override EffectClassification Classification => EffectClassification.Positive;
- }
+ ///
+ public override EffectClassification Classification => EffectClassification.Positive;
}
\ No newline at end of file
diff --git a/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs b/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs
index 1001f9e..25daa52 100644
--- a/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs
+++ b/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs
@@ -1,20 +1,19 @@
-namespace SecretAPI.Features.Effects
-{
- using CustomPlayerEffects;
- using PlayerStatsSystem;
+namespace SecretAPI.Features.Effects;
+
+using CustomPlayerEffects;
+using PlayerStatsSystem;
- ///
- /// Grants a player temporary damage immunity.
- ///
- public class TemporaryDamageImmunity : CustomPlayerEffect, IDamageModifierEffect
- {
- ///
- public bool DamageModifierActive => IsEnabled;
+///
+/// Grants a player temporary damage immunity.
+///
+public class TemporaryDamageImmunity : CustomPlayerEffect, IDamageModifierEffect
+{
+ ///
+ public bool DamageModifierActive => IsEnabled;
- ///
- public override EffectClassification Classification => EffectClassification.Technical;
+ ///
+ public override EffectClassification Classification => EffectClassification.Technical;
- ///
- public float GetDamageModifier(float baseDamage, DamageHandlerBase handler, HitboxType hitboxType) => 0;
- }
+ ///
+ public float GetDamageModifier(float baseDamage, DamageHandlerBase handler, HitboxType hitboxType) => 0;
}
\ No newline at end of file
diff --git a/SecretAPI/Features/IPriority.cs b/SecretAPI/Features/IPriority.cs
index f337724..6f0666b 100644
--- a/SecretAPI/Features/IPriority.cs
+++ b/SecretAPI/Features/IPriority.cs
@@ -1,13 +1,12 @@
-namespace SecretAPI.Features
+namespace SecretAPI.Features;
+
+///
+/// Handles IPriority.
+///
+public interface IPriority
{
///
- /// Handles IPriority.
+ /// Gets the current priority.
///
- public interface IPriority
- {
- ///
- /// Gets the current priority.
- ///
- public int Priority { get; }
- }
+ public int Priority { get; }
}
\ No newline at end of file
diff --git a/SecretAPI/Features/IRegister.cs b/SecretAPI/Features/IRegister.cs
index 11ba45b..11d04b4 100644
--- a/SecretAPI/Features/IRegister.cs
+++ b/SecretAPI/Features/IRegister.cs
@@ -1,71 +1,70 @@
-namespace SecretAPI.Features
+namespace SecretAPI.Features;
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+///
+/// Interface used to define a type that should auto register.
+///
+public interface IRegister
{
- using System;
- using System.Collections.Generic;
- using System.Reflection;
+ ///
+ /// A list of all registered items for an assembly.
+ ///
+ private static Dictionary> registerables = new();
///
- /// Interface used to define a type that should auto register.
+ /// Attempts to register the object.
///
- public interface IRegister
+ public void TryRegister();
+
+ ///
+ /// Attempts to unregister the object.
+ ///
+ public void TryUnregister()
{
- ///
- /// A list of all registered items for an assembly.
- ///
- private static Dictionary> registerables = new();
+ // default empty to prevent breaking change
+ }
- ///
- /// Attempts to register the object.
- ///
- public void TryRegister();
+ ///
+ /// Registers all .
+ ///
+ /// The assembly to register from.
+ public static void RegisterAll(Assembly? assembly = null)
+ {
+ assembly ??= Assembly.GetCallingAssembly();
- ///
- /// Attempts to unregister the object.
- ///
- public void TryUnregister()
- {
- // default empty to prevent breaking change
- }
+ registerables.TryAdd(assembly, new());
- ///
- /// Registers all .
- ///
- /// The assembly to register from.
- public static void RegisterAll(Assembly? assembly = null)
+ foreach (Type type in assembly.GetTypes())
{
- assembly ??= Assembly.GetCallingAssembly();
-
- registerables.TryAdd(assembly, new());
+ if (type.IsAbstract || type.IsInterface)
+ continue;
- foreach (Type type in assembly.GetTypes())
- {
- if (type.IsAbstract || type.IsInterface)
- continue;
+ if (!typeof(IRegister).IsAssignableFrom(type))
+ continue;
- if (!typeof(IRegister).IsAssignableFrom(type))
- continue;
+ object obj = Activator.CreateInstance(type);
+ if (obj is not IRegister register)
+ continue;
- object obj = Activator.CreateInstance(type);
- if (obj is not IRegister register)
- continue;
-
- registerables[assembly].Add(register);
- register.TryRegister();
- }
+ registerables[assembly].Add(register);
+ register.TryRegister();
}
+ }
- ///
- /// Unregisters all from an .
- ///
- /// The assembly to unregister from.
- public static void UnRegisterAll(Assembly? assembly = null)
- {
- assembly ??= Assembly.GetCallingAssembly();
+ ///
+ /// Unregisters all from an .
+ ///
+ /// The assembly to unregister from.
+ public static void UnRegisterAll(Assembly? assembly = null)
+ {
+ assembly ??= Assembly.GetCallingAssembly();
- foreach (IRegister register in registerables[assembly])
- register.TryUnregister();
+ foreach (IRegister register in registerables[assembly])
+ register.TryUnregister();
- registerables.Remove(assembly);
- }
+ registerables.Remove(assembly);
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/PrefabManager.cs b/SecretAPI/Features/PrefabManager.cs
index a5d3c7a..80250c3 100644
--- a/SecretAPI/Features/PrefabManager.cs
+++ b/SecretAPI/Features/PrefabManager.cs
@@ -1,47 +1,46 @@
-namespace SecretAPI.Features
+namespace SecretAPI.Features;
+
+using System;
+using System.Linq;
+using Interactables.Interobjects;
+using MapGeneration;
+
+///
+/// Manages prefabs that have variants and cannot be easily used within .
+///
+public static class PrefabManager
{
- using System;
- using System.Linq;
- using Interactables.Interobjects;
- using MapGeneration;
+ private const string LczDoorName = "LCZ BreakableDoor";
+ private const string HczDoorName = "HCZ BreakableDoor";
+ private const string HczBulkDoorName = "HCZ BulkDoor";
+ private const string EzDoorName = "EZ BreakableDoor";
+
+ ///
+ /// Gets the prefab.
+ ///
+ public static ReferenceHub PlayerPrefab => PrefabStore.Prefab;
+
+ ///
+ /// Gets the found in .
+ ///
+ public static BasicDoor LczDoorPrefab => field ??= GetDoor(LczDoorName);
///
- /// Manages prefabs that have variants and cannot be easily used within .
+ /// Gets the found in .
///
- public static class PrefabManager
- {
- private const string LczDoorName = "LCZ BreakableDoor";
- private const string HczDoorName = "HCZ BreakableDoor";
- private const string HczBulkDoorName = "HCZ BulkDoor";
- private const string EzDoorName = "EZ BreakableDoor";
-
- ///
- /// Gets the prefab.
- ///
- public static ReferenceHub PlayerPrefab => PrefabStore.Prefab;
-
- ///
- /// Gets the found in .
- ///
- public static BasicDoor LczDoorPrefab => field ??= GetDoor(LczDoorName);
-
- ///
- /// Gets the found in .
- ///
- public static BasicDoor HczDoorPrefab => field ??= GetDoor(HczDoorName);
-
- ///
- /// Gets the found in .
- ///
- public static BasicDoor HczBulkDoorPrefab => field ??= GetDoor(HczBulkDoorName);
-
- ///
- /// Gets the found in .
- ///
- public static BasicDoor EzDoorPrefab => field ??= GetDoor(EzDoorName);
-
- private static BasicDoor GetDoor(string name)
- => PrefabStore.AllComponentPrefabs.FirstOrDefault(d => d.name == name)
- ?? throw new InvalidOperationException($"[PrefabManager] Failed to get door named {name} | Report this as a bug!");
- }
+ public static BasicDoor HczDoorPrefab => field ??= GetDoor(HczDoorName);
+
+ ///
+ /// Gets the found in .
+ ///
+ public static BasicDoor HczBulkDoorPrefab => field ??= GetDoor(HczBulkDoorName);
+
+ ///
+ /// Gets the found in .
+ ///
+ public static BasicDoor EzDoorPrefab => field ??= GetDoor(EzDoorName);
+
+ private static BasicDoor GetDoor(string name)
+ => PrefabStore.AllComponentPrefabs.FirstOrDefault(d => d.name == name)
+ ?? throw new InvalidOperationException($"[PrefabManager] Failed to get door named {name} | Report this as a bug!");
}
\ No newline at end of file
diff --git a/SecretAPI/Features/PrefabStore.cs b/SecretAPI/Features/PrefabStore.cs
index 3c0b675..b60c89e 100644
--- a/SecretAPI/Features/PrefabStore.cs
+++ b/SecretAPI/Features/PrefabStore.cs
@@ -1,61 +1,60 @@
-namespace SecretAPI.Features
+namespace SecretAPI.Features;
+
+using System.Collections.Generic;
+using System.Linq;
+using Interactables.Interobjects;
+using Mirror;
+using NorthwoodLib.Pools;
+using UnityEngine;
+
+///
+/// Handles the storing of a prefab.
+///
+/// The prefab to use.
+/// For Doors use .
+public static class PrefabStore
+ where TPrefab : NetworkBehaviour
{
- using System.Collections.Generic;
- using System.Linq;
- using Interactables.Interobjects;
- using Mirror;
- using NorthwoodLib.Pools;
- using UnityEngine;
-
///
- /// Handles the storing of a prefab.
+ /// Gets the first prefab found of the specified type.
///
- /// The prefab to use.
- /// For Doors use .
- public static class PrefabStore
- where TPrefab : NetworkBehaviour
+ public static TPrefab Prefab
{
- ///
- /// Gets the first prefab found of the specified type.
- ///
- public static TPrefab Prefab
+ get
{
- get
- {
- if (field)
- return field;
+ if (field)
+ return field;
- if (typeof(TPrefab) == typeof(ReferenceHub))
- return field = NetworkManager.singleton.playerPrefab.GetComponent();
+ if (typeof(TPrefab) == typeof(ReferenceHub))
+ return field = NetworkManager.singleton.playerPrefab.GetComponent();
- return field = AllComponentPrefabs.FirstOrDefault()!;
- }
+ return field = AllComponentPrefabs.FirstOrDefault()!;
}
+ }
- ///
- /// Gets every single prefab associated with this component.
- ///
- /// Used to find all of a base type (such as ).
- public static TPrefab[] AllComponentPrefabs
+ ///
+ /// Gets every single prefab associated with this component.
+ ///
+ /// Used to find all of a base type (such as ).
+ public static TPrefab[] AllComponentPrefabs
+ {
+ get
{
- get
- {
- if (field != null)
- return field;
+ if (field != null)
+ return field;
- List allPrefabs = ListPool.Shared.Rent();
+ List allPrefabs = ListPool.Shared.Rent();
- foreach (GameObject gameObject in NetworkClient.prefabs.Values)
- {
- if (gameObject.TryGetComponent(out TPrefab prefab))
- allPrefabs.Add(prefab);
- }
+ foreach (GameObject gameObject in NetworkClient.prefabs.Values)
+ {
+ if (gameObject.TryGetComponent(out TPrefab prefab))
+ allPrefabs.Add(prefab);
+ }
- field = allPrefabs.ToArray();
- ListPool.Shared.Return(allPrefabs);
+ field = allPrefabs.ToArray();
+ ListPool.Shared.Return(allPrefabs);
- return field;
- }
+ return field;
}
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/Registry.cs b/SecretAPI/Features/Registry.cs
index ea3b88d..2aae151 100644
--- a/SecretAPI/Features/Registry.cs
+++ b/SecretAPI/Features/Registry.cs
@@ -1,22 +1,21 @@
-namespace SecretAPI.Features
-{
- using System.Collections.Generic;
+namespace SecretAPI.Features;
+
+using System.Collections.Generic;
+///
+/// Handles the registration of objects and ids.
+///
+/// The type of the registry.
+public static class Registry
+{
///
- /// Handles the registration of objects and ids.
+ /// Gets the registered objects.
///
- /// The type of the registry.
- public static class Registry
- {
- ///
- /// Gets the registered objects.
- ///
- public static List Registered { get; } = [];
+ public static List Registered { get; } = [];
- ///
- /// Gets or sets the current id iteration.
- ///
- /// You should increment this with ++CurrentId when using it.
- public static int CurrentId { get; set; }
- }
+ ///
+ /// Gets or sets the current id iteration.
+ ///
+ /// You should increment this with ++CurrentId when using it.
+ public static int CurrentId { get; set; }
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
index 7ef676c..dfff551 100644
--- a/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomButtonSetting.cs
@@ -1,73 +1,72 @@
-namespace SecretAPI.Features.UserSettings
-{
- using System;
- using global::UserSettings.ServerSpecific;
+namespace SecretAPI.Features.UserSettings;
+
+using System;
+using global::UserSettings.ServerSpecific;
+///
+/// Wraps .
+///
+public abstract class CustomButtonSetting : CustomSetting, ISetting
+{
///
- /// Wraps .
+ /// Initializes a new instance of the class.
///
- public abstract class CustomButtonSetting : CustomSetting, ISetting
+ /// The button base.
+ protected CustomButtonSetting(SSButton button)
+ : base(button)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The button base.
- protected CustomButtonSetting(SSButton button)
- : base(button)
- {
- Base = button;
- }
+ Base = button;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the button.
- /// The setting's label.
- /// The button text.
- /// The time to hold.
- /// The hint to show.
- protected CustomButtonSetting(int? id, string label, string buttonText, float? holdTimeSeconds = null, string? hint = null)
- : this(new SSButton(id, label, buttonText, holdTimeSeconds, hint))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the button.
+ /// The setting's label.
+ /// The button text.
+ /// The time to hold.
+ /// The hint to show.
+ protected CustomButtonSetting(int? id, string label, string buttonText, float? holdTimeSeconds = null, string? hint = null)
+ : this(new SSButton(id, label, buttonText, holdTimeSeconds, hint))
+ {
+ }
- ///
- public new SSButton Base { get; }
+ ///
+ public new SSButton Base { get; }
- ///
- /// Gets the of the last press.
- ///
- public TimeSpan LastPress => Base.SyncLastPress.Elapsed;
+ ///
+ /// Gets the of the last press.
+ ///
+ public TimeSpan LastPress => Base.SyncLastPress.Elapsed;
- ///
- /// Gets or sets the text of the button.
- ///
- public string Text
+ ///
+ /// Gets or sets the text of the button.
+ ///
+ public string Text
+ {
+ get => Base.ButtonText;
+ set
{
- get => Base.ButtonText;
- set
- {
- Base.ButtonText = value;
- SendButtonUpdate();
- }
+ Base.ButtonText = value;
+ SendButtonUpdate();
}
+ }
- ///
- /// Gets or sets the amount of time to hold the button in seconds.
- ///
- public float RequiredHoldTime
+ ///
+ /// Gets or sets the amount of time to hold the button in seconds.
+ ///
+ public float RequiredHoldTime
+ {
+ get => Base.HoldTimeSeconds;
+ set
{
- get => Base.HoldTimeSeconds;
- set
- {
- Base.HoldTimeSeconds = value;
- SendButtonUpdate();
- }
+ Base.HoldTimeSeconds = value;
+ SendButtonUpdate();
}
-
- ///
- /// Sends an update to that or has updated.
- ///
- private void SendButtonUpdate() => Base.SendButtonUpdate(Text, RequiredHoldTime, false, IsKnownOwnerHub);
}
+
+ ///
+ /// Sends an update to that or has updated.
+ ///
+ private void SendButtonUpdate() => Base.SendButtonUpdate(Text, RequiredHoldTime, false, IsKnownOwnerHub);
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
index ec18e97..2bb966a 100644
--- a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
@@ -1,116 +1,115 @@
-namespace SecretAPI.Features.UserSettings
-{
- using System;
- using global::UserSettings.ServerSpecific;
+namespace SecretAPI.Features.UserSettings;
+
+using System;
+using global::UserSettings.ServerSpecific;
+///
+/// Custom wrapper.
+///
+public abstract class CustomDropdownSetting : CustomSetting, ISetting
+{
///
- /// Custom wrapper.
+ /// Initializes a new instance of the class.
///
- public abstract class CustomDropdownSetting : CustomSetting, ISetting
+ /// The base setting to create the wrapper with.
+ protected CustomDropdownSetting(SSDropdownSetting setting)
+ : base(setting)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The base setting to create the wrapper with.
- protected CustomDropdownSetting(SSDropdownSetting setting)
- : base(setting)
- {
- Base = setting;
- }
+ Base = setting;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the setting.
- /// The setting's label.
- /// The array of string options to give.
- /// The default option (int index).
- /// The entry type.
- /// The hint to show.
- /// The .
- /// See .
- protected CustomDropdownSetting(
- int? id,
- string label,
- string[] options,
- int defaultOptionIndex = 0,
- SSDropdownSetting.DropdownEntryType entryType = SSDropdownSetting.DropdownEntryType.Regular,
- string? hint = null,
- byte collectionId = byte.MaxValue,
- bool isServerSetting = false)
- : this(new SSDropdownSetting(id, label, options, defaultOptionIndex, entryType, hint, collectionId, isServerSetting))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The array of string options to give.
+ /// The default option (int index).
+ /// The entry type.
+ /// The hint to show.
+ /// The .
+ /// See .
+ protected CustomDropdownSetting(
+ int? id,
+ string label,
+ string[] options,
+ int defaultOptionIndex = 0,
+ SSDropdownSetting.DropdownEntryType entryType = SSDropdownSetting.DropdownEntryType.Regular,
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSDropdownSetting(id, label, options, defaultOptionIndex, entryType, hint, collectionId, isServerSetting))
+ {
+ }
- ///
- public new SSDropdownSetting Base { get; }
+ ///
+ public new SSDropdownSetting Base { get; }
- ///
- public override bool HasValueChanged => LastSelectedIndex != SyncedIndex;
+ ///
+ public override bool HasValueChanged => LastSelectedIndex != SyncedIndex;
- ///
- /// Gets the index the client is claiming to have selected.
- ///
- /// This is not validated, if you need it validated use .
- public int SyncedIndex => Base.SyncSelectionIndexRaw;
+ ///
+ /// Gets the index the client is claiming to have selected.
+ ///
+ /// This is not validated, if you need it validated use .
+ public int SyncedIndex => Base.SyncSelectionIndexRaw;
- ///
- /// Gets the selected index after validation.
- ///
- public int ValidatedSelectedIndex => Math.Clamp(SyncedIndex, 0, Options.Length - 1);
+ ///
+ /// Gets the selected index after validation.
+ ///
+ public int ValidatedSelectedIndex => Math.Clamp(SyncedIndex, 0, Options.Length - 1);
- ///
- /// Gets or sets the options.
- ///
- public string[] Options
+ ///
+ /// Gets or sets the options.
+ ///
+ public string[] Options
+ {
+ get => Base.Options;
+ set
{
- get => Base.Options;
- set
- {
- Base.Options = value;
- SendDropdownUpdate();
- }
+ Base.Options = value;
+ SendDropdownUpdate();
}
+ }
- ///
- /// Gets the selected option as string.
- ///
- public string SelectedOption => Options[ValidatedSelectedIndex];
-
- ///
- /// Gets the .
- ///
- /// Refer to https://github.com/HubertMoszka/Server-Specific-Settings-System/blob/main/SSDropdownSetting.cs#L151 for proper documentation.
- public SSDropdownSetting.DropdownEntryType EntryType => Base.EntryType;
-
- ///
- /// Gets the last selected index, or -1 if none was selected previously.
- ///
- public int LastSelectedIndex { get; private set; } = -1;
-
- ///
- /// Gets the selected option prior to the most recent call as a string, or null if none was selected previously.
- ///
- public string? LastSelectedOption => LastSelectedIndex < 0 || LastSelectedIndex >= Options.Length ? null : Options[LastSelectedIndex];
-
- ///
- /// Sends an update to that this has been updated on Server. Only works if is true.
- ///
- /// The new ID selected.
- public void SendServerUpdate(int selectionId) => Base.SendValueUpdate(selectionId, false, IsKnownOwnerHub);
-
- ///
- protected internal override void HandleBeforeSettingUpdate()
- {
- base.HandleBeforeSettingUpdate();
+ ///
+ /// Gets the selected option as string.
+ ///
+ public string SelectedOption => Options[ValidatedSelectedIndex];
- if (LastUpdateType != SettingResponseType.Initial)
- LastSelectedIndex = SyncedIndex;
- }
+ ///
+ /// Gets the .
+ ///
+ /// Refer to https://github.com/HubertMoszka/Server-Specific-Settings-System/blob/main/SSDropdownSetting.cs#L151 for proper documentation.
+ public SSDropdownSetting.DropdownEntryType EntryType => Base.EntryType;
+
+ ///
+ /// Gets the last selected index, or -1 if none was selected previously.
+ ///
+ public int LastSelectedIndex { get; private set; } = -1;
+
+ ///
+ /// Gets the selected option prior to the most recent call as a string, or null if none was selected previously.
+ ///
+ public string? LastSelectedOption => LastSelectedIndex < 0 || LastSelectedIndex >= Options.Length ? null : Options[LastSelectedIndex];
+
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// The new ID selected.
+ public void SendServerUpdate(int selectionId) => Base.SendValueUpdate(selectionId, false, IsKnownOwnerHub);
- ///
- /// Sends an update to that has been updated.
- ///
- private void SendDropdownUpdate() => Base.SendDropdownUpdate(Options, false, IsKnownOwnerHub);
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+
+ if (LastUpdateType != SettingResponseType.Initial)
+ LastSelectedIndex = SyncedIndex;
}
+
+ ///
+ /// Sends an update to that has been updated.
+ ///
+ private void SendDropdownUpdate() => Base.SendDropdownUpdate(Options, false, IsKnownOwnerHub);
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomHeader.cs b/SecretAPI/Features/UserSettings/CustomHeader.cs
index 8822806..d906ea8 100644
--- a/SecretAPI/Features/UserSettings/CustomHeader.cs
+++ b/SecretAPI/Features/UserSettings/CustomHeader.cs
@@ -1,30 +1,28 @@
-namespace SecretAPI.Features.UserSettings
-{
- using System;
- using global::UserSettings.ServerSpecific;
+namespace SecretAPI.Features.UserSettings;
+
+using global::UserSettings.ServerSpecific;
+///
+/// Wraps .
+///
+public class CustomHeader : ISetting
+{
///
- /// Wraps .
+ /// Initializes a new instance of the class.
///
- public class CustomHeader : ISetting
+ /// The label to show.
+ /// Reduced padding.
+ /// Hint displayed.
+ public CustomHeader(string label, bool reducedPadding = false, string? hint = null)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The label to show.
- /// Reduced padding.
- /// Hint displayed.
- public CustomHeader(string label, bool reducedPadding = false, string? hint = null)
- {
- Base = new SSGroupHeader(label, reducedPadding, hint);
- }
+ Base = new SSGroupHeader(label, reducedPadding, hint);
+ }
- ///
- /// Gets a for Example purposes.
- ///
- public static CustomHeader Examples { get; } = new("Examples", hint: "Features used as examples");
+ ///
+ /// Gets a for Example purposes.
+ ///
+ public static CustomHeader Examples { get; } = new("Examples", hint: "Features used as examples");
- ///
- public SSGroupHeader Base { get; }
- }
+ ///
+ public SSGroupHeader Base { get; }
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs b/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
index 32ae2aa..27a7ced 100644
--- a/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomKeybindSetting.cs
@@ -1,51 +1,50 @@
-namespace SecretAPI.Features.UserSettings
-{
- using global::UserSettings.ServerSpecific;
- using UnityEngine;
+namespace SecretAPI.Features.UserSettings;
+
+using global::UserSettings.ServerSpecific;
+using UnityEngine;
+///
+/// Wrapper for .
+///
+public abstract class CustomKeybindSetting : CustomSetting, ISetting
+{
///
- /// Wrapper for .
+ /// Initializes a new instance of the class.
///
- public abstract class CustomKeybindSetting : CustomSetting, ISetting
+ /// The setting to wrap.
+ protected CustomKeybindSetting(SSKeybindSetting setting)
+ : base(setting)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The setting to wrap.
- protected CustomKeybindSetting(SSKeybindSetting setting)
- : base(setting)
- {
- Base = setting;
- }
+ Base = setting;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the setting.
- /// The setting's label.
- /// The suggested key.
- /// Whether to prevent interaction in a GUI.
- /// Whether to allow spectators to trigger.
- /// The hint to show.
- /// The .
- protected CustomKeybindSetting(
- int? id,
- string label,
- KeyCode suggestedKey = KeyCode.None,
- bool preventInteractionOnGui = true,
- bool allowSpectatorTrigger = true,
- string? hint = null,
- byte collectionId = byte.MaxValue)
- : this(new SSKeybindSetting(id, label, suggestedKey, preventInteractionOnGui, allowSpectatorTrigger, hint, collectionId))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The suggested key.
+ /// Whether to prevent interaction in a GUI.
+ /// Whether to allow spectators to trigger.
+ /// The hint to show.
+ /// The .
+ protected CustomKeybindSetting(
+ int? id,
+ string label,
+ KeyCode suggestedKey = KeyCode.None,
+ bool preventInteractionOnGui = true,
+ bool allowSpectatorTrigger = true,
+ string? hint = null,
+ byte collectionId = byte.MaxValue)
+ : this(new SSKeybindSetting(id, label, suggestedKey, preventInteractionOnGui, allowSpectatorTrigger, hint, collectionId))
+ {
+ }
- ///
- public new SSKeybindSetting Base { get; }
+ ///
+ public new SSKeybindSetting Base { get; }
- ///
- /// Gets a value indicating whether the keybind is pressed.
- ///
- public bool IsPressed => Base.SyncIsPressed;
- }
+ ///
+ /// Gets a value indicating whether the keybind is pressed.
+ ///
+ public bool IsPressed => Base.SyncIsPressed;
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
index 2c75305..3e10dbb 100644
--- a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -1,116 +1,115 @@
-namespace SecretAPI.Features.UserSettings
-{
- using System;
- using global::UserSettings.ServerSpecific;
- using TMPro;
+namespace SecretAPI.Features.UserSettings;
+
+using System;
+using global::UserSettings.ServerSpecific;
+using TMPro;
+///
+/// Wrapper for .
+///
+public abstract class CustomPlainTextSetting : CustomSetting, ISetting
+{
///
- /// Wrapper for .
+ /// Initializes a new instance of the class.
///
- public abstract class CustomPlainTextSetting : CustomSetting, ISetting
+ /// The setting to create wrapper from.
+ protected CustomPlainTextSetting(SSPlaintextSetting setting)
+ : base(setting)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The setting to create wrapper from.
- protected CustomPlainTextSetting(SSPlaintextSetting setting)
- : base(setting)
- {
- Base = setting;
- }
+ Base = setting;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the setting.
- /// The setting's label.
- /// The placeholder to use for the setting.
- /// The max allowed characters.
- /// The content type.
- /// The hint to display for the setting.
- /// The .
- /// See .
- protected CustomPlainTextSetting(
- int? id,
- string label,
- string placeholder = "...",
- int characterLimit = 64,
- TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard,
- string? hint = null,
- byte collectionId = byte.MaxValue,
- bool isServerSetting = false)
- : this(new SSPlaintextSetting(id, label, placeholder, characterLimit, contentType, hint, collectionId, isServerSetting))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The placeholder to use for the setting.
+ /// The max allowed characters.
+ /// The content type.
+ /// The hint to display for the setting.
+ /// The .
+ /// See .
+ protected CustomPlainTextSetting(
+ int? id,
+ string label,
+ string placeholder = "...",
+ int characterLimit = 64,
+ TMP_InputField.ContentType contentType = TMP_InputField.ContentType.Standard,
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSPlaintextSetting(id, label, placeholder, characterLimit, contentType, hint, collectionId, isServerSetting))
+ {
+ }
- ///
- public new SSPlaintextSetting Base { get; }
+ ///
+ public new SSPlaintextSetting Base { get; }
- ///
- /// Gets the input text prior to the most recent call.
- ///
- public string LastInputText { get; private set; } = string.Empty;
+ ///
+ /// Gets the input text prior to the most recent call.
+ ///
+ public string LastInputText { get; private set; } = string.Empty;
- ///
- /// Gets the synced input text.
- ///
- public string InputText => Base.SyncInputText;
+ ///
+ /// Gets the synced input text.
+ ///
+ public string InputText => Base.SyncInputText;
- ///
- /// Gets or sets the content type.
- ///
- public TMP_InputField.ContentType ContentType
+ ///
+ /// Gets or sets the content type.
+ ///
+ public TMP_InputField.ContentType ContentType
+ {
+ get => Base.ContentType;
+ set
{
- get => Base.ContentType;
- set
- {
- Base.ContentType = value;
- SendPlaintextUpdate();
- }
+ Base.ContentType = value;
+ SendPlaintextUpdate();
}
+ }
- ///
- /// Gets or sets the placeholder.
- ///
- public string Placeholder
+ ///
+ /// Gets or sets the placeholder.
+ ///
+ public string Placeholder
+ {
+ get => Base.Placeholder;
+ set
{
- get => Base.Placeholder;
- set
- {
- Base.Placeholder = value;
- SendPlaintextUpdate();
- }
+ Base.Placeholder = value;
+ SendPlaintextUpdate();
}
+ }
- ///
- /// Gets or sets the character limit.
- ///
- public int CharacterLimit
+ ///
+ /// Gets or sets the character limit.
+ ///
+ public int CharacterLimit
+ {
+ get => Base.CharacterLimit;
+ set
{
- get => Base.CharacterLimit;
- set
- {
- Base.CharacterLimit = value;
- SendPlaintextUpdate();
- }
+ Base.CharacterLimit = value;
+ SendPlaintextUpdate();
}
+ }
- ///
- /// Sends an update to that this has been updated on Server. Only works if is true.
- ///
- /// The new text.
- public void SendServerUpdate(string text) => Base.SendValueUpdate(text, false, IsKnownOwnerHub);
-
- ///
- protected internal override void HandleBeforeSettingUpdate()
- {
- base.HandleBeforeSettingUpdate();
- LastInputText = InputText;
- }
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// The new text.
+ public void SendServerUpdate(string text) => Base.SendValueUpdate(text, false, IsKnownOwnerHub);
- ///
- /// Sends an update to the that or has changed values.
- ///
- private void SendPlaintextUpdate() => Base.SendPlaintextUpdate(Placeholder, (ushort)Math.Clamp(CharacterLimit, ushort.MinValue, ushort.MaxValue), ContentType, false, IsKnownOwnerHub);
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ LastInputText = InputText;
}
+
+ ///
+ /// Sends an update to the that or has changed values.
+ ///
+ private void SendPlaintextUpdate() => Base.SendPlaintextUpdate(Placeholder, (ushort)Math.Clamp(CharacterLimit, ushort.MinValue, ushort.MaxValue), ContentType, false, IsKnownOwnerHub);
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index 1190e7d..686d803 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -1,379 +1,378 @@
-namespace SecretAPI.Features.UserSettings
+namespace SecretAPI.Features.UserSettings;
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using global::UserSettings.ServerSpecific;
+using LabApi.Events.Handlers;
+using LabApi.Features.Enums;
+using LabApi.Features.Wrappers;
+using Mirror;
+using NorthwoodLib.Pools;
+using SecretAPI.Extensions;
+
+///
+/// Wraps .
+///
+public abstract class CustomSetting : ISetting
{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using global::UserSettings.ServerSpecific;
- using LabApi.Events.Handlers;
- using LabApi.Features.Enums;
- using LabApi.Features.Wrappers;
- using Mirror;
- using NorthwoodLib.Pools;
- using SecretAPI.Extensions;
-
- ///
- /// Wraps .
- ///
- public abstract class CustomSetting : ISetting
+ private static readonly Dictionary> ReceivedPlayerSettings = [];
+
+ static CustomSetting()
{
- private static readonly Dictionary> ReceivedPlayerSettings = [];
+ SecretApi.Harmony.PatchCategory(nameof(CustomSetting), SecretApi.Assembly);
- static CustomSetting()
- {
- SecretApi.Harmony.PatchCategory(nameof(CustomSetting), SecretApi.Assembly);
+ ServerSpecificSettingsSync.SendOnJoinFilter = null;
+ ServerSpecificSettingsSync.DefinedSettings ??= []; // fix null ref
+ ServerSpecificSettingsSync.ServerOnSettingValueReceived += OnSettingsUpdated;
- ServerSpecificSettingsSync.SendOnJoinFilter = null;
- ServerSpecificSettingsSync.DefinedSettings ??= []; // fix null ref
- ServerSpecificSettingsSync.ServerOnSettingValueReceived += OnSettingsUpdated;
+ PlayerEvents.Joined += ev => SendSettingsToPlayer(ev.Player);
+ PlayerEvents.GroupChanged += ev => SendSettingsToPlayer(ev.Player);
+ PlayerEvents.Left += ev => RemoveStoredPlayer(ev.Player);
+ }
- PlayerEvents.Joined += ev => SendSettingsToPlayer(ev.Player);
- PlayerEvents.GroupChanged += ev => SendSettingsToPlayer(ev.Player);
- PlayerEvents.Left += ev => RemoveStoredPlayer(ev.Player);
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The setting to use for custom setting.
+ protected CustomSetting(ServerSpecificSettingBase setting)
+ {
+ Base = setting;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The setting to use for custom setting.
- protected CustomSetting(ServerSpecificSettingBase setting)
- {
- Base = setting;
- }
+ ///
+ /// Gets the registered custom settings.
+ ///
+ public static List CustomSettings { get; } = [];
- ///
- /// Gets the registered custom settings.
- ///
- public static List CustomSettings { get; } = [];
-
- ///
- /// Gets a dictionary of player to their received custom settings.
- ///
- public static IReadOnlyDictionary> PlayerSettings => ReceivedPlayerSettings;
-
- ///
- public ServerSpecificSettingBase Base { get; }
-
- ///
- /// Gets the known owner.
- ///
- /// This is null on the original object.
- public Player? KnownOwner { get; private set; }
-
- ///
- /// Gets the of the setting.
- ///
- public abstract CustomHeader Header { get; }
-
- ///
- /// Gets an enum indicating the type of the last update.
- ///
- /// When used inside of it will indicate the current status.
- public SettingResponseType LastUpdateType { get; private set; } = SettingResponseType.None;
-
- ///
- /// Gets a value indicating whether the current value received is different to that prior to the most recent call.
- ///
- public virtual bool HasValueChanged { get; }
-
- ///
- /// Gets or sets a value indicating whether the setting is server side.
- ///
- /// This will result in client not saving the setting values and allows the server to change the setting .
- public bool IsServerSetting
- {
- get => Base.IsServerOnly;
- set => Base.IsServerOnly = value;
- }
+ ///
+ /// Gets a dictionary of player to their received custom settings.
+ ///
+ public static IReadOnlyDictionary> PlayerSettings => ReceivedPlayerSettings;
- ///
- /// Gets a value indicating whether the setting is the default and not tied to a .
- ///
- public bool IsDefaultSetting => KnownOwner == null;
+ ///
+ public ServerSpecificSettingBase Base { get; }
- ///
- /// Gets or sets the current label.
- ///
- public string Label
- {
- get => Base.Label;
- set
- {
- Base.Label = value;
- SendSettingUpdate();
- }
- }
+ ///
+ /// Gets the known owner.
+ ///
+ /// This is null on the original object.
+ public Player? KnownOwner { get; private set; }
- ///
- /// Gets or sets the description hint to show.
- ///
- public string DescriptionHint
- {
- get => Base.HintDescription;
- set
- {
- Base.HintDescription = value;
- SendSettingUpdate();
- }
- }
+ ///
+ /// Gets the of the setting.
+ ///
+ public abstract CustomHeader Header { get; }
- ///
- /// Gets the current id.
- ///
- public int Id
- {
- get => Base.SettingId;
- init => Base.SettingId = value;
- }
+ ///
+ /// Gets an enum indicating the type of the last update.
+ ///
+ /// When used inside of it will indicate the current status.
+ public SettingResponseType LastUpdateType { get; private set; } = SettingResponseType.None;
- ///
- /// Gets or sets the Collection ID for the setting. Defaults to .
- ///
- /// Setting value between 0-20 will allow sharing between servers on the same Account ID.
- public byte CollectionId
+ ///
+ /// Gets a value indicating whether the current value received is different to that prior to the most recent call.
+ ///
+ public virtual bool HasValueChanged { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the setting is server side.
+ ///
+ /// This will result in client not saving the setting values and allows the server to change the setting .
+ public bool IsServerSetting
+ {
+ get => Base.IsServerOnly;
+ set => Base.IsServerOnly = value;
+ }
+
+ ///
+ /// Gets a value indicating whether the setting is the default and not tied to a .
+ ///
+ public bool IsDefaultSetting => KnownOwner == null;
+
+ ///
+ /// Gets or sets the current label.
+ ///
+ public string Label
+ {
+ get => Base.Label;
+ set
{
- get => Base.CollectionId;
- set => Base.CollectionId = value;
+ Base.Label = value;
+ SendSettingUpdate();
}
+ }
- ///
- /// Gets or sets a value indicating whether the setting should be shared between servers.
- ///
- /// should be used if you are using different settings with the same ID and need extra control.
- public bool IsShared
+ ///
+ /// Gets or sets the description hint to show.
+ ///
+ public string DescriptionHint
+ {
+ get => Base.HintDescription;
+ set
{
- get => CollectionId < ServerSpecificSettingBase.MaxCollections;
- set => CollectionId = value ? byte.MinValue : byte.MaxValue;
+ Base.HintDescription = value;
+ SendSettingUpdate();
}
+ }
- ///
- /// Registers a collection of settings.
- ///
- /// The settings to register.
- public static void Register(params CustomSetting[] settings) => CustomSettings.AddRange(settings);
-
- ///
- /// Registers a collection of settings.
- ///
- /// The settings to register.
- public static void Register(IEnumerable settings) => CustomSettings.AddRange(settings);
-
- ///
- /// Unregisters collection of settings.
- ///
- /// The settings to unregister.
- public static void UnRegister(params CustomSetting[] settings) => CustomSettings.RemoveAll(settings.Contains);
-
- ///
- /// Unregisters a collection of settings.
- ///
- /// The settings to unregister.
- public static void UnRegister(IEnumerable settings) => CustomSettings.RemoveAll(settings.Contains);
-
- ///
- /// Tries to get player specific setting.
- ///
- /// The player to get settings of.
- /// The setting found.
- /// The setting type to find.
- /// Whether setting was found.
- public static bool TryGetPlayerSetting(Player player, [NotNullWhen(true)] out TSetting? setting)
- where TSetting : CustomSetting
- {
- setting = null;
+ ///
+ /// Gets the current id.
+ ///
+ public int Id
+ {
+ get => Base.SettingId;
+ init => Base.SettingId = value;
+ }
- if (!PlayerSettings.TryGetValue(player, out List? settings))
- return false;
+ ///
+ /// Gets or sets the Collection ID for the setting. Defaults to .
+ ///
+ /// Setting value between 0-20 will allow sharing between servers on the same Account ID.
+ public byte CollectionId
+ {
+ get => Base.CollectionId;
+ set => Base.CollectionId = value;
+ }
- foreach (CustomSetting toCheck in settings)
- {
- if (toCheck is TSetting value)
- {
- setting = value;
- return true;
- }
- }
+ ///
+ /// Gets or sets a value indicating whether the setting should be shared between servers.
+ ///
+ /// should be used if you are using different settings with the same ID and need extra control.
+ public bool IsShared
+ {
+ get => CollectionId < ServerSpecificSettingBase.MaxCollections;
+ set => CollectionId = value ? byte.MinValue : byte.MaxValue;
+ }
- return false;
- }
+ ///
+ /// Registers a collection of settings.
+ ///
+ /// The settings to register.
+ public static void Register(params CustomSetting[] settings) => CustomSettings.AddRange(settings);
- ///
- /// Gets a player's .
- ///
- /// The ID of the setting.
- /// The player of which to get the setting from.
- /// The setting class to check for.
- /// The found matching the params, otherwise null.
- public static T? GetPlayerSetting(int id, Player player)
- where T : CustomSetting => PlayerSettings.TryGetValue(player, out List settings)
- ? settings.FirstOrDefault(s => s.Base.SettingId == id && s.GetType() == typeof(T)) as T
- : null;
-
- ///
- /// Gets a , used for validation.
- ///
- /// The type of the base setting.
- /// The ID of the setting.
- /// The found matching the params, otherwise null.
- public static CustomSetting? Get(Type type, int id)
- => CustomSettings.FirstOrDefault(s => s.Base.SettingId == id && s.Base.GetType() == type);
-
- ///
- /// Gets a , used for validation.
- ///
- /// The ID of the setting.
- /// The setting class to check for.
- /// The found matching the params, otherwise null.
- public static T? Get(int id)
- where T : CustomSetting => CustomSettings.FirstOrDefault(s => s.Base.SettingId == id && s.GetType() == typeof(T)) as T;
-
- ///
- /// Resyncs all settings to all players.
- ///
- /// The version of the setting. If null will use .
- public static void ResyncServer(int? version = null)
- {
- // only update to real players that are authenticated
- foreach (Player player in Player.GetAll(PlayerSearchFlags.AuthenticatedPlayers))
- SendSettingsToPlayer(player, version);
- }
+ ///
+ /// Registers a collection of settings.
+ ///
+ /// The settings to register.
+ public static void Register(IEnumerable settings) => CustomSettings.AddRange(settings);
- ///
- /// Updates the settings of a player based on .
- ///
- /// The player to update.
- /// The version of the setting. If null will use .
- /// This will be automatically called on and .
- public static void SendSettingsToPlayer(Player player, int? version = null)
- {
- if (player.IsHost || player.IsNpc)
- return;
+ ///
+ /// Unregisters collection of settings.
+ ///
+ /// The settings to unregister.
+ public static void UnRegister(params CustomSetting[] settings) => CustomSettings.RemoveAll(settings.Contains);
- List playerSettings = ListPool.Shared.Rent();
- foreach (CustomSetting setting in CustomSettings)
- {
- if (!setting.CanView(player))
- continue;
+ ///
+ /// Unregisters a collection of settings.
+ ///
+ /// The settings to unregister.
+ public static void UnRegister(IEnumerable settings) => CustomSettings.RemoveAll(settings.Contains);
- CustomSetting playerSpecific = EnsurePlayerSpecificSetting(player, setting);
- playerSpecific.PersonalizeSetting();
- playerSettings.Add(playerSpecific);
- }
+ ///
+ /// Tries to get player specific setting.
+ ///
+ /// The player to get settings of.
+ /// The setting found.
+ /// The setting type to find.
+ /// Whether setting was found.
+ public static bool TryGetPlayerSetting(Player player, [NotNullWhen(true)] out TSetting? setting)
+ where TSetting : CustomSetting
+ {
+ setting = null;
- List ordered = ListPool.Shared.Rent();
- foreach (IGrouping grouping in playerSettings.GroupBy(static setting => setting.Header))
+ if (!PlayerSettings.TryGetValue(player, out List? settings))
+ return false;
+
+ foreach (CustomSetting toCheck in settings)
+ {
+ if (toCheck is TSetting value)
{
- ordered.Add(grouping.Key.Base);
- ordered.AddRange(grouping.Select(static setting => setting.Base));
+ setting = value;
+ return true;
}
+ }
- if (ServerSpecificSettingsSync.DefinedSettings != null)
- ordered.AddRange(ServerSpecificSettingsSync.DefinedSettings);
+ return false;
+ }
- ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, ordered.ToArray(), version);
+ ///
+ /// Gets a player's .
+ ///
+ /// The ID of the setting.
+ /// The player of which to get the setting from.
+ /// The setting class to check for.
+ /// The found matching the params, otherwise null.
+ public static T? GetPlayerSetting(int id, Player player)
+ where T : CustomSetting => PlayerSettings.TryGetValue(player, out List settings)
+ ? settings.FirstOrDefault(s => s.Base.SettingId == id && s.GetType() == typeof(T)) as T
+ : null;
- ListPool.Shared.Return(playerSettings);
- ListPool.Shared.Return(ordered);
- }
+ ///
+ /// Gets a , used for validation.
+ ///
+ /// The type of the base setting.
+ /// The ID of the setting.
+ /// The found matching the params, otherwise null.
+ public static CustomSetting? Get(Type type, int id)
+ => CustomSettings.FirstOrDefault(s => s.Base.SettingId == id && s.Base.GetType() == type);
- ///
- /// Checks whether a is equal to .
- ///
- /// The to check.
- /// Whether is equal to Owner .
- internal bool IsKnownOwnerHub(ReferenceHub? hub) => hub && KnownOwner?.ReferenceHub == hub;
-
- ///
- /// Called before , adding and .
- ///
- /// This will not have the current status.
- protected internal virtual void HandleBeforeSettingUpdate()
- {
- LastUpdateType = LastUpdateType == SettingResponseType.None
- ? SettingResponseType.Initial
- : SettingResponseType.Update;
- }
+ ///
+ /// Gets a , used for validation.
+ ///
+ /// The ID of the setting.
+ /// The setting class to check for.
+ /// The found matching the params, otherwise null.
+ public static T? Get(int id)
+ where T : CustomSetting => CustomSettings.FirstOrDefault(s => s.Base.SettingId == id && s.GetType() == typeof(T)) as T;
+
+ ///
+ /// Resyncs all settings to all players.
+ ///
+ /// The version of the setting. If null will use .
+ public static void ResyncServer(int? version = null)
+ {
+ // only update to real players that are authenticated
+ foreach (Player player in Player.GetAll(PlayerSearchFlags.AuthenticatedPlayers))
+ SendSettingsToPlayer(player, version);
+ }
- ///
- /// Resyncs the setting to its owner.
- ///
- protected void ResyncToOwner()
+ ///
+ /// Updates the settings of a player based on .
+ ///
+ /// The player to update.
+ /// The version of the setting. If null will use .
+ /// This will be automatically called on and .
+ public static void SendSettingsToPlayer(Player player, int? version = null)
+ {
+ if (player.IsHost || player.IsNpc)
+ return;
+
+ List playerSettings = ListPool.Shared.Rent();
+ foreach (CustomSetting setting in CustomSettings)
{
- if (KnownOwner == null)
- return;
+ if (!setting.CanView(player))
+ continue;
- SendSettingsToPlayer(KnownOwner);
+ CustomSetting playerSpecific = EnsurePlayerSpecificSetting(player, setting);
+ playerSpecific.PersonalizeSetting();
+ playerSettings.Add(playerSpecific);
}
- ///
- /// Checks if a player is able to view a setting.
- ///
- /// The player to check.
- /// A value indicating whether a player is able to view the setting.
- protected virtual bool CanView(Player player) => true;
-
- ///
- /// Creates a duplicate of the current setting. Used to properly per player sync and implement .
- ///
- /// The duplicate setting created.
- protected abstract CustomSetting CreateDuplicate();
-
- ///
- /// Called before setting is sent to a player.
- ///
- /// This should be used to personalize a setting per player, such as supporters having more options.
- protected virtual void PersonalizeSetting()
+ List ordered = ListPool.Shared.Rent();
+ foreach (IGrouping grouping in playerSettings.GroupBy(static setting => setting.Header))
{
+ ordered.Add(grouping.Key.Base);
+ ordered.AddRange(grouping.Select(static setting => setting.Base));
}
- ///
- /// Called when client sends a new value on the setting.
- ///
- /// You can use to get the current update type.
- protected abstract void HandleSettingUpdate();
+ if (ServerSpecificSettingsSync.DefinedSettings != null)
+ ordered.AddRange(ServerSpecificSettingsSync.DefinedSettings);
- private static void RemoveStoredPlayer(Player player) => ReceivedPlayerSettings.Remove(player);
+ ServerSpecificSettingsSync.SendToPlayer(player.ReferenceHub, ordered.ToArray(), version);
- private static void OnSettingsUpdated(ReferenceHub hub, ServerSpecificSettingBase settingBase)
- {
- if (hub.IsHost)
- return;
+ ListPool.Shared.Return(playerSettings);
+ ListPool.Shared.Return(ordered);
+ }
+
+ ///
+ /// Checks whether a is equal to .
+ ///
+ /// The to check.
+ /// Whether is equal to Owner .
+ internal bool IsKnownOwnerHub(ReferenceHub? hub) => hub && KnownOwner?.ReferenceHub == hub;
- Player player = Player.Get(hub);
+ ///
+ /// Called before , adding and .
+ ///
+ /// This will not have the current status.
+ protected internal virtual void HandleBeforeSettingUpdate()
+ {
+ LastUpdateType = LastUpdateType == SettingResponseType.None
+ ? SettingResponseType.Initial
+ : SettingResponseType.Update;
+ }
- CustomSetting? setting = CustomSettings.FirstOrDefault(s => s.Base.SettingId == settingBase.SettingId);
- if (setting == null || !setting.CanView(player))
- return;
+ ///
+ /// Resyncs the setting to its owner.
+ ///
+ protected void ResyncToOwner()
+ {
+ if (KnownOwner == null)
+ return;
- // validate setting existence and then write data from client
- CustomSetting newSettingPlayer = EnsurePlayerSpecificSetting(player, setting);
- newSettingPlayer.HandleBeforeSettingUpdate();
+ SendSettingsToPlayer(KnownOwner);
+ }
- NetworkWriterPooled valueWriter = NetworkWriterPool.Get();
- settingBase.SerializeValue(valueWriter);
- newSettingPlayer.Base.DeserializeValue(new NetworkReader(valueWriter.buffer));
- NetworkWriterPool.Return(valueWriter);
+ ///
+ /// Checks if a player is able to view a setting.
+ ///
+ /// The player to check.
+ /// A value indicating whether a player is able to view the setting.
+ protected virtual bool CanView(Player player) => true;
- newSettingPlayer.HandleSettingUpdate();
- }
+ ///
+ /// Creates a duplicate of the current setting. Used to properly per player sync and implement .
+ ///
+ /// The duplicate setting created.
+ protected abstract CustomSetting CreateDuplicate();
- private static CustomSetting EnsurePlayerSpecificSetting(Player player, CustomSetting toMatch)
- {
- List settings = ReceivedPlayerSettings.GetOrAdd(player, static () => []);
- CustomSetting? currentSetting = settings.FirstOrDefault(s => s.Id == toMatch.Id);
- if (currentSetting == null)
- {
- currentSetting = toMatch.CreateDuplicate();
- currentSetting.KnownOwner = player;
- settings.Add(currentSetting);
- }
+ ///
+ /// Called before setting is sent to a player.
+ ///
+ /// This should be used to personalize a setting per player, such as supporters having more options.
+ protected virtual void PersonalizeSetting()
+ {
+ }
+
+ ///
+ /// Called when client sends a new value on the setting.
+ ///
+ /// You can use to get the current update type.
+ protected abstract void HandleSettingUpdate();
+
+ private static void RemoveStoredPlayer(Player player) => ReceivedPlayerSettings.Remove(player);
+
+ private static void OnSettingsUpdated(ReferenceHub hub, ServerSpecificSettingBase settingBase)
+ {
+ if (hub.IsHost)
+ return;
+
+ Player player = Player.Get(hub);
+
+ CustomSetting? setting = CustomSettings.FirstOrDefault(s => s.Base.SettingId == settingBase.SettingId);
+ if (setting == null || !setting.CanView(player))
+ return;
- return currentSetting;
+ // validate setting existence and then write data from client
+ CustomSetting newSettingPlayer = EnsurePlayerSpecificSetting(player, setting);
+ newSettingPlayer.HandleBeforeSettingUpdate();
+
+ NetworkWriterPooled valueWriter = NetworkWriterPool.Get();
+ settingBase.SerializeValue(valueWriter);
+ newSettingPlayer.Base.DeserializeValue(new NetworkReader(valueWriter.buffer));
+ NetworkWriterPool.Return(valueWriter);
+
+ newSettingPlayer.HandleSettingUpdate();
+ }
+
+ private static CustomSetting EnsurePlayerSpecificSetting(Player player, CustomSetting toMatch)
+ {
+ List settings = ReceivedPlayerSettings.GetOrAdd(player, static () => []);
+ CustomSetting? currentSetting = settings.FirstOrDefault(s => s.Id == toMatch.Id);
+ if (currentSetting == null)
+ {
+ currentSetting = toMatch.CreateDuplicate();
+ currentSetting.KnownOwner = player;
+ settings.Add(currentSetting);
}
- ///
- /// Sends an update to that or has changed.
- ///
- private void SendSettingUpdate() => Base.SendUpdate(Label, DescriptionHint, false, IsKnownOwnerHub);
+ return currentSetting;
}
-}
+
+ ///
+ /// Sends an update to that or has changed.
+ ///
+ private void SendSettingUpdate() => Base.SendUpdate(Label, DescriptionHint, false, IsKnownOwnerHub);
+}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
index 1831934..3bb9cb5 100644
--- a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
@@ -1,169 +1,168 @@
-namespace SecretAPI.Features.UserSettings
-{
- using global::UserSettings.ServerSpecific;
- using UnityEngine;
+namespace SecretAPI.Features.UserSettings;
+
+using global::UserSettings.ServerSpecific;
+using UnityEngine;
+///
+/// Wrapper for .
+///
+public abstract class CustomSliderSetting : CustomSetting, ISetting
+{
///
- /// Wrapper for .
+ /// Initializes a new instance of the class.
///
- public abstract class CustomSliderSetting : CustomSetting, ISetting
+ /// The setting to wrap.
+ protected CustomSliderSetting(SSSliderSetting setting)
+ : base(setting)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The setting to wrap.
- protected CustomSliderSetting(SSSliderSetting setting)
- : base(setting)
- {
- Base = setting;
- }
+ Base = setting;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the setting.
- /// The setting's label.
- /// The slider's minimum value.
- /// The slider's maximum value.
- /// The default value for the slider.
- /// Whether it should be an integer (false for float).
- /// Value to string format.
- /// The final display format.
- /// The hint to display.
- /// The .
- /// See .
- protected CustomSliderSetting(
- int? id,
- string label,
- float minValue,
- float maxValue,
- float defaultValue = 0.0f,
- bool integer = false,
- string valueToStringFormat = "0.##",
- string finalDisplayFormat = "{0}",
- string? hint = null,
- byte collectionId = byte.MaxValue,
- bool isServerSetting = false)
- : this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, integer, valueToStringFormat, finalDisplayFormat, hint, collectionId, isServerSetting))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The slider's minimum value.
+ /// The slider's maximum value.
+ /// The default value for the slider.
+ /// Whether it should be an integer (false for float).
+ /// Value to string format.
+ /// The final display format.
+ /// The hint to display.
+ /// The .
+ /// See .
+ protected CustomSliderSetting(
+ int? id,
+ string label,
+ float minValue,
+ float maxValue,
+ float defaultValue = 0.0f,
+ bool integer = false,
+ string valueToStringFormat = "0.##",
+ string finalDisplayFormat = "{0}",
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSSliderSetting(id, label, minValue, maxValue, defaultValue, integer, valueToStringFormat, finalDisplayFormat, hint, collectionId, isServerSetting))
+ {
+ }
- ///
- public new SSSliderSetting Base { get; }
+ ///
+ public new SSSliderSetting Base { get; }
- ///
- public override bool HasValueChanged => !Mathf.Approximately(LastSelectedValueFloat, SelectedValueFloat);
+ ///
+ public override bool HasValueChanged => !Mathf.Approximately(LastSelectedValueFloat, SelectedValueFloat);
- ///
- /// Gets the selected value prior to the most recent call as a float.
- ///
- public float LastSelectedValueFloat { get; private set; }
+ ///
+ /// Gets the selected value prior to the most recent call as a float.
+ ///
+ public float LastSelectedValueFloat { get; private set; }
- ///
- /// Gets the selected value prior to the most recent call as an int.
- ///
- public int LastSelectedValueInt => Mathf.RoundToInt(LastSelectedValueFloat);
+ ///
+ /// Gets the selected value prior to the most recent call as an int.
+ ///
+ public int LastSelectedValueInt => Mathf.RoundToInt(LastSelectedValueFloat);
- ///
- /// Gets the synced value selected as a float.
- ///
- public float SelectedValueFloat => Base.SyncFloatValue;
+ ///
+ /// Gets the synced value selected as a float.
+ ///
+ public float SelectedValueFloat => Base.SyncFloatValue;
- ///
- /// Gets the synced value selected as an integer.
- ///
- public int SelectedValueInt => Base.SyncIntValue;
+ ///
+ /// Gets the synced value selected as an integer.
+ ///
+ public int SelectedValueInt => Base.SyncIntValue;
- ///
- /// Gets or sets the value to string format.
- ///
- public string ValueToStringFormat
+ ///
+ /// Gets or sets the value to string format.
+ ///
+ public string ValueToStringFormat
+ {
+ get => Base.ValueToStringFormat;
+ set
{
- get => Base.ValueToStringFormat;
- set
- {
- Base.ValueToStringFormat = value;
- SendSliderUpdate();
- }
+ Base.ValueToStringFormat = value;
+ SendSliderUpdate();
}
+ }
- ///
- /// Gets or sets the final display format.
- ///
- public string FinalDisplayFormat
+ ///
+ /// Gets or sets the final display format.
+ ///
+ public string FinalDisplayFormat
+ {
+ get => Base.FinalDisplayFormat;
+ set
{
- get => Base.FinalDisplayFormat;
- set
- {
- Base.FinalDisplayFormat = value;
- SendSliderUpdate();
- }
+ Base.FinalDisplayFormat = value;
+ SendSliderUpdate();
}
+ }
- ///
- /// Gets or sets the minimum value of the setting.
- ///
- public float MinimumValue
+ ///
+ /// Gets or sets the minimum value of the setting.
+ ///
+ public float MinimumValue
+ {
+ get => Base.MinValue;
+ set
{
- get => Base.MinValue;
- set
- {
- Base.MinValue = value;
- SendSliderUpdate();
- }
+ Base.MinValue = value;
+ SendSliderUpdate();
}
+ }
- ///
- /// Gets or sets the maximum value of the setting.
- ///
- public float MaximumValue
+ ///
+ /// Gets or sets the maximum value of the setting.
+ ///
+ public float MaximumValue
+ {
+ get => Base.MaxValue;
+ set
{
- get => Base.MaxValue;
- set
- {
- Base.MaxValue = value;
- SendSliderUpdate();
- }
+ Base.MaxValue = value;
+ SendSliderUpdate();
}
+ }
- ///
- /// Gets or sets the default value of the setting.
- ///
- public float DefaultValue
- {
- get => Base.DefaultValue;
- set => Base.DefaultValue = value;
- }
+ ///
+ /// Gets or sets the default value of the setting.
+ ///
+ public float DefaultValue
+ {
+ get => Base.DefaultValue;
+ set => Base.DefaultValue = value;
+ }
- ///
- /// Gets or sets a value indicating whether to use integer. False will use float.
- ///
- public bool UseInteger
+ ///
+ /// Gets or sets a value indicating whether to use integer. False will use float.
+ ///
+ public bool UseInteger
+ {
+ get => Base.Integer;
+ set
{
- get => Base.Integer;
- set
- {
- Base.Integer = value;
- SendSliderUpdate();
- }
+ Base.Integer = value;
+ SendSliderUpdate();
}
+ }
- ///
- /// Sends an update to that this has been updated on Server. Only works if is true.
- ///
- /// The new value that this is set to.
- public void SendServerUpdate(float value) => Base.SendValueUpdate(value, false, IsKnownOwnerHub);
-
- ///
- protected internal override void HandleBeforeSettingUpdate()
- {
- base.HandleBeforeSettingUpdate();
- LastSelectedValueFloat = SelectedValueFloat;
- }
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// The new value that this is set to.
+ public void SendServerUpdate(float value) => Base.SendValueUpdate(value, false, IsKnownOwnerHub);
- ///
- /// Sends an update that any of the slider values have been updated.
- ///
- private void SendSliderUpdate() => Base.SendSliderUpdate(MinimumValue, MaximumValue, UseInteger, ValueToStringFormat, FinalDisplayFormat, false, IsKnownOwnerHub);
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ LastSelectedValueFloat = SelectedValueFloat;
}
+
+ ///
+ /// Sends an update that any of the slider values have been updated.
+ ///
+ private void SendSliderUpdate() => Base.SendSliderUpdate(MinimumValue, MaximumValue, UseInteger, ValueToStringFormat, FinalDisplayFormat, false, IsKnownOwnerHub);
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs b/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
index e70c67c..220de78 100644
--- a/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomTextAreaSetting.cs
@@ -1,61 +1,60 @@
-namespace SecretAPI.Features.UserSettings
-{
- using global::UserSettings.ServerSpecific;
- using TMPro;
+namespace SecretAPI.Features.UserSettings;
+
+using global::UserSettings.ServerSpecific;
+using TMPro;
+///
+/// Wrapper for .
+///
+public abstract class CustomTextAreaSetting : CustomSetting, ISetting
+{
///
- /// Wrapper for .
+ /// Initializes a new instance of the class.
///
- public abstract class CustomTextAreaSetting : CustomSetting, ISetting
+ /// The setting to wrap.
+ protected CustomTextAreaSetting(SSTextArea setting)
+ : base(setting)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The setting to wrap.
- protected CustomTextAreaSetting(SSTextArea setting)
- : base(setting)
- {
- Base = setting;
- }
+ Base = setting;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the setting.
- /// The content of the setting.
- /// The foldout mode.
- /// The collapsed text.
- /// The align for the text.
- protected CustomTextAreaSetting(
- int? id,
- string content,
- SSTextArea.FoldoutMode foldoutMode = SSTextArea.FoldoutMode.NotCollapsable,
- string? collapsedText = null,
- TextAlignmentOptions textAlignment = TextAlignmentOptions.TopLeft)
- : this(new SSTextArea(id, content, foldoutMode, collapsedText, textAlignment))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The content of the setting.
+ /// The foldout mode.
+ /// The collapsed text.
+ /// The align for the text.
+ protected CustomTextAreaSetting(
+ int? id,
+ string content,
+ SSTextArea.FoldoutMode foldoutMode = SSTextArea.FoldoutMode.NotCollapsable,
+ string? collapsedText = null,
+ TextAlignmentOptions textAlignment = TextAlignmentOptions.TopLeft)
+ : this(new SSTextArea(id, content, foldoutMode, collapsedText, textAlignment))
+ {
+ }
- ///
- public new SSTextArea Base { get; }
+ ///
+ public new SSTextArea Base { get; }
- ///
- /// Gets or sets the current content. This is equal to .
- ///
- public string Content
- {
- get => Label;
- set => Label = value;
- }
+ ///
+ /// Gets or sets the current content. This is equal to .
+ ///
+ public string Content
+ {
+ get => Label;
+ set => Label = value;
+ }
- ///
- /// Gets the foldout mode.
- ///
- public SSTextArea.FoldoutMode Foldout => Base.Foldout;
+ ///
+ /// Gets the foldout mode.
+ ///
+ public SSTextArea.FoldoutMode Foldout => Base.Foldout;
- ///
- /// Gets the of the setting.
- ///
- public TextAlignmentOptions AlignmentOptions => Base.AlignmentOptions;
- }
+ ///
+ /// Gets the of the setting.
+ ///
+ public TextAlignmentOptions AlignmentOptions => Base.AlignmentOptions;
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
index d73cc61..d042ad2 100644
--- a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
@@ -1,114 +1,113 @@
-namespace SecretAPI.Features.UserSettings
-{
- using global::UserSettings.ServerSpecific;
+namespace SecretAPI.Features.UserSettings;
+
+using global::UserSettings.ServerSpecific;
+///
+/// Wrapper for .
+///
+public abstract class CustomTwoButtonSetting : CustomSetting, ISetting
+{
///
- /// Wrapper for .
+ /// Initializes a new instance of the class.
///
- public abstract class CustomTwoButtonSetting : CustomSetting, ISetting
+ /// The setting to wrap.
+ protected CustomTwoButtonSetting(SSTwoButtonsSetting button)
+ : base(button)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The setting to wrap.
- protected CustomTwoButtonSetting(SSTwoButtonsSetting button)
- : base(button)
- {
- Base = button;
- }
+ Base = button;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the setting.
- /// The setting's label.
- /// The first option.
- /// The second option.
- /// Whether the second option should be default. Default: false.
- /// The hint to show.
- /// The .
- /// See .
- protected CustomTwoButtonSetting(
- int? id,
- string label,
- string optionA,
- string optionB,
- bool defaultIsB = false,
- string? hint = null,
- byte collectionId = byte.MaxValue,
- bool isServerSetting = false)
- : this(new SSTwoButtonsSetting(id, label, optionA, optionB, defaultIsB, hint, collectionId, isServerSetting))
- {
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the setting.
+ /// The setting's label.
+ /// The first option.
+ /// The second option.
+ /// Whether the second option should be default. Default: false.
+ /// The hint to show.
+ /// The .
+ /// See .
+ protected CustomTwoButtonSetting(
+ int? id,
+ string label,
+ string optionA,
+ string optionB,
+ bool defaultIsB = false,
+ string? hint = null,
+ byte collectionId = byte.MaxValue,
+ bool isServerSetting = false)
+ : this(new SSTwoButtonsSetting(id, label, optionA, optionB, defaultIsB, hint, collectionId, isServerSetting))
+ {
+ }
- ///
- public new SSTwoButtonsSetting Base { get; }
+ ///
+ public new SSTwoButtonsSetting Base { get; }
- ///
- /// Gets or sets the current text for the first option.
- ///
- public string OptionA
+ ///
+ /// Gets or sets the current text for the first option.
+ ///
+ public string OptionA
+ {
+ get => Base.OptionA;
+ set
{
- get => Base.OptionA;
- set
- {
- Base.OptionA = value;
- SendOptionsUpdate();
- }
+ Base.OptionA = value;
+ SendOptionsUpdate();
}
+ }
- ///
- /// Gets or sets the current text for the second option.
- ///
- public string OptionB
+ ///
+ /// Gets or sets the current text for the second option.
+ ///
+ public string OptionB
+ {
+ get => Base.OptionB;
+ set
{
- get => Base.OptionB;
- set
- {
- Base.OptionB = value;
- SendOptionsUpdate();
- }
+ Base.OptionB = value;
+ SendOptionsUpdate();
}
+ }
- ///
- public override bool HasValueChanged => WasLastOptionB != IsOptionB;
-
- ///
- /// Gets a value indicating whether the value prior to the most recent call was Option B.
- ///
- public bool WasLastOptionB { get; private set; }
+ ///
+ public override bool HasValueChanged => WasLastOptionB != IsOptionB;
- ///
- /// Gets a value indicating whether the selected option is currently the first.
- ///
- public bool IsOptionA => Base.SyncIsA;
+ ///
+ /// Gets a value indicating whether the value prior to the most recent call was Option B.
+ ///
+ public bool WasLastOptionB { get; private set; }
- ///
- /// Gets a value indicating whether the selected option is currently the second.
- ///
- public bool IsOptionB => Base.SyncIsB;
+ ///
+ /// Gets a value indicating whether the selected option is currently the first.
+ ///
+ public bool IsOptionA => Base.SyncIsA;
- ///
- /// Gets a value indicating whether the selected option is currently set to the default.
- ///
- public bool IsDefault => Base.DefaultIsB ? IsOptionB : IsOptionA;
+ ///
+ /// Gets a value indicating whether the selected option is currently the second.
+ ///
+ public bool IsOptionB => Base.SyncIsB;
- ///
- /// Sends an update to that this has been updated on Server. Only works if is true.
- ///
- /// Whether the setting is set to B value now.
- public void SendServerUpdate(bool isB) => Base.SendValueUpdate(isB, false, IsKnownOwnerHub);
+ ///
+ /// Gets a value indicating whether the selected option is currently set to the default.
+ ///
+ public bool IsDefault => Base.DefaultIsB ? IsOptionB : IsOptionA;
- ///
- protected internal override void HandleBeforeSettingUpdate()
- {
- base.HandleBeforeSettingUpdate();
- WasLastOptionB = IsOptionB;
- }
+ ///
+ /// Sends an update to that this has been updated on Server. Only works if is true.
+ ///
+ /// Whether the setting is set to B value now.
+ public void SendServerUpdate(bool isB) => Base.SendValueUpdate(isB, false, IsKnownOwnerHub);
- ///
- /// Sends an update to the that or has changed values.
- ///
- private void SendOptionsUpdate() => Base.SendTwoButtonUpdate(OptionA, OptionB, false, IsKnownOwnerHub);
+ ///
+ protected internal override void HandleBeforeSettingUpdate()
+ {
+ base.HandleBeforeSettingUpdate();
+ WasLastOptionB = IsOptionB;
}
+
+ ///
+ /// Sends an update to the that or has changed values.
+ ///
+ private void SendOptionsUpdate() => Base.SendTwoButtonUpdate(OptionA, OptionB, false, IsKnownOwnerHub);
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/ISetting.cs b/SecretAPI/Features/UserSettings/ISetting.cs
index 6c35959..383c7f6 100644
--- a/SecretAPI/Features/UserSettings/ISetting.cs
+++ b/SecretAPI/Features/UserSettings/ISetting.cs
@@ -1,17 +1,16 @@
-namespace SecretAPI.Features.UserSettings
-{
- using global::UserSettings.ServerSpecific;
+namespace SecretAPI.Features.UserSettings;
+
+using global::UserSettings.ServerSpecific;
+///
+/// Interface for to handle the Base.
+///
+/// The setting being wrapped.
+public interface ISetting
+ where T : ServerSpecificSettingBase
+{
///
- /// Interface for to handle the Base.
+ /// Gets the base of the setting.
///
- /// The setting being wrapped.
- public interface ISetting
- where T : ServerSpecificSettingBase
- {
- ///
- /// Gets the base of the setting.
- ///
- public T Base { get; }
- }
+ public T Base { get; }
}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/SettingResponseType.cs b/SecretAPI/Features/UserSettings/SettingResponseType.cs
index ea3f5db..9403cd9 100644
--- a/SecretAPI/Features/UserSettings/SettingResponseType.cs
+++ b/SecretAPI/Features/UserSettings/SettingResponseType.cs
@@ -1,23 +1,22 @@
-namespace SecretAPI.Features.UserSettings
+namespace SecretAPI.Features.UserSettings;
+
+///
+/// The type of response.
+///
+public enum SettingResponseType
{
///
- /// The type of response.
+ /// Indicates that no response has been recorded.
///
- public enum SettingResponseType
- {
- ///
- /// Indicates that no response has been recorded.
- ///
- None,
+ None,
- ///
- /// Indicates that this is the initial response.
- ///
- Initial,
+ ///
+ /// Indicates that this is the initial response.
+ ///
+ Initial,
- ///
- /// Indicates that this is an update, changing the value.
- ///
- Update,
- }
+ ///
+ /// Indicates that this is an update, changing the value.
+ ///
+ Update,
}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs b/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs
index d7b94cb..abbb6f7 100644
--- a/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs
+++ b/SecretAPI/Patches/Features/SendSettingsPlayerSync.cs
@@ -1,22 +1,21 @@
-namespace SecretAPI.Patches.Features
-{
- using HarmonyLib;
- using LabApi.Features.Wrappers;
- using SecretAPI.Attributes;
- using SecretAPI.Features.UserSettings;
- using UserSettings.ServerSpecific;
+namespace SecretAPI.Patches.Features;
+
+using HarmonyLib;
+using LabApi.Features.Wrappers;
+using SecretAPI.Attributes;
+using SecretAPI.Features.UserSettings;
+using UserSettings.ServerSpecific;
- ///
- /// Fixes to resync with .
- ///
- [HarmonyPatchCategory(nameof(CustomSetting))]
- [HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.SendToPlayer), [typeof(ReferenceHub)])]
- internal static class SendSettingsPlayerSync
+///
+/// Fixes to resync with .
+///
+[HarmonyPatchCategory(nameof(CustomSetting))]
+[HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.SendToPlayer), [typeof(ReferenceHub)])]
+internal static class SendSettingsPlayerSync
+{
+ private static bool Prefix(ReferenceHub hub)
{
- private static bool Prefix(ReferenceHub hub)
- {
- CustomSetting.SendSettingsToPlayer(Player.Get(hub));
- return false;
- }
+ CustomSetting.SendSettingsToPlayer(Player.Get(hub));
+ return false;
}
}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/SendSettingsServerSync.cs b/SecretAPI/Patches/Features/SendSettingsServerSync.cs
index e84d1ff..4f68402 100644
--- a/SecretAPI/Patches/Features/SendSettingsServerSync.cs
+++ b/SecretAPI/Patches/Features/SendSettingsServerSync.cs
@@ -1,21 +1,20 @@
-namespace SecretAPI.Patches.Features
-{
- using HarmonyLib;
- using SecretAPI.Attributes;
- using SecretAPI.Features.UserSettings;
- using UserSettings.ServerSpecific;
+namespace SecretAPI.Patches.Features;
+
+using HarmonyLib;
+using SecretAPI.Attributes;
+using SecretAPI.Features.UserSettings;
+using UserSettings.ServerSpecific;
- ///
- /// Fixes to resync with .
- ///
- [HarmonyPatchCategory(nameof(CustomSetting))]
- [HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.SendToAll))]
- internal static class SendSettingsServerSync
+///
+/// Fixes to resync with .
+///
+[HarmonyPatchCategory(nameof(CustomSetting))]
+[HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.SendToAll))]
+internal static class SendSettingsServerSync
+{
+ private static bool Prefix()
{
- private static bool Prefix()
- {
- CustomSetting.ResyncServer();
- return false;
- }
+ CustomSetting.ResyncServer();
+ return false;
}
}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs b/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
index a1b1e74..d366fec 100644
--- a/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
+++ b/SecretAPI/Patches/Features/SettingsOriginalDefinitionFix.cs
@@ -1,26 +1,25 @@
-namespace SecretAPI.Patches.Features
-{
- using HarmonyLib;
- using SecretAPI.Attributes;
- using SecretAPI.Features.UserSettings;
- using UserSettings.ServerSpecific;
+namespace SecretAPI.Patches.Features;
- ///
- /// Fixes on custom settings.
- ///
- [HarmonyPatchCategory(nameof(CustomSetting))]
- [HarmonyPatch(typeof(ServerSpecificSettingBase), nameof(ServerSpecificSettingBase.OriginalDefinition), MethodType.Getter)]
- internal static class SettingsOriginalDefinitionFix
- {
+using HarmonyLib;
+using SecretAPI.Attributes;
+using SecretAPI.Features.UserSettings;
+using UserSettings.ServerSpecific;
+
+///
+/// Fixes on custom settings.
+///
+[HarmonyPatchCategory(nameof(CustomSetting))]
+[HarmonyPatch(typeof(ServerSpecificSettingBase), nameof(ServerSpecificSettingBase.OriginalDefinition), MethodType.Getter)]
+internal static class SettingsOriginalDefinitionFix
+{
#pragma warning disable SA1313
- private static void Postfix(ServerSpecificSettingBase __instance, ref ServerSpecificSettingBase __result)
+ private static void Postfix(ServerSpecificSettingBase __instance, ref ServerSpecificSettingBase __result)
#pragma warning restore SA1313
- {
- // Prevent handling non SecretAPI settings.
- if (__result != null)
- return;
+ {
+ // Prevent handling non SecretAPI settings.
+ if (__result != null)
+ return;
- __result = CustomSetting.Get(__instance.GetType(), __instance.SettingId)?.Base ?? null!;
- }
+ __result = CustomSetting.Get(__instance.GetType(), __instance.SettingId)?.Base ?? null!;
}
}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs b/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
index 39cf781..0b6c5f7 100644
--- a/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
+++ b/SecretAPI/Patches/Features/SettingsSyncValidateFix.cs
@@ -1,26 +1,25 @@
-namespace SecretAPI.Patches.Features
-{
- using HarmonyLib;
- using SecretAPI.Attributes;
- using SecretAPI.Features.UserSettings;
- using UserSettings.ServerSpecific;
+namespace SecretAPI.Patches.Features;
- ///
- /// Fixes validation for .
- ///
- [HarmonyPatchCategory(nameof(CustomSetting))]
- [HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.ServerPrevalidateClientResponse))]
- internal static class SettingsSyncValidateFix
- {
+using HarmonyLib;
+using SecretAPI.Attributes;
+using SecretAPI.Features.UserSettings;
+using UserSettings.ServerSpecific;
+
+///
+/// Fixes validation for .
+///
+[HarmonyPatchCategory(nameof(CustomSetting))]
+[HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.ServerPrevalidateClientResponse))]
+internal static class SettingsSyncValidateFix
+{
#pragma warning disable SA1313
- private static void Postfix(SSSClientResponse msg, ref bool __result)
+ private static void Postfix(SSSClientResponse msg, ref bool __result)
#pragma warning restore SA1313
- {
- // prevent overriding already validated settings
- if (__result)
- return;
+ {
+ // prevent overriding already validated settings
+ if (__result)
+ return;
- __result = CustomSetting.Get(msg.SettingType, msg.Id) != null;
- }
+ __result = CustomSetting.Get(msg.SettingType, msg.Id) != null;
}
}
\ No newline at end of file
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index f41521c..9033e9a 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -1,60 +1,59 @@
-namespace SecretAPI
+namespace SecretAPI;
+
+using System;
+using System.Reflection;
+using HarmonyLib;
+using LabApi.Features;
+using LabApi.Loader.Features.Plugins;
+using LabApi.Loader.Features.Plugins.Enums;
+using SecretAPI.Attributes;
+
+///
+/// Main class handling loading API.
+///
+public class SecretApi : Plugin
{
- using System;
- using System.Reflection;
- using HarmonyLib;
- using LabApi.Features;
- using LabApi.Loader.Features.Plugins;
- using LabApi.Loader.Features.Plugins.Enums;
- using SecretAPI.Attributes;
+ ///
+ public override string Name => "SecretAPI";
- ///
- /// Main class handling loading API.
- ///
- public class SecretApi : Plugin
- {
- ///
- public override string Name => "SecretAPI";
+ ///
+ public override string Description => "API for SCP:SL";
- ///
- public override string Description => "API for SCP:SL";
+ ///
+ public override string Author => "@obvEve";
- ///
- public override string Author => "@obvEve";
+ ///
+ public override LoadPriority Priority => LoadPriority.Highest;
- ///
- public override LoadPriority Priority => LoadPriority.Highest;
+ ///
+ public override Version Version { get; } = Assembly.GetName().Version;
- ///
- public override Version Version { get; } = Assembly.GetName().Version;
+ ///
+ public override Version RequiredApiVersion => LabApiProperties.CurrentVersion;
- ///
- public override Version RequiredApiVersion => LabApiProperties.CurrentVersion;
+ ///
+ /// We use transparent here because this is an API and should not interfere by itself with game logic.
+ public override bool IsTransparent => true;
- ///
- /// We use transparent here because this is an API and should not interfere by itself with game logic.
- public override bool IsTransparent => true;
-
- ///
- /// Gets the harmony to use for the API.
- ///
- internal static Harmony Harmony { get; } = new("SecretAPI" + DateTime.Now);
+ ///
+ /// Gets the harmony to use for the API.
+ ///
+ internal static Harmony Harmony { get; } = new("SecretAPI" + DateTime.Now);
- ///
- /// Gets the Assembly of the API.
- ///
- internal static Assembly Assembly { get; } = typeof(SecretApi).Assembly;
+ ///
+ /// Gets the Assembly of the API.
+ ///
+ internal static Assembly Assembly { get; } = typeof(SecretApi).Assembly;
- ///
- public override void Enable()
- {
- CallOnLoadAttribute.Load(Assembly);
- }
+ ///
+ public override void Enable()
+ {
+ CallOnLoadAttribute.Load(Assembly);
+ }
- ///
- public override void Disable()
- {
- Harmony.UnpatchAll(Harmony.Id);
- }
+ ///
+ public override void Disable()
+ {
+ Harmony.UnpatchAll(Harmony.Id);
}
}
\ No newline at end of file
From ff35b6adfff99bda5163c6ac907f9ded3b60c9e6 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 30 Mar 2026 19:58:09 +0200
Subject: [PATCH 15/37] CustomSetting:HasValueChanged -> Explicitly default to
false
---
SecretAPI/Features/UserSettings/CustomSetting.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index 686d803..e6c86df 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -74,7 +74,7 @@ protected CustomSetting(ServerSpecificSettingBase setting)
///
/// Gets a value indicating whether the current value received is different to that prior to the most recent call.
///
- public virtual bool HasValueChanged { get; }
+ public virtual bool HasValueChanged { get; } = false;
///
/// Gets or sets a value indicating whether the setting is server side.
From 1e175cf0eb6934101d4a3d8c9bc760ab711def60 Mon Sep 17 00:00:00 2001
From: Evelyn <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:27:24 +0200
Subject: [PATCH 16/37] Improvements to RoomExtensions (#84)
* TryGetTeleportLocation
* RoomSafetyFailReason
* RoomSafetyFailReason.KnownBad + public KnownUnsafeRooms
* fix RoomSafetyFailReason.KnownBad being unused
* Fix errors
---
SecretAPI/Enums/RoomSafetyFailReason.cs | 43 +++++++++++++++
SecretAPI/Extensions/RoomExtensions.cs | 70 ++++++++++++++++++++++---
2 files changed, 106 insertions(+), 7 deletions(-)
create mode 100644 SecretAPI/Enums/RoomSafetyFailReason.cs
diff --git a/SecretAPI/Enums/RoomSafetyFailReason.cs b/SecretAPI/Enums/RoomSafetyFailReason.cs
new file mode 100644
index 0000000..a8b305b
--- /dev/null
+++ b/SecretAPI/Enums/RoomSafetyFailReason.cs
@@ -0,0 +1,43 @@
+namespace SecretAPI.Enums;
+
+using System;
+using MapGeneration;
+using SecretAPI.Extensions;
+using UnityEngine;
+
+///
+/// Reasons why should fail.
+///
+[Flags]
+public enum RoomSafetyFailReason
+{
+ ///
+ /// No fail.
+ ///
+ None = 0,
+
+ ///
+ /// Room safety check will fail if warhead has gone off and room is not part of .
+ ///
+ Warhead = 1 << 0,
+
+ ///
+ /// Room safety check will fail if decontamination has gone off and room is part of .
+ ///
+ Decontamination = 1 << 1,
+
+ ///
+ /// Room safety check will fail if room has .
+ ///
+ Tesla = 1 << 2,
+
+ ///
+ /// Room safety check will fail if the listed room fails a check.
+ ///
+ MissingFloor = 1 << 3,
+
+ ///
+ /// Room safety check will fail if the listed room is part of .
+ ///
+ KnownBad = 1 << 4,
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/RoomExtensions.cs b/SecretAPI/Extensions/RoomExtensions.cs
index 5225af3..e95fefe 100644
--- a/SecretAPI/Extensions/RoomExtensions.cs
+++ b/SecretAPI/Extensions/RoomExtensions.cs
@@ -1,8 +1,12 @@
namespace SecretAPI.Extensions;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using LabApi.Features.Wrappers;
using MapGeneration;
+using PlayerRoles.FirstPersonControl;
+using PlayerRoles.PlayableScps.Scp106;
+using SecretAPI.Enums;
using UnityEngine;
///
@@ -10,9 +14,13 @@
///
public static class RoomExtensions
{
- private static readonly List KnownUnsafeRooms =
+ private const float RaycastDistance = 2;
+
+ ///
+ /// Gets a list of that will be denied by .
+ ///
+ public static List KnownUnsafeRooms { get; } =
[
- RoomName.HczTesla, // Instant death
RoomName.EzEvacShelter, // Stuck permanently
RoomName.EzCollapsedTunnel, // Stuck permanently
RoomName.HczWaysideIncinerator, // Death
@@ -23,18 +31,66 @@ public static class RoomExtensions
/// Gets whether a room is safe to teleport to. Will consider decontamination, warhead, teslas and void rooms.
///
/// The room to check.
+ /// Reasons why the safety check should fail.
/// Whether the room is safe to teleport to.
- public static bool IsSafeToTeleport(this Room room)
+ public static bool IsSafeToTeleport(this Room room, RoomSafetyFailReason failReasons)
{
- if (Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
+ if (failReasons.HasFlag(RoomSafetyFailReason.Warhead) && Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
return false;
- if (Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
+ if (failReasons.HasFlag(RoomSafetyFailReason.Decontamination) && Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
return false;
- if (KnownUnsafeRooms.Contains(room.Name))
+ if (failReasons.HasFlag(RoomSafetyFailReason.Tesla) && room.Name == RoomName.HczTesla)
+ return false;
+
+ if (failReasons.HasFlag(RoomSafetyFailReason.KnownBad) && KnownUnsafeRooms.Contains(room.Name))
+ return false;
+
+ if (failReasons.HasFlag(RoomSafetyFailReason.MissingFloor) && !Physics.Raycast(room.Position, Vector3.down, out _, RaycastDistance, FpcStateProcessor.Mask))
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Tries to get a location to teleport a to.
+ ///
+ /// The player to attempt to get a teleport position from.
+ /// The position found if any, otherwise null.
+ /// If set to anything other than will only attempt to find in that zone.
+ /// The default radius allowed nea the found spot.
+ /// Whether a valid teleport position was correctly found.
+ public static bool TryGetTeleportLocation(this Player player, [NotNullWhen(true)] out Vector3? position, FacilityZone zone = FacilityZone.None, float defaultRadius = Scp106PocketExitFinder.RaycastRange)
+ {
+ position = null;
+ return player.RoleBase is IFpcRole fpc && TryGetTeleportLocation(fpc, out position, zone);
+ }
+
+ ///
+ /// Tries to get a location to teleport a to.
+ ///
+ /// The to attempt to get a teleport position from.
+ /// The position found if any, otherwise null.
+ /// If set to anything other than will only attempt to find in that zone.
+ /// The default radius allowed nea the found spot.
+ /// Whether a valid teleport position was correctly found.
+ public static bool TryGetTeleportLocation(this IFpcRole fpc, [NotNullWhen(true)] out Vector3? position, FacilityZone zone = FacilityZone.None, float defaultRadius = Scp106PocketExitFinder.RaycastRange)
+ {
+ position = null;
+
+ IEnumerable poses = zone == FacilityZone.None
+ ? SafeLocationFinder.GetLocations(null, null)
+ : Scp106PocketExitFinder.GetPosesForZone(zone);
+
+ if (!poses.TryGetRandomValue(out Pose pose))
return false;
- return Physics.Raycast(room.Position, Vector3.down, out _, 2);
+ float radius = defaultRadius;
+ if (Room.TryGetRoomAtPosition(pose.position, out Room? room))
+ radius = Scp106PocketExitFinder.GetRaycastRange(room.Zone);
+
+ position = SafeLocationFinder.GetSafePosition(pose.position, pose.forward, radius, fpc.FpcModule.CharController);
+ return true;
}
}
\ No newline at end of file
From aadb2e45d36d6f2417fd0f31fc86d9ecf48e332b Mon Sep 17 00:00:00 2001
From: Evelyn <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:28:20 +0200
Subject: [PATCH 17/37] Prefab Debugging (#88)
---
SecretAPI/Debugging/PrefabDebugging.cs | 51 ++++++++++++++++++++++++++
SecretAPI/Features/PrefabManager.cs | 48 ++++++++++++++++++++----
2 files changed, 92 insertions(+), 7 deletions(-)
create mode 100644 SecretAPI/Debugging/PrefabDebugging.cs
diff --git a/SecretAPI/Debugging/PrefabDebugging.cs b/SecretAPI/Debugging/PrefabDebugging.cs
new file mode 100644
index 0000000..d82e9f2
--- /dev/null
+++ b/SecretAPI/Debugging/PrefabDebugging.cs
@@ -0,0 +1,51 @@
+#if DEBUG
+
+namespace SecretAPI.Debugging;
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using LabApi.Events.Handlers;
+using Mirror;
+using SecretAPI.Attributes;
+using SecretAPI.Features;
+using UnityEngine;
+using Logger = LabApi.Features.Console.Logger;
+
+///
+/// Debugs basegame prefabs by logging information about them.
+///
+internal static class PrefabDebugging
+{
+ ///
+ /// Loads the prefab debugging.
+ ///
+ [CallOnLoad]
+ internal static void Load()
+ {
+ ServerEvents.WaitingForPlayers += OnWaiting;
+ }
+
+ private static void OnWaiting()
+ {
+ foreach (PropertyInfo properties in typeof(PrefabManager).GetProperties())
+ {
+ try
+ {
+ if (properties.GetValue(null) == null)
+ Logger.Error($"[PrefabDebugging] {properties.Name} returned a null value!");
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"[PrefabDebugging] {properties.Name} ran into an exception: {ex}");
+ }
+ }
+
+ foreach (KeyValuePair pair in NetworkClient.prefabs)
+ {
+ Logger.Debug($"[PrefabDebugging] Key ({pair.Key}) - Value ({pair.Value.name})");
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/SecretAPI/Features/PrefabManager.cs b/SecretAPI/Features/PrefabManager.cs
index 80250c3..8008760 100644
--- a/SecretAPI/Features/PrefabManager.cs
+++ b/SecretAPI/Features/PrefabManager.cs
@@ -2,8 +2,11 @@
using System;
using System.Linq;
+using AdminToys;
using Interactables.Interobjects;
using MapGeneration;
+using Mirror;
+using UnityEngine;
///
/// Manages prefabs that have variants and cannot be easily used within .
@@ -15,6 +18,12 @@ public static class PrefabManager
private const string HczBulkDoorName = "HCZ BulkDoor";
private const string EzDoorName = "EZ BreakableDoor";
+ private const string EzArmCameraToyName = "EzArmCameraToy";
+ private const string EzCameraToyName = "EzCameraToy";
+ private const string LczCameraToyName = "LczCameraToy";
+ private const string HczCameraToyName = "HczCameraToy";
+ private const string SzCameraToyName = "SzCameraToy";
+
///
/// Gets the prefab.
///
@@ -23,24 +32,49 @@ public static class PrefabManager
///
/// Gets the found in .
///
- public static BasicDoor LczDoorPrefab => field ??= GetDoor(LczDoorName);
+ public static BasicDoor LczDoorPrefab => field ??= GetOrThrow(LczDoorName);
///
/// Gets the found in .
///
- public static BasicDoor HczDoorPrefab => field ??= GetDoor(HczDoorName);
+ public static BasicDoor HczDoorPrefab => field ??= GetOrThrow(HczDoorName);
///
/// Gets the found in .
///
- public static BasicDoor HczBulkDoorPrefab => field ??= GetDoor(HczBulkDoorName);
+ public static BasicDoor HczBulkDoorPrefab => field ??= GetOrThrow(HczBulkDoorName);
///
/// Gets the found in .
///
- public static BasicDoor EzDoorPrefab => field ??= GetDoor(EzDoorName);
+ public static BasicDoor EzDoorPrefab => field ??= GetOrThrow(EzDoorName);
+
+ ///
+ /// Gets the found in , with an arm extension.
+ ///
+ public static Scp079CameraToy EzArmCameraToyPrefab => field ??= GetOrThrow(EzArmCameraToyName);
+
+ ///
+ /// Gets the found in .
+ ///
+ public static Scp079CameraToy EzCameraToyPrefab => field ??= GetOrThrow(EzCameraToyName);
+
+ ///
+ /// Gets the found in .
+ ///
+ public static Scp079CameraToy LczCameraToyPrefab => field ??= GetOrThrow(LczCameraToyName);
+
+ ///
+ /// Gets the found in .
+ ///
+ public static Scp079CameraToy HczCameraToyPrefab => field ??= GetOrThrow(HczCameraToyName);
+
+ ///
+ /// Gets the found in .
+ ///
+ public static Scp079CameraToy SzCameraToyPrefab => field ??= GetOrThrow(SzCameraToyName);
- private static BasicDoor GetDoor(string name)
- => PrefabStore.AllComponentPrefabs.FirstOrDefault(d => d.name == name)
- ?? throw new InvalidOperationException($"[PrefabManager] Failed to get door named {name} | Report this as a bug!");
+ private static T GetOrThrow(string name)
+ where T : NetworkBehaviour => PrefabStore.AllComponentPrefabs.FirstOrDefault(c => c.name == name)
+ ?? throw new InvalidOperationException($"[PrefabManager] Failed to component ({typeof(T).Name}) by name {name} | Report this as a bug");
}
\ No newline at end of file
From b0ccc130a60c36c119b87bdf030635d4e0c2ec82 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:32:00 +0200
Subject: [PATCH 18/37] fix: nuget push
---
.github/workflows/nuget.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index 1be9c83..6ad684c 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -33,7 +33,7 @@ jobs:
- name: Build and Pack NuGet
env:
SL_REFERENCES: ${{ env.REFERENCES_PATH }}
- run: dotnet pack -c Release --output ${{ NUGET_PACKAGED_PATH }}
+ run: dotnet pack -c Release --output ${{ env.NUGET_PACKAGED_PATH }}
- name: Push NuGet package
run: |
From 98da5087b428619c38343fa3a41d53df6e976feb Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:35:03 +0200
Subject: [PATCH 19/37] Bump to V3.0.0-beta2
---
SecretAPI/SecretAPI.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 48282bb..7feb83f 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -4,7 +4,7 @@
net48
latest
enable
- 3.0.0-beta1
+ 3.0.0-beta2
true
From 08c3dc6189cd9e03b1cebe97a732d86b5e9f77cf Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:40:52 +0200
Subject: [PATCH 20/37] docs: Remove outdated mention of RoleExtensions and add
CallOnLoadAttribute mention in README.md
---
.github/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/README.md b/.github/README.md
index dd60495..02d086c 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -3,11 +3,11 @@
# Features
- CollectionExtensions: Extensions to provide utility for collections like lists and arrays.
-- RoleExtensions: Extensions to help with handling role specific tasks. Like getting a role's spawn point.
- RoomExtensions: Extensions to help with room specific tasks, like checking if a room is safe to teleport to.
- HarmonyExtensions: Extensions to provide more utility to Harmony patching, like adding some updated Harmony features which can't be utilised, i.e. patching by category.
- CustomPlayerEffect: Create custom status effects using the base-game system.
-- IRegister: Handle auto registering certain plugin features inheriting this interface and then running `IRegister.RegisterAll()` in your plugin's initialise method.
+- IRegister: Handle auto registering certain plugin features inheriting this interface and then running `IRegister.RegisterAll(Assembly)` in your plugin's initialise method.
+- CallOnLoadAttribute: Allows calling static initialize methods on enable via ``CallOnLoadAttribute.Load(Assembly)``
- CustomSetting: Server Specific Settings without the management hassle, control everything for 1 setting in 1 class, including permissions. A better setting system overall.
# Examples
From 65cc8f2fbc596ccb7aa2e5c030060049a4fe7cb5 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:41:20 +0200
Subject: [PATCH 21/37] CustomHeader::Label & ReducedPadding getters
---
SecretAPI/Features/UserSettings/CustomHeader.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/SecretAPI/Features/UserSettings/CustomHeader.cs b/SecretAPI/Features/UserSettings/CustomHeader.cs
index d906ea8..5dc96cc 100644
--- a/SecretAPI/Features/UserSettings/CustomHeader.cs
+++ b/SecretAPI/Features/UserSettings/CustomHeader.cs
@@ -25,4 +25,14 @@ public CustomHeader(string label, bool reducedPadding = false, string? hint = nu
///
public SSGroupHeader Base { get; }
+
+ ///
+ /// Gets the label of the header.
+ ///
+ public string Label => Base.Label;
+
+ ///
+ /// Gets a value indicating whether the padding should be reduced.
+ ///
+ public bool ReducedPadding => Base.ReducedPadding;
}
\ No newline at end of file
From 2f85f453b45ca6d4117994478cff06fed2bc3aed Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 14:53:53 +0200
Subject: [PATCH 22/37] fix: CustomPlainTextSetting not implementing
HasValueChanged
---
SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
index 3e10dbb..cc759c4 100644
--- a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -46,6 +46,9 @@ protected CustomPlainTextSetting(
///
public new SSPlaintextSetting Base { get; }
+ ///
+ public override bool HasValueChanged => LastInputText != InputText;
+
///
/// Gets the input text prior to the most recent call.
///
From 49c79cb4d6ea17cbdcae892b7850e801e6e860b6 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 15:56:57 +0200
Subject: [PATCH 23/37] Improve github actions
---
.github/workflows/nuget.yml | 28 +++++++++++-----------------
.github/workflows/pull_request.yml | 23 +++++++++--------------
2 files changed, 20 insertions(+), 31 deletions(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index 6ad684c..7cb7a3c 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -6,12 +6,11 @@ on:
jobs:
build:
- runs-on: windows-latest
+ runs-on: ubuntu-latest
env:
- REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip
REFERENCES_PATH: ${{ github.workspace }}/References
- NUGET_PACKAGED_PATH: ${{ github.workspace }}/nupkgs
+ BUILD_OUTPUT: ${{ github.workspace }}/Output
steps:
- name: Checkout
@@ -20,22 +19,17 @@ jobs:
- name: Setup Dotnet
uses: actions/setup-dotnet@v4.0.1
- - name: Download References
- shell: pwsh
- run: |
- Invoke-WebRequest -Uri ${{ env.REFERENCES_URL }} -OutFile "${{ github.workspace }}/References.zip"
- Expand-Archive -Path "${{ github.workspace }}/References.zip" -DestinationPath ${{ env.REFERENCES_PATH }}
-
-# - name: Rename Assembly-CSharp-Publicized to Assembly-CSharp
-# shell: pwsh
-# run: Rename-Item -Path "${{ env.REFERENCES_PATH }}\Assembly-CSharp-Publicized.dll" -NewName "Assembly-CSharp.dll"
+ - name: Download SCP:SL References
+ uses: Axwabo/scpsl-references-downloader@v1
+ id: refs
+ with:
+ directory: $ {{ env.REFERENCES_PATH }}
- name: Build and Pack NuGet
env:
- SL_REFERENCES: ${{ env.REFERENCES_PATH }}
- run: dotnet pack -c Release --output ${{ env.NUGET_PACKAGED_PATH }}
+ SL_REFERENCES: ${{ steps.refs.outputs.target }}
+ run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
- name: Push NuGet package
- run: |
- $PackageFile = (Get-ChildItem -Path "${NUGET_PACKAGED_PATH}" -Include 'SecretAPI.*.nupkg' -Recurse | Select-Object -First 1).FullName
- dotnet nuget push $PackageFile --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
\ No newline at end of file
+ working-directory: ${{ env.BUILD_OUTPUT }}
+ run: dotnet nuget push SecretAPI.*.nupkg
\ No newline at end of file
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 2de576e..59cab2c 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -10,8 +10,8 @@ jobs:
runs-on: windows-latest
env:
- REFERENCES_URL: https://exmod-team.github.io/SL-References/Dev.zip
REFERENCES_PATH: ${{ github.workspace }}/References
+ BUILD_OUTPUT: ${{ github.workspace }}/Output
steps:
- name: Checkout
@@ -20,25 +20,20 @@ jobs:
- name: Setup Dotnet
uses: actions/setup-dotnet@v4.0.1
- - name: Download References
- shell: pwsh
- run: |
- Invoke-WebRequest -Uri ${{ env.REFERENCES_URL }} -OutFile "${{ github.workspace }}/References.zip"
- Expand-Archive -Path "${{ github.workspace }}/References.zip" -DestinationPath ${{ env.REFERENCES_PATH }}
-
-# - name: Rename Assembly-CSharp-Publicized to Assembly-CSharp
-# shell: pwsh
-# run: Rename-Item -Path "${{ env.REFERENCES_PATH }}\Assembly-CSharp-Publicized.dll" -NewName "Assembly-CSharp.dll"
+ - name: Download SCP:SL References
+ uses: Axwabo/scpsl-references-downloader@v1
+ id: refs
+ with:
+ directory: $ {{ env.REFERENCES_PATH }}
- name: Build
env:
- SL_REFERENCES: ${{ env.REFERENCES_PATH }}
- shell: pwsh
- run: dotnet build -c Release
+ SL_REFERENCES: ${{ steps.refs.outputs.target }}
+ run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
- name: Upload
uses: actions/upload-artifact@v4
with:
name: Build Result
- path: ${{ github.workspace }}/**/bin/Release/net48/*SecretAPI*.dll
+ path: ${{ env.BUILD_OUTPUT }}
retention-days: 7
\ No newline at end of file
From ed82973094c9d4d5767454c5bd29a37cf5bcd485 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 16:00:58 +0200
Subject: [PATCH 24/37] fix actions? i added a space by accident
---
.github/workflows/nuget.yml | 2 +-
.github/workflows/pull_request.yml | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index 7cb7a3c..4f5c14e 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -23,7 +23,7 @@ jobs:
uses: Axwabo/scpsl-references-downloader@v1
id: refs
with:
- directory: $ {{ env.REFERENCES_PATH }}
+ directory: ${{ env.REFERENCES_PATH }}
- name: Build and Pack NuGet
env:
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 59cab2c..75be90d 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -24,12 +24,12 @@ jobs:
uses: Axwabo/scpsl-references-downloader@v1
id: refs
with:
- directory: $ {{ env.REFERENCES_PATH }}
+ directory: ${{ env.REFERENCES_PATH }}
- name: Build
env:
SL_REFERENCES: ${{ steps.refs.outputs.target }}
- run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
+ run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
- name: Upload
uses: actions/upload-artifact@v4
From 9f5a723cadc1631e52f071b37d33db46f88f532a Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 2 Apr 2026 16:05:09 +0200
Subject: [PATCH 25/37] Fix redundant step + only upload SecretAPI
---
.github/workflows/nuget.yml | 3 ---
.github/workflows/pull_request.yml | 5 +----
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index 4f5c14e..c0f73c3 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -16,9 +16,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- - name: Setup Dotnet
- uses: actions/setup-dotnet@v4.0.1
-
- name: Download SCP:SL References
uses: Axwabo/scpsl-references-downloader@v1
id: refs
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 75be90d..5267b93 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -17,9 +17,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- - name: Setup Dotnet
- uses: actions/setup-dotnet@v4.0.1
-
- name: Download SCP:SL References
uses: Axwabo/scpsl-references-downloader@v1
id: refs
@@ -35,5 +32,5 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Build Result
- path: ${{ env.BUILD_OUTPUT }}
+ path: ${{ env.BUILD_OUTPUT }}\SecretAPI*.dll
retention-days: 7
\ No newline at end of file
From 53762137f4baa0a8bc29cc835f23a79d44be8f0a Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Sun, 5 Apr 2026 00:28:10 +0200
Subject: [PATCH 26/37] Minor code improvements
---
SecretAPI/Extensions/FastEnums.cs | 16 +++++++++
SecretAPI/Extensions/PlayerExtensions.cs | 8 ++---
SecretAPI/Extensions/RoomExtensions.cs | 10 +++---
.../Features/Effects/CustomPlayerEffect.cs | 33 +++++++++----------
SecretAPI/Features/PrefabManager.cs | 2 +-
5 files changed, 41 insertions(+), 28 deletions(-)
create mode 100644 SecretAPI/Extensions/FastEnums.cs
diff --git a/SecretAPI/Extensions/FastEnums.cs b/SecretAPI/Extensions/FastEnums.cs
new file mode 100644
index 0000000..1dcfe39
--- /dev/null
+++ b/SecretAPI/Extensions/FastEnums.cs
@@ -0,0 +1,16 @@
+namespace SecretAPI.Extensions;
+
+using System;
+using SecretAPI.Enums;
+
+///
+/// Faster flag checking for .
+///
+public static class FastEnums
+{
+ ///
+ public static bool HasFlagFast(this DoorPermissionCheck @enum, DoorPermissionCheck flag) => (@enum & flag) == flag;
+
+ ///
+ public static bool HasFlagFast(this RoomSafetyFailReason @enum, RoomSafetyFailReason flag) => (@enum & flag) == flag;
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/PlayerExtensions.cs b/SecretAPI/Extensions/PlayerExtensions.cs
index b4a8a18..afd6335 100644
--- a/SecretAPI/Extensions/PlayerExtensions.cs
+++ b/SecretAPI/Extensions/PlayerExtensions.cs
@@ -29,19 +29,19 @@ public StatusEffectBase GetEffect(string name)
/// Whether a valid permission was found.
public bool HasDoorPermission(IDoorPermissionRequester requester, DoorPermissionCheck checkFlags = DoorPermissionCheck.Default)
{
- if (checkFlags.HasFlag(DoorPermissionCheck.Bypass) && player.IsBypassEnabled)
+ if (checkFlags.HasFlagFast(DoorPermissionCheck.Bypass) && player.IsBypassEnabled)
return true;
- if (checkFlags.HasFlag(DoorPermissionCheck.Role) && player.RoleBase is IDoorPermissionProvider roleProvider && requester.PermissionsPolicy.CheckPermissions(roleProvider.GetPermissions(requester)))
+ if (checkFlags.HasFlagFast(DoorPermissionCheck.Role) && player.RoleBase is IDoorPermissionProvider roleProvider && requester.PermissionsPolicy.CheckPermissions(roleProvider.GetPermissions(requester)))
return true;
foreach (Item item in player.Items)
{
bool isCurrent = item == player.CurrentItem;
- if (!checkFlags.HasFlag(DoorPermissionCheck.CurrentItem) && isCurrent)
+ if (!checkFlags.HasFlagFast(DoorPermissionCheck.CurrentItem) && isCurrent)
continue;
- if (!checkFlags.HasFlag(DoorPermissionCheck.InventoryExcludingCurrent) && !isCurrent)
+ if (!checkFlags.HasFlagFast(DoorPermissionCheck.InventoryExcludingCurrent) && !isCurrent)
continue;
if (item.Base is IDoorPermissionProvider itemProvider && requester.PermissionsPolicy.CheckPermissions(itemProvider.GetPermissions(requester)))
diff --git a/SecretAPI/Extensions/RoomExtensions.cs b/SecretAPI/Extensions/RoomExtensions.cs
index e95fefe..9b23350 100644
--- a/SecretAPI/Extensions/RoomExtensions.cs
+++ b/SecretAPI/Extensions/RoomExtensions.cs
@@ -35,19 +35,19 @@ public static class RoomExtensions
/// Whether the room is safe to teleport to.
public static bool IsSafeToTeleport(this Room room, RoomSafetyFailReason failReasons)
{
- if (failReasons.HasFlag(RoomSafetyFailReason.Warhead) && Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
+ if (failReasons.HasFlagFast(RoomSafetyFailReason.Warhead) && Warhead.IsDetonated && room.Zone != FacilityZone.Surface)
return false;
- if (failReasons.HasFlag(RoomSafetyFailReason.Decontamination) && Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
+ if (failReasons.HasFlagFast(RoomSafetyFailReason.Decontamination) && Decontamination.IsDecontaminating && room.Zone == FacilityZone.LightContainment)
return false;
- if (failReasons.HasFlag(RoomSafetyFailReason.Tesla) && room.Name == RoomName.HczTesla)
+ if (failReasons.HasFlagFast(RoomSafetyFailReason.Tesla) && room.Name == RoomName.HczTesla)
return false;
- if (failReasons.HasFlag(RoomSafetyFailReason.KnownBad) && KnownUnsafeRooms.Contains(room.Name))
+ if (failReasons.HasFlagFast(RoomSafetyFailReason.KnownBad) && KnownUnsafeRooms.Contains(room.Name))
return false;
- if (failReasons.HasFlag(RoomSafetyFailReason.MissingFloor) && !Physics.Raycast(room.Position, Vector3.down, out _, RaycastDistance, FpcStateProcessor.Mask))
+ if (failReasons.HasFlagFast(RoomSafetyFailReason.MissingFloor) && !Physics.Raycast(room.Position, Vector3.down, out _, RaycastDistance, FpcStateProcessor.Mask))
return false;
return true;
diff --git a/SecretAPI/Features/Effects/CustomPlayerEffect.cs b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
index 6fedb8d..c4b784a 100644
--- a/SecretAPI/Features/Effects/CustomPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
@@ -16,8 +16,6 @@
///
public abstract class CustomPlayerEffect : StatusEffectBase
{
- private static bool isLoaded;
-
///
/// Gets a list of types to register (Must inherit ).
/// Must be , can be gotten through typeof(Scp207)
@@ -43,25 +41,24 @@ internal static void Initialize()
EffectsToRegister.Add(typeof(StaminaUsageDisablerEffect));
EffectsToRegister.Add(typeof(SprintDisablerEffect));
- SceneManager.sceneLoaded += (_, _) =>
- {
- if (isLoaded)
- return;
+ SceneManager.sceneLoaded += OnSceneLoaded;
+ }
- isLoaded = true;
+ private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
+ {
+ SceneManager.sceneLoaded -= OnSceneLoaded;
- Transform playerEffects = PrefabStore.Prefab.playerEffectsController.effectsGameObject.transform;
- foreach (Type type in EffectsToRegister)
+ Transform playerEffects = PrefabStore.Prefab.playerEffectsController.effectsGameObject.transform;
+ foreach (Type type in EffectsToRegister)
+ {
+ if (!typeof(StatusEffectBase).IsAssignableFrom(type))
{
- if (!typeof(StatusEffectBase).IsAssignableFrom(type))
- {
- Logger.Error($"[CustomPlayerEffect.Initialize] {type.FullName} is not a valid StatusEffectBase and thus could not be registered!");
- continue;
- }
-
- // register effect into prefab
- new GameObject(type.Name, type).transform.parent = playerEffects;
+ Logger.Error($"[CustomPlayerEffect.Initialize] {type.FullName} is not a valid StatusEffectBase and thus could not be registered!");
+ continue;
}
- };
+
+ // register effect into prefab
+ new GameObject(type.Name, type).transform.parent = playerEffects;
+ }
}
}
\ No newline at end of file
diff --git a/SecretAPI/Features/PrefabManager.cs b/SecretAPI/Features/PrefabManager.cs
index 8008760..cfb46c2 100644
--- a/SecretAPI/Features/PrefabManager.cs
+++ b/SecretAPI/Features/PrefabManager.cs
@@ -76,5 +76,5 @@ public static class PrefabManager
private static T GetOrThrow(string name)
where T : NetworkBehaviour => PrefabStore.AllComponentPrefabs.FirstOrDefault(c => c.name == name)
- ?? throw new InvalidOperationException($"[PrefabManager] Failed to component ({typeof(T).Name}) by name {name} | Report this as a bug");
+ ?? throw new InvalidOperationException($"[PrefabManager] Failed to get component ({typeof(T).Name}) by name {name} | Report this as a bug");
}
\ No newline at end of file
From 1ca4da34e3a9b9572a2cd610d10e0111adc1343b Mon Sep 17 00:00:00 2001
From: Evelyn <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 20:22:47 +0200
Subject: [PATCH 27/37] PlayerRoundIgnore (#89)
* PlayerRoundIgnore
* Handle player leaving
---
SecretAPI/Debugging/PatchDebugger.cs | 22 +++++++
SecretAPI/Enums/RoundIgnoreStatus.cs | 26 ++++++++
SecretAPI/Extensions/FastEnums.cs | 3 +
SecretAPI/Features/PlayerRoundIgnore.cs | 39 ++++++++++++
.../Patches/Features/RoundEndIgnorePatch.cs | 54 ++++++++++++++++
.../Patches/Features/RoundIgnoreCountPatch.cs | 61 +++++++++++++++++++
6 files changed, 205 insertions(+)
create mode 100644 SecretAPI/Debugging/PatchDebugger.cs
create mode 100644 SecretAPI/Enums/RoundIgnoreStatus.cs
create mode 100644 SecretAPI/Features/PlayerRoundIgnore.cs
create mode 100644 SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
create mode 100644 SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
diff --git a/SecretAPI/Debugging/PatchDebugger.cs b/SecretAPI/Debugging/PatchDebugger.cs
new file mode 100644
index 0000000..652c502
--- /dev/null
+++ b/SecretAPI/Debugging/PatchDebugger.cs
@@ -0,0 +1,22 @@
+#if DEBUG
+
+namespace SecretAPI.Debugging;
+
+using SecretAPI.Attributes;
+
+///
+/// Patch debugger.
+///
+internal static class PatchDebugger
+{
+ ///
+ /// Loads the debugs for patches and ensures all patches have loaded.
+ ///
+ [CallOnLoad]
+ internal static void Load()
+ {
+ SecretApi.Harmony.PatchAll(SecretApi.Assembly);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/SecretAPI/Enums/RoundIgnoreStatus.cs b/SecretAPI/Enums/RoundIgnoreStatus.cs
new file mode 100644
index 0000000..ee9eafb
--- /dev/null
+++ b/SecretAPI/Enums/RoundIgnoreStatus.cs
@@ -0,0 +1,26 @@
+namespace SecretAPI.Enums;
+
+using System;
+
+///
+/// Defines how a player should be ignored during a round.
+///
+[Flags]
+public enum RoundIgnoreStatus
+{
+ ///
+ /// Player will not be ignored.
+ ///
+ None = 0,
+
+ ///
+ /// Player is ignored from .
+ ///
+ RoundEndingCheck = 1 << 0,
+
+ ///
+ /// Player is ignored from .
+ ///
+ /// This will not reflect when round is not in progress (either due to it being ended or due to lobby).
+ ScpTargetCount = 1 << 1,
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/FastEnums.cs b/SecretAPI/Extensions/FastEnums.cs
index 1dcfe39..2273f85 100644
--- a/SecretAPI/Extensions/FastEnums.cs
+++ b/SecretAPI/Extensions/FastEnums.cs
@@ -13,4 +13,7 @@ public static class FastEnums
///
public static bool HasFlagFast(this RoomSafetyFailReason @enum, RoomSafetyFailReason flag) => (@enum & flag) == flag;
+
+ ///
+ public static bool HasFlagFast(this RoundIgnoreStatus @enum, RoundIgnoreStatus flag) => (@enum & flag) == flag;
}
\ No newline at end of file
diff --git a/SecretAPI/Features/PlayerRoundIgnore.cs b/SecretAPI/Features/PlayerRoundIgnore.cs
new file mode 100644
index 0000000..5b7e589
--- /dev/null
+++ b/SecretAPI/Features/PlayerRoundIgnore.cs
@@ -0,0 +1,39 @@
+namespace SecretAPI.Features;
+
+using System.Collections.Generic;
+using LabApi.Events.Arguments.PlayerEvents;
+using LabApi.Events.Handlers;
+using LabApi.Features.Wrappers;
+using SecretAPI.Enums;
+using SecretAPI.Extensions;
+
+///
+/// Handles allowing easier ignoring of players for .
+///
+public static class PlayerRoundIgnore
+{
+ extension(Player player)
+ {
+ ///
+ /// Gets or sets the players current .
+ ///
+ public RoundIgnoreStatus RoundIgnoreStatus
+ {
+ get => PlayerToStatus.GetValueOrDefault(player, RoundIgnoreStatus.None);
+ set => PlayerToStatus[player] = value;
+ }
+ }
+
+ static PlayerRoundIgnore()
+ {
+ SecretApi.Harmony.PatchCategory(nameof(PlayerRoundIgnore));
+ PlayerEvents.Left += OnPlayerLeft;
+ }
+
+ ///
+ /// Gets a collection of to their current .
+ ///
+ public static Dictionary PlayerToStatus { get; } = new();
+
+ private static void OnPlayerLeft(PlayerLeftEventArgs ev) => PlayerToStatus.Remove(ev.Player);
+}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs b/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
new file mode 100644
index 0000000..d8611b1
--- /dev/null
+++ b/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
@@ -0,0 +1,54 @@
+namespace SecretAPI.Patches.Features;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using HarmonyLib;
+using LabApi.Features.Wrappers;
+using PlayerRoles;
+using SecretAPI.Attributes;
+using SecretAPI.Enums;
+using SecretAPI.Extensions;
+using SecretAPI.Features;
+
+///
+/// Handles patching to implement into .
+///
+[HarmonyPatchCategory(nameof(PlayerRoundIgnore))]
+[HarmonyPatch]
+internal static class RoundEndIgnorePatch
+{
+ private const string StateMachine = "<_ProcessServerSideCode>d__58";
+ private const string MoveNext = "MoveNext";
+ private const int ReferenceHubLocalIndex = 20;
+
+ private static MethodInfo TargetMethod()
+ {
+ // typeof(RoundSummary).GetNestedTypes(AccessTools.all).ForEach(type => Logger.Debug(type.FullName ?? "NULL"));
+ Type nestedType = typeof(RoundSummary).GetNestedTypes(AccessTools.all)
+ .FirstOrDefault(type => type.Name is StateMachine) ?? throw new Exception($"Could not locate state machine for {StateMachine}");
+
+ // nestedType.GetMethods(AccessTools.all).ForEach(method => Logger.Debug(method.Name));
+ MethodInfo moveNextMethod = nestedType.GetMethods(AccessTools.all)
+ .FirstOrDefault(x => x.Name.Contains(MoveNext)) ?? throw new Exception($"Could not locate {MoveNext} method in state machine");
+
+ return moveNextMethod;
+ }
+
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ CodeMatcher matcher = new CodeMatcher(instructions, generator)
+ .MatchEndForward(new CodeMatch(CodeInstruction.Call(typeof(PlayerRolesUtils), nameof(PlayerRolesUtils.GetTeam), [typeof(ReferenceHub)])))
+ .CreateLabel(out Label skip)
+ .Insert(
+ new CodeInstruction(OpCodes.Ldloc_S, ReferenceHubLocalIndex),
+ CodeInstruction.Call(typeof(RoundEndIgnorePatch), nameof(IsPlayerIgnored)),
+ new CodeInstruction(OpCodes.Brtrue_S, skip));
+
+ return matcher.InstructionEnumeration();
+ }
+
+ private static bool IsPlayerIgnored(ReferenceHub hub) => Player.Get(hub).RoundIgnoreStatus.HasFlagFast(RoundIgnoreStatus.RoundEndingCheck);
+}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs b/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
new file mode 100644
index 0000000..0a1f32e
--- /dev/null
+++ b/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
@@ -0,0 +1,61 @@
+namespace SecretAPI.Patches.Features;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using HarmonyLib;
+using LabApi.Features.Wrappers;
+using Mirror;
+using SecretAPI.Attributes;
+using SecretAPI.Enums;
+using SecretAPI.Extensions;
+using SecretAPI.Features;
+
+///
+/// Handles patching to implement into .
+///
+[HarmonyPatchCategory(nameof(PlayerRoundIgnore))]
+[HarmonyPatch]
+internal static class RoundIgnoreCountPatch
+{
+ private const string StateMachine = "<>c";
+ private const string UpdateTargetCount = "UpdateTargetCount";
+
+ private static MethodInfo TargetMethod()
+ {
+ Type nestedType = typeof(RoundSummary).GetNestedTypes(AccessTools.all)
+ .FirstOrDefault(currentType => currentType.Name is StateMachine) ?? throw new Exception("Could not locate state machine for RoundSummary::<>c");
+
+ // nestedType.GetMethods(AccessTools.all).ForEach(method => Logger.Debug(method.Name));
+ MethodInfo updateCountMethod = nestedType.GetMethods(AccessTools.all)
+ .FirstOrDefault(x => x.Name.Contains(UpdateTargetCount)) ?? throw new Exception($"Could not locate {UpdateTargetCount} method in state machine");
+
+ return updateCountMethod;
+ }
+
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ CodeMatcher matcher = new CodeMatcher(instructions, generator)
+ .Start()
+ .CreateLabel(out Label skip)
+ .Insert(
+ new CodeInstruction(OpCodes.Ldarg_0),
+ CodeInstruction.Call(typeof(RoundIgnoreCountPatch), nameof(IsPlayerIgnored)),
+ new CodeInstruction(OpCodes.Brtrue_S, skip));
+
+ return matcher.InstructionEnumeration();
+ }
+
+ private static bool IsPlayerIgnored(ReferenceHub hub)
+ {
+ if (!NetworkServer.active || !Round.IsRoundStarted)
+ return false;
+
+ if (hub == null)
+ return false;
+
+ return hub && Player.Get(hub).RoundIgnoreStatus.HasFlagFast(RoundIgnoreStatus.ScpTargetCount);
+ }
+}
\ No newline at end of file
From 16dfc1b1b6ad4f876d6402a99a631f7e46bb508c Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 20:57:13 +0200
Subject: [PATCH 28/37] 3.0-beta3
---
.../Features/Effects/CustomPlayerEffect.cs | 5 ++---
.../{SprintDisablerEffect.cs => Depleted.cs} | 4 ++--
...inaUsageDisablerEffect.cs => Energized.cs} | 4 ++--
.../Effects/TemporaryDamageImmunity.cs | 19 -------------------
.../UserSettings/CustomDropdownSetting.cs | 2 +-
.../Features/UserSettings/CustomHeader.cs | 2 +-
.../UserSettings/CustomPlainTextSetting.cs | 2 +-
.../Features/UserSettings/CustomSetting.cs | 4 ++--
.../UserSettings/CustomSliderSetting.cs | 2 +-
.../UserSettings/CustomTwoButtonSetting.cs | 2 +-
SecretAPI/SecretAPI.csproj | 2 +-
11 files changed, 14 insertions(+), 34 deletions(-)
rename SecretAPI/Features/Effects/{SprintDisablerEffect.cs => Depleted.cs} (77%)
rename SecretAPI/Features/Effects/{StaminaUsageDisablerEffect.cs => Energized.cs} (74%)
delete mode 100644 SecretAPI/Features/Effects/TemporaryDamageImmunity.cs
diff --git a/SecretAPI/Features/Effects/CustomPlayerEffect.cs b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
index c4b784a..0e63469 100644
--- a/SecretAPI/Features/Effects/CustomPlayerEffect.cs
+++ b/SecretAPI/Features/Effects/CustomPlayerEffect.cs
@@ -37,9 +37,8 @@ public abstract class CustomPlayerEffect : StatusEffectBase
internal static void Initialize()
{
SecretApi.Harmony.PatchCategory(nameof(CustomPlayerEffect), SecretApi.Assembly);
- EffectsToRegister.Add(typeof(TemporaryDamageImmunity));
- EffectsToRegister.Add(typeof(StaminaUsageDisablerEffect));
- EffectsToRegister.Add(typeof(SprintDisablerEffect));
+ EffectsToRegister.Add(typeof(Energized));
+ EffectsToRegister.Add(typeof(Depleted));
SceneManager.sceneLoaded += OnSceneLoaded;
}
diff --git a/SecretAPI/Features/Effects/SprintDisablerEffect.cs b/SecretAPI/Features/Effects/Depleted.cs
similarity index 77%
rename from SecretAPI/Features/Effects/SprintDisablerEffect.cs
rename to SecretAPI/Features/Effects/Depleted.cs
index 44f197d..82da797 100644
--- a/SecretAPI/Features/Effects/SprintDisablerEffect.cs
+++ b/SecretAPI/Features/Effects/Depleted.cs
@@ -3,9 +3,9 @@
using PlayerRoles.FirstPersonControl;
///
-/// Effect that disables sprinting for a player. Sets stamina to 0 and disables regen.
+/// Effect that disables sprinting for a player and disables regeneration of it.
///
-public class SprintDisablerEffect : CustomPlayerEffect, IStaminaModifier
+public class Depleted : CustomPlayerEffect, IStaminaModifier
{
///
public bool StaminaModifierActive => IsEnabled;
diff --git a/SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs b/SecretAPI/Features/Effects/Energized.cs
similarity index 74%
rename from SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs
rename to SecretAPI/Features/Effects/Energized.cs
index d20d6a3..e340605 100644
--- a/SecretAPI/Features/Effects/StaminaUsageDisablerEffect.cs
+++ b/SecretAPI/Features/Effects/Energized.cs
@@ -3,9 +3,9 @@
using PlayerRoles.FirstPersonControl;
///
-/// Effect that disables stamina usage.
+/// Effect that removes stamina usage, granting infinite stamina.
///
-public class StaminaUsageDisablerEffect : CustomPlayerEffect, IStaminaModifier
+public class Energized : CustomPlayerEffect, IStaminaModifier
{
///
public bool StaminaModifierActive => IsEnabled;
diff --git a/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs b/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs
deleted file mode 100644
index 25daa52..0000000
--- a/SecretAPI/Features/Effects/TemporaryDamageImmunity.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace SecretAPI.Features.Effects;
-
-using CustomPlayerEffects;
-using PlayerStatsSystem;
-
-///
-/// Grants a player temporary damage immunity.
-///
-public class TemporaryDamageImmunity : CustomPlayerEffect, IDamageModifierEffect
-{
- ///
- public bool DamageModifierActive => IsEnabled;
-
- ///
- public override EffectClassification Classification => EffectClassification.Technical;
-
- ///
- public float GetDamageModifier(float baseDamage, DamageHandlerBase handler, HitboxType hitboxType) => 0;
-}
\ No newline at end of file
diff --git a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
index 2bb966a..feb3ed0 100644
--- a/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomDropdownSetting.cs
@@ -100,7 +100,7 @@ public string[] Options
public void SendServerUpdate(int selectionId) => Base.SendValueUpdate(selectionId, false, IsKnownOwnerHub);
///
- protected internal override void HandleBeforeSettingUpdate()
+ protected override void HandleBeforeSettingUpdate()
{
base.HandleBeforeSettingUpdate();
diff --git a/SecretAPI/Features/UserSettings/CustomHeader.cs b/SecretAPI/Features/UserSettings/CustomHeader.cs
index 5dc96cc..e9ab950 100644
--- a/SecretAPI/Features/UserSettings/CustomHeader.cs
+++ b/SecretAPI/Features/UserSettings/CustomHeader.cs
@@ -19,7 +19,7 @@ public CustomHeader(string label, bool reducedPadding = false, string? hint = nu
}
///
- /// Gets a for Example purposes.
+ /// Gets a used to contain all the example settings of SecretAPI.Examples.
///
public static CustomHeader Examples { get; } = new("Examples", hint: "Features used as examples");
diff --git a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
index cc759c4..dcea8fb 100644
--- a/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomPlainTextSetting.cs
@@ -105,7 +105,7 @@ public int CharacterLimit
public void SendServerUpdate(string text) => Base.SendValueUpdate(text, false, IsKnownOwnerHub);
///
- protected internal override void HandleBeforeSettingUpdate()
+ protected override void HandleBeforeSettingUpdate()
{
base.HandleBeforeSettingUpdate();
LastInputText = InputText;
diff --git a/SecretAPI/Features/UserSettings/CustomSetting.cs b/SecretAPI/Features/UserSettings/CustomSetting.cs
index e6c86df..373a793 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -286,8 +286,8 @@ public static void SendSettingsToPlayer(Player player, int? version = null)
///
/// Called before , adding and .
///
- /// This will not have the current status.
- protected internal virtual void HandleBeforeSettingUpdate()
+ /// This will not have the current status and should always call back to base.
+ protected virtual void HandleBeforeSettingUpdate()
{
LastUpdateType = LastUpdateType == SettingResponseType.None
? SettingResponseType.Initial
diff --git a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
index 3bb9cb5..92cfd49 100644
--- a/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSliderSetting.cs
@@ -155,7 +155,7 @@ public bool UseInteger
public void SendServerUpdate(float value) => Base.SendValueUpdate(value, false, IsKnownOwnerHub);
///
- protected internal override void HandleBeforeSettingUpdate()
+ protected override void HandleBeforeSettingUpdate()
{
base.HandleBeforeSettingUpdate();
LastSelectedValueFloat = SelectedValueFloat;
diff --git a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
index d042ad2..9fae6b9 100644
--- a/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomTwoButtonSetting.cs
@@ -100,7 +100,7 @@ public string OptionB
public void SendServerUpdate(bool isB) => Base.SendValueUpdate(isB, false, IsKnownOwnerHub);
///
- protected internal override void HandleBeforeSettingUpdate()
+ protected override void HandleBeforeSettingUpdate()
{
base.HandleBeforeSettingUpdate();
WasLastOptionB = IsOptionB;
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 7feb83f..54fa7df 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -4,7 +4,7 @@
net48
latest
enable
- 3.0.0-beta2
+ 3.0.0-beta3
true
From 4affa215effdc110f4d57e4accfa409f9b385b20 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 21:26:40 +0200
Subject: [PATCH 29/37] Consistency
---
.github/workflows/nuget.yml | 2 +-
.github/workflows/pull_request.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index c0f73c3..ff4c76f 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -22,7 +22,7 @@ jobs:
with:
directory: ${{ env.REFERENCES_PATH }}
- - name: Build and Pack NuGet
+ - name: Build
env:
SL_REFERENCES: ${{ steps.refs.outputs.target }}
run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 5267b93..8307bcc 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -7,7 +7,7 @@ on:
jobs:
build:
- runs-on: windows-latest
+ runs-on: ubuntu-latest
env:
REFERENCES_PATH: ${{ github.workspace }}/References
From 41852c85417514e5d9603f06be62ce71f4ec260e Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 21:37:30 +0200
Subject: [PATCH 30/37] Test setting up dotnet
---
.github/workflows/pull_request.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 8307bcc..459c93f 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -17,6 +17,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
+ - name: Setup Dotnet
+ uses: actions/setup-dotnet@v5.2.0
+
- name: Download SCP:SL References
uses: Axwabo/scpsl-references-downloader@v1
id: refs
From a4b20be3d7a78690df303b23ae8ef601bfcd5409 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 21:44:15 +0200
Subject: [PATCH 31/37] List References
---
.github/workflows/pull_request.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 459c93f..8aaf08a 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -26,6 +26,9 @@ jobs:
with:
directory: ${{ env.REFERENCES_PATH }}
+ - name: List references
+ run: ls ${{ env.REFERENCES_PATH }}
+
- name: Build
env:
SL_REFERENCES: ${{ steps.refs.outputs.target }}
From c03402c67ed1699906f96eeefc5b853e5eb61a88 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 21:46:33 +0200
Subject: [PATCH 32/37] ls steps.refs.outputs.target
---
.github/workflows/pull_request.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 8aaf08a..c6b9161 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -27,7 +27,7 @@ jobs:
directory: ${{ env.REFERENCES_PATH }}
- name: List references
- run: ls ${{ env.REFERENCES_PATH }}
+ run: ls ${{ steps.refs.outputs.target }}
- name: Build
env:
From 0aa728694b5c085d839d301c09631f6e17dca08b Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 8 Apr 2026 21:55:13 +0200
Subject: [PATCH 33/37] Remove debug
---
.github/workflows/pull_request.yml | 6 ------
1 file changed, 6 deletions(-)
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index c6b9161..8307bcc 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -17,18 +17,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- - name: Setup Dotnet
- uses: actions/setup-dotnet@v5.2.0
-
- name: Download SCP:SL References
uses: Axwabo/scpsl-references-downloader@v1
id: refs
with:
directory: ${{ env.REFERENCES_PATH }}
- - name: List references
- run: ls ${{ steps.refs.outputs.target }}
-
- name: Build
env:
SL_REFERENCES: ${{ steps.refs.outputs.target }}
From bd683e0f65542dff57a5e63234b770148daa48d3 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 9 Apr 2026 13:05:37 +0200
Subject: [PATCH 34/37] Fix: Workflow
---
.github/workflows/nuget.yml | 7 +------
.github/workflows/pull_request.yml | 5 -----
Directory.Build.props | 17 +++++++++++++++++
Directory.Build.targets | 22 ++++++++++++++++++++++
SecretAPI/SecretAPI.csproj | 30 ++++++++++++------------------
5 files changed, 52 insertions(+), 29 deletions(-)
create mode 100644 Directory.Build.props
create mode 100644 Directory.Build.targets
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index ff4c76f..ca52a08 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -9,7 +9,6 @@ jobs:
runs-on: ubuntu-latest
env:
- REFERENCES_PATH: ${{ github.workspace }}/References
BUILD_OUTPUT: ${{ github.workspace }}/Output
steps:
@@ -19,14 +18,10 @@ jobs:
- name: Download SCP:SL References
uses: Axwabo/scpsl-references-downloader@v1
id: refs
- with:
- directory: ${{ env.REFERENCES_PATH }}
- name: Build
- env:
- SL_REFERENCES: ${{ steps.refs.outputs.target }}
run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
- name: Push NuGet package
working-directory: ${{ env.BUILD_OUTPUT }}
- run: dotnet nuget push SecretAPI.*.nupkg
\ No newline at end of file
+ run: dotnet nuget push SecretAPI.*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
\ No newline at end of file
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 8307bcc..c06c820 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -10,7 +10,6 @@ jobs:
runs-on: ubuntu-latest
env:
- REFERENCES_PATH: ${{ github.workspace }}/References
BUILD_OUTPUT: ${{ github.workspace }}/Output
steps:
@@ -20,12 +19,8 @@ jobs:
- name: Download SCP:SL References
uses: Axwabo/scpsl-references-downloader@v1
id: refs
- with:
- directory: ${{ env.REFERENCES_PATH }}
- name: Build
- env:
- SL_REFERENCES: ${{ steps.refs.outputs.target }}
run: dotnet build -c Release --output ${{ env.BUILD_OUTPUT }}
- name: Upload
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..af589d3
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,17 @@
+
+
+ enable
+ 3.0.0-beta3
+
+
+
+ obvEve
+ obvEve
+
+ true
+
+ git
+ https://github.com/obvEve/SecretAPI
+ MIT
+
+
\ No newline at end of file
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..38f22b4
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,22 @@
+
+
+
+ $(SL_REFERENCES)
+ $(ReferencePath);$(AssemblySearchPaths)
+
+
+
+
+ $(MSBuildThisFileDirectory)ReferencePath.props.user
+
+
+
+
+
+ ]]>
+
+
+
+
+
\ No newline at end of file
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 54fa7df..10c65bd 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -3,21 +3,15 @@
net48
latest
- enable
- 3.0.0-beta3
true
true
true
- obvEve
SecretAPI
API to extend SCP:SL LabAPI
- git
- https://github.com/obvEve/SecretAPI
README.md
- MIT
@@ -37,18 +31,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
From e07b8e0e98d00a05d192d8c31e9887860de2d3cc Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 9 Apr 2026 13:15:23 +0200
Subject: [PATCH 35/37] Remove hintpath + use LabApi
---
SecretAPI.Examples/SecretAPI.Examples.csproj | 25 +++++++++++---------
SecretAPI/SecretAPI.csproj | 2 +-
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/SecretAPI.Examples/SecretAPI.Examples.csproj b/SecretAPI.Examples/SecretAPI.Examples.csproj
index 076e87e..f4209f1 100644
--- a/SecretAPI.Examples/SecretAPI.Examples.csproj
+++ b/SecretAPI.Examples/SecretAPI.Examples.csproj
@@ -9,23 +9,26 @@
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 10c65bd..c70f909 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -32,7 +32,7 @@
-
+
From 447ad380e31a2f9399c74b3fccd060132709beb6 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Thu, 9 Apr 2026 13:19:15 +0200
Subject: [PATCH 36/37] Use .props more
---
Directory.Build.props | 8 ++++++++
SecretAPI.Examples/SecretAPI.Examples.csproj | 9 ---------
SecretAPI/SecretAPI.csproj | 8 --------
3 files changed, 8 insertions(+), 17 deletions(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index af589d3..a0d80bf 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -14,4 +14,12 @@
https://github.com/obvEve/SecretAPI
MIT
+
+
+ true
+ recommended
+ true
+ ../stylecop.ruleset
+ True
+
\ No newline at end of file
diff --git a/SecretAPI.Examples/SecretAPI.Examples.csproj b/SecretAPI.Examples/SecretAPI.Examples.csproj
index f4209f1..7dbfccb 100644
--- a/SecretAPI.Examples/SecretAPI.Examples.csproj
+++ b/SecretAPI.Examples/SecretAPI.Examples.csproj
@@ -2,7 +2,6 @@
net48
latest
- enable
true
@@ -30,12 +29,4 @@
-
-
- true
- recommended
- true
- ../stylecop.ruleset
- True
-
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index c70f909..0302f70 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -45,12 +45,4 @@
-
- true
- recommended
- true
- ../stylecop.ruleset
- True
-
-
From fc6477a7cf37bd984052e39f220c3b221fbd2a8e Mon Sep 17 00:00:00 2001
From: Evelyn <85962933+obvEve@users.noreply.github.com>
Date: Fri, 10 Apr 2026 13:46:40 +0200
Subject: [PATCH 37/37] Fix stuff (#91)
* Try fix stuff
* Fix it finally
---
SecretAPI/Attributes/CallOnLoadAttribute.cs | 18 +++-----
.../{PrefabDebugging.cs => PrefabDebugger.cs} | 2 +-
SecretAPI/Extensions/ReflectionExtensions.cs | 44 +++++++++++++++----
.../Patches/Features/RoundEndIgnorePatch.cs | 21 ++++-----
.../Patches/Features/RoundIgnoreCountPatch.cs | 28 ++++--------
5 files changed, 60 insertions(+), 53 deletions(-)
rename SecretAPI/Debugging/{PrefabDebugging.cs => PrefabDebugger.cs} (97%)
diff --git a/SecretAPI/Attributes/CallOnLoadAttribute.cs b/SecretAPI/Attributes/CallOnLoadAttribute.cs
index 4ece8ce..ab0e265 100644
--- a/SecretAPI/Attributes/CallOnLoadAttribute.cs
+++ b/SecretAPI/Attributes/CallOnLoadAttribute.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using SecretAPI.Extensions;
using SecretAPI.Features;
///
@@ -50,21 +51,14 @@ public static void Load(Assembly? assembly = null)
internal static void CallAttributeMethodPriority(Assembly assembly)
where TAttribute : Attribute, IPriority
{
- const BindingFlags methodFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
Dictionary methods = new();
-
- // get all types
- foreach (Type type in assembly.GetTypes())
+ foreach (MethodInfo method in assembly.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
- // get all static methods
- foreach (MethodInfo method in type.GetMethods(methodFlags))
- {
- TAttribute? attribute = method.GetCustomAttribute();
- if (attribute == null)
- continue;
+ TAttribute? attribute = method.GetCustomAttribute();
+ if (attribute == null)
+ continue;
- methods.Add(attribute, method);
- }
+ methods.Add(attribute, method);
}
foreach (KeyValuePair method in methods.OrderBy(static v => v.Key.Priority))
diff --git a/SecretAPI/Debugging/PrefabDebugging.cs b/SecretAPI/Debugging/PrefabDebugger.cs
similarity index 97%
rename from SecretAPI/Debugging/PrefabDebugging.cs
rename to SecretAPI/Debugging/PrefabDebugger.cs
index d82e9f2..dc2a893 100644
--- a/SecretAPI/Debugging/PrefabDebugging.cs
+++ b/SecretAPI/Debugging/PrefabDebugger.cs
@@ -15,7 +15,7 @@ namespace SecretAPI.Debugging;
///
/// Debugs basegame prefabs by logging information about them.
///
-internal static class PrefabDebugging
+internal static class PrefabDebugger
{
///
/// Loads the prefab debugging.
diff --git a/SecretAPI/Extensions/ReflectionExtensions.cs b/SecretAPI/Extensions/ReflectionExtensions.cs
index f367c6e..32767cf 100644
--- a/SecretAPI/Extensions/ReflectionExtensions.cs
+++ b/SecretAPI/Extensions/ReflectionExtensions.cs
@@ -1,8 +1,10 @@
namespace SecretAPI.Extensions;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using HarmonyLib;
///
/// Extensions for reflection.
@@ -36,16 +38,42 @@ public static string GetLongFuncName(Type type, MethodInfo method)
}
///
- /// Copies the properties.
+ /// Searches through an assembly and returns all based on the provided flags.
+ ///
+ /// The assembly to search through.
+ /// The used for the method search within a .
+ /// A collection of based on the provided assembly and flags.
+ public static IEnumerable GetMethods(this Assembly assembly, BindingFlags flags)
+ => assembly.GetTypes().SelectMany(type => type.GetMethods(flags));
+
+ ///
+ /// Gets a nested within a .
+ ///
+ /// The containing the needed method.
+ /// The name of the nested type, this does not need to be exact.
+ /// The name of the method, this does not need to be exact.
+ /// The found method, or null if none matched.
+ public static MethodInfo? GetNestedMethod(this Type type, string nestedTypeName, string methodName)
+ {
+ return type.GetNestedTypes(AccessTools.all)
+ .Where(t => t.Name.Contains(nestedTypeName))
+ .SelectMany(t => t.GetMethods(AccessTools.all))
+ .FirstOrDefault(m => m.Name.Contains(methodName));
+ }
+
+ ///
+ /// Copies the properties from a onto another instance.
///
/// The source of the properties to copy.
- /// Where to copy to.
- public static void CopyProperties(this object source, object destination)
+ /// Where the source properties should be copied to, this should match the same as source.
+ public static void CopyPropertiesTo(this object source, object destination)
{
- Type destinationType = destination.GetType();
- foreach (PropertyInfo property in source.GetType().GetProperties())
- {
- destinationType.GetProperty(property.Name)?.SetValue(destination, property.GetValue(source));
- }
+ Type type = source.GetType();
+
+ if (type != destination.GetType())
+ throw new InvalidOperationException($"[ReflectionExtensions.CopyPropertiesTo] Source and destination types are mismatched: {type.FullName} | {destination.GetType().FullName}");
+
+ foreach (PropertyInfo property in type.GetProperties())
+ property.SetValue(destination, property.GetValue(source));
}
}
\ No newline at end of file
diff --git a/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs b/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
index d8611b1..0a6dc7a 100644
--- a/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
+++ b/SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
+using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using PlayerRoles;
using SecretAPI.Attributes;
@@ -14,37 +15,33 @@
using SecretAPI.Features;
///
-/// Handles patching to implement into .
+/// Handles patching to implement into .
///
[HarmonyPatchCategory(nameof(PlayerRoundIgnore))]
[HarmonyPatch]
internal static class RoundEndIgnorePatch
{
- private const string StateMachine = "<_ProcessServerSideCode>d__58";
+ private const string StateMachine = "_ProcessServerSideCode";
private const string MoveNext = "MoveNext";
private const int ReferenceHubLocalIndex = 20;
+ private const int SkipAdvanceAmount = 4; // amount needed to get to IL_0131: br IL_01bd
private static MethodInfo TargetMethod()
{
- // typeof(RoundSummary).GetNestedTypes(AccessTools.all).ForEach(type => Logger.Debug(type.FullName ?? "NULL"));
- Type nestedType = typeof(RoundSummary).GetNestedTypes(AccessTools.all)
- .FirstOrDefault(type => type.Name is StateMachine) ?? throw new Exception($"Could not locate state machine for {StateMachine}");
-
- // nestedType.GetMethods(AccessTools.all).ForEach(method => Logger.Debug(method.Name));
- MethodInfo moveNextMethod = nestedType.GetMethods(AccessTools.all)
- .FirstOrDefault(x => x.Name.Contains(MoveNext)) ?? throw new Exception($"Could not locate {MoveNext} method in state machine");
-
- return moveNextMethod;
+ return typeof(RoundSummary).GetNestedMethod(StateMachine, MoveNext)
+ ?? throw new Exception($"Could not locate state machine for {StateMachine} | {MoveNext}");
}
private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
{
CodeMatcher matcher = new CodeMatcher(instructions, generator)
.MatchEndForward(new CodeMatch(CodeInstruction.Call(typeof(PlayerRolesUtils), nameof(PlayerRolesUtils.GetTeam), [typeof(ReferenceHub)])))
+ .Advance(SkipAdvanceAmount)
.CreateLabel(out Label skip)
+ .Advance(-SkipAdvanceAmount)
.Insert(
new CodeInstruction(OpCodes.Ldloc_S, ReferenceHubLocalIndex),
- CodeInstruction.Call(typeof(RoundEndIgnorePatch), nameof(IsPlayerIgnored)),
+ new CodeInstruction(OpCodes.Callvirt, AccessTools.Method(typeof(RoundEndIgnorePatch), nameof(IsPlayerIgnored))),
new CodeInstruction(OpCodes.Brtrue_S, skip));
return matcher.InstructionEnumeration();
diff --git a/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs b/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
index 0a1f32e..51b79a1 100644
--- a/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
+++ b/SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
+using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using Mirror;
using SecretAPI.Attributes;
@@ -25,14 +26,8 @@ internal static class RoundIgnoreCountPatch
private static MethodInfo TargetMethod()
{
- Type nestedType = typeof(RoundSummary).GetNestedTypes(AccessTools.all)
- .FirstOrDefault(currentType => currentType.Name is StateMachine) ?? throw new Exception("Could not locate state machine for RoundSummary::<>c");
-
- // nestedType.GetMethods(AccessTools.all).ForEach(method => Logger.Debug(method.Name));
- MethodInfo updateCountMethod = nestedType.GetMethods(AccessTools.all)
- .FirstOrDefault(x => x.Name.Contains(UpdateTargetCount)) ?? throw new Exception($"Could not locate {UpdateTargetCount} method in state machine");
-
- return updateCountMethod;
+ return typeof(RoundSummary).GetNestedMethod(StateMachine, UpdateTargetCount)
+ ?? throw new Exception($"Could not locate state machine for {StateMachine} | {UpdateTargetCount}");
}
private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator)
@@ -41,21 +36,14 @@ private static IEnumerable Transpiler(IEnumerable Player.Get(hub).RoundIgnoreStatus.HasFlagFast(RoundIgnoreStatus.ScpTargetCount);
}
\ No newline at end of file