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 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 diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Shipped.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..3d2453f --- /dev/null +++ b/SecretAPI.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -0,0 +1,6 @@ +### New Rules + + Rule ID | Category | Severity | Notes +------------|----------|----------|--------------------- + SG001 | Usage | Error | MustBeAccessibleMethod + SG002 | Usage | Error | MustBeStaticMethod \ 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..7f14306 --- /dev/null +++ b/SecretAPI.SourceGenerators/Builders/ClassBuilder.cs @@ -0,0 +1,80 @@ +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"); + } + + 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) + { + 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); + + 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, 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..8361192 --- /dev/null +++ b/SecretAPI.SourceGenerators/Diagnostics.cs @@ -0,0 +1,20 @@ +namespace SecretAPI.SourceGenerators; + +internal static class Diagnostics +{ + internal static readonly DiagnosticDescriptor MustBeAccessibleMethod = new( + "SG001", + "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( + "SG002", + "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..dce991e --- /dev/null +++ b/SecretAPI.SourceGenerators/Generators/CallOnLoadGenerator.cs @@ -0,0 +1,130 @@ +namespace SecretAPI.SourceGenerators.Generators; + +/// +/// Code generator for CallOnLoad/CallOnUnload +/// TODO: Implement IRegister source generation +/// +[Generator] +public class CallOnLoadGenerator : IIncrementalGenerator +{ + private const string GeneratedClassName = "SecretApiGenerated"; + 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); + + context.RegisterSourceOutput(callProvider.Collect(), Generate); + } + + 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, 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, + ImmutableArray<(IMethodSymbol method, bool isLoad, bool isUnload)> methods) + { + if (methods.IsEmpty) + return; + + IMethodSymbol[] loadCalls = methods + .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, m.method)) + .Select(m => m.method) + .OrderBy(m => GetPriority(m, CallOnUnloadAttributeLocation)) + .ToArray(); + + if (!loadCalls.Any() && !unloadCalls.Any()) + return; + + // ClassBuilder classBuilder = ClassBuilder.CreateBuilder(pluginInfo.Item2) + ClassBuilder classBuilder = ClassBuilder.CreateBuilder(ClassDeclaration(GeneratedClassName)) + .AddUsingStatements("System") + .AddModifiers(SyntaxKind.InternalKeyword, SyntaxKind.StaticKeyword); + + classBuilder.StartMethodCreation("OnLoad", SyntaxKind.VoidKeyword) + .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword) + .AddStatements(MethodCallStatements(loadCalls)) + .FinishMethodBuild(); + + classBuilder.StartMethodCreation("OnUnload", SyntaxKind.VoidKeyword) + .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword) + .AddStatements(MethodCallStatements(unloadCalls)) + .FinishMethodBuild(); + + classBuilder.Build(context, $"{GeneratedClassName}.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..8869bf3 --- /dev/null +++ b/SecretAPI.SourceGenerators/GlobalUsings.cs @@ -0,0 +1,18 @@ +//? 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.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..ea01185 --- /dev/null +++ b/SecretAPI.SourceGenerators/SecretAPI.SourceGenerators.csproj @@ -0,0 +1,39 @@ + + + + netstandard2.0 + 14 + true + enable + + + + true + true + SecretAPI.SourceGenerators + Source Generators for SecretAPI. + README.md + + true + false + true + + + + + True + \ + + + + + + + + + + + + + + diff --git a/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs new file mode 100644 index 0000000..02a4988 --- /dev/null +++ b/SecretAPI.SourceGenerators/Utils/GeneratedIdentifyUtils.cs @@ -0,0 +1,21 @@ +namespace SecretAPI.SourceGenerators.Utils; + +internal static class GeneratedIdentifyUtils +{ + private static SyntaxToken CurrentVersion => Literal(typeof(GeneratedIdentifyUtils).Assembly.GetName().Version.ToString()); + + private static AttributeSyntax GetGeneratedCodeAttributeSyntax() + => Attribute(IdentifierName("GeneratedCode")) + .WithArgumentList( + AttributeArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("SecretAPI.SourceGenerators"))), + Token(SyntaxKind.CommaToken), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, CurrentVersion)), + }))); + + internal static AttributeListSyntax GetGeneratedCodeAttributeListSyntax() + => AttributeList(SingletonSeparatedList(GetGeneratedCodeAttributeSyntax())); +} \ 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.slnx b/SecretAPI.slnx index 99c20fd..6995b49 100644 --- a/SecretAPI.slnx +++ b/SecretAPI.slnx @@ -1,4 +1,5 @@ + 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/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/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..566844a 100644 --- a/SecretAPI/Extensions/ReflectionExtensions.cs +++ b/SecretAPI/Extensions/ReflectionExtensions.cs @@ -11,6 +11,30 @@ /// 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 . + /// + /// 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. /// @@ -34,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; } /// 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 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); } diff --git a/SecretAPI/SecretAPI.csproj b/SecretAPI/SecretAPI.csproj index 0302f70..db3c6ac 100644 --- a/SecretAPI/SecretAPI.csproj +++ b/SecretAPI/SecretAPI.csproj @@ -26,11 +26,13 @@ - + + + diff --git a/SecretAPI/SecretApi.cs b/SecretAPI/SecretApi.cs index 9033e9a..2a288bd 100644 --- a/SecretAPI/SecretApi.cs +++ b/SecretAPI/SecretApi.cs @@ -48,7 +48,7 @@ public class SecretApi : Plugin /// public override void Enable() { - CallOnLoadAttribute.Load(Assembly); + SecretApiGenerated.OnLoad(); } /// @@ -56,4 +56,4 @@ public override void Disable() { Harmony.UnpatchAll(Harmony.Id); } -} \ No newline at end of file +}