From e0137647f01f4b093053d7adc08300af28009e3c Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 27 Apr 2026 15:59:56 +0200
Subject: [PATCH 01/18] SourceGeneration
---
.../AnalyzerReleases.Shipped.md | 3 +
.../AnalyzerReleases.Unshipped.md | 7 +
.../Builders/Builder.cs | 19 +++
.../Builders/ClassBuilder.cs | 60 +++++++
.../Builders/MethodBuilder.cs | 44 +++++
SecretAPI.SourceGenerators/Diagnostics.cs | 28 ++++
.../Generators/CallOnLoadGenerator.cs | 152 ++++++++++++++++++
SecretAPI.SourceGenerators/GlobalUsings.cs | 19 +++
.../SecretAPI.SourceGenerators.csproj | 26 +++
.../Utils/GeneratedIdentifyUtils.cs | 19 +++
.../Utils/GenericTypeUtils.cs | 12 ++
.../Utils/MethodParameter.cs | 40 +++++
.../Utils/MethodUtils.cs | 20 +++
SecretAPI.SourceGenerators/Utils/TypeUtils.cs | 10 ++
SecretAPI.sln | 6 +
SecretAPI/SecretAPI.csproj | 2 +
SecretAPI/SecretApi.cs | 2 +-
17 files changed, 468 insertions(+), 1 deletion(-)
create mode 100644 SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md
create mode 100644 SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
create mode 100644 SecretAPI.SourceGenerators/Builders/Builder.cs
create mode 100644 SecretAPI.SourceGenerators/Builders/ClassBuilder.cs
create mode 100644 SecretAPI.SourceGenerators/Builders/MethodBuilder.cs
create mode 100644 SecretAPI.SourceGenerators/Diagnostics.cs
create mode 100644 SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
create mode 100644 SecretAPI.SourceGenerators/GlobalUsings.cs
create mode 100644 SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj
create mode 100644 SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
create mode 100644 SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs
create mode 100644 SecretAPI.SourceGenerators/Utils/MethodParameter.cs
create mode 100644 SecretAPI.SourceGenerators/Utils/MethodUtils.cs
create mode 100644 SecretAPI.SourceGenerators/Utils/TypeUtils.cs
diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md
new file mode 100644
index 0000000..60b59dd
--- /dev/null
+++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md
@@ -0,0 +1,3 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
new file mode 100644
index 0000000..ad9f9dc
--- /dev/null
+++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -0,0 +1,7 @@
+### New Rules
+
+ Rule ID | Category | Severity | Notes
+------------|----------|----------|---------------------
+ SecretGen0 | Usage | Error | CA6000_AnalyzerName
+ SecretGen1 | Usage | Error | CA6000_AnalyzerName
+ SecretGen2 | Usage | Error | CA6000_AnalyzerName
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Builders/Builder.cs b/SecretAPI.SourceGenerators/Builders/Builder.cs
new file mode 100644
index 0000000..4cc8b63
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Builders/Builder.cs
@@ -0,0 +1,19 @@
+namespace SecretAPI.SourceGenerators.Builders;
+
+///
+/// Base of a builder.
+///
+/// The this is handling.
+internal abstract class Builder
+ where TBuilder : Builder
+{
+ protected readonly List _modifiers = new();
+
+ internal TBuilder AddModifiers(params SyntaxKind[] modifiers)
+ {
+ foreach (SyntaxKind token in modifiers)
+ _modifiers.Add(Token(token));
+
+ return (TBuilder)this;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs
new file mode 100644
index 0000000..671cf80
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs
@@ -0,0 +1,60 @@
+namespace SecretAPI.SourceGenerators.Builders;
+
+internal class ClassBuilder : Builder
+{
+ private NamespaceDeclarationSyntax _namespaceDeclaration;
+ private ClassDeclarationSyntax _classDeclaration;
+
+ private readonly List _usings = new();
+ private readonly List _methods = new();
+
+ private ClassBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration)
+ {
+ _namespaceDeclaration = namespaceDeclaration;
+ _classDeclaration = classDeclaration;
+
+ AddUsingStatements("System.CodeDom.Compiler");
+ }
+
+ internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass)
+ => CreateBuilder(NamespaceDeclaration(ParseName(namedClass.ContainingNamespace.ToDisplayString())), ClassDeclaration(namedClass.Name));
+
+ internal static ClassBuilder CreateBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration)
+ => new(namespaceDeclaration, classDeclaration);
+
+ internal ClassBuilder AddUsingStatements(params string[] usingStatements)
+ {
+ foreach (string statement in usingStatements)
+ {
+ UsingDirectiveSyntax usings = UsingDirective(ParseName(statement));
+ if (!_usings.Any(existing => existing.IsEquivalentTo(usings)))
+ _usings.Add(usings);
+ }
+
+ return this;
+ }
+
+ internal MethodBuilder StartMethodCreation(string methodName, TypeSyntax returnType) => new(this, methodName, returnType);
+ internal MethodBuilder StartMethodCreation(string methodName, SyntaxKind returnType) => StartMethodCreation(methodName, GetPredefinedTypeSyntax(returnType));
+
+ internal void AddMethodDefinition(MethodDeclarationSyntax method) => _methods.Add(method);
+
+ internal CompilationUnitSyntax Build()
+ {
+ _classDeclaration = _classDeclaration
+ .AddAttributeLists(GetGeneratedCodeAttributeListSyntax())
+ .AddModifiers(_modifiers.ToArray())
+ .AddMembers(_methods.Cast().ToArray());
+
+ _namespaceDeclaration = _namespaceDeclaration
+ .AddUsings(_usings.ToArray())
+ .AddMembers(_classDeclaration);
+
+ return CompilationUnit()
+ .AddMembers(_namespaceDeclaration)
+ .NormalizeWhitespace()
+ .WithLeadingTrivia(Comment("// "), LineFeed, Comment("#pragma warning disable"), LineFeed, Comment("#nullable enable"), LineFeed, LineFeed);
+ }
+
+ internal void Build(SourceProductionContext context, string name) => context.AddSource(name, Build().ToFullString());
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Builders/MethodBuilder.cs b/SecretAPI.SourceGenerators/Builders/MethodBuilder.cs
new file mode 100644
index 0000000..6538e4a
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Builders/MethodBuilder.cs
@@ -0,0 +1,44 @@
+namespace SecretAPI.SourceGenerators.Builders;
+
+internal class MethodBuilder : Builder
+{
+ private readonly ClassBuilder _classBuilder;
+ private readonly List _parameters = new();
+ private readonly List _statements = new();
+ private readonly string _methodName;
+ private readonly TypeSyntax _returnType;
+
+ internal MethodBuilder(ClassBuilder classBuilder, string methodName, TypeSyntax returnType)
+ {
+ _classBuilder = classBuilder;
+ _methodName = methodName;
+ _returnType = returnType;
+ }
+
+ internal MethodBuilder AddStatements(params StatementSyntax[] statements)
+ {
+ _statements.AddRange(statements);
+ return this;
+ }
+
+ internal MethodBuilder AddParameters(params MethodParameter[] parameters)
+ {
+ foreach (MethodParameter parameter in parameters)
+ _parameters.Add(parameter.Syntax);
+
+ return this;
+ }
+
+ internal ClassBuilder FinishMethodBuild()
+ {
+ BlockSyntax body = _statements.Any() ? Block(_statements) : Block();
+
+ MethodDeclarationSyntax methodDeclaration = MethodDeclaration(_returnType, _methodName)
+ .AddModifiers(_modifiers.ToArray())
+ .AddParameterListParameters(_parameters.ToArray())
+ .WithBody(body);
+
+ _classBuilder.AddMethodDefinition(methodDeclaration);
+ return _classBuilder;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Diagnostics.cs b/SecretAPI.SourceGenerators/Diagnostics.cs
new file mode 100644
index 0000000..8b3e2b4
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Diagnostics.cs
@@ -0,0 +1,28 @@
+namespace SecretAPI.SourceGenerators;
+
+internal static class Diagnostics
+{
+ internal static readonly DiagnosticDescriptor MustBePartialPluginClass = new(
+ "SecretGen0",
+ "Plugin class must be partial",
+ "Plugin class '{0}' is missing partial modifier",
+ "Usage",
+ DiagnosticSeverity.Error,
+ true);
+
+ internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new(
+ "SecretGen1",
+ "Method must be accessible",
+ "Method '{0}' has accessibility '{1}', which is not supported for generated calls",
+ "Usage",
+ DiagnosticSeverity.Error,
+ true);
+
+ internal static readonly DiagnosticDescriptor MustBeStaticMethod = new(
+ "SecretGen2",
+ "Method must be static",
+ "Method '{0}' is not marked as static",
+ "Usage",
+ DiagnosticSeverity.Error,
+ true);
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
new file mode 100644
index 0000000..7147eb0
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
@@ -0,0 +1,152 @@
+namespace SecretAPI.SourceGenerators.Generators;
+
+///
+/// Code generator for CallOnLoad/CallOnUnload
+///
+[Generator]
+public class CallOnLoadGenerator : IIncrementalGenerator
+{
+ private const string PluginNamespace = "LabApi.Loader.Features.Plugins";
+ private const string PluginBaseClassName = "Plugin";
+ private const string CallOnLoadAttributeLocation = "SecretAPI.Attributes.CallOnLoadAttribute";
+ private const string CallOnUnloadAttributeLocation = "SecretAPI.Attributes.CallOnUnloadAttribute";
+
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ IncrementalValuesProvider methodProvider =
+ context.SyntaxProvider.CreateSyntaxProvider(
+ static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 },
+ static (ctx, _) =>
+ ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as IMethodSymbol)
+ .Where(static m => m is not null)!;
+
+ IncrementalValuesProvider<(IMethodSymbol method, bool isLoad, bool isUnload)> callProvider =
+ methodProvider.Select(static (method, _) => (
+ method,
+ HasAttribute(method, CallOnLoadAttributeLocation),
+ HasAttribute(method, CallOnUnloadAttributeLocation)))
+ .Where(static m => m.Item2 || m.Item3);
+
+ IncrementalValuesProvider<(ClassDeclarationSyntax?, INamedTypeSymbol?)> pluginClassProvider =
+ context.SyntaxProvider.CreateSyntaxProvider(
+ static (node, _) => node is ClassDeclarationSyntax,
+ static (ctx, _) => (
+ ctx.Node as ClassDeclarationSyntax, ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol))
+ .Where(static c => c.Item2 != null && !c.Item2.IsAbstract && c.Item2.BaseType?.Name == PluginBaseClassName &&
+ c.Item2.BaseType.ContainingNamespace.ToDisplayString() == PluginNamespace);
+
+ context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) =>
+ {
+ Generate(context, new Tuple(data.Left.Item1, data.Left.Item2), data.Right);
+ });
+ }
+
+ private static bool HasAttribute(IMethodSymbol? method, string attributeLocation)
+ {
+ if (method == null)
+ return false;
+
+ foreach (AttributeData attribute in method.GetAttributes())
+ {
+ if (attribute.AttributeClass?.ToDisplayString() == attributeLocation)
+ return true;
+ }
+
+ return false;
+ }
+
+ private static int GetPriority(IMethodSymbol method, string attributeLocation)
+ {
+ AttributeData? attribute = method.GetAttributes()
+ .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == attributeLocation);
+ if (attribute == null)
+ return 0;
+
+ if (attribute.ConstructorArguments.Length > 0)
+ return (int)attribute.ConstructorArguments[0].Value!;
+
+ return 0;
+ }
+
+ private static bool ValidateMethod(SourceProductionContext context, Tuple pluginInfo, IMethodSymbol method)
+ {
+ bool isValid = true;
+
+ if (!method.IsStatic)
+ {
+ context.ReportDiagnostic(
+ Diagnostic.Create(
+ Diagnostics.MustBeStaticMethod,
+ method.Locations.FirstOrDefault(),
+ method.Name));
+
+ isValid = false;
+ }
+
+ if (method.DeclaredAccessibility is Accessibility.Private)
+ {
+ context.ReportDiagnostic(
+ Diagnostic.Create(
+ Diagnostics.MustBeAccessibleMethod,
+ method.Locations.FirstOrDefault(),
+ method.Name,
+ method.DeclaredAccessibility));
+
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ private static void Generate(
+ SourceProductionContext context,
+ Tuple pluginInfo,
+ ImmutableArray<(IMethodSymbol method, bool isLoad, bool isUnload)> methods)
+ {
+ if (pluginInfo.Item1 == null || pluginInfo.Item2 == null || methods.IsEmpty)
+ return;
+
+ if (!pluginInfo.Item1.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
+ {
+ context.ReportDiagnostic(
+ Diagnostic.Create(
+ Diagnostics.MustBePartialPluginClass,
+ pluginInfo.Item1.GetLocation(),
+ pluginInfo.Item1.Identifier.Text
+ )
+ );
+ }
+
+ IMethodSymbol[] loadCalls = methods
+ .Where(m => m.isLoad && ValidateMethod(context, pluginInfo, m.method))
+ .Select(m => m.method)
+ .OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation))
+ .ToArray();
+
+ IMethodSymbol[] unloadCalls = methods
+ .Where(m => m.isUnload && ValidateMethod(context, pluginInfo, m.method))
+ .Select(m => m.method)
+ .OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation))
+ .ToArray();
+
+ if (!loadCalls.Any() && !unloadCalls.Any())
+ return;
+
+ ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginInfo.Item2)
+ .AddUsingStatements("System")
+ .AddModifiers(SyntaxKind.PartialKeyword);
+
+ classBuilder.StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword)
+ .AddModifiers(SyntaxKind.PublicKeyword)
+ .AddStatements(MethodCallStatements(loadCalls))
+ .FinishMethodBuild();
+
+ classBuilder.StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword)
+ .AddModifiers(SyntaxKind.PublicKeyword)
+ .AddStatements(MethodCallStatements(unloadCalls))
+ .FinishMethodBuild();
+
+ classBuilder.Build(context, $"{pluginInfo.Item2.Name}.g.cs");
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/GlobalUsings.cs b/SecretAPI.SourceGenerators/GlobalUsings.cs
new file mode 100644
index 0000000..eb69204
--- /dev/null
+++ b/SecretAPI.SourceGenerators/GlobalUsings.cs
@@ -0,0 +1,19 @@
+//? Utils from other places
+global using Microsoft.CodeAnalysis;
+global using Microsoft.CodeAnalysis.CSharp;
+global using Microsoft.CodeAnalysis.CSharp.Syntax;
+global using System.Collections.Immutable;
+
+//? Static utils from other places
+global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+global using static Microsoft.CodeAnalysis.CSharp.SyntaxFacts;
+
+//? Utils from SecretAPI
+global using SecretAPI.SourceGenerators.Builders;
+global using SecretAPI.SourceGenerators.Utils;
+
+//? Static utils from SecretAPI
+global using static SecretAPI.SourceGenerators.Utils.GenericTypeUtils;
+global using static SecretAPI.SourceGenerators.Utils.GeneratedIdentifyUtils;
+global using static SecretAPI.SourceGenerators.Utils.MethodUtils;
+global using static SecretAPI.SourceGenerators.Utils.TypeUtils;
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj
new file mode 100644
index 0000000..b6bd4b5
--- /dev/null
+++ b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj
@@ -0,0 +1,26 @@
+
+
+
+ netstandard2.0
+ 14
+ true
+ enable
+
+
+
+ true
+ false
+ Library
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
new file mode 100644
index 0000000..063d3b1
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
@@ -0,0 +1,19 @@
+namespace SecretAPI.SourceGenerators.Utils;
+
+internal static class GeneratedIdentifyUtils
+{
+ private static AttributeSyntax GetGeneratedCodeAttributeSyntax()
+ => Attribute(IdentifierName("GeneratedCode"))
+ .WithArgumentList(
+ AttributeArgumentList(
+ SeparatedList(
+ new SyntaxNodeOrToken[]
+ {
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))),
+ Token(SyntaxKind.CommaToken),
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))),
+ })));
+
+ internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax()
+ => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax()));
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs b/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs
new file mode 100644
index 0000000..f530632
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs
@@ -0,0 +1,12 @@
+namespace SecretAPI.SourceGenerators.Utils;
+
+internal static class GenericTypeUtils
+{
+ internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType)
+ => GenericName(genericName)
+ .WithTypeArgumentList(
+ TypeArgumentList(
+ SingletonSeparatedList(
+ PredefinedType(
+ Token(predefinedType)))));
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Utils/MethodParameter.cs b/SecretAPI.SourceGenerators/Utils/MethodParameter.cs
new file mode 100644
index 0000000..b2dd8db
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Utils/MethodParameter.cs
@@ -0,0 +1,40 @@
+namespace SecretAPI.SourceGenerators.Utils;
+
+///
+/// Represents a method parameter used during code generation.
+///
+internal readonly struct MethodParameter
+{
+ private readonly SyntaxList _attributeLists;
+ private readonly SyntaxTokenList _modifiers;
+ private readonly TypeSyntax? _type;
+ private readonly SyntaxToken _identifier;
+ private readonly EqualsValueClauseSyntax? _default;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The name of the parameter.
+ /// The parameter type. May be for implicitly-typed parameters.
+ /// Optional parameter modifiers (e.g. ref, out, in).
+ /// Optional attribute lists applied to the parameter.
+ /// Optional default value.
+ internal MethodParameter(
+ string identifier,
+ TypeSyntax? type = null,
+ SyntaxTokenList modifiers = default,
+ SyntaxList attributeLists = default,
+ EqualsValueClauseSyntax? @default = null)
+ {
+ _identifier = IsValidIdentifier(identifier)
+ ? Identifier(identifier)
+ : throw new ArgumentException("Identifier is not valid.", nameof(identifier));
+
+ _type = type;
+ _modifiers = modifiers;
+ _attributeLists = attributeLists;
+ _default = @default;
+ }
+
+ public ParameterSyntax Syntax => Parameter(_attributeLists, _modifiers, _type, _identifier, _default);
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Utils/MethodUtils.cs b/SecretAPI.SourceGenerators/Utils/MethodUtils.cs
new file mode 100644
index 0000000..d0f4b05
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Utils/MethodUtils.cs
@@ -0,0 +1,20 @@
+namespace SecretAPI.SourceGenerators.Utils;
+
+internal static class MethodUtils
+{
+ internal static StatementSyntax MethodCallStatement(string typeName, string methodName) =>
+ MethodCallStatement(ParseTypeName(typeName), IdentifierName(methodName));
+
+ internal static StatementSyntax MethodCallStatement(TypeSyntax type, IdentifierNameSyntax method)
+ => ExpressionStatement(
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ type, method)));
+
+ internal static StatementSyntax[] MethodCallStatements(IMethodSymbol[] methodCalls)
+ {
+ IEnumerable statements = methodCalls.Select(s => MethodCallStatement(s.ContainingType.ToDisplayString(), s.Name));
+ return statements.ToArray();
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Utils/TypeUtils.cs b/SecretAPI.SourceGenerators/Utils/TypeUtils.cs
new file mode 100644
index 0000000..1b59b10
--- /dev/null
+++ b/SecretAPI.SourceGenerators/Utils/TypeUtils.cs
@@ -0,0 +1,10 @@
+namespace SecretAPI.SourceGenerators.Utils;
+
+internal static class TypeUtils
+{
+ internal static PredefinedTypeSyntax GetPredefinedTypeSyntax(SyntaxKind kind)
+ => PredefinedType(Token(kind));
+
+ internal static TypeSyntax GetTypeSyntax(string typeIdentifier)
+ => IdentifierName(typeIdentifier);
+}
\ No newline at end of file
diff --git a/SecretAPI.sln b/SecretAPI.sln
index 4ed659a..ad97fde 100644
--- a/SecretAPI.sln
+++ b/SecretAPI.sln
@@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI", "SecretAPI\Secr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI.Examples", "SecretAPI.Examples\SecretAPI.Examples.csproj", "{0064C982-5FE1-4B65-82F9-2EEF85651188}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretAPI.SourceGenerators", "SecretAPI.SourceGenerators\SecretAPI.SourceGenerators.csproj", "{15C8D708-16DE-4533-AEB6-003C3EDCAD45}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -18,5 +20,9 @@ Global
{0064C982-5FE1-4B65-82F9-2EEF85651188}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0064C982-5FE1-4B65-82F9-2EEF85651188}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0064C982-5FE1-4B65-82F9-2EEF85651188}.Release|Any CPU.Build.0 = Release|Any CPU
+ {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {15C8D708-16DE-4533-AEB6-003C3EDCAD45}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 0302f70..055b9de 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -31,6 +31,8 @@
+
+
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 9033e9a..941f7b3 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -11,7 +11,7 @@
///
/// Main class handling loading API.
///
-public class SecretApi : Plugin
+public partial class SecretApi : Plugin
{
///
public override string Name => "SecretAPI";
From ff689db62795fd254cf33305e3409e957d4eec06 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:01:24 +0200
Subject: [PATCH 02/18] Removed unused class
---
SecretAPI.SourceGenerators/GlobalUsings.cs | 1 -
.../Utils/GeneratedIdentifyUtils.cs | 2 +-
SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs | 12 ------------
3 files changed, 1 insertion(+), 14 deletions(-)
delete mode 100644 SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs
diff --git a/SecretAPI.SourceGenerators/GlobalUsings.cs b/SecretAPI.SourceGenerators/GlobalUsings.cs
index eb69204..8869bf3 100644
--- a/SecretAPI.SourceGenerators/GlobalUsings.cs
+++ b/SecretAPI.SourceGenerators/GlobalUsings.cs
@@ -13,7 +13,6 @@
global using SecretAPI.SourceGenerators.Utils;
//? Static utils from SecretAPI
-global using static SecretAPI.SourceGenerators.Utils.GenericTypeUtils;
global using static SecretAPI.SourceGenerators.Utils.GeneratedIdentifyUtils;
global using static SecretAPI.SourceGenerators.Utils.MethodUtils;
global using static SecretAPI.SourceGenerators.Utils.TypeUtils;
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
index 063d3b1..da236f0 100644
--- a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
+++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
@@ -13,7 +13,7 @@ private static AttributeSyntax GetGeneratedCodeAttributeSyntax()
Token(SyntaxKind.CommaToken),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))),
})));
-
+
internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax()
=> AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax()));
}
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs b/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs
deleted file mode 100644
index f530632..0000000
--- a/SecretAPI.SourceGenerators/Utils/GenericTypeUtils.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace SecretAPI.SourceGenerators.Utils;
-
-internal static class GenericTypeUtils
-{
- internal static TypeSyntax GetSingleGenericTypeSyntax(string genericName, SyntaxKind predefinedType)
- => GenericName(genericName)
- .WithTypeArgumentList(
- TypeArgumentList(
- SingletonSeparatedList(
- PredefinedType(
- Token(predefinedType)))));
-}
\ No newline at end of file
From a9decf7a499b0b90b8c20fa932b5326e8a089e73 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:18:00 +0200
Subject: [PATCH 03/18] Remove unused pluginInfo parameter
---
.../Generators/CallOnLoadGenerator.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
index 7147eb0..6171c97 100644
--- a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
+++ b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
@@ -69,7 +69,7 @@ private static int GetPriority(IMethodSymbol method, string attributeLocation)
return 0;
}
- private static bool ValidateMethod(SourceProductionContext context, Tuple pluginInfo, IMethodSymbol method)
+ private static bool ValidateMethod(SourceProductionContext context, IMethodSymbol method)
{
bool isValid = true;
@@ -119,13 +119,13 @@ private static void Generate(
}
IMethodSymbol[] loadCalls = methods
- .Where(m => m.isLoad && ValidateMethod(context, pluginInfo, m.method))
+ .Where(m => m.isLoad && ValidateMethod(context, m.method))
.Select(m => m.method)
.OrderBy(m => GetPriority(m, CallOnLoadAttributeLocation))
.ToArray();
IMethodSymbol[] unloadCalls = methods
- .Where(m => m.isUnload && ValidateMethod(context, pluginInfo, m.method))
+ .Where(m => m.isUnload && ValidateMethod(context, m.method))
.Select(m => m.method)
.OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation))
.ToArray();
From f6322bcc01477ec88c8a32d50b05abb4a5d00753 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:26:55 +0200
Subject: [PATCH 04/18] Rules
---
SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md | 6 +++---
SecretAPI.SourceGenerators/Diagnostics.cs | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
index ad9f9dc..08f562d 100644
--- a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
+++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -2,6 +2,6 @@
Rule ID | Category | Severity | Notes
------------|----------|----------|---------------------
- SecretGen0 | Usage | Error | CA6000_AnalyzerName
- SecretGen1 | Usage | Error | CA6000_AnalyzerName
- SecretGen2 | Usage | Error | CA6000_AnalyzerName
\ No newline at end of file
+ SG001 | Usage | Error | MustBePartialPluginClass
+ SG002 | Usage | Error | MustBeAccessibleMethod
+ SG003 | Usage | Error | MustBeStaticMethod
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Diagnostics.cs b/SecretAPI.SourceGenerators/Diagnostics.cs
index 8b3e2b4..21d8073 100644
--- a/SecretAPI.SourceGenerators/Diagnostics.cs
+++ b/SecretAPI.SourceGenerators/Diagnostics.cs
@@ -3,7 +3,7 @@
internal static class Diagnostics
{
internal static readonly DiagnosticDescriptor MustBePartialPluginClass = new(
- "SecretGen0",
+ "SG001",
"Plugin class must be partial",
"Plugin class '{0}' is missing partial modifier",
"Usage",
@@ -11,7 +11,7 @@ internal static class Diagnostics
true);
internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new(
- "SecretGen1",
+ "SG002",
"Method must be accessible",
"Method '{0}' has accessibility '{1}', which is not supported for generated calls",
"Usage",
@@ -19,7 +19,7 @@ internal static class Diagnostics
true);
internal static readonly DiagnosticDescriptor MustBeStaticMethod = new(
- "SecretGen2",
+ "SG003",
"Method must be static",
"Method '{0}' is not marked as static",
"Usage",
From de7fb2f34a504161ea0024ac3cd061dccac2de00 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 27 Apr 2026 16:37:47 +0200
Subject: [PATCH 05/18] Fix version number & OnLoad() not being utilized
---
SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs | 6 ++++--
SecretAPI/SecretApi.cs | 2 +-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
index da236f0..02a4988 100644
--- a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
+++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs
@@ -2,6 +2,8 @@
internal static class GeneratedIdentifyUtils
{
+ private static SyntaxToken CurrentVersion => Literal(typeof(GeneratedIdentifyUtils).Assembly.GetName().Version.ToString());
+
private static AttributeSyntax GetGeneratedCodeAttributeSyntax()
=> Attribute(IdentifierName("GeneratedCode"))
.WithArgumentList(
@@ -9,9 +11,9 @@ private static AttributeSyntax GetGeneratedCodeAttributeSyntax()
SeparatedList(
new SyntaxNodeOrToken[]
{
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.CodeGeneration"))),
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.SourceGenerators"))),
Token(SyntaxKind.CommaToken),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("1.0.0"))),
+ AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, CurrentVersion)),
})));
internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax()
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 941f7b3..7c5931d 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -48,7 +48,7 @@ public partial class SecretApi : Plugin
///
public override void Enable()
{
- CallOnLoadAttribute.Load(Assembly);
+ OnLoad();
}
///
From 34cf80c9832fea052be2fbc41ba7f922c33d19e7 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 29 Apr 2026 21:54:33 +0200
Subject: [PATCH 06/18] Fix: Add SourceGenerators as a project to solution
---
SecretAPI.slnx | 1 +
1 file changed, 1 insertion(+)
diff --git a/SecretAPI.slnx b/SecretAPI.slnx
index 99c20fd..6995b49 100644
--- a/SecretAPI.slnx
+++ b/SecretAPI.slnx
@@ -1,4 +1,5 @@
+
From ec9c08f96ec3eb3fd98680c55b91bd964695ad21 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Sat, 2 May 2026 22:29:17 +0200
Subject: [PATCH 07/18] Source Generate: Use SecretApiGenerated naming
---
.../Builders/ClassBuilder.cs | 32 +++++++++++---
.../Generators/CallOnLoadGenerator.cs | 42 +++++--------------
SecretAPI/SecretApi.cs | 2 +-
3 files changed, 37 insertions(+), 39 deletions(-)
diff --git a/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs
index 671cf80..7f14306 100644
--- a/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs
+++ b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs
@@ -2,13 +2,13 @@
internal class ClassBuilder : Builder
{
- private NamespaceDeclarationSyntax _namespaceDeclaration;
+ private NamespaceDeclarationSyntax? _namespaceDeclaration;
private ClassDeclarationSyntax _classDeclaration;
private readonly List _usings = new();
private readonly List _methods = new();
- private ClassBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration)
+ private ClassBuilder(NamespaceDeclarationSyntax? namespaceDeclaration, ClassDeclarationSyntax classDeclaration)
{
_namespaceDeclaration = namespaceDeclaration;
_classDeclaration = classDeclaration;
@@ -16,12 +16,19 @@ private ClassBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDecla
AddUsingStatements("System.CodeDom.Compiler");
}
+ private ClassBuilder(ClassDeclarationSyntax classDeclaration)
+ : this(null, classDeclaration)
+ {
+ }
+
internal static ClassBuilder CreateBuilder(INamedTypeSymbol namedClass)
=> CreateBuilder(NamespaceDeclaration(ParseName(namedClass.ContainingNamespace.ToDisplayString())), ClassDeclaration(namedClass.Name));
internal static ClassBuilder CreateBuilder(NamespaceDeclarationSyntax namespaceDeclaration, ClassDeclarationSyntax classDeclaration)
=> new(namespaceDeclaration, classDeclaration);
+ internal static ClassBuilder CreateBuilder(ClassDeclarationSyntax classDeclaration) => new(classDeclaration);
+
internal ClassBuilder AddUsingStatements(params string[] usingStatements)
{
foreach (string statement in usingStatements)
@@ -46,14 +53,27 @@ internal CompilationUnitSyntax Build()
.AddModifiers(_modifiers.ToArray())
.AddMembers(_methods.Cast().ToArray());
- _namespaceDeclaration = _namespaceDeclaration
+ _namespaceDeclaration = _namespaceDeclaration?
.AddUsings(_usings.ToArray())
.AddMembers(_classDeclaration);
- return CompilationUnit()
- .AddMembers(_namespaceDeclaration)
+ CompilationUnitSyntax unit = CompilationUnit();
+
+ if (_namespaceDeclaration != null)
+ {
+ _namespaceDeclaration = _namespaceDeclaration
+ .AddUsings(_usings.ToArray())
+ .AddMembers(_classDeclaration);
+ unit = unit.AddMembers(_namespaceDeclaration);
+ }
+ else
+ {
+ unit = unit.AddUsings(_usings.ToArray()).AddMembers(_classDeclaration);
+ }
+
+ return unit
.NormalizeWhitespace()
- .WithLeadingTrivia(Comment("// "), LineFeed, Comment("#pragma warning disable"), LineFeed, Comment("#nullable enable"), LineFeed, LineFeed);
+ .WithLeadingTrivia(Comment("// "), LineFeed, LineFeed, Comment("#pragma warning disable"), LineFeed, Comment("#nullable enable"), LineFeed, LineFeed);
}
internal void Build(SourceProductionContext context, string name) => context.AddSource(name, Build().ToFullString());
diff --git a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
index 6171c97..dce991e 100644
--- a/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
+++ b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs
@@ -2,12 +2,12 @@
///
/// Code generator for CallOnLoad/CallOnUnload
+/// TODO: Implement IRegister source generation
///
[Generator]
public class CallOnLoadGenerator : IIncrementalGenerator
{
- private const string PluginNamespace = "LabApi.Loader.Features.Plugins";
- private const string PluginBaseClassName = "Plugin";
+ private const string GeneratedClassName = "SecretApiGenerated";
private const string CallOnLoadAttributeLocation = "SecretAPI.Attributes.CallOnLoadAttribute";
private const string CallOnUnloadAttributeLocation = "SecretAPI.Attributes.CallOnUnloadAttribute";
@@ -27,19 +27,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
HasAttribute(method, CallOnLoadAttributeLocation),
HasAttribute(method, CallOnUnloadAttributeLocation)))
.Where(static m => m.Item2 || m.Item3);
-
- IncrementalValuesProvider<(ClassDeclarationSyntax?, INamedTypeSymbol?)> pluginClassProvider =
- context.SyntaxProvider.CreateSyntaxProvider(
- static (node, _) => node is ClassDeclarationSyntax,
- static (ctx, _) => (
- ctx.Node as ClassDeclarationSyntax, ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol))
- .Where(static c => c.Item2 != null && !c.Item2.IsAbstract && c.Item2.BaseType?.Name == PluginBaseClassName &&
- c.Item2.BaseType.ContainingNamespace.ToDisplayString() == PluginNamespace);
- context.RegisterSourceOutput(pluginClassProvider.Combine(callProvider.Collect()), static (context, data) =>
- {
- Generate(context, new Tuple(data.Left.Item1, data.Left.Item2), data.Right);
- });
+ context.RegisterSourceOutput(callProvider.Collect(), Generate);
}
private static bool HasAttribute(IMethodSymbol? method, string attributeLocation)
@@ -101,23 +90,11 @@ private static bool ValidateMethod(SourceProductionContext context, IMethodSymbo
private static void Generate(
SourceProductionContext context,
- Tuple pluginInfo,
ImmutableArray<(IMethodSymbol method, bool isLoad, bool isUnload)> methods)
{
- if (pluginInfo.Item1 == null || pluginInfo.Item2 == null || methods.IsEmpty)
+ if (methods.IsEmpty)
return;
- if (!pluginInfo.Item1.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
- {
- context.ReportDiagnostic(
- Diagnostic.Create(
- Diagnostics.MustBePartialPluginClass,
- pluginInfo.Item1.GetLocation(),
- pluginInfo.Item1.Identifier.Text
- )
- );
- }
-
IMethodSymbol[] loadCalls = methods
.Where(m => m.isLoad && ValidateMethod(context, m.method))
.Select(m => m.method)
@@ -133,20 +110,21 @@ private static void Generate(
if (!loadCalls.Any() && !unloadCalls.Any())
return;
- ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginInfo.Item2)
+ // ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginInfo.Item2)
+ ClassBuilder classBuilder = ClassBuilder.CreateBuilder(ClassDeclaration(GeneratedClassName))
.AddUsingStatements("System")
- .AddModifiers(SyntaxKind.PartialKeyword);
+ .AddModifiers(SyntaxKind.InternalKeyword, SyntaxKind.StaticKeyword);
classBuilder.StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword)
- .AddModifiers(SyntaxKind.PublicKeyword)
+ .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)
.AddStatements(MethodCallStatements(loadCalls))
.FinishMethodBuild();
classBuilder.StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword)
- .AddModifiers(SyntaxKind.PublicKeyword)
+ .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)
.AddStatements(MethodCallStatements(unloadCalls))
.FinishMethodBuild();
- classBuilder.Build(context, $"{pluginInfo.Item2.Name}.g.cs");
+ classBuilder.Build(context, $"{GeneratedClassName}.g.cs");
}
}
\ No newline at end of file
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 7c5931d..1c44952 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -48,7 +48,7 @@ public partial class SecretApi : Plugin
///
public override void Enable()
{
- OnLoad();
+ SecretApiGenerated.OnLoad();
}
///
From 9be338329c08cb500d81f3c2dcad9c26d3d3c747 Mon Sep 17 00:00:00 2001
From: Evelyn <85962933+obvEve@users.noreply.github.com>
Date: Mon, 4 May 2026 14:53:28 +0200
Subject: [PATCH 08/18] Remove partial modifier
---
SecretAPI/SecretApi.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs
index 1c44952..2a288bd 100644
--- a/SecretAPI/SecretApi.cs
+++ b/SecretAPI/SecretApi.cs
@@ -11,7 +11,7 @@
///
/// Main class handling loading API.
///
-public partial class SecretApi : Plugin
+public class SecretApi : Plugin
{
///
public override string Name => "SecretAPI";
@@ -56,4 +56,4 @@ public override void Disable()
{
Harmony.UnpatchAll(Harmony.Id);
}
-}
\ No newline at end of file
+}
From c08364187294afd30e2546135fa30ed6dbb805ee Mon Sep 17 00:00:00 2001
From: Lumi
Date: Mon, 4 May 2026 17:02:26 +0100
Subject: [PATCH 09/18] Merge pull request #98
* feat: admin toy extensions
* feat: reorganisation
* docs: fix T cref error
* feat: item extensions
* feat: pickup extensions
* Remove .As, replaced with Cast and CastTypeSafely
---
.../AdminToys/AdminToyExtensions.Camera.cs | 68 +++++++++++
.../AdminToyExtensions.Interactable.cs | 91 ++++++++++++++
.../AdminToys/AdminToyExtensions.Light.cs | 114 ++++++++++++++++++
.../AdminToys/AdminToyExtensions.Primitive.cs | 87 +++++++++++++
.../AdminToys/AdminToyExtensions.Text.cs | 35 ++++++
.../AdminToys/AdminToyExtensions.Waypoint.cs | 46 +++++++
.../AdminToys/AdminToyExtensions.cs | 60 +++++++++
.../Items/ItemExtensions.Firearms.cs | 57 +++++++++
.../Items/ItemExtensions.MicroHID.cs | 57 +++++++++
.../Extensions/Items/ItemExtensions.Scp127.cs | 35 ++++++
.../Items/ItemExtensions.Scp1509.cs | 67 ++++++++++
SecretAPI/Extensions/Items/ItemExtensions.cs | 21 ++++
.../Pickups/PickupExtensions.Grenades.cs | 86 +++++++++++++
.../Pickups/PickupExtensions.Jailbird.cs | 46 +++++++
.../Pickups/PickupExtensions.MicroHID.cs | 46 +++++++
.../Pickups/PickupExtensions.Radio.cs | 46 +++++++
.../Extensions/Pickups/PickupExtensions.cs | 61 ++++++++++
SecretAPI/Extensions/ReflectionExtensions.cs | 22 ++++
18 files changed, 1045 insertions(+)
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.Camera.cs
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.Interactable.cs
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.Light.cs
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.Primitive.cs
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.Text.cs
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.Waypoint.cs
create mode 100644 SecretAPI/Extensions/AdminToys/AdminToyExtensions.cs
create mode 100644 SecretAPI/Extensions/Items/ItemExtensions.Firearms.cs
create mode 100644 SecretAPI/Extensions/Items/ItemExtensions.MicroHID.cs
create mode 100644 SecretAPI/Extensions/Items/ItemExtensions.Scp127.cs
create mode 100644 SecretAPI/Extensions/Items/ItemExtensions.Scp1509.cs
create mode 100644 SecretAPI/Extensions/Items/ItemExtensions.cs
create mode 100644 SecretAPI/Extensions/Pickups/PickupExtensions.Grenades.cs
create mode 100644 SecretAPI/Extensions/Pickups/PickupExtensions.Jailbird.cs
create mode 100644 SecretAPI/Extensions/Pickups/PickupExtensions.MicroHID.cs
create mode 100644 SecretAPI/Extensions/Pickups/PickupExtensions.Radio.cs
create mode 100644 SecretAPI/Extensions/Pickups/PickupExtensions.cs
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Camera.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Camera.cs
new file mode 100644
index 0000000..dbb5996
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Camera.cs
@@ -0,0 +1,68 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using LabApi.Features.Wrappers;
+using UnityEngine;
+
+///
+/// Extensions for camera toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(CameraToy toy)
+ {
+ ///
+ /// Modify the label of this .
+ ///
+ /// The label.
+ /// The modified .
+ public CameraToy WithLabel(string label)
+ {
+ toy.Label = label;
+ return toy;
+ }
+
+ ///
+ /// Modify the room of this .
+ ///
+ /// The room.
+ /// The modified .
+ public CameraToy WithRoom(Room room)
+ {
+ toy.Room = room;
+ return toy;
+ }
+
+ ///
+ /// Modify the vertical constrains of this .
+ ///
+ /// The constraints.
+ /// The modified .
+ public CameraToy WithVerticalConstraints(Vector2 constraint)
+ {
+ toy.VerticalConstraints = constraint;
+ return toy;
+ }
+
+ ///
+ /// Modify the horizontal constrains of this .
+ ///
+ /// The constraints.
+ /// The modified .
+ public CameraToy WithHorizontalConstraints(Vector2 constraint)
+ {
+ toy.HorizontalConstraint = constraint;
+ return toy;
+ }
+
+ ///
+ /// Modify the zoom constrains of this .
+ ///
+ /// The constraints.
+ /// The modified .
+ public CameraToy WithZoomConstraints(Vector2 constraint)
+ {
+ toy.ZoomConstraints = constraint;
+ return toy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Interactable.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Interactable.cs
new file mode 100644
index 0000000..e5394fe
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Interactable.cs
@@ -0,0 +1,91 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using System;
+using global::AdminToys;
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for interactable toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(InteractableToy toy)
+ {
+ ///
+ /// Call an when this is interacted with.
+ ///
+ /// The to run.
+ /// The modified .
+ public InteractableToy WhenInteracted(Action action)
+ {
+ toy.OnInteracted += action;
+ return toy;
+ }
+
+ ///
+ /// Call an when this is being searched.
+ ///
+ /// The to run.
+ /// The modified .
+ public InteractableToy WhenSearching(Action action)
+ {
+ toy.OnSearching += action;
+ return toy;
+ }
+
+ ///
+ /// Call an when this has been searched.
+ ///
+ /// The to run.
+ /// The modified .
+ public InteractableToy WhenSearched(Action action)
+ {
+ toy.OnSearched += action;
+ return toy;
+ }
+
+ ///
+ /// Call an when this has had an aborted search.
+ ///
+ /// The to run.
+ /// The modified .
+ public InteractableToy WhenSearchAborted(Action action)
+ {
+ toy.OnSearchAborted += action;
+ return toy;
+ }
+
+ ///
+ /// Modify the shape of this .
+ ///
+ /// The shape.
+ /// The modified .
+ public InteractableToy WithShape(InvisibleInteractableToy.ColliderShape shape)
+ {
+ toy.Shape = shape;
+ return toy;
+ }
+
+ ///
+ /// Modify how long a user should interact with before triggering the event.
+ ///
+ /// The duration.
+ /// The modified .
+ public InteractableToy WithInteractionDuration(float duration)
+ {
+ toy.InteractionDuration = duration;
+ return toy;
+ }
+
+ ///
+ /// Modify whether this can be interacted with.
+ ///
+ /// The lock state.
+ /// The modified .
+ public InteractableToy IsLocked(bool locked)
+ {
+ toy.IsLocked = locked;
+ return toy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Light.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Light.cs
new file mode 100644
index 0000000..fd4ff69
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Light.cs
@@ -0,0 +1,114 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using LabApi.Features.Wrappers;
+using UnityEngine;
+
+///
+/// Extensions for Light Source Toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(LightSourceToy toy)
+ {
+ ///
+ /// Modify the intensity of this .
+ ///
+ /// The intensity.
+ /// The modified .
+ public LightSourceToy WithIntensity(float intensity)
+ {
+ toy.Intensity = intensity;
+ return toy;
+ }
+
+ ///
+ /// Modify the range of this .
+ ///
+ /// The range.
+ /// The modified .
+ public LightSourceToy WithRange(float range)
+ {
+ toy.Range = range;
+ return toy;
+ }
+
+ ///
+ /// Modify the color of this .
+ ///
+ /// The color.
+ /// The modified .
+ public LightSourceToy WithColor(Color color)
+ {
+ toy.Color = color;
+ return toy;
+ }
+
+ ///
+ /// Modify the shadow type of this .
+ ///
+ /// The shadow type.
+ /// The modified .
+ public LightSourceToy WithShadowType(LightShadows type)
+ {
+ toy.ShadowType = type;
+ return toy;
+ }
+
+ ///
+ /// Modify the shadow strength of this .
+ ///
+ /// The strength.
+ /// The modified .
+ public LightSourceToy WithShadowStrength(float strength)
+ {
+ toy.ShadowStrength = strength;
+ return toy;
+ }
+
+ ///
+ /// Modify the type of this .
+ ///
+ /// The type.
+ /// The modified .
+ public LightSourceToy WithType(LightType lightType)
+ {
+ toy.Type = lightType;
+ return toy;
+ }
+
+ ///
+ /// Modify the shape of this .
+ ///
+ /// The shape.
+ /// The modified .
+#pragma warning disable CS0618 // Type or member is obsolete
+ public LightSourceToy WithShape(LightShape shape)
+#pragma warning restore CS0618 // Type or member is obsolete
+ {
+ toy.Shape = shape;
+ return toy;
+ }
+
+ ///
+ /// Modify the spot angle of this .
+ ///
+ /// The angle.
+ /// The modified .
+ public LightSourceToy WithSpotAngle(float angle)
+ {
+ toy.SpotAngle = angle;
+ return toy;
+ }
+
+ ///
+ /// Modify the inner spot angle of this .
+ ///
+ /// The angle.
+ /// The modified .
+ public LightSourceToy WithInnerSpotAngle(float angle)
+ {
+ toy.InnerSpotAngle = angle;
+ return toy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Primitive.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Primitive.cs
new file mode 100644
index 0000000..f7ff3c5
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Primitive.cs
@@ -0,0 +1,87 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using global::AdminToys;
+using UnityEngine;
+using PrimitiveObjectToy = LabApi.Features.Wrappers.PrimitiveObjectToy;
+
+///
+/// Extensions for primitive object toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(PrimitiveObjectToy toy)
+ {
+ ///
+ /// Gets or sets a value indicating whether this is collidable.
+ ///
+ public bool Collidable
+ {
+ get => (toy.Flags & PrimitiveFlags.Collidable) == PrimitiveFlags.Collidable;
+ set => toy.Flags = value ? toy.Flags | PrimitiveFlags.Collidable : toy.Flags & ~PrimitiveFlags.Collidable;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether this is visible.
+ ///
+ public bool Visible
+ {
+ get => (toy.Flags & PrimitiveFlags.Visible) == PrimitiveFlags.Visible;
+ set => toy.Flags = value ? toy.Flags | PrimitiveFlags.Visible : toy.Flags & ~PrimitiveFlags.Visible;
+ }
+
+ ///
+ /// Modify whether this is collidable.
+ ///
+ /// The collidable state.
+ /// The modified .
+ public PrimitiveObjectToy WithCollidable(bool collidable)
+ {
+ toy.Collidable = collidable;
+ return toy;
+ }
+
+ ///
+ /// Modify whether this is visible.
+ ///
+ /// The visibility state.
+ /// The modified .
+ public PrimitiveObjectToy WithVisible(bool visible)
+ {
+ toy.Visible = visible;
+ return toy;
+ }
+
+ ///
+ /// Modify the type of this .
+ ///
+ /// The type.
+ /// The modified .
+ public PrimitiveObjectToy WithType(PrimitiveType type)
+ {
+ toy.Type = type;
+ return toy;
+ }
+
+ ///
+ /// Modify the color of this .
+ ///
+ /// The color.
+ /// The modified .
+ public PrimitiveObjectToy WithColor(Color color)
+ {
+ toy.Color = color;
+ return toy;
+ }
+
+ ///
+ /// Modify the flags of this .
+ ///
+ /// The flags.
+ /// The modified .
+ public PrimitiveObjectToy WithFlags(PrimitiveFlags flags)
+ {
+ toy.Flags = flags;
+ return toy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Text.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Text.cs
new file mode 100644
index 0000000..0a3a743
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Text.cs
@@ -0,0 +1,35 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using LabApi.Features.Wrappers;
+using UnityEngine;
+
+///
+/// Extension methods for text toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(TextToy toy)
+ {
+ ///
+ /// Modify the text of this .
+ ///
+ /// The text.
+ /// The modified .
+ public TextToy WithText(string text)
+ {
+ toy.TextFormat = text;
+ return toy;
+ }
+
+ ///
+ /// Modify the size of this .
+ ///
+ /// The size.
+ /// The modified .
+ public TextToy WithSize(Vector2 size)
+ {
+ toy.DisplaySize = size;
+ return toy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Waypoint.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Waypoint.cs
new file mode 100644
index 0000000..0e4e906
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.Waypoint.cs
@@ -0,0 +1,46 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using LabApi.Features.Wrappers;
+using UnityEngine;
+
+///
+/// Extension methods for waypoint toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(WaypointToy toy)
+ {
+ ///
+ /// Modify the bounds size of this .
+ ///
+ /// The size.
+ /// The modified .
+ public WaypointToy WithBoundsSize(Vector3 size)
+ {
+ toy.BoundsSize = size;
+ return toy;
+ }
+
+ ///
+ /// Modify whether this has visible bounds.
+ ///
+ /// The visibility state.
+ /// The modified .
+ public WaypointToy WithVisualiseBounds(bool visible)
+ {
+ toy.VisualizeBounds = visible;
+ return toy;
+ }
+
+ ///
+ /// Modify the priority bias of this .
+ ///
+ /// The bias.
+ /// The modified .
+ public WaypointToy WithPriorityBias(float bias)
+ {
+ toy.PriorityBias = bias;
+ return toy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/AdminToys/AdminToyExtensions.cs b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.cs
new file mode 100644
index 0000000..68e3a73
--- /dev/null
+++ b/SecretAPI/Extensions/AdminToys/AdminToyExtensions.cs
@@ -0,0 +1,60 @@
+namespace SecretAPI.Extensions.AdminToys;
+
+using LabApi.Features.Wrappers;
+using UnityEngine;
+using CapybaraToy = LabApi.Features.Wrappers.CapybaraToy;
+
+///
+/// Extensions for admin toys.
+///
+public static partial class AdminToyExtensions
+{
+ extension(T toy)
+ where T : AdminToy
+ {
+ ///
+ /// Modify the position of this .
+ ///
+ /// The position to change to.
+ /// The modified .
+ public T WithPosition(Vector3 pos)
+ {
+ toy.Position = pos;
+ return toy;
+ }
+
+ ///
+ /// Modify the rotation of this .
+ ///
+ /// The rotation to change to.
+ /// The modified .
+ public T WithRotation(Quaternion rot)
+ {
+ toy.Rotation = rot;
+ return toy;
+ }
+
+ ///
+ /// Modify the scale of this .
+ ///
+ /// The scale to change to.
+ /// The modified .
+ public T WithScale(Vector3 scale)
+ {
+ toy.Scale = scale;
+ return toy;
+ }
+ }
+
+ ///
+ /// Updates whether the capybara has enabled colliders.
+ ///
+ /// The .
+ /// Whether the colliders are enabled.
+ /// The modified .
+ public static CapybaraToy WithCollidersEnabled(this CapybaraToy toy, bool enabled)
+ {
+ toy.CollidersEnabled = enabled;
+ return toy;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Items/ItemExtensions.Firearms.cs b/SecretAPI/Extensions/Items/ItemExtensions.Firearms.cs
new file mode 100644
index 0000000..fbe50e6
--- /dev/null
+++ b/SecretAPI/Extensions/Items/ItemExtensions.Firearms.cs
@@ -0,0 +1,57 @@
+namespace SecretAPI.Extensions.Items;
+
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for firearms.
+///
+public static partial class ItemExtensions
+{
+ extension(T item)
+ where T : FirearmItem
+ {
+ ///
+ /// Modifies the stored ammo in this .
+ ///
+ /// The ammo.
+ /// The modified .
+ public T WithStoredAmmo(int ammo)
+ {
+ item.StoredAmmo = ammo;
+ return item;
+ }
+
+ ///
+ /// Modifies whether the magazine is inserted in this .
+ ///
+ /// Whether the magazine is inserted.
+ /// The modified .
+ public T WithMagazineInserted(bool magazineInserted)
+ {
+ item.MagazineInserted = magazineInserted;
+ return item;
+ }
+
+ ///
+ /// Modifies whether the firearm is cocked in this .
+ ///
+ /// Whether the firearm is cocked.
+ /// The modified .
+ public T WithCocked(bool cocked)
+ {
+ item.Cocked = cocked;
+ return item;
+ }
+
+ ///
+ /// Modifies the chambered ammo in this .
+ ///
+ /// The ammo.
+ /// The modified .
+ public T WithChamberedAmmo(int ammo)
+ {
+ item.ChamberedAmmo = ammo;
+ return item;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Items/ItemExtensions.MicroHID.cs b/SecretAPI/Extensions/Items/ItemExtensions.MicroHID.cs
new file mode 100644
index 0000000..c2977a6
--- /dev/null
+++ b/SecretAPI/Extensions/Items/ItemExtensions.MicroHID.cs
@@ -0,0 +1,57 @@
+namespace SecretAPI.Extensions.Items;
+
+using InventorySystem.Items.MicroHID.Modules;
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for Micro H.I.D.
+///
+public static partial class ItemExtensions
+{
+ extension(MicroHIDItem item)
+ {
+ ///
+ /// Modify the energy of this .
+ ///
+ /// The energy.
+ /// The modified .
+ public MicroHIDItem WithEnergy(float energy)
+ {
+ item.Energy = energy;
+ return item;
+ }
+
+ ///
+ /// Modify the broken state of this .
+ ///
+ /// Whether the item is broken or not.
+ /// The modified .
+ public MicroHIDItem WithBrokenState(bool brokenState)
+ {
+ item.IsBroken = brokenState;
+ return item;
+ }
+
+ ///
+ /// Modify the phase of this .
+ ///
+ /// The phase.
+ /// The modified .
+ public MicroHIDItem WithPhase(MicroHidPhase phase)
+ {
+ item.Phase = phase;
+ return item;
+ }
+
+ ///
+ /// Modify the firing mode of this .
+ ///
+ /// The firing mode.
+ /// The modified .
+ public MicroHIDItem WithFiringMode(MicroHidFiringMode mode)
+ {
+ item.FiringMode = mode;
+ return item;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Items/ItemExtensions.Scp127.cs b/SecretAPI/Extensions/Items/ItemExtensions.Scp127.cs
new file mode 100644
index 0000000..1f1ba8c
--- /dev/null
+++ b/SecretAPI/Extensions/Items/ItemExtensions.Scp127.cs
@@ -0,0 +1,35 @@
+namespace SecretAPI.Extensions.Items;
+
+using InventorySystem.Items.Firearms.Modules.Scp127;
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for SCP-127.
+///
+public static partial class ItemExtensions
+{
+ extension(Scp127Firearm item)
+ {
+ ///
+ /// Modifies the tier of this .
+ ///
+ /// The tier.
+ /// The modified .
+ public Scp127Firearm WithTier(Scp127Tier tier)
+ {
+ item.Tier = tier;
+ return item;
+ }
+
+ ///
+ /// Modifies the experience of this .
+ ///
+ /// The experience.
+ /// The modified .
+ public Scp127Firearm WithExperience(float experience)
+ {
+ item.Experience = experience;
+ return item;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Items/ItemExtensions.Scp1509.cs b/SecretAPI/Extensions/Items/ItemExtensions.Scp1509.cs
new file mode 100644
index 0000000..d78780a
--- /dev/null
+++ b/SecretAPI/Extensions/Items/ItemExtensions.Scp1509.cs
@@ -0,0 +1,67 @@
+namespace SecretAPI.Extensions.Items;
+
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for SCP-1509.
+///
+public static partial class ItemExtensions
+{
+ extension(Scp1509Item item)
+ {
+ ///
+ /// Modifies the shield regen rate of this .
+ ///
+ /// The rate.
+ /// The modified .
+ public Scp1509Item WithShieldRegenRate(float rate)
+ {
+ item.ShieldRegenRate = rate;
+ return item;
+ }
+
+ ///
+ /// Modifies the shield decay rate of this .
+ ///
+ /// The rate.
+ /// The modified .
+ public Scp1509Item WithShieldDecayRate(float rate)
+ {
+ item.ShieldDecayRate = rate;
+ return item;
+ }
+
+ ///
+ /// Modifies the unequip decay delay of this .
+ ///
+ /// The time to start decaying.
+ /// The modified .
+ public Scp1509Item WithUnequipDecayDelay(float time)
+ {
+ item.UnequipDecayDelay = time;
+ return item;
+ }
+
+ ///
+ /// Modifies the revive cooldown of this .
+ ///
+ /// The cooldown.
+ /// The modified .
+ public Scp1509Item WithReviveCooldown(double cooldown)
+ {
+ item.ReviveCooldown = cooldown;
+ return item;
+ }
+
+ ///
+ /// Modifies the equipped HS of this .
+ ///
+ /// The hume shield.
+ /// The modified .
+ public Scp1509Item WithEquippedHumeShield(float hs)
+ {
+ item.EquippedHS = hs;
+ return item;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Items/ItemExtensions.cs b/SecretAPI/Extensions/Items/ItemExtensions.cs
new file mode 100644
index 0000000..ac671c9
--- /dev/null
+++ b/SecretAPI/Extensions/Items/ItemExtensions.cs
@@ -0,0 +1,21 @@
+namespace SecretAPI.Extensions.Items;
+
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for items.
+///
+public static partial class ItemExtensions
+{
+ ///
+ /// Modifies the battery percentage of this .
+ ///
+ /// The .
+ /// The percentage.
+ /// The modified .
+ public static RadioItem WithBatteryPercent(this RadioItem item, byte battery)
+ {
+ item.BatteryPercent = battery;
+ return item;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Pickups/PickupExtensions.Grenades.cs b/SecretAPI/Extensions/Pickups/PickupExtensions.Grenades.cs
new file mode 100644
index 0000000..152b6fa
--- /dev/null
+++ b/SecretAPI/Extensions/Pickups/PickupExtensions.Grenades.cs
@@ -0,0 +1,86 @@
+namespace SecretAPI.Extensions.Pickups;
+
+using LabApi.Features.Wrappers;
+using UnityEngine;
+
+///
+/// Extensions for grenade projectiles.
+///
+public static partial class PickupExtensions
+{
+ extension(T pickup)
+ where T : TimedGrenadeProjectile
+ {
+ ///
+ /// Modifies the remaining time of this .
+ ///
+ /// The time.
+ /// The modified .
+ public T WithRemainingTime(double time)
+ {
+ pickup.RemainingTime = time;
+ return pickup;
+ }
+ }
+
+ extension(ExplosiveGrenadeProjectile pickup)
+ {
+ ///
+ /// Modifies the max explosion radius of this .
+ ///
+ /// The radius.
+ /// The modified .
+ public ExplosiveGrenadeProjectile WithMaxRadius(float radius)
+ {
+ pickup.MaxRadius = radius;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the scp damage multiplier of this .
+ ///
+ /// The multiplier.
+ /// The modified .
+ public ExplosiveGrenadeProjectile WithScpDamageMultiplier(float multiplier)
+ {
+ pickup.ScpDamageMultiplier = multiplier;
+ return pickup;
+ }
+ }
+
+ ///
+ /// Modifies the flash time of this .
+ ///
+ /// The flashbang.
+ /// The time.
+ /// The modified .
+ public static FlashbangProjectile WithBlindTime(this FlashbangProjectile pickup, float time)
+ {
+ pickup.BaseBlindTime = time;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the velocity of this .
+ ///
+ /// The SCP-018 instance.
+ /// The velocity.
+ /// The modified .
+ public static Scp018Projectile WithVelocity(this Scp018Projectile pickup, Vector3 velocity)
+ {
+ pickup.Velocity = velocity;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the lockdown duration of this .
+ ///
+ /// The SCP-2176 instance.
+ /// The duration.
+ /// The modified .
+ public static Scp2176Projectile WithLockdownDuration(this Scp2176Projectile pickup, float duration)
+ {
+ pickup.LockdownDuration = duration;
+ return pickup;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Pickups/PickupExtensions.Jailbird.cs b/SecretAPI/Extensions/Pickups/PickupExtensions.Jailbird.cs
new file mode 100644
index 0000000..8f1aad2
--- /dev/null
+++ b/SecretAPI/Extensions/Pickups/PickupExtensions.Jailbird.cs
@@ -0,0 +1,46 @@
+namespace SecretAPI.Extensions.Pickups;
+
+using InventorySystem.Items.Jailbird;
+using JailbirdPickup = LabApi.Features.Wrappers.JailbirdPickup;
+
+///
+/// Extensions for jailbirds.
+///
+public static partial class PickupExtensions
+{
+ extension(JailbirdPickup pickup)
+ {
+ ///
+ /// Modifies the total damage dealt of this .
+ ///
+ /// The damage.
+ /// The modified .
+ public JailbirdPickup WithTotalDamageDealt(float damage)
+ {
+ pickup.TotalDamageDealt += damage;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the total charges performed of this .
+ ///
+ /// The charges.
+ /// The modified .
+ public JailbirdPickup WithTotalChargesPerformed(int charges)
+ {
+ pickup.TotalChargesPerformed += charges;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the wear state of this .
+ ///
+ /// The state.
+ /// The modified .
+ public JailbirdPickup WithWearState(JailbirdWearState state)
+ {
+ pickup.WearState = state;
+ return pickup;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Pickups/PickupExtensions.MicroHID.cs b/SecretAPI/Extensions/Pickups/PickupExtensions.MicroHID.cs
new file mode 100644
index 0000000..c749688
--- /dev/null
+++ b/SecretAPI/Extensions/Pickups/PickupExtensions.MicroHID.cs
@@ -0,0 +1,46 @@
+namespace SecretAPI.Extensions.Pickups;
+
+using InventorySystem.Items.MicroHID.Modules;
+using LabApi.Features.Wrappers;
+
+///
+/// Extensions for Micro H.I.D.
+///
+public static partial class PickupExtensions
+{
+ extension(MicroHIDPickup pickup)
+ {
+ ///
+ /// Modify the energy of this .
+ ///
+ /// The energy.
+ /// The modified .
+ public MicroHIDPickup WithEnergy(float energy)
+ {
+ pickup.Energy = energy;
+ return pickup;
+ }
+
+ ///
+ /// Modify the phase of this .
+ ///
+ /// The phase.
+ /// The modified .
+ public MicroHIDPickup WithPhase(MicroHidPhase phase)
+ {
+ pickup.Phase = phase;
+ return pickup;
+ }
+
+ ///
+ /// Modify the firing mode of this .
+ ///
+ /// The firing mode.
+ /// The modified .
+ public MicroHIDPickup WithFiringMode(MicroHidFiringMode mode)
+ {
+ pickup.FiringMode = mode;
+ return pickup;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Pickups/PickupExtensions.Radio.cs b/SecretAPI/Extensions/Pickups/PickupExtensions.Radio.cs
new file mode 100644
index 0000000..f45b142
--- /dev/null
+++ b/SecretAPI/Extensions/Pickups/PickupExtensions.Radio.cs
@@ -0,0 +1,46 @@
+namespace SecretAPI.Extensions.Pickups;
+
+using InventorySystem.Items.Radio;
+using RadioPickup = LabApi.Features.Wrappers.RadioPickup;
+
+///
+/// Extensions for radios.
+///
+public static partial class PickupExtensions
+{
+ extension(RadioPickup pickup)
+ {
+ ///
+ /// Modifies the enabled state of this .
+ ///
+ /// The enabled state.
+ /// The modified .
+ public RadioPickup WithEnabledState(bool enabled)
+ {
+ pickup.IsEnabled = enabled;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the range level of this .
+ ///
+ /// The range.
+ /// The modified .
+ public RadioPickup WithRangeLevel(RadioMessages.RadioRangeLevel range)
+ {
+ pickup.RangeLevel = range;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the battery of this .
+ ///
+ /// The battery.
+ /// The modified .
+ public RadioPickup WithBattery(float battery)
+ {
+ pickup.Battery = battery;
+ return pickup;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/Pickups/PickupExtensions.cs b/SecretAPI/Extensions/Pickups/PickupExtensions.cs
new file mode 100644
index 0000000..0d530b3
--- /dev/null
+++ b/SecretAPI/Extensions/Pickups/PickupExtensions.cs
@@ -0,0 +1,61 @@
+namespace SecretAPI.Extensions.Pickups;
+
+using InventorySystem.Items.Usables.Scp330;
+using LabApi.Features.Wrappers;
+using Scp330Pickup = LabApi.Features.Wrappers.Scp330Pickup;
+
+///
+/// Extensions for pickups.
+///
+public static partial class PickupExtensions
+{
+ extension(T pickup)
+ where T : Pickup
+ {
+ ///
+ /// Modifies the weight of this .
+ ///
+ /// The weight.
+ /// The modified .
+ public T WithWeight(float weight)
+ {
+ pickup.Weight = weight;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the locked state of this .
+ ///
+ /// The lock state.
+ /// The modified .
+ public T WithLockedState(bool locked)
+ {
+ pickup.IsLocked = locked;
+ return pickup;
+ }
+ }
+
+ ///
+ /// Modifies the ammo in this .
+ ///
+ /// The pickup.
+ /// The ammo.
+ /// The modified .
+ public static AmmoPickup WithAmmo(this AmmoPickup pickup, ushort ammo)
+ {
+ pickup.Ammo = ammo;
+ return pickup;
+ }
+
+ ///
+ /// Modifies the exposed candy in this .
+ ///
+ /// The pickup.
+ /// The candy kind.
+ /// The modified .
+ public static Scp330Pickup WithExposedCandy(this Scp330Pickup pickup, CandyKindID kind)
+ {
+ pickup.ExposedCandy = kind;
+ return pickup;
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/Extensions/ReflectionExtensions.cs b/SecretAPI/Extensions/ReflectionExtensions.cs
index 32767cf..340cf68 100644
--- a/SecretAPI/Extensions/ReflectionExtensions.cs
+++ b/SecretAPI/Extensions/ReflectionExtensions.cs
@@ -11,6 +11,28 @@
///
public static class ReflectionExtensions
{
+ ///
+ /// Casts an object into .
+ /// This will throw an exception if is not of type .
+ ///
+ /// The source object to cast from.
+ /// The new type to cast to.
+ /// The source after being cast to T.
+ public static T Cast(this object source)
+ where T : class => (T)source;
+
+ ///
+ /// Casts an object of to .
+ /// This will require to be derived from .
+ ///
+ /// The source to cast from.
+ /// The original type.
+ /// The type to cast to.
+ /// The source after being cast to .
+ public static T2 CastTypeSafely(this T1 source)
+ where T1 : class
+ where T2 : T1 => (T2)source;
+
///
/// Gets the long name of a function.
///
From d1b85e5f8fae14eea44ac8263e6ed60cc8e8bfd0 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 4 May 2026 18:05:44 +0200
Subject: [PATCH 10/18] Remove unused diagnostic
---
SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md | 5 ++---
SecretAPI.SourceGenerators/Diagnostics.cs | 8 --------
2 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
index 08f562d..3d2453f 100644
--- a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
+++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -2,6 +2,5 @@
Rule ID | Category | Severity | Notes
------------|----------|----------|---------------------
- SG001 | Usage | Error | MustBePartialPluginClass
- SG002 | Usage | Error | MustBeAccessibleMethod
- SG003 | Usage | Error | MustBeStaticMethod
\ No newline at end of file
+ SG001 | Usage | Error | MustBeAccessibleMethod
+ SG002 | Usage | Error | MustBeStaticMethod
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Diagnostics.cs b/SecretAPI.SourceGenerators/Diagnostics.cs
index 21d8073..a010b5f 100644
--- a/SecretAPI.SourceGenerators/Diagnostics.cs
+++ b/SecretAPI.SourceGenerators/Diagnostics.cs
@@ -2,14 +2,6 @@
internal static class Diagnostics
{
- internal static readonly DiagnosticDescriptor MustBePartialPluginClass = new(
- "SG001",
- "Plugin class must be partial",
- "Plugin class '{0}' is missing partial modifier",
- "Usage",
- DiagnosticSeverity.Error,
- true);
-
internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new(
"SG002",
"Method must be accessible",
From 122a66fdfca792b6c4ab1938bab0eb707f0421e4 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 4 May 2026 18:11:02 +0200
Subject: [PATCH 11/18] Fix: Diagnostic ids
---
SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md | 4 +---
SecretAPI.SourceGenerators/Diagnostics.cs | 4 ++--
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md
index 60b59dd..5f28270 100644
--- a/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md
+++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md
@@ -1,3 +1 @@
-; Shipped analyzer releases
-; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
-
+
\ No newline at end of file
diff --git a/SecretAPI.SourceGenerators/Diagnostics.cs b/SecretAPI.SourceGenerators/Diagnostics.cs
index a010b5f..8361192 100644
--- a/SecretAPI.SourceGenerators/Diagnostics.cs
+++ b/SecretAPI.SourceGenerators/Diagnostics.cs
@@ -3,7 +3,7 @@
internal static class Diagnostics
{
internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new(
- "SG002",
+ "SG001",
"Method must be accessible",
"Method '{0}' has accessibility '{1}', which is not supported for generated calls",
"Usage",
@@ -11,7 +11,7 @@ internal static class Diagnostics
true);
internal static readonly DiagnosticDescriptor MustBeStaticMethod = new(
- "SG003",
+ "SG002",
"Method must be static",
"Method '{0}' is not marked as static",
"Usage",
From ef9a1cfebcc656840704bc8ecf96b436f94a6357 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 4 May 2026 18:49:40 +0200
Subject: [PATCH 12/18] Version bump
---
Directory.Build.props | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index 077926f..3bc488e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
enable
- 3.1.2
+ 3.2.0
From 13cb386c6de6a6af28904f4b1c8a23c07ca4189b Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 4 May 2026 18:49:57 +0200
Subject: [PATCH 13/18] Fix: Nuget package for SourceGenerators not being
correct
---
.../SecretAPI.SourceGenerators.csproj | 21 +++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj
index b6bd4b5..ea01185 100644
--- a/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj
+++ b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj
@@ -7,13 +7,26 @@
enable
-
+
+ true
+ true
+ SecretAPI.SourceGenerators
+ Source Generators for SecretAPI.
+ README.md
+
true
- false
- Library
- true
+ false
+ true
+
+
+ True
+ \
+
+
+
+
From 6bfe4aabc7a9b9f556b52004e98cf67d417816a4 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Mon, 4 May 2026 20:09:31 +0200
Subject: [PATCH 14/18] docs: Add Source Generator mention in README.md
---
.github/README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/README.md b/.github/README.md
index 02d086c..d353b21 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -13,5 +13,9 @@
# Examples
You can find examples inside the `SecretAPI.Examples` folder above, this contains some example settings, patches using categories and some more.
+# Source Generation
+- SecretAPI includes a SourceGenerator via the Nuget package ``SecretAPI.SourceGenerators``
+ - This will generate ``SecretApiGenerated.cs`` which can be used with ``CallOn(Un)LoadAttribute``
+
# Support
* For any issues create an [Issue](https://github.com/Misfiy/SecretAPI/issues/new) or contact me on [Discord](https://discord.gg/RYzahv3vfC).
\ No newline at end of file
From c34a85bf25c59e3150aa34b57854c7fe0c1815a9 Mon Sep 17 00:00:00 2001
From: OMEGA3065
Date: Tue, 5 May 2026 20:41:06 +0200
Subject: [PATCH 15/18] feat: DecalHelpers which allow for spawning of Decals
without the need of a ImpactEffectsModule instance.
---
SecretAPI/Features/DecalHelpers.cs | 111 +++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
create mode 100644 SecretAPI/Features/DecalHelpers.cs
diff --git a/SecretAPI/Features/DecalHelpers.cs b/SecretAPI/Features/DecalHelpers.cs
new file mode 100644
index 0000000..2283287
--- /dev/null
+++ b/SecretAPI/Features/DecalHelpers.cs
@@ -0,0 +1,111 @@
+namespace SecretAPI.Features;
+
+using System;
+using Decals;
+using InventorySystem.Items;
+using InventorySystem.Items.Autosync;
+using InventorySystem.Items.Firearms.Modules;
+using Mirror;
+using RelativePositioning;
+using UnityEngine;
+using Utils.Networking;
+
+///
+/// Helps with handling Decals () and sending them to clients without the need for a firearm with the .
+///
+public static class DecalHelpers
+{
+ private static bool hasData = false;
+ private static ItemType itemType = ItemType.None;
+ private static byte subcomponentIndex = 0;
+
+ ///
+ /// Creates an for spawning a decal that can be sent to players.
+ ///
+ /// The position where the decal will spawn.
+ /// The position from which the decal will be applied to a surface.
+ /// The type of decal to spawn.
+ /// The message which is to be sent to players.
+ public static AutosyncMessage GetDecalMessage(Vector3 position, Vector3 startPosition, DecalPoolType type)
+ {
+ RelativePosition hitPoint = new(position);
+ RelativePosition startRaycastPoint = new(startPosition);
+
+ (ItemType itemTypeId, byte moduleIndex) = GetItemData();
+
+ using NetworkWriterPooled? writer = NetworkWriterPool.Get();
+ writer.WriteByte(moduleIndex);
+ writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal);
+ writer.WriteByte((byte)type);
+ writer.WriteRelativePosition(hitPoint);
+ writer.WriteRelativePosition(startRaycastPoint);
+
+ return new AutosyncMessage(writer, new ItemIdentifier(itemTypeId, 0));
+ }
+
+ ///
+ /// Spawns a decal.
+ ///
+ /// The position where the decal will spawn.
+ /// The position from which the decal will be applied to a surface.
+ /// The type of decal to spawn.
+ public static void SpawnDecalFromPosition(Vector3 position, Vector3 startPosition, DecalPoolType type = DecalPoolType.Blood)
+ => GetDecalMessage(position, startPosition, type).SendToAuthenticated();
+
+ ///
+ /// Spawns a decal.
+ ///
+ /// The position where the decal will spawn.
+ /// The normal of the face the decal is on.
+ /// The type of decal to spawn.
+ public static void SpawnDecalFromNormal(Vector3 position, Vector3 normal, DecalPoolType type = DecalPoolType.Blood)
+ => GetDecalMessage(position, position + normal, type).SendToAuthenticated();
+
+ ///
+ /// Spawns a decal.
+ ///
+ /// The position where the decal will spawn.
+ /// The normal rotation of the face the decal is on.
+ /// The type of decal to spawn.
+ public static void SpawnDecalFromNormal(Vector3 position, Quaternion normal, DecalPoolType type = DecalPoolType.Blood)
+ => GetDecalMessage(position, position + (normal * Vector3.forward), type).SendToAuthenticated();
+
+ ///
+ /// Spawns a decal.
+ ///
+ /// The position where the decal will spawn.
+ /// The direction opposite to the normal of the face the decal will be placed on.
+ /// The type of decal to spawn.
+ public static void SpawnDecalFromDirection(Vector3 position, Vector3 direction, DecalPoolType type = DecalPoolType.Blood)
+ => GetDecalMessage(position, position - direction, type).SendToAuthenticated();
+
+ ///
+ /// Spawns a decal.
+ ///
+ /// The position where the decal will spawn.
+ /// The direction opposite to the normal of the face the decal will be placed on.
+ /// The type of decal to spawn.
+ public static void SpawnDecalFromDirection(Vector3 position, Quaternion direction, DecalPoolType type = DecalPoolType.Blood)
+ => GetDecalMessage(position, position - (direction * Vector3.forward), type).SendToAuthenticated();
+
+ private static (ItemType ItemType, byte SubcomponentIndex) GetItemData()
+ {
+ if (hasData)
+ return (itemType, subcomponentIndex);
+
+ foreach (ModularAutosyncItem? autoItem in ModularAutosyncItem.AllTemplates)
+ {
+ for (byte b = 0; b < autoItem.AllSubcomponents.Length; b++)
+ {
+ if (autoItem.AllSubcomponents[b] is not ImpactEffectsModule)
+ continue;
+ subcomponentIndex = b;
+ itemType = autoItem.ItemTypeId;
+ hasData = true;
+ return (itemType, subcomponentIndex);
+ }
+ }
+
+ throw new InvalidOperationException("Couldn't find the `InventorySystem.Items.Firearms.Modules.ImpactEffectsModule` in the any ModularAutosyncItem!");
+ }
+}
\ No newline at end of file
From 2461876719872b2d50bf933426cb3ceb28513feb Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Tue, 5 May 2026 20:58:21 +0200
Subject: [PATCH 16/18] CodeMatcherExtensions.cs
---
SecretAPI/Extensions/CodeMatcherExtensions.cs | 75 +++++++++++++++++++
SecretAPI/SecretAPI.csproj | 2 +-
2 files changed, 76 insertions(+), 1 deletion(-)
create mode 100644 SecretAPI/Extensions/CodeMatcherExtensions.cs
diff --git a/SecretAPI/Extensions/CodeMatcherExtensions.cs b/SecretAPI/Extensions/CodeMatcherExtensions.cs
new file mode 100644
index 0000000..12c726b
--- /dev/null
+++ b/SecretAPI/Extensions/CodeMatcherExtensions.cs
@@ -0,0 +1,75 @@
+namespace SecretAPI.Extensions;
+
+using System;
+using System.Linq;
+using System.Reflection.Emit;
+using HarmonyLib;
+
+///
+/// Extensions related to .
+///
+public static class CodeMatcherExtensions
+{
+ extension(CodeMatcher matcher)
+ {
+ ///
+ /// Sets to the end and then backtracks X amount.
+ ///
+ /// The amount to reverse. Must be positive number.
+ /// The current .
+ public CodeMatcher EndAndBacktrack(int backtrackCount)
+ {
+ matcher.End();
+ matcher.Advance(-backtrackCount);
+ return matcher;
+ }
+
+ ///
+ /// Declares a local to be used of a certain .
+ ///
+ /// The of the local to declare.
+ /// The declared.
+ /// The current .
+ public CodeMatcher DeclareLocal(Type localType, out LocalBuilder localBuilder)
+ {
+ localBuilder = matcher.generator.DeclareLocal(localType);
+ return matcher;
+ }
+
+ ///
+ /// Declares a local to be used of a certain .
+ ///
+ /// The of the local to declare.
+ /// The index of the local declared.
+ /// The current .
+ public CodeMatcher DeclareLocal(Type localType, out int localIndex)
+ {
+ DeclareLocal(matcher, localType, out LocalBuilder builder);
+ localIndex = builder.LocalIndex;
+ return matcher;
+ }
+
+ ///
+ /// Gets the first label at the current position.
+ ///
+ /// The first label at the current position.
+ /// The current .
+ public CodeMatcher GetFirstLabel(out Label label)
+ {
+ label = matcher.Labels.FirstOrDefault();
+ return matcher;
+ }
+
+ ///
+ /// Gets the first label at a specific position.
+ ///
+ /// The position to get label at.
+ /// The label at the position.
+ /// The current .
+ public CodeMatcher GetFirstLabelAt(int position, out Label label)
+ {
+ label = matcher.codes[position].labels.FirstOrDefault();
+ return matcher;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj
index 055b9de..db3c6ac 100644
--- a/SecretAPI/SecretAPI.csproj
+++ b/SecretAPI/SecretAPI.csproj
@@ -26,7 +26,7 @@
-
+
From d6d5778431aa64aabd7e237a1f1632f0dd43f291 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 6 May 2026 12:00:26 +0200
Subject: [PATCH 17/18] ReflectionExtensions: Cache GetLongFuncName
---
SecretAPI/Extensions/ReflectionExtensions.cs | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/SecretAPI/Extensions/ReflectionExtensions.cs b/SecretAPI/Extensions/ReflectionExtensions.cs
index 340cf68..566844a 100644
--- a/SecretAPI/Extensions/ReflectionExtensions.cs
+++ b/SecretAPI/Extensions/ReflectionExtensions.cs
@@ -11,6 +11,8 @@
///
public static class ReflectionExtensions
{
+ private static readonly Dictionary<(Type, MethodInfo), string> CachedLongFuncNames = new();
+
///
/// Casts an object into .
/// This will throw an exception if is not of type .
@@ -56,7 +58,12 @@ public static string GetLongFuncName(Type type, string methodName)
/// 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))})";
+ if (CachedLongFuncNames.TryGetValue((type, method), out string? longFunc))
+ return longFunc;
+
+ longFunc = $"{method.ReturnType.FullName} {type.FullName}::{method.Name}({string.Join(",", method.GetParameters().Select(x => x.ParameterType.FullName))})";
+ CachedLongFuncNames.Add((type, method), longFunc);
+ return longFunc;
}
///
From ad3178ad714a2968cedc82b37e5dbae36a9828b2 Mon Sep 17 00:00:00 2001
From: Eve <85962933+obvEve@users.noreply.github.com>
Date: Wed, 6 May 2026 17:55:08 +0200
Subject: [PATCH 18/18] fix: IsCurrentlyAccessible not being set correctly
---
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 5e5f042..95e681b 100644
--- a/SecretAPI/Features/UserSettings/CustomSetting.cs
+++ b/SecretAPI/Features/UserSettings/CustomSetting.cs
@@ -296,7 +296,7 @@ public static void SendSettingsToPlayer(Player player, int? version = null)
CustomSetting playerSpecific = EnsurePlayerSpecificSetting(player, setting);
playerSpecific.PersonalizeSetting();
- setting.IsCurrentlyAccessible = true;
+ playerSpecific.IsCurrentlyAccessible = true;
playerSettings.Add(playerSpecific);
}